使用 Spring Data 创建只读存储库
Posted
技术标签:
【中文标题】使用 Spring Data 创建只读存储库【英文标题】:Creating a read-only repository with SpringData 【发布时间】:2012-06-21 20:33:21 【问题描述】:是否可以?
我有一些链接到视图的实体和一些子实体,我想为它们提供一个存储库,其中包含一些方法,如 findAll()
、findOne()
和一些带有 @Query
annotation 的方法。我想避免提供像 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 方法。 GettingInvocation 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)中执行自定义查询