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

时间:2014-08-07 16:22:43

标签: 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;

  // ...
}

但是,在基于类似

的界面声明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

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

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

4 个答案:

答案 0 :(得分:5)

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

  1. 您需要在repositoryFactoryBeanClass中提供自定义EnableJpaRepositories,例如类似的东西:
  2. import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
    
    /**
     * @author goraczka
     */
    @EnableJpaRepositories(
        repositoryFactoryBeanClass = InterfaceBasedJpaRepositoryFactoryBean.class
    )
    public class DatabaseConfig {
    }
    
    1. 然后您需要实施InterfaceBasedJpaRepositoryFactoryBean。它是一个Spring钩子,可以为存储库bean创建自定义工厂。
    2. 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上注册的实体类进行匹配。
      2. 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分钟就做到了,并意识到目前尚无解决方案。我真的需要一个。我没有为它创建任何测试,但似乎工作。欢迎改进。

答案 1 :(得分:1)

在回复@goroncy之后,我还必须重写方法 getRepositoryMetadata

let params = ["clients" : convertedString, "submitted" : 1]
...
Alamofire.request(url + req_task, method: .put, parameters: params, encoding: JSONEncoding(options: []), headers:headers).responseJSON { response in
...

我增加了对基于实体的存储库的兼容性支持,而不仅仅是基于接口。默认情况下启用。

@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;
}

您可以通过从 InterfaceBasedJpaRepositoryFactoryBean

调用2参数构造函数来禁用它
private boolean allowNonInterfaceTypes = true;

完整课程仍为

@Override
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
    return new InterfaceBasedJpaRepositoryFactory(entityManager, false);
}

它对我来说适用于spring-data-jpa 2.0.8.RELEASE,摘自spring-boot-starter-data-jpa 2.0.3.RELEASE

对不起,如果有任何错误,我是在前一段时间做的。

答案 2 :(得分:0)

我遇到了同样的问题,并在存储库接口上使用@NoRepositoryBean解决了这个问题,该接口使用的是接口而不是具体的类(感谢that blog post):

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中。

答案 3 :(得分:-2)

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