使用 Spring Data 创建只读存储库

Posted

技术标签:

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

是否可以?

我有一些链接到视图的实体和一些子实体,我想为它们提供一个存储库,其中包含一些方法,如 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

谢谢!

【问题讨论】:

【参考方案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,详情在***.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,正如上面评论中所解释的那样......另见***.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();

【讨论】:

那是因为已经重命名了几个方法;见***.com/questions/44101061/…【参考方案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.");
    

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

【讨论】:

以上是关于使用 Spring Data 创建只读存储库的主要内容,如果未能解决你的问题,请参考以下文章

Spring Data Elasticsearch父/子文档存储库/测试执行错误

在 JPA 存储库(Spring Data Jpa)中执行自定义查询

如何在 Spring java 中设置存储库?

使用 Spring-Data-Solr 注入存储库

Spring Boot MongoDB REST - 自定义存储库方法

Core Data SQLite 存储在更新后变为只读