如何基于实体接口声明存储库?

在一个新项目中,我们希望使用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 { } 

Spring上下文无法初始化,原因是exception

 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.(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的任何示例,所以这有可能吗? 如果有,怎么样?

似乎如果我们不能使用接口声明存储库,那么根本不会使用这些接口,因为我们最终会在我们的服务中到处都有显式的强制转换,甚至在我们处理generics时都会进行未经检查的强制转换( ListIterable ……)。

这是您的问题的解决方案。 我不知道为什么Spring的人决定将他们的存储库建立在具体的类上。 但至少他们有可能改变这种状况。

  1. 您需要在EnableJpaRepositories提供自定义repositoryFactoryBeanClass ,例如:
 import org.springframework.data.jpa.repository.config.EnableJpaRepositories; /** * @author goraczka */ @EnableJpaRepositories( repositoryFactoryBeanClass = InterfaceBasedJpaRepositoryFactoryBean.class ) public class DatabaseConfig { } 
  1. 然后,您需要实现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, S, ID> extends JpaRepositoryFactoryBean { public InterfaceBasedJpaRepositoryFactoryBean(Class repositoryInterface) { super(repositoryInterface); } protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) { return new InterfaceBasedJpaRepositoryFactory(entityManager); } } 
  1. 最后但并非最不重要的是,自定义存储库bean工厂尝试将存储库本身定义的接口与在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> 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  JpaEntityInformation getEntityInformation(Class domainClass) { Assert.isTrue(domainClass.isInterface(), "You are using interface based jpa repository support. " + "The entity type used in DAO should be an interface"); Class domainInterface = domainClass; Class entityClass = interfaceToEntityClassMap.get(domainInterface); Assert.notNull(entityClass, "Entity class for a interface" + domainInterface + " not found!"); return (JpaEntityInformation) JpaEntityInformationSupport.getEntityInformation(entityClass, entityManager); } } 

不要因为任何错误而抨击我。 我在阅读完这个问题后10分钟就做到了,并意识到目前尚无解决方案。 我真的需要一个。 我没有为它创建任何测试,但似乎工作。 欢迎改进。

接口Person缺少@Entity注释,因此不会被识别为托管对象。 我认为将@Entity注释放在Person接口上也无济于事,因为这个注释不会被inheritance。 我想你应该忘记Person接口,或者只是在存储库声明中使用PersonEntity。 实际上没有检查代码 – 如果这个答案是错误的,那就非常抱歉……