Skip to content

MybatisPlus 插件

crane4j 提供 MybatisPlus 的扩展组件,允许基于 MybatisPlusBaseMapper 自动构建数据源容器,以便快速地实现查询关联数据并用于填充。

1.安装

在开始前,请先确保已经引入必要的 crane4j 配置,然后在此基础上,额外的引入下述依赖:

xml
<!-- 引入 crane4j-extension-mybatis-plus -->
<dependency>
    <groupId>cn.crane4j</groupId>
    <artifactId>crane4j-extension-mybatis-plus</artifactId>
    <version>${last-version}</version>
</dependency>

<!-- 引入 mybatis-plus 依赖,若已有则可以跳过 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>${last-version}</version>
</dependency>

此后当项目启动时,将会自动加载相关的组件。

2.注册Mapper

在使用前,你需要通过自动注册、手动注册或懒加载的方式向 crane4j 注册 BaseMapper 接口。

2.1.懒加载

在 Spring 环境中,用户默认不需要进行额外的操作,AssembleMpAnnotationHandler 会在用户使用时根据 beanName 自动从 Spring 上下文中获得对应的 Mapper,并完成自动注册,即懒加载。

2.2.自动注册

在 Spring 环境中,用户也可以指定 auto-register-mappertrue 开启自动注册,相关配置如下:

yml
crane4j:
 mybatis-plus:
  auto-register-mapper: true # 启动自动注册
  includes: xxxMapper, xxxMapper # 仅注册指定 Mapper
  excludes: xxxMapper, xxxMapper # 仅排除指定 Mapper

在开启自动注册的情况下,spring 上下文中任何符合 includesexcludes 规则的 Mapper 都会被注册。

2.3.手动注册

用户也可以获取 AssembleMpAnnotationHandler 进行手动注册:

java
AssembleMpAnnotationHandler handler = SpringUtil.getBean(AssembleMpAnnotationHandler.class);
handler.registerRepository("xxxMapper", xxxMapper);

3.使用

用户可以使用 @AssembleMp 注解来配置以 BaseMapper 接口的查询方法作为数据源的装配操作。这个注解会被配置解析器中的专门的注解解析器 AssembleMpAnnotationHandler 解析为 AssembleOperation

在使用时,由于可以同时指定查询的条件字段查询字段,从而有四种情况:

  • 根据默认主键查询全部字段;
  • 根据默认主键查询指定字段;
  • 根据指定外键查询全部字段;
  • 根据指定外键查询指定字段;

假设我们有一个数据库表映射对象如下:

java
@TableName("foo")
public class Foo {
    @TableId
    private Integer id;
    @TableField("user_name")
    private String userName;
    @TableField("user_age")
    private Integer userAge;
}

并且在 Spring 上下文中已经有了一个继承 BaseMapper 接口的 FooMapper bean,其 bean 名称默认为 fooMapper

3.1.根据主键查询全部字段

java
public class Foo {
    @AssembleMp(
        mapper = "fooMapper",
        props = @Mapping(src = "name", ref = "name")
    )
    private Integer id;
    private String name;
}

当执行装配时,数据源等同于基于 id 批量查询出来的 Foo 对象,SQL 为 select * from foo where id in ?

3.2.根据主键查询指定字段

java
@AssembleMp(
    mapper = "fooMapper",
    selects = {"userName", "userAge"} // 要查询的字段
)
private Integer id;

上述配置相当于使用 QueryWrapper 构建并执行了 select user_name AS userName, user_age AS userAge, id from foo where id in ? 这条 SQL,查询出的数据将按照 Foo 中配置的主键 id 进行分组。

查询字段名为实体类中对应的属性名,构建 SQL 时会自动转换为查询 SQL

TIP

默认情况下,crane4j 将使用被 @TableId 注解标记的属性作为主键。

3.3.根据指定外键查询全部字段

java
@AssembleMp(
    mapper = "fooMapper",
    where = "userName" // 查询的条件字段
)
private String name;

上述配置相当于使用 QueryWrapper 构建并执行了 select * from foo where user_name in ? 这条 SQL,查询出的数据将按照用户指定的 userName 属性进行分组。

3.4.根据指定外键查询指定字段

java
@AssembleMp(
    mapper = "fooMapper",
    selects = {"userName", "userAge"}, // 要查询的字段
    where = "userName" // 查询的条件字段
)
private String name;

上述配置相当于使用 QueryWrapper 构建并执行了 select user_age AS userAge, user_name AS userName from foo where user_name in ? 这条 SQL,查询出的数据将按照用户指定的 name 属性进行分组。

TIP

由于查询出的数据需要根据用户指定的外键字段进行分组,并与键值对应,因此如果用户指定了查询字段,但未包含该外键字段时,将自动在查询字段后面追加该外键字段。

