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

Did*_*r L 10 java hibernate spring-data-jpa

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

  // ...
}
Run Code Online (Sandbox Code Playgroud)

但是,在声明基于类似接口的Spring Data存储库时

public interface PersonRepository extends JpaRepository<Person, Long> {

}
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

我没有找到任何依赖于接口而不是具体类型的Repository的例子,所以这有可能吗?如果是,怎么样?

似乎如果我们不能使用接口来声明存储库,那么根本不可能使用这些接口,因为我们最终会在服务中的每个地方都使用显式强制转换,甚至在我们处理泛型时立即进行未经检查的强制转换(List,Iterable......).

gor*_*ncy 6

这是您的问题的解决方案。我不知道为什么Spring家伙决定将其存储库建立在具体的类上。但是至少他们使改变它成为可能。

  1. 您需要提供自定义repositoryFactoryBeanClassEnableJpaRepositories例如:
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

/**
 * @author goraczka
 */
@EnableJpaRepositories(
    repositoryFactoryBeanClass = InterfaceBasedJpaRepositoryFactoryBean.class
)
public class DatabaseConfig {
}
Run Code Online (Sandbox Code Playgroud)
  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<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);
    }
}
Run Code Online (Sandbox Code Playgroud)
  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<?>, ? 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

不要因为任何错误而羞辱我。阅读此问题并意识到尚无解决方案后,我在10分钟内完成了操作。我真的需要一个。我尚未为此进行任何测试,但似乎可以工作。欢迎改进。


Ant*_* O. 6

我遇到了同样的问题,并使用@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
}
Run Code Online (Sandbox Code Playgroud)

然后,使用扩展另一个的具体存储库:

public interface PersonEntityRepository extends PersonRepository<PersonEntity> {
    // code all specific methods that use fields in PersonEntity class
}
Run Code Online (Sandbox Code Playgroud)

该注释至少存在于spring-data-commons-2.1.9.RELEASE.jar.


shl*_*i33 -2

接口 Person 缺少 @Entity 注释,因此不被识别为托管对象。我认为将 @Entity 注释放在 Person 接口上也无济于事,因为该注释不是继承的。我认为您应该忘记 Person 接口,或者只在存储库声明中使用 PersonEntity 。实际上并没有签入代码 - 如果这个答案是错误的,那么非常抱歉......

  • 我的意思是,JPA 允许在任何地方使用接口,包括对于关系,您可以使用“targetEntity”告诉 JPA 具体类,同时仅使用接口声明字段。 (2认同)