使用 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. 在 Jmix 主程序类或者扩展组件的配置类上添加 @EnableJmixDataRepositories 注解:

    import io.jmix.core.repository.EnableJmixDataRepositories;
    // ...
    
    @SpringBootApplication
    @EnableJmixDataRepositories
    public class DemoApplication implements AppShellConfigurator {

    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 必须与实体匹配。

JmixDataRepository 继承的数据加载方法支持一个 JmixDataRepositoryContext 类型的附加参数。可以通过这个参数将从 UI 组件搜集的过滤、分页、排序等参数传递给 LoadContext 对象。从而使用 data repository 也可以无缝支持 genericFiltersimplePaginationdataGrid 组件的全部功能。

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

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

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

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

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

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

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

@ApplyConstraints(false)
public interface ProductRepository extends JmixDataRepository<Product, UUID> {
    @Override
    Iterable<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);

与 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);

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

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

查询方法还支持一个特殊的 fetch plan 参数:

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

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

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

一个 支持缓存 的查询:

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