使用 Data Repositories

Spring Data repositories 为实体操作提供了非常有用的抽象,特别是实现业务逻辑的时候。

Jmix data repositories 基于 Spring Data 构建,但是在底层使用的是 DataManager。这样既可以使用方便的 repository 接口,又能支持 Jmix 中的高级数据访问功能,比如 实体事件跨数据存储实体引用数据访问权限检查 等。

使用 Data Repositories

Jmix data repository 有两种创建方式:使用 Data Repository 向导,或按照下面的步骤手动创建。

  1. 创建一个接口,继承自 JmixDataRepository。使用实体类和实体 ID 类作为 JmixDataRepository 的参数。示例:

    public interface CustomerRepository extends JmixDataRepository<Customer, UUID> {
    }
  2. 在基础包中创建一个新的 Spring 配置类,并添加 @EnableJmixDataRepositories 注解:

    package com.company.demo;
    
    import io.jmix.core.repository.EnableJmixDataRepositories;
    import org.springframework.context.annotation.Configuration;
    
    @EnableJmixDataRepositories
    @Configuration
    public class JmixDataRepositoryConfiguration {
    }

    Jmix 会初始化应用程序和扩展组件基础包内的所有 data repositories。如果需要对搜索和初始化 repositories 做进一步自定义,可以使用注解的 basePackagesexcludeFiltersincludeFilters 属性配置。

  3. 在 Spring bean 或 UI 控制器内使用 @Autowired 注解注入 repository:

    @Autowired
    private CustomerRepository customerRepository;

JmixDataRepository 的功能

JmixDataRepository 接口派生自标准的 Spring Data PagingAndSortingRepository。此外,提供了一些 Jmix 特有的方法:

  • 数据加载方法支持 fetch plan,比如 findById()findAll()

  • create() 方法 初始化 一个新实体。

  • 返回非 Optional 对象的 getById() 方法会在找不到实体时抛出异常。

  • getDataManager() 返回默认方法中使用的 DataManager

  • save() 方法可以对实体进行持久化,并返回保存后的实例,实例的加载使用的是指定的 fetch plan。方法接收需要保存的实体和加载实体使用的 fetch plan 作为参数。实体不能为 null,fetch plan 必须与实体匹配。

JmixDataRepositoryContext

JmixDataRepository 继承的 repository 的加载方法支持附加的 JmixDataRepositoryContext 类型参数。可以通过这个参数将从 UI 组件搜集的过滤、分页和排序参数传递给 LoadContext 对象。这样一来,genericFilter 通用过滤器simplePagination 简单分页器dataGrid 数据网格 组件的所有功能都可以与 Data Repositories 无缝集成。

在构建使用 repository 数据加载器的视图时,Studio 会生成一个 loadFromRepositoryDelegate 处理方法,使用 PageableJmixDataRepositoryContext 参数。

LoadContext 转换为 JmixDataRepositoryContext

使用 JmixDataRepositoryUtils.buildRepositoryContext() 静态方法可将 LoadContext 转换为 JmixDataRepositoryContext

@Install(to = "ordersDl", target = Target.DATA_LOADER)
private List<Order> customersDlLoadDelegate(LoadContext<Order> loadContext) {
    JmixDataRepositoryContext repositoryContext = JmixDataRepositoryUtils.buildRepositoryContext(loadContext);
    return repository.findAll(repositoryContext);
}
在转换过程中,由于保留了上下文的设置,因此可以修改 LoadContext 然后转换为 JmixDataRepositoryContext

安全限制

Data Repositories 可以使用 io.jmix.core.repository.ApplyConstraints 注解。如果注解的值是 false,则 Repository 使用 UnconstrainedDataManager 而非 DataManager。默认为 true

@ApplyConstraints 不仅可以应用在类上,也可以应用在单独的方法上,只对这些使用了注解的方法启用或忽略安全约束。

public interface OrderRepository extends JmixDataRepository<Order, UUID> {
    @Override
    List<Order> findAll(Sort sort, @Nullable FetchPlan fetchPlan);

    @Override
    @ApplyConstraints(false)
    List<Order> findAll(FetchPlan fetchPlan);

    @ApplyConstraints(false)
    List<Order> findByIdNotNull();
}

