Генерация репозитория Spring и соглашение об именах свойств
Я использую javax.persistence
и Spring, и у меня есть простой класс сущности и простой CrudRepository
которые работают хорошо, пока я не попытаюсь добавить свой собственный findByXXX()
метод.
Мой класс сущности выглядит следующим образом:
@Entity
public class Node
{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column (name = "ID")
private Integer mID;
/** The node's activity. */
@Column (name = "Activity", nullable = false, length = 200)
private String mActivity;
// ... other properties.
public Integer getId() { return mID; }
public void setId(Integer id) { mID = id; }
public String getActivity() { return mActivity; }
public void setActivity(String activity) { mActivity = activity; }
...
}
Вы заметите, что я использую соглашение об именовании префикса 'm' в свойствах экземпляра. Это нормально работает с моим хранилищем, пока я не добавлю свой собственный findByXXX()
методы. Так что я могу сохранить и восстановить Node
из хранилища данных, используя стандартные методы CRUD просто отлично.
Когда я пытаюсь добавить свой собственный метод запроса, таким образом:
public interface NodeRepository extends CrudRepository<Node, Integer>
{
public List<Node> findByActivity(String activity);
}
моя система ломается. В частности, автоматическое подключение моего хранилища завершается неудачно (см. Трассировку стека в конце этого поста). Больше по счастливой случайности, чем по дизайну, я обнаружил, что если я изменил свой класс узла, чтобы больше не использовать префикс "m" в свойствах, проблема исчезла. То есть работает следующий код:
@Entity
public class Node
{
...
/** The node's activity. */
@Column (name = "Activity", nullable = false, length = 200)
private String activity;
// ... other properties.
public String getActivity() { return activity; }
public void setActivity(final String activity) { this.activity = activity; }
...
}
Казалось бы, когда весна строит findByActivity()
Метод поиска Node
бин по имени свойства (например, activity
), а не свойства методов (например, getActivity(), setActivity()
).
Кто-нибудь знает, так ли это на самом деле? Если так, есть ли способ обойти это, чтобы я мог придерживаться соглашения об именах? Я не привязан к этому, это просто то, к чему я привык.
PS: я использую Spring 3.2.x и Java 7
ИЗМЕНЕНО для добавления трассировки стека - программа работает изнутри SpringJUnit4ClassRunner:
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@32f2e8ca: defining beans [nodeRepository,org.springframework.data.repository.core.support.RepositoryInterfaceAwareBeanPostProcessor#0,org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor#0,dataSource,entityManagerFactory,transactionManager,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.annotation.internalPersistenceAnnotationProcessor,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor]; root of factory hierarchy
Nov 18, 2013 6:59:10 PM org.springframework.test.context.TestContextManager prepareTestInstance
SEVERE: Caught exception while allowing TestExecutionListener [org.springframework.test.context.support.DependencyInjectionTestExecutionListener@57b3fe3c] to prepare test instance [com.example.persistence.JPAXMLCfgTest@69f8421f]
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.example.persistence.JPAXMLCfgTest': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.example.persistence.NodeRepository com.example.persistence.JPAXMLCfgTest.mRepo; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'nodeRepository': FactoryBean threw exception on object creation; nested exception is java.lang.NullPointerException
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:287)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1106)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireBeanProperties(AbstractAutowireCapableBeanFactory.java:374)
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:110)
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:75)
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:312)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:211)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:288)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:284)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:231)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:88)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:174)
at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:252)
at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:141)
at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:112)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:189)
at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:165)
at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85)
at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:115)
at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:75)
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.example.persistence.NodeRepository com.example.persistence.JPAXMLCfgTest.mRepo; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'nodeRepository': FactoryBean threw exception on object creation; nested exception is java.lang.NullPointerException
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:513)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:92)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:284)
... 32 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'nodeRepository': FactoryBean threw exception on object creation; nested exception is java.lang.NullPointerException
at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:149)
at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:102)
at org.springframework.beans.factory.support.AbstractBeanFactory.getObjectForBeanInstance(AbstractBeanFactory.java:1442)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:248)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:871)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:813)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:730)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:485)
... 34 more
Caused by: java.lang.NullPointerException
at org.springframework.data.jpa.repository.query.QueryUtils.isEntityPath(QueryUtils.java:462)
at org.springframework.data.jpa.repository.query.QueryUtils.toExpressionRecursively(QueryUtils.java:445)
at org.springframework.data.jpa.repository.query.JpaQueryCreator$PredicateBuilder.build(JpaQueryCreator.java:197)
at org.springframework.data.jpa.repository.query.JpaQueryCreator.toPredicate(JpaQueryCreator.java:144)
at org.springframework.data.jpa.repository.query.JpaQueryCreator.create(JpaQueryCreator.java:86)
at org.springframework.data.jpa.repository.query.JpaQueryCreator.create(JpaQueryCreator.java:44)
at org.springframework.data.repository.query.parser.AbstractQueryCreator.createCriteria(AbstractQueryCreator.java:109)
at org.springframework.data.repository.query.parser.AbstractQueryCreator.createQuery(AbstractQueryCreator.java:88)
at org.springframework.data.repository.query.parser.AbstractQueryCreator.createQuery(AbstractQueryCreator.java:73)
at org.springframework.data.jpa.repository.query.PartTreeJpaQuery$QueryPreparer.<init>(PartTreeJpaQuery.java:98)
at org.springframework.data.jpa.repository.query.PartTreeJpaQuery$CountQueryPreparer.<init>(PartTreeJpaQuery.java:166)
at org.springframework.data.jpa.repository.query.PartTreeJpaQuery.<init>(PartTreeJpaQuery.java:60)
at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:90)
at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateIfNotFoundQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:162)
at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$AbstractQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:68)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.<init>(RepositoryFactorySupport.java:290)
at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:158)
at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.getObject(RepositoryFactoryBeanSupport.java:162)
at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.getObject(RepositoryFactoryBeanSupport.java:44)
at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:142)
... 42 more
2 ответа
Смотрите здесь документацию о том, как решаются запросы.
http://docs.spring.io/spring-data/jpa/docs/1.4.2.RELEASE/reference/html/repositories.html
так не хватает названия вашего метода:
public List<Node> findByMActivity(String activity);
или перемещение ваших отображений в методы, а не в поля, единственный очевидный способ, который я вижу, - это на самом деле аннотировать этот метод с помощью @Query или определять именованный запрос в другом месте, на которое этот метод будет ссылаться.
@Query("my custom query")
public List<Node> findByActivity(String activity);
Чтобы сделать это более общим способом, я полагаю, вы бы смотрели на переопределение или передачу пользовательского QueryResolver:
org.springframework.data.jpa.repository.query.JpaQueryMethod
Во всяком случае, все кажется большим хлопотом только из-за того, что ваши поля имеют префикс "m".
Хотя мне нравится ответ @Alan Hay, другой вариант - добавить слой "services" в ваше решение. Я делал это в прошлом, чтобы создать интерфейс для операций CRUD на моем уровне моделей.
Вы можете создать класс служб с именем нужного вам метода get, т.е. findByActivity
вызывая класс хранилища findByMActivity(...
метод.
Это также позволяет вам иметь слой моделей со всеми доступными методами CRUD, и объект служб будет использовать только те операции CRUD, которые относятся к этой конкретной службе, то есть ActivityService. По сути, вы можете создать несколько интерфейсов для одних и тех же данных в зависимости от действий, выполняемых с этими данными.
Тогда ваш класс сервисного уровня может иметь любые имена методов, которые будут иметь смысл для пользователя, то есть findByActivity против findByMActivity. Мартин Фаулер хорошо описывает уровень услуг.
Ваше решение может не вызывать дополнительный уровень, но это может быть дополнительная абстракция, которая может решить такие проблемы с интерфейсом, как это, где было бы дороже изменить имя переменной домена.