Запрос типа Entity (дискриминатор) с JPA и Spring Boot

Я пытаюсь понять магию Spring Boot и JPA. У меня есть следующие классы:

Базовый класс Asset (также может быть абстрактным в моем случае) Класс Model наследуется от Asset.

// ASSET CLASS

import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorType;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.STRING)
@DiscriminatorValue(value = "asset")
public class Asset {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    private String name;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

И мой класс Model.

// MODEL CLASS

import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;


@Entity
@DiscriminatorValue(value = "model")
public class Model extends Asset {

    private long version;

    public long getVersion() { return version; }

    public void setVersion(long version) { this.version = version; }
}

Я создал репозиторий, в котором я хочу запросить findAllAssetsByType(), но, похоже, я делаю что-то совершенно неправильное. Вот интерфейс AssetRepository

//ASSET REPOSITORY

import assetservice.entity.Asset;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;

public interface AssetRepository extends JpaRepository<Asset, Long> {

    //big problem - store specific - if discriminiator changes this has to change too...
    //@Query("select a from Asset a where type = ?1")
    List<Asset> findAssetsByType(String type);
}

Я также написал небольшой тест, чтобы проиллюстрировать, что я хочу сделать:

// TEST REPOSITORY 
import static org.assertj.core.api.Assertions.assertThat;

import assetservice.core.AppLogger;
import assetservice.dao.AssetRepository;
import java.util.ArrayList;
import java.util.List;
import org.apache.logging.log4j.Logger;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@DataJpaTest
public class TestAsset {

    private static final Logger logger = AppLogger.getLogger();

    @Autowired
    private AssetRepository ar;

    @Test
    public void doSomething1() {
        Asset expected = new Asset();
        expected.setName("MyTestAsset");
        ar.save(expected);
        Asset actual = this.ar.findOne(expected.getId());
        assertThat(actual).isEqualTo(expected);
    }

    @Test
    public void doSomething2() {
        Asset expected = new Model();
        expected.setName("MyTestModel");
        ar.save(expected);
        Asset actual = this.ar.findOne(expected.getId());
        assertThat(actual).isEqualTo(expected);
    }

    @Test
    public void doSomething3() {
        Asset expectedModel = new Model();
        Asset expectedAsset = new Asset();
        expectedModel.setName("MyTestModel");
        expectedAsset.setName("MyTestAsset");
        ar.save(expectedModel);
        ar.save(expectedAsset);
        Asset actualModel = this.ar.findOne(expectedModel.getId());
        assertThat(actualModel).isEqualTo(expectedModel);

        Asset actualAsset = this.ar.findOne(expectedAsset.getId());
        assertThat(actualAsset).isEqualTo(actualAsset);

        List<Asset> expectedAssetList = new ArrayList<>();
        expectedAssetList.add(this.ar.findOne(expectedModel.getId()));
        List<Asset> actualAssetList = this.ar.findAssetsByType("model");

        logger.info("\n\n\n\n\n\n\n\n");
        logger.info(expectedAssetList);
        logger.info(actualAssetList);
        logger.info("\n\n\n\n\n\n\n\n");
    }
}

Тесты doSomething1 и doSomething2 работают как положено. Тест doSomething3 не проходит с

Failed to load ApplicationContext
java.lang.IllegalStateException: Failed to load ApplicationContext
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124)
    at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:83)
    at org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener.prepareTestInstance(SpringBootDependencyInjectionTestExecutionListener.java:47)
    at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:230)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:228)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:287)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:289)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:247)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.runTestClass(JUnitTestClassExecuter.java:114)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.execute(JUnitTestClassExecuter.java:57)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassProcessor.processTestClass(JUnitTestClassProcessor.java:66)
    at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32)
    at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)
    at com.sun.proxy.$Proxy1.processTestClass(Unknown Source)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:109)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:146)
    at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:128)
    at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:404)
    at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:63)
    at org.gradle.internal.concurrent.StoppableExecutorImpl$1.run(StoppableExecutorImpl.java:46)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:55)
    at java.lang.Thread.run(Thread.java:748)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'assetRepository': Invocation of init method failed; nested exception is org.springframework.data.mapping.PropertyReferenceException: No property type found for type Asset!
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1628)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:555)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:742)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:693)
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:360)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:303)
    at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:120)
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:98)
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:116)
    ... 47 more
Caused by: org.springframework.data.mapping.PropertyReferenceException: No property type found for type Asset!
    at org.springframework.data.mapping.PropertyPath.<init>(PropertyPath.java:77)
    at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:329)
    at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:309)
    at org.springframework.data.mapping.PropertyPath.from(PropertyPath.java:272)
    at org.springframework.data.mapping.PropertyPath.from(PropertyPath.java:243)
    at org.springframework.data.repository.query.parser.Part.<init>(Part.java:76)
    at org.springframework.data.repository.query.parser.PartTree$OrPart.<init>(PartTree.java:247)
    at org.springframework.data.repository.query.parser.PartTree$Predicate.buildTree(PartTree.java:398)
    at org.springframework.data.repository.query.parser.PartTree$Predicate.<init>(PartTree.java:378)
    at org.springframework.data.repository.query.parser.PartTree.<init>(PartTree.java:89)
    at org.springframework.data.jpa.repository.query.PartTreeJpaQuery.<init>(PartTreeJpaQuery.java:64)
    at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:103)
    at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateIfNotFoundQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:214)
    at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$AbstractQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:77)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.<init>(RepositoryFactorySupport.java:436)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:221)
    at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.initAndReturn(RepositoryFactoryBeanSupport.java:277)
    at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:263)
    at org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean.afterPropertiesSet(JpaRepositoryFactoryBean.java:101)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1687)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1624)
    ... 62 more

Кажется, это не удается из-за Caused by: org.springframework.data.mapping.PropertyReferenceException: No property type found for type Asset!.

Мой вопрос: могу ли я в этом случае обойти пользовательские запросы или querydsl? Как вы можете видеть, я уже заработал с помощью @Query("select a from Asset a where type = ?1"), но это не использует автоматический Spring Boots... Допустим, у меня есть больше типов активов, мне кажется, что запрос для определенного типа довольно громоздкий, так как мне нужно будет создать экземпляр соответствующий тип объекта для каждого запроса, который я хочу сделать, например:

@Autowire
private ModelRepository mr;

...
List<Model> models = mr.findAll();

Было бы намного проще сделать что-то вроде этого:

@Autowire
private AssetRepository ar;

...
List<Model> models = ar.findAllByType("models");

Должен ли я использовать EntityManager для запроса? Есть ли лучший способ, чем @Query? Должен ли я просто создать таблицу «Актив» и использовать столбец типа «тип» вместо определения всех сущностей?


person maiksensi    schedule 04.07.2017    source источник


Ответы (1)


в свой класс активов вы не включили тип столбца. Таким образом, ваш класс должен выглядеть примерно так

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
private String type;

public long getId() {
    return id;
}

public void setId(long id) {
    this.id = id;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public String getType() {
    return type;
}

public void setType(String type) {
    this.type = type;
}

Дайте мне знать, если это решило вашу проблему.

person Periklis Douvitsas    schedule 04.07.2017
comment
Оно делает. Я добавил @Column(insertable = false, updateable = false) частный тип String; но это хорошая практика? Что вы думаете о моем другом вопросе: должен ли я просто создать таблицу активов и использовать столбец, подобный типу, вместо определения всех сущностей? - person maiksensi; 04.07.2017
comment
Если вы хотите иметь разные классы для каждого типа, вы можете это сделать (если это удобно для нужд вашего бизнеса). В то же время у вас может быть тип столбца в активе сущности, и вы можете определить все сущности, которые расширяют актив. Теперь, когда вы определяете List‹Asset› models = ar.findAllByType(models); список, который будет возвращен, на самом деле будет списком модели. Вам не нужно вызывать mr.findAll() для каждого другого типа активов. Вы можете использовать List‹Asset› models = ar.findAllByType(models); который возвращает фактически модели List‹Asset› models = ar.findAllByType(test1); который возвращает test1 и т. д. - person Periklis Douvitsas; 04.07.2017