【问题标题】:Spring Data: Override save methodSpring Data:覆盖保存方法
【发布时间】:2012-10-13 17:20:19
【问题描述】:

我正在考虑一个项目的弹簧数据。是否可以覆盖每个默认生成的保存方法?如果是,如何?

【问题讨论】:

  • 你想达到什么目的?也许 AOP 是更好的方法?
  • 不要使用id来判断相关实体是否是新的。该实体是不可变的,这意味着如果用户更改它,系统应该使用更改的数据创建一个新的(或使用与该数据相等的现有实体。这是我唯一关心的问题,否则 spring data + querydsl 看起来对我的项目很有希望。
  • 如何实现Persistable?这对你有用吗?您还在使用 Spring Data JPA 或其他一些后端数据库吗?
  • 将是 Spring 数据 JPA。我的所有实体都已正确注释。我唯一关心的是这个问题。如何覆盖 Crude 或 Jpa 存储库的保存方法?

标签: java spring-data


【解决方案1】:

没有让它很好地工作,所以我将我需要的逻辑放入一个服务类中,并保持存储库保存方法不变。

【讨论】:

    【解决方案2】:

    我猜你扩展了 SimpleJpaRepository :

    public class **CustomSimpleJpaRepository** extends SimpleJpaRepository {
    
    @Transactional
    public <S extends T> S save(S entity) { //do what you want instead }
    }
    

    然后通过扩展确保使用它而不是默认的 SimpleJpaRepository:

    public class CustomJpaRepositoryFactory extends JpaRepositoryFactory {
    
        protected <T, ID extends Serializable> JpaRepository<?, ?> getTargetRepository(RepositoryMetadata metadata, EntityManager entityManager) {
    
          Class<?> repositoryInterface = metadata.getRepositoryInterface();
          JpaEntityInformation<?, Serializable> entityInformation = getEntityInformation(metadata.getDomainType());
    
          SimpleJpaRepository<?, ?> repo = isQueryDslExecutor(repositoryInterface) ? new QueryDslJpaRepository(
                entityInformation, entityManager) : new CustomSimpleJpaRepository(entityInformation, entityManager);
        repo.setLockMetadataProvider(lockModePostProcessor.getLockMetadataProvider());
    
          return repo;
      }
    }
    

    还没有完成,我们还需要有自己的工厂 bean 才能在 config xml 中使用它:

    public class CustomRepositoryFactoryBean <T extends JpaRepository<S, ID>, S, ID extends Serializable> extends JpaRepositoryFactoryBean<T, S, ID> {
    
    protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
        return new **CustomJpaRepositoryFactory**(entityManager);
    }
    

    }

    配置:

    <jpa:repositories base-package="bla.bla.dao" factory-class="xxxx.**CustomRepositoryFactoryBean**"/>
    

    希望对你有帮助。

    【讨论】:

    • isQueryDslExecutor() 方法似乎是私有代码,LockModePostProcessor 类不存在。我确实有LockModeRepositoryPostProcessor,但是使用相同的方法。这是你的意思吗?
    【解决方案3】:

    使用 JPA 事件侦听器,例如 @PrePersist、@PreUpdate。如果底层 JPA 提供程序支持此功能,这将起作用。这是 JPA 2 的特性,所以最新的 Hibernate、EclipseLink 等应该支持它。

    【讨论】:

    • 这不起作用,因为需要运行的逻辑/代码必须访问数据库,我不想在我的实体类中引用数据访问层。
    • 有几个问题。首先,这些注解可能具有提供者特定的行为;其次,他们能做的事情非常有限;第三,这并没有回答操作问题,因为它与弹簧数据无关;最后,这些注释都没有用于覆盖 save 方法的目的,即使是 JPA 的 persist 方法也没有任何价值。这个答案应该被广泛反对。
    【解决方案4】:

    像往常一样简单地创建您的自定义接口,并在那里声明您想要覆盖的方法,并使用与CrudRepository(或JpaRepository等)公开的方法相同的签名。假设您有一个 MyEntity 实体和一个 MyEntityRepository 存储库,并且您想要覆盖默认自动生成的 save 方法 MyEntityRepository 采用唯一的实体实例,然后定义:

    public interface MyEntityRepositoryCustom {
      <S extends MyEntity> S save(S entity);
    }
    

    并像往常一样在您的MyEntityRepositoryImpl 中实现此方法:

    @Transactional
    public class MyEntityRepositoryImpl implements MyEntityRepositoryCustom {
      public <S extends MyEntity> S save(S entity) {
        // your implementation
      }
    }
    

    然后,像往常一样,让MyEntityRepository 扩展MyEntityRepositoryCustom

    这样做,Spring Data JPA 将调用您的 MyEntityRepositoryImplsave 方法而不是默认实现。 至少这适用于 Spring Data JPA 1.7.2 中的 delete 方法。


    “引用不明确”错误

    正如一些评论者所报告的那样,可能是从某个 Spring Data JPA 版本或 javac 版本开始的(我不能说它是什么时候开始失败的,但我可以肯定它之前工作过)javac 编译器开始在被覆盖的方法上给出一个编译错误:“模糊引用”。 Eclipse JDT 不返回此错误,代码在运行时有效,实际上我打开Bug 463770 的原因是:要么是javac 错误,要么是不符合javac 的JDT 错误。 这就是说,卢卡斯在下面发布了解决方法,乍一看似乎与上面描述的相同。实际上,区别在于MyEntityRepositoryCustom,声明必须包含泛型类型&lt;S&gt;,即使它显然没有用。因此,如果您遇到此错误,请将自定义接口声明更改为:

    public interface MyEntityRepositoryCustom<S> {
      <S extends MyEntity> S save(S entity);
    }
    

    并让标准存储库接口实现MyEntityRepositoryCustom&lt;MyEntity&gt; 而不仅仅是MyEntityRepositoryCustom

    但是,如果您不想生成自定义接口,您可以为save 方法2 提取一个单独的片段接口。那么解决方案如下:

    public interface MyEntityRepositoryCustom {
      // custom methods
    }
    
    public interface CustomizedSaveRepository<T> {
      <S extends T> S save(S entity);
    }
    
    @Transactional
    public class MyEntityRepositoryImpl implements MyEntityRepositoryCustom,
     CustomizedSaveRepository<MyEntity> {
      
      @Override
      public MyEntity save(MyEntity entity) {
        // your implementation for CustomizedSaveRepository#save
      }
    
      // implementations of MyEntityRepositoryCustom
    }
    

    【讨论】:

    • 这个确实有效。使它起作用的重要一点是保留命名约定。也就是说,MyEntityRepositoryImpl 类名必须像&lt;main repository interface name&gt;Implnot 那样构建,例如MyEntityRepositoryCustomImpl。在后一种情况下它不起作用。
    • 酷,但是如何从 MyEntityRepositoryImpl 调用默认的 JPARepository.save 方法?
    • @DanielPinyol 您让 Spring 在您的 MyEntityRepositoryImpl 中注入实体管理器,然后在其上调用 persist(Object),而不是默认的 JPARepository 实现。为此,您可以使用@PersistenceContext
    • 我刚收到这个错误:java:reference to save is ambiguous
    • 这在 Spring Boot 2.1.1 中不起作用,出现 ambiguous reference 错误。
    【解决方案5】:

    要覆盖默认生成的保存方法,您需要在您自己的自定义存储库实现中使用 Spring Data 存储库实现的聚合。

    存储库接口:

    public interface UserRepository extends CrudRepository<User, String>{
    
    }
    

    您的存储库实现:

    @Repository("customUserRepository")
    public class CustomUserRepository implements UserRepository {
    
        @Autowired
        @Qualifier("userRepository") // inject Spring implementation here
        private UserRepository userRepository;
    
        public User save(User user) {
            User user = userRepository.save(entity);
            // Your custom code goes here
            return user;
        }
    
        // Delegate other methods here ...
    
        @Override
        public User findOne(String s) {
            return userRepository.findOne(s);
        }
    }
    

    然后在您的服务中使用您的自定义实现:

    @Autowired
    @Qualifier("customUserRepository")
    private UserRepository userRepository;
    

    【讨论】:

    • 好一个。谢谢。一个小建议。当我尝试时,我遇到了循环依赖问题。因此,为 userRepository 添加 @Lazy 就可以解决问题了。
    • 不错 :) 我只是在原始 Spring 存储库上使用了 Qualifier,并用 @Primary 注释了我的覆盖版本以防止在任何地方使用名称!
    • @Ignat Sachivko 自您发布解决方案以来已经有几年了。尽管如此,它非常适合我的需求,我很乐意让它发挥作用。我的问题是当我运行代码时会弹出一个异常org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.lvr.tests.UserRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}。但是,IntelliJ 完全理解 bean 定义,并且从 @AutowireCustomUserRepository。你有什么想法为什么它不起作用?
    【解决方案6】:

    如果您要重用原始方法,这可能会有所帮助。只需在实现类中注入EntityManager

    public interface MyEntityRepositoryCustom {
      <S extends MyEntity> S save(S entity);
    }
    
    public class MyEntityRepositoryImpl implements MyEntityRepositoryCustom {
    
        // optionally specify unitName, if there are more than one
        @PersistenceContext(unitName = PRIMARY_ENTITY_MANAGER_FACTORY)
        private EntityManager entityManager;
    
        /**
         * @see org.springframework.data.jpa.repository.support.SimpleJpaRepository
         */
        @Transactional
        public <S extends MyEntity> S save(S entity) {
            // do your logic here
            JpaEntityInformation<MyEntity, ?> entityInformation = JpaEntityInformationSupport.getMetadata(MyEntity.class, entityManager);
            if (entityInformation.isNew(entity)) {
                entityManager.persist(entity);
                return entity;
            } else {
                return entityManager.merge(entity);
            }
        }
    }
    

    【讨论】:

    • 你的实现类应该命名为 MyEntityRepositoryCustomImpl 而不是 MyEntityRepositoryImpl。对吗?
    • 能够访问原始方法是关键。不幸的是,我遇到了一个例外Cannot resolve method getMetadata(...。我检查了代码,JpaEntityInformationSupport 类不包含这样的方法(getMetadata),至少从spring-data-jpa 2.1 开始,所以我曾经在旧版本的 Spring 中使用它:(
    【解决方案7】:

    如果您只使用接口,则可以使用默认方法对CrudRepositoryJpaRepository 进行简单的覆盖:

    
    public interface MyCustomRepository extends CrudRepository<T, ID> {
    
      @Override
      default <S extends T> S save(S entity)
      {
        throw new UnsupportedOperationException("writes not allowed");
      }
    }
    

    【讨论】:

    • 如果我想调用 super.save 怎么办?
    【解决方案8】:

    我在 OpenJDK 11 上使用 Spring Boot 2.1.4 并且还不断从编译器收到 ambiguous reference 错误(尽管我的 IDE 使用的 Eclipse JDT 编译器没有问题,所以我没有发现这一点直到我尝试在我的 IDE 之外构建它)。

    我基本上最终在我的扩展接口中定义了一个具有不同名称的方法,然后在调用普通的save() 时在我的主存储库接口中使用default 覆盖来调用它。

    这是一个例子:

    像往常一样为您的自定义逻辑定义接口:

    public interface MyEntityRepositoryCustomSaveAction {
        public MyEntity saveSafely(MyEntity entity);
    }
    

    让您的存储库扩展该接口:

    public interface MyEntityRepository extends JpaRepository<MyEntity, MyEntityId>,
      MyEntityRepositoryCustomSaveAction {
    
        @Override
        @SuppressWarnings("unchecked")
        default MyEntity save(MyEntity entity)
        {
            return saveSafely(entity);
        }
    }
    

    请注意,我们已经从 JpaRepository 覆盖了 save()(嗯,真的是 CrudRepositoryJpaRepository 扩展了)来调用我们的自定义方法。编译器会针对未经检查的转换发出警告,因此您是否想使用 @SuppressWarnings 将其静音。

    使用您的自定义逻辑遵循 Impl 类的约定

    public class MyEntityRepositoryCustomSaveActionImpl implements 
      MyEntityRepositoryCustomSaveAction {
    
        @PersistenceContext
        private EntityManager entityManager;
    
        @Override
        public MyEntity saveSafely(MyEntity entity) {
           //whatever custom logic you need
        }
    
    }
    

    【讨论】:

    • 像魅力一样工作。感谢发帖
    【解决方案9】:

    @ytterrr 的解决方案有效,但对于较旧的 Spring Data 版本,至少对于 Spring Data 2.1,这种方法不仅可以覆盖任何存储库方法,还可以访问底层功能(访问到实体管理器进行持久化、删除、查找...):

    public interface MyEntityRepositoryCustom {
      <S extends MyEntity> S save(S entity);
    }
    
    public class MyEntityRepositoryImpl implements MyEntityRepositoryCustom {
    
        final JpaEntityInformation<MyEntity, ?> entityInformation;
        EntityManager em;
    
        public MyEntityRepositoryImpl(EntityManager entityManager) {
            this.entityInformation = JpaEntityInformationSupport.getEntityInformation(MyEntity.class, entityManager);
            this.em = entityManager;
        }
    
        /**
         * @see org.springframework.data.jpa.repository.support.SimpleJpaRepository
         */
        @Transactional
        public <S extends MyEntity> S save(S entity) {
    
            // do your logic here
    
            if (entityInformation.isNew(entity)) {
                em.persist(entity);
                return entity;
            } else {
                return em.merge(entity);
            }
        }
    }
    

    【讨论】:

      【解决方案10】:

      为了正确覆盖保存方法,您必须创建一个接口,该接口具有在 CrudRepository 上声明的原始方法的正确签名,包括泛型

      public interface MyCustomRepository<T> {
          <S extends T> S save(S entity);
      }
      

      然后,创建你的实现(后缀 Impl 在类名中很重要)

      public class MyCustomRepositoryImpl implements MyCustomRepository<MyBean> {
      
          @Autowired
          private EntityManager entityManager;
      
      
          @Override
          public <S extends MyBean> S save(S entity) {
             /**
               your custom implementation comes here ...
               i think the default one is just        
              return this.entityManager.persist(entity);
             */
          }
      
      }
      

      最后,使用之前创建的接口扩展您的存储库

      @RepositoryRestResource
      @Repository
      public interface MyBeanRepository extends PagingAndSortingRepository<MyBean, Long>, MyCustomRepository<MyBean> {}
      

      【讨论】:

        【解决方案11】:

        什么对我有用(Spring boot 2.x Java 11),即使不是完全干净。 它使用 IDE 和 Maven 和 Gradle 编译。 Lucas 的上述解决方案不适用于 JpaRepository。

        public interface MyRepo extends JpaRepository<MyType, Long>, MyRepoCustom{
        
           //Implemented in MyRepoCustom
           public MyType save(MyType mytypeEntity);
        }
        

        自定义接口(重复声明,不好):

        public interface MyRepoCustom{
            public MyType save(MyType mytypeEntity);
        }
        

        自定义Impl:

        @Repository
        public class MyRepoImpl implements MyRepoCustom{
            @PersistenceContext
            private EntityManager em;
        
            @Transactional
            public MyType save(MyType mytypeEntity) {
               //type safe implementation
            }
        }
        

        【讨论】:

          【解决方案12】:

          我正在将应用程序从 Spring Boot 1.5 更新到 Spring Boot 2.0,我发现我们在保存方法中的一些自定义行为突然不再起作用,结果我们不得不更新我们存储库上的保存方法可以正常工作。请注意,我必须在函数上添加泛型类参数和泛型参数才能使 Eclipse 内部的构建和通过 CLI (gradle) 工作。

          所以我像这样更改了我的自定义存储库界面:

          interface ScoreRepositoryCustom {
            Score save(Score score);
          }
          

          到这个(匹配 CrudRepository 中的签名):

          interface ScoreRepositoryCustom<T> {
            <S extends T> S save(S to);
          }
          

          【讨论】:

            猜你喜欢
            • 2018-06-25
            • 2014-06-05
            • 1970-01-01
            • 1970-01-01
            • 2015-03-13
            • 1970-01-01
            • 1970-01-01
            • 2016-08-14
            • 1970-01-01
            相关资源
            最近更新 更多