4.指定查询字段 SQL

通常情况下,建议用户始终使用实体类中的属性名作为查询字段/查询外键crane4j 会借助 MP 的 TableInfo 将其转换为对应的表字段 SQL。

然而,有时确实需要自定义查询字段的情况,因此可以直接编写自定义 SQL 作为查询字段。

例如,假设有以下 Bean

java
@TableName("foo")
public class FooDO {
    @TableId
    private Integer id;
    @TableField("user_name")
    private String name;
    @TableField("user_age")
    private Integer age;
}

然后装配配置如下:

java
public class FooVO {
    @AssembleMp(
        mapper = "fooMapper",
        selects = {"user_name AS name", "userAge AS age"}
    )
    private Integer id;
    private String name;
    private String age;
}

最终执行的 SQL 为:select user_age AS age, user_name AS name from foo where user_name in ?

WARNING

需要注意的是,该查询是基于 QueryWrapper 完成的,因此在这种情况下,查询的表字段可能与用户的对象属性不一致,且无法自动设置别名。

5.对结果分组

与方法容器类似,基于 MyBatisPlus 的查询也允许指定查询结果的一对一或一对多映射类型。例如:

java
public class DeptEmpVO {
    @AssembleMp(
        mapper = "empMapper", where = "deptId", // 根据部门 id 查询员工集合
        mappingType = MappingType.ONE_TO_MANY, // 按部门 id 进行一对多映射
        handlerType = OneToManyAssembleOperationHandler.class, // 一对多映射处理器
        props = @Mapping(src = "name", ref = "deptNames") // 将指定部门下所有的员工名称映射到 empNames 集合
    )
    private Integer deptId;
    private List<String> empNames;
}

上述示例中,使用 @AssembleMp 注解指定在 empMapper 中根据部门 id 查询员工集合,然后按部门 id 进行一对多映射。最后,将员工集合中的员工名称映射到 DeptEmpVO 对象的 empNames 集合中。

这样,我们可以实现多对一的映射关系。

同样的,如果分组的时候 key 可能重复,那么你可以像使用 @ContainerMethod 配置数据源容器那样,通过 duplicateStrategy 属性来配置处理策略:

java
public class DeptEmpVO {
    @AssembleMp(
        mapper = "empMapper", where = "deptId",
      	duplicateStrategy = DuplicateStrategy.DISCARD_OLD, // 如果 key 重复,那么默认使用新覆盖旧
        props = @Mapping(src = "name", ref = "deptNames")
    )
    private Integer deptId;
    private List<String> empNames;
}

6.多数据源

Crane4j 提供指定指定数据源的方法,但是考虑不同的项目中实现多数据源的方案并不相同,因此这里不提供具体的实现。

6.1.实现数据源切换器

如果你希望指定使用某个特定的数据源来完成查询,那么你需要实现 AssembleMpAnnotationHandler.DataSourceSwitcher 接口,在里面定义具体的切换数据源的逻辑:

java
public class ExampleDataSourceSwitcher 
  	implements AssembleMpAnnotationHandler.DataSourceSwitcher {
  	
      @Override
      public void beforeInvoke(String dataSource) {
          // 在这里定义切换数据源的逻辑
      }
  
      @Override
      public void afterInvoke(String dataSource) {
          // 在这里定义完成查询后清理上下文的逻辑
      }
}

6.2.注册切换器

此后,你可以将该数据源切换器设置到 AssembleMpAnnotationHandler 中:

java
public AssembleMpAnnotationHandler assembleMpAnnotationHandler(
    AnnotationFinder annotationFinder, Crane4jGlobalConfiguration globalConfiguration,
    MethodInvokerContainerCreator methodInvokerContainerCreator, BeanFactory beanFactory) {
    AssembleMpAnnotationHandler handler = new AssembleMpAnnotationHandler(
        annotationFinder, globalConfiguration, methodInvokerContainerCreator
    );
    handler.setRepositoryTargetProvider(new MapperLazyLoader(beanFactory));
  
  	// 设置数据源切换器
    ExampleDataSourceSwitcher dss = new ExampleDataSourceSwitcher();
  	handler.setDataSourceSwitcher(dss);
  	
    return handler;
}

6.3.指定数据源

最后,在使用的时候直接在 @AssembleMp 中通过 datasource 指定要使用的数据源即可:

java
@AssembleMp(
    mapper = "fooMapper",
    where = "userName",
  	datasource = "ds2" // 指定使用数据源
)
private String name;

WARNING

考虑到一些多数据源的切换可能会依赖 ThreadLocal 实现,如果确实如此,那么你使用异步填充时可能需要注意一下上下文切换的问题。

关于异步填充,具体请参见:异步填充