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

2022-09-03 15:13:51

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

我还没有找到任何依赖于接口而不是具体类型的存储库的示例,所以这有可能吗?如果是,如何?

似乎如果我们不能使用接口来声明存储库,那么使用这些接口将非常困难,因为我们最终会在服务中到处都有显式强制转换,甚至在我们处理泛型(,...)时立即进行未经检查的强制转换。ListIterable


答案 1

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

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

/**
 * @author goraczka
 */
@EnableJpaRepositories(
    repositoryFactoryBeanClass = InterfaceBasedJpaRepositoryFactoryBean.class
)
public class DatabaseConfig {
}
  1. 然后,您需要实现 .它是一个 Spring 钩子,用于为存储库 Bean 创建自定义工厂。InterfaceBasedJpaRepositoryFactoryBean
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);
    }
}
  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);
    }
}

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


答案 2

我遇到了同样的问题,并使用存储库接口解决了它,该接口使用接口而不是以这种方式使用具体类(感谢那篇博客文章):@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


推荐