【问题标题】:Creating a read-only repository with SpringData使用 Spring Data 创建只读存储库
【发布时间】:2012-06-21 20:33:21
【问题描述】:

是否可以使用 Spring Data 创建只读存储库?

我有一些链接到视图的实体和一些子实体,我想为它们提供一个存储库,其中包含一些方法,如 findAll()findOne() 和一些带有 @Queryannotation 的方法。我想避免提供像 save(…)delete(…) 这样的方法,因为它们没有意义并且可能会产生错误。

public interface ContactRepository extends JpaRepository<ContactModel, Integer>, JpaSpecificationExecutor<ContactModel> {
    List<ContactModel> findContactByAddress_CityModel_Id(Integer cityId);

    List<ContactModel> findContactByAddress_CityModel_Region_Id(Integer regionId);

    // ... methods using @Query

    // no need to save/flush/delete
}

谢谢!

【问题讨论】:

    标签: java spring jpa spring-data spring-data-jpa


    【解决方案1】:

    是的,要走的路是添加一个手工制作的基础存储库。你通常使用这样的东西:

    public interface ReadOnlyRepository<T, ID extends Serializable> extends Repository<T, ID> {
    
      T findOne(ID id);
    
      Iterable<T> findAll();
    }
    

    您现在可以让具体的 repos 扩展刚刚定义的一个:

    public interface PersonRepository extends ReadOnlyRepository<Person, Long> {
    
      T findByEmailAddress(String emailAddress);
    }
    

    定义基础 repo 的关键部分是方法声明带有与CrudRepository 中声明的方法非常相同的签名,如果是这种情况,我们仍然可以将调用路由到实现 bean 支持存储库代理。我已经在 SpringSource 博客中写了关于该主题的更详细的 blog post

    【讨论】:

    • 有一件棘手的事情。如果我使用像 HSQL 这样的内存数据库进行集成测试,我可以使用视图的 save 方法来创建测试数据,而不是在其对象之间创建关系并填充它们。所以在这种情况下,我只需要视图的保存方法来测试而不是生产代码。有没有办法实现它?
    • 请注意,从2.0 M3(Kay) 版本开始,findOne 方法已重命名为findById,详情在stackoverflow.com/questions/44101061/…
    • 假设您仍然希望 Spring 创建一些 PagingAndSortingRepository 方法的实现,但不是全部。那么你将如何创建一个具有相同界面但没有保存和删除方法的PagingAndSortingReadOnlyRepository
    • 对于较新版本,此解决方案会引发 Caused by: org.springframework.data.mapping.PropertyReferenceException: No property findOne found for type CustomEntity! 异常。请改用findById
    • 为了提高效率,您可以通过在接口中添加@Transactional(readonly=true) 将存储库事务标记为只读:docs.spring.io/spring-framework/docs/current/javadoc-api/org/…
    【解决方案2】:

    为了扩展 Oliver Gierke 的回答,在最新版本的 Spring Data 中,您需要在 ReadOnlyRepository(父接口)上添加 @NoRepositoryBean 注释以防止应用程序启动错误:

    import org.springframework.data.repository.NoRepositoryBean;
    import org.springframework.data.repository.Repository;
    
    @NoRepositoryBean
    public interface ReadOnlyRepository<T, ID extends Serializable> extends Repository<T, ID> {
    
        T findOne(ID id);
    
        List<T> findAll();
    
    }
    

    【讨论】:

    • 不再需要 findOne 方法。
    • Getting Invocation of init method failed; nested exception is org.springframework.data.mapping.PropertyReferenceException: No property findOne found for type 启动时出错。删除T findOne(ID id);后,可以正常使用,但无法通过URL查看单个项目。
    • 可能是因为它没有被称为findById,正如上面评论中所解释的那样......另见stackoverflow.com/questions/44101061/… ???
    【解决方案3】:

    据我们在文档中看到的,这可以通过实现 org.springframework.data.repository.Repository 来实现。

    【讨论】:

      【解决方案4】:

      这是只读的PagingAndSortingRepository

      package com.oracle.odc.data.catalog.service.core.repository;
      
      import org.springframework.data.domain.Page;
      import org.springframework.data.domain.Pageable;
      import org.springframework.data.domain.Sort;
      import org.springframework.data.repository.NoRepositoryBean;
      import org.springframework.data.repository.PagingAndSortingRepository;
      import org.springframework.data.rest.core.annotation.RestResource;
      
      /**
       * Extension of {@link PagingAndSortingRepository} but without modification capabilities
       *
       * @author XYZ
       * @see Sort
       * @see Pageable
       * @see Page
       */
      @NoRepositoryBean
      public interface ReadOnlyPagingAndSortingRepository<T, ID> extends PagingAndSortingRepository<T, ID> {
      
          @Override
          @RestResource(exported=false)
          <S extends T> S save(S entity);
      
          @Override
          @RestResource(exported=false)
          <S extends T> Iterable<S> saveAll(Iterable<S> entities);
      
          @Override
          @RestResource(exported=false)
          void deleteById(ID id);
      
          @Override
          @RestResource(exported=false)
          void delete(T entity);
      
          @Override
          @RestResource(exported=false)
          void deleteAll(Iterable<? extends T> entities);
      
          @Override
          @RestResource(exported=false)
          void deleteAll();
      
      }
      

      如果你尝试 POST 或 DELETE,你会得到 405 (Method Not Allowed)。

      【讨论】:

      • 这可以防止在运行时执行,但不会被开发人员在编译时使用。
      【解决方案5】:

      对我来说,以下工作。使用 Oliver 的解决方案,我在启动时收到错误 Invocation of init method failed; nested exception is org.springframework.data.mapping.PropertyReferenceException: No property findOne found for type

      @NoRepositoryBean
      public interface ReadOnlyRepository<T,ID> extends Repository<T, ID> {
          Optional<T> findById(ID var1);
          boolean existsById(ID var1);
          Iterable<T> findAll();
          Iterable<T> findAllById(Iterable<ID> var1);
          long count();
      }
      

      【讨论】:

      【解决方案6】:

      或者,如果您想自己实现或阻止此操作 - 您可以执行以下操作(适用于 Java 8 及更高版本):

      import org.springframework.data.jpa.repository.JpaRepository;
      import org.springframework.data.repository.NoRepositoryBean;
      import org.springframework.lang.NonNull;
      
      import java.util.List;
      
      @NoRepositoryBean
      public interface ReadOnlyRepository<T, ID> extends JpaRepository<T, ID> {
      
          @Override
          @NonNull
          default <S extends T> S save(@NonNull S entity) {
              throw new RuntimeException("Action not allowed");
          }
      
          @Override
          @NonNull
          default <S extends T> List<S> saveAll(@NonNull Iterable<S> iterable) {
              throw new RuntimeException("Action not allowed.");
          }
      
          @Override
          @NonNull
          default <S extends T> S saveAndFlush(@NonNull S s) {
              throw new RuntimeException("Action not allowed.");
          }
      
          @Override
          default void delete(@NonNull T entity) {
              throw new RuntimeException("Action not allowed.");
          }
      
          @Override
          default void deleteAll() {
              throw new RuntimeException("Action not allowed.");
          }
      
          @Override
          default void deleteAll(@NonNull Iterable<? extends T> entities) {
              throw new RuntimeException("Action not allowed.");
          }
      
          @Override
          default void deleteAllInBatch() {
             throw new RuntimeException("Action not allowed.");
          }
      
          @Override
          default void deleteById(@NonNull ID id) {
             throw new RuntimeException("Action not allowed.");
          }
      
          @Override
          default void deleteInBatch(@NonNull Iterable<T> iterable) {
              throw new RuntimeException("Action not allowed.");
          }
      }
      

      希望我能帮助别人(ノ^∇^)

      【讨论】:

        猜你喜欢
        • 2012-04-12
        • 1970-01-01
        • 1970-01-01
        • 2014-12-25
        • 2016-01-08
        • 2019-01-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多