上面的示例中,@ApplyConstraints(false) 用在了两个方法上,这两个方法会使用 UnconstrainedDataManager

而在下面的示例中,整个类都禁用了安全约束,但在几个方法启用了约束:

@ApplyConstraints(false)
public interface ProductRepository extends JmixDataRepository<Product, UUID> {
    @Override
    List<Product> findAll(Sort sort, @Nullable FetchPlan fetchPlan);

    @Override
    @ApplyConstraints
    Page<Product> findAll(Pageable pageable);

    List<Product> getByIdNotNull();

    @ApplyConstraints
    List<Product> searchByIdNotNull();

    List<Product> searchById(UUID id);
}

findAll()searchByIdNotNull() 方法会使用常规的 DataManager,而其他方法使用 UnconstrainedDataManager

注解

可以使用下列注解自定义查询方法:

  • @io.jmix.core.repository.Query 定义 JPQL 查询语句,与 Spring Data JPA 的 @Query 注解类似。

  • @io.jmix.core.repository.FetchPlan 定义加载数据时使用的 fetch plan。

  • @io.jmix.core.repository.QueryHints@jakarta.persistence.QueryHint 可以配置 关闭软删除使用查询缓存 以及 加载动态属性

如果方法名/查询与方法参数使用了不同的 fetch plan 或 hint,那么最后的值会按照优先级从高往低使用。

FetchPlan:

  1. FetchPlan 参数。方法中的 fetch plan 参数具有最高优先级。

  2. JmixDataRepositoryContext#fetchPlan 参数。

  3. @FetchPlan 注解值。

Hints:

  1. JmixDataRepositoryContext 参数。

  2. @QueryHints 注解值。

For hints with the same key, the value from the higher priority source will override the value from the lower priority source. Different keys will be merged.

方法示例

派生方法

Jmix data repositories 支持从方法名生成查询语句的 Spring Data 标准功能,示例:

List<Customer> findByEmailContainingIgnoreCase(String emailPart);

查询方法可以使用 Pageable 进行分页和排序:

Page<Customer> findByEmailContainingIgnoreCase(String emailPart, Pageable pageable);

@Query 注解

与 Spring Data JPA 类似,可以使用 @io.jmix.core.repository.Query 注解显式定义 JPQL 语句:

@Query("select c from sample_Customer c where c.email like :email")
List<Customer> findCustomersByEmail(@Param("email") String emailPart);

如果查询返回多个纯值和/或聚合结果,应该在 @Query 注解的 properties 属性中提供属性名称。该方法应返回 KeyValueEntity 的列表。每个实体实例将必须包含查询中定义的属性。例如:

@Query(
        value = "select c.grade, count(c) from sample_Customer c group by c.grade",
        properties = {"grade", "count"}
)
List<KeyValueEntity> getCountGroupByGrade();

如果查询返回的是纯值列表,方法的返回类型直接定义为该值类型的列表:

@Query(value = "select distinct c.grade from sample_Customer c")
List<CustomerGrade> getAllGrades();

如果查询返回的是单一纯值,方法的返回类型直接定义为该值的类型:

@Query(value = "select count(c) from sample_Customer c where c.grade = ?1")
Long getCountByGrade(CustomerGrade grade);

Jmix 特定参数

Jmix data repositories 可以使用 fetch plan 作为查询方法的参数:

List<Customer> findByEmailContainingIgnoreCase(String emailPart, FetchPlan fetchPlan);

共享的 fetch plan 可以在查询方法的 @io.jmix.core.repository.FetchPlan 注解中定义:

@FetchPlan("customer-minimal")
List<Customer> findByEmail(String email);

一个 支持缓存 的查询可以通过 @io.jmix.core.repository.QueryHints@jakarta.persistence.QueryHint 注解配置:

@QueryHints(@QueryHint(name = PersistenceHints.CACHEABLE, value = "true"))
List<Customer> findByEmail(String email);

还可以在 repository 方法中使用 @io.jmix.core.repository.QueryHints@jakarta.persistence.QueryHint 加载实体的 动态属性

@QueryHints({@QueryHint(name = DynAttrQueryHints.LOAD_DYN_ATTR, value = "true")})
List<Customer> findByEmail(String email);

在需要返回带有动态属性的实体时,可以使用这个方法。