在一个新项目中,我们希望使用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
我没有发现任何仓库依赖于接口而不是具体类型的例子,所以这是可能的吗?如果是,怎么做?
看起来,如果我们不能使用接口来声明存储库,那么使用这些接口将非常困难,因为我们将在服务中到处使用显式强制转换,甚至在处理泛型(List
, Iterable
…)时使用未检查的强制转换。
这是解决你问题的办法。我不知道为什么Spring的人决定把他们的存储库建立在具体的类上。但至少他们让改变这一切成为可能。
- 您需要在
EnableJpaRepositories
中提供自定义repositoryFactoryBeanClass
,例如:
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); } }
- 最后但并非最不重要的是自定义存储库bean工厂,它试图将存储库本身定义的接口与
EntityManager
上注册的实体类相匹配。
不要因为我的任何错误而责备我。在看完这个问题后,我在10分钟内完成了这个问题,并意识到还没有解决方案。我真的需要一个。我没有为它创建任何测试,但似乎工作。我们欢迎改进。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); } }
- 最后但并非最不重要的是自定义存储库bean工厂,它试图将存储库本身定义的接口与
我遇到了同样的问题,并使用@NoRepositoryBean
在使用接口而不是具体类的存储库接口上解决了这个问题(感谢那篇博客文章):
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
中存在。
在@goroncy的回复之后,我还必须重写方法getRepositoryMetadata
@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;
}
我添加了对基于实体的存储库的兼容性支持,而不仅仅是接口。默认启用
private boolean allowNonInterfaceTypes = true;
可以通过调用InterfaceBasedJpaRepositoryFactoryBean
中的2参数构造函数来禁用它@Override
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
return new InterfaceBasedJpaRepositoryFactory(entityManager, false);
}
完整的类保持为
import java.lang.reflect.Field;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;
import javax.persistence.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.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments;
import org.springframework.util.Assert;
public class InterfaceBasedJpaRepositoryFactory extends JpaRepositoryFactory {
private final Map<? extends Class<?>, ? extends Class<?>> interfaceToEntityClassMap;
private final EntityManager entityManager;
private boolean allowNonInterfaceTypes = true;
public InterfaceBasedJpaRepositoryFactory(EntityManager entityManager) {
super(entityManager);
this.entityManager = entityManager;
this.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));
}
public InterfaceBasedJpaRepositoryFactory(EntityManager entityManager, boolean paramAllowNonInterfaceTypes) {
this(entityManager);
this.allowNonInterfaceTypes = paramAllowNonInterfaceTypes;
}
@Override
@SuppressWarnings("unchecked")
public <T, ID> JpaEntityInformation<T, ID> getEntityInformation(Class<T> domainClass) {
JpaEntityInformation<T, ID> ret = null;
// Interface entities
if (this.allowNonInterfaceTypes == false) {
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 = this.interfaceToEntityClassMap.get(domainInterface);
Assert.notNull(entityClass, String.format("Entity class for a interface %s not found!", domainInterface));
ret = (JpaEntityInformation<T, ID>) JpaEntityInformationSupport.getEntityInformation(entityClass, entityManager);
}else {
// Class entities
ret = super.getEntityInformation(domainClass);
}
return ret;
}
@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;
}
}
它为我工作与spring-data-jpa 2.0.8。RELEASE来自spring-boot-start -data-jpa 2.0.3.RELEASE
如果有什么错误,我很抱歉,我很久以前就做过了。
Person接口缺少@Entity注释,因此不能识别为托管对象。我认为将@Entity注释放在Person接口上也没有帮助,因为该注释不是继承的。我认为你应该忘记Person接口,或者在存储库声明中使用PersonEntity。没有实际检查代码-所以非常抱歉,如果这个答案是错误的…