在一个新项目中,我们希望使用Spring Data JPA并为所有JPA实体定义接口,例如:像这样:
public interface Person extends Serializable {
void setId(Long id);
Long getId();
void setLastName(String lastName);
String getLastName();
void setFirstName(String firstName);
String getFirstName();
// ...
}
@Entity
@Table(name = "t_persons")
public class PersonEntity implements Person {
private static final long serialVersionUID = 1L;
@Id
@Column
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column
private String firstName;
@Column
private String lastName;
// ...
}
但是,在基于类似
的界面声明Spring Data存储库时public interface PersonRepository extends JpaRepository<Person, Long> {
}
Spring上下文无法初始化,其原因是
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'personRepository': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Not an managed type: interface com.example.Person
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1513)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:521)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:458)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:293)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:223)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:290)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:191)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:917)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:860)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:775)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:489)
... 24 more
Caused by: java.lang.IllegalArgumentException: Not an managed type: interface com.example.Person
at org.hibernate.ejb.metamodel.MetamodelImpl.managedType(MetamodelImpl.java:171)
at org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation.<init>(JpaMetamodelEntityInformation.java:70)
at org.springframework.data.jpa.repository.support.JpaEntityInformationSupport.getMetadata(JpaEntityInformationSupport.java:65)
at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getEntityInformation(JpaRepositoryFactory.java:146)
at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getTargetRepository(JpaRepositoryFactory.java:84)
at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getTargetRepository(JpaRepositoryFactory.java:67)
at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:150)
at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.initAndReturn(RepositoryFactoryBeanSupport.java:224)
at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:210)
at org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean.afterPropertiesSet(JpaRepositoryFactoryBean.java:84)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1572)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1510)
... 34 more
我没有找到任何依赖于接口而不是具体类型的Repository的例子,所以这有可能吗?如果是的话,怎么样?
似乎如果我们不能使用接口来声明存储库,那么根本不会使用这些接口,因为我们最终会在我们的服务中到处都有显式的转换,甚至我们只要未经检查的转换处理泛型(List
,Iterable
...)。
答案 0 :(得分:5)
这是您的问题的解决方案。我不知道为什么Spring的人决定将他们的存储库建立在具体的类上。但至少他们可以改变这种状况。
repositoryFactoryBeanClass
中提供自定义EnableJpaRepositories
,例如类似的东西:import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
/**
* @author goraczka
*/
@EnableJpaRepositories(
repositoryFactoryBeanClass = InterfaceBasedJpaRepositoryFactoryBean.class
)
public class DatabaseConfig {
}
InterfaceBasedJpaRepositoryFactoryBean
。它是一个Spring钩子,可以为存储库bean创建自定义工厂。import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import javax.persistence.EntityManager;
/**
* @author goraczka
*/
public class InterfaceBasedJpaRepositoryFactoryBean<T extends Repository<S, ID>, S, ID>
extends JpaRepositoryFactoryBean<T, S, ID> {
public InterfaceBasedJpaRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
super(repositoryInterface);
}
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
return new InterfaceBasedJpaRepositoryFactory(entityManager);
}
}
EntityManager
上注册的实体类进行匹配。import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.JpaEntityInformationSupport;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
import org.springframework.util.Assert;
import javax.persistence.EntityManager;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author goraczka
*/
public class InterfaceBasedJpaRepositoryFactory extends JpaRepositoryFactory {
private final Map<? extends Class<?>, ? extends Class<?>> interfaceToEntityClassMap;
private final EntityManager entityManager;
public InterfaceBasedJpaRepositoryFactory(EntityManager entityManager) {
super(entityManager);
this.entityManager = entityManager;
interfaceToEntityClassMap = entityManager
.getMetamodel()
.getEntities()
.stream()
.flatMap(et -> Arrays.stream(et.getJavaType().getInterfaces())
.map(it -> new AbstractMap.SimpleImmutableEntry<>(it, et.getJavaType())))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (possibleDuplicateInterface, v) -> v));
}
@Override
@SuppressWarnings("unchecked")
public <T, ID> JpaEntityInformation<T, ID> getEntityInformation(Class<T> domainClass) {
Assert.isTrue(domainClass.isInterface(), "You are using interface based jpa repository support. " +
"The entity type used in DAO should be an interface");
Class<T> domainInterface = domainClass;
Class<?> entityClass = interfaceToEntityClassMap.get(domainInterface);
Assert.notNull(entityClass, "Entity class for a interface" + domainInterface + " not found!");
return (JpaEntityInformation<T, ID>) JpaEntityInformationSupport.getEntityInformation(entityClass, entityManager);
}
}
不要因为任何错误而抨击我。我在阅读完这个问题后10分钟就做到了,并意识到目前尚无解决方案。我真的需要一个。我没有为它创建任何测试,但似乎工作。欢迎改进。
答案 1 :(得分:1)
在回复@goroncy之后,我还必须重写方法 getRepositoryMetadata
let params = ["clients" : convertedString, "submitted" : 1]
...
Alamofire.request(url + req_task, method: .put, parameters: params, encoding: JSONEncoding(options: []), headers:headers).responseJSON { response in
...
我增加了对基于实体的存储库的兼容性支持,而不仅仅是基于接口。默认情况下启用。
@Override
protected RepositoryMetadata getRepositoryMetadata(Class<?> repositoryInterface) {
RepositoryMetadata ret = super.getRepositoryMetadata(repositoryInterface);
Class<?> clazz = ret.getClass();
try {
Field f = clazz.getDeclaredField("domainType");
boolean isAccessible = f.isAccessible();
f.setAccessible(true);
Class<?> actualValue = (Class<?>) f.get(ret);
Class<?> newValue = this.interfaceToEntityClassMap.get(actualValue);
f.set(ret, newValue);
f.setAccessible(isAccessible);
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
return ret;
}
您可以通过从 InterfaceBasedJpaRepositoryFactoryBean
调用2参数构造函数来禁用它private boolean allowNonInterfaceTypes = true;
完整课程仍为
@Override
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
return new InterfaceBasedJpaRepositoryFactory(entityManager, false);
}
它对我来说适用于spring-data-jpa 2.0.8.RELEASE,摘自spring-boot-starter-data-jpa 2.0.3.RELEASE
对不起,如果有任何错误,我是在前一段时间做的。
答案 2 :(得分:0)
我遇到了同样的问题,并在存储库接口上使用@NoRepositoryBean
解决了这个问题,该接口使用的是接口而不是具体的类(感谢that blog post):
import org.springframework.data.repository.NoRepositoryBean;
@NoRepositoryBean
public interface PersonRepository<P extends Person> extends JpaRepository<P, Long> {
// code all generic methods using fields in Person interface
}
然后,使用扩展其他内容的具体存储库:
public interface PersonEntityRepository extends PersonRepository<PersonEntity> {
// code all specific methods that use fields in PersonEntity class
}
此注释至少存在于spring-data-commons-2.1.9.RELEASE.jar
中。
答案 3 :(得分:-2)
接口Person缺少@Entity注释,因此不会被识别为托管对象。 我认为将@Entity注释放在Person接口上也无济于事,因为这个注释不会被继承。我想你应该忘记Person接口,或者只是在存储库声明中使用PersonEntity。 实际上没有检查代码 - 如果这个答案是错误的,那就非常抱歉...