如何基于实体接口声明存储库? [英] How to declare repositories based on entity interfaces?

查看:41
本文介绍了如何基于实体接口声明存储库?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在一个新项目中,我们想使用Spring Data JPA并为所有JPA实体定义接口,例如像这样:

In a new project we would like to use Spring Data JPA and define interfaces for all JPA entities, e.g. like this:

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存储库时

However, when declaring a Spring Data repository based on the interface like

public interface PersonRepository extends JpaRepository<Person, Long> {

}

Spring上下文无法初始化,且异常的原因是

The Spring context fails to initialize with an exception whose cause is

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示例,所以这有可能吗?如果可以,怎么办?

I haven't found any example of Repository relying on the interface instead of the concrete type so is this possible at all? And if yes, how?

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

It seems that if we cannot use the interface to declare the repositories, then it will be very difficult to use those interfaces at all since we will end up with explicit casts everywhere in our services, and even unchecked casts as soon as we deal with generics (List, Iterable…).

推荐答案

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

Here is a solution to your problem. I don't know why Spring guys decided to base their repositories on concrete classes. But at least they made it possible to change that.

  1. 您需要在EnableJpaRepositories中提供自定义的repositoryFactoryBeanClass,例如像这样的东西:
  1. You need to provide custom repositoryFactoryBeanClass in EnableJpaRepositories e.g. something like that:

import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

/**
 * @author goraczka
 */
@EnableJpaRepositories(
    repositoryFactoryBeanClass = InterfaceBasedJpaRepositoryFactoryBean.class
)
public class DatabaseConfig {
}

  1. 然后您需要实现InterfaceBasedJpaRepositoryFactoryBean.这是一个Spring挂钩,可用于为存储库bean创建自定义工厂.
  1. Then you need to implement InterfaceBasedJpaRepositoryFactoryBean. It is a Spring hook to enable creating a custom factory for repository beans.

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上注册的实体类进行匹配.
  1. And last but not least the custom repository bean factory that tries to match interface defined on the repository itself with a entity class registered on 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分钟内完成了该操作.我真的需要一个.我尚未为此进行任何测试,但似乎可以正常工作.欢迎改进.

Don't bash me for any mistakes. I did it in 10 minutes after reading this question and realizing that there is no solution for it yet. And I really needed one. I did not create any tests for it yet but seems to work. Improvements are welcomed.

这篇关于如何基于实体接口声明存储库?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