MyBatisPlus整合mybatisplus-plus和MyBatis-Plus-Join同时支持默认的方法和多表联查加多主键查询

前情提要:

在工作上的时候遇到一个情况,一个实体类没有唯一主键而是由两到三个字段组成的复合主键比如:

class User {
  
  private String org;
  
  private String userId;

  private String name;  

}

在需求中这种类的主键就是 org+userId 来组成的联合主键,如果使用mp的话不能使用mp自带的方便快捷的XXXById方法了,

因为XXXById方法需要只有一个主键才能使用,并不支持多个的复合主键,@TableId只允许有一个标注,具体源码在TableInfoHelper这个类里面,

 private static void initTableFields(Configuration configuration, Class<?> clazz, GlobalConfig globalConfig, TableInfo tableInfo, List<String> excludeProperty) {
        AnnotationHandler annotationHandler = globalConfig.getAnnotationHandler();
        PostInitTableInfoHandler postInitTableInfoHandler = globalConfig.getPostInitTableInfoHandler();
        Reflector reflector = tableInfo.getReflector();
        List<Field> list = getAllFields(clazz, annotationHandler);
        // 标记是否读取到主键
        boolean isReadPK = false;
        // 是否存在 @TableId 注解
        boolean existTableId = isExistTableId(list, annotationHandler);
        // 是否存在 @TableLogic 注解
        boolean existTableLogic = isExistTableLogic(list, annotationHandler);

        List<TableFieldInfo> fieldList = new ArrayList<>(list.size());
        for (Field field : list) {
            if (excludeProperty.contains(field.getName())) {
                continue;
            }

            boolean isPK = false;
            OrderBy orderBy = annotationHandler.getAnnotation(field, OrderBy.class);
            boolean isOrderBy = orderBy != null;
            /* 主键ID 初始化 */
            if (existTableId) {
                TableId tableId = annotationHandler.getAnnotation(field, TableId.class);
                if (tableId != null) {
                    if (isReadPK) {
                        throw ExceptionUtils.mpe("@TableId can't more than one in Class: \"%s\".", clazz.getName());
                    }

                    initTableIdWithAnnotation(globalConfig, tableInfo, field, tableId);
                    isPK = isReadPK = true;
                }
            } else if (!isReadPK) {
                isPK = isReadPK = initTableIdWithoutAnnotation(globalConfig, tableInfo, field);
            }

            if (isPK) {
                if (orderBy != null) {
                    tableInfo.getOrderByFields().add(new OrderFieldInfo(tableInfo.getKeyColumn(), orderBy.asc(), orderBy.sort()));
                }
                continue;
            }
            final TableField tableField = annotationHandler.getAnnotation(field, TableField.class);

            /* 有 @TableField 注解的字段初始化 */
            if (tableField != null) {
                TableFieldInfo tableFieldInfo = new TableFieldInfo(globalConfig, tableInfo, field, tableField, reflector, existTableLogic, isOrderBy);
                fieldList.add(tableFieldInfo);
                postInitTableInfoHandler.postFieldInfo(tableFieldInfo, configuration);
                continue;
            }

            /* 无 @TableField  注解的字段初始化 */
            TableFieldInfo tableFieldInfo = new TableFieldInfo(globalConfig, tableInfo, field, reflector, existTableLogic, isOrderBy);
            fieldList.add(tableFieldInfo);
            postInitTableInfoHandler.postFieldInfo(tableFieldInfo, configuration);
        }

        /* 字段列表 */
        tableInfo.setFieldList(fieldList);

        /* 未发现主键注解,提示警告信息 */
        if (!isReadPK) {
            logger.warn(String.format("Can not find table primary key in Class: \"%s\".", clazz.getName()));
        }
    }

如果有多个主键,则会报错,如果依旧想用mp来做这种联合主键的查询,那就不使用@TableId注解,它只会⚠警告

public class DefaultSqlInjector extends AbstractSqlInjector {

    @Override
    public List<AbstractMethod> getMethodList(Configuration configuration, Class<?> mapperClass, TableInfo tableInfo) {
        GlobalConfig.DbConfig dbConfig = GlobalConfigUtils.getDbConfig(configuration);
        Stream.Builder<AbstractMethod> builder = Stream.<AbstractMethod>builder()
            .add(new Insert(dbConfig.isInsertIgnoreAutoIncrementColumn()))
            .add(new Delete())
            .add(new Update())
            .add(new SelectCount())
            .add(new SelectMaps())
            .add(new SelectObjs())
            .add(new SelectList());
        if (tableInfo.havePK()) {
            builder.add(new DeleteById())
                .add(new DeleteByIds())
                .add(new UpdateById())
                .add(new SelectById())
                .add(new SelectBatchByIds());
        } else {
            logger.warn(String.format("%s ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method.",
                tableInfo.getEntityType()));
        }
        return builder.build().collect(toList());
    }
}

具体就是这里检测的,如果没有标注@TableId则havePk返回的false

然后在查询的时候构建条件

 Wrappers.lambdaQuery().eq(User::getOrg, "org").eq(User::getUserId,"1");

然后执行,就能有联合主键的效果,但是这样每次都要构建条件select,update,都要,个人感觉很不方便,如果一个类还好,当实体类变多了之后这样子就会很麻烦,

这里就要引入今天的主角之一了mybatisplus-plus

<dependency>
        <groupId>com.github.jeffreyning</groupId>
        <artifactId>mybatisplus-plus</artifactId>
        <version>1.7.5-RELEASE</version>
</dependency>

他兼容mp,使用的时候只需要给复合主键标记上他的注解@MppMultiId,表名使用联合主键,即可,这个是兼容@TableId的,可以两个注解同时标注在一个字段上面

class User {

  @MppMultiId
  private String org;
  
  @MppMultiId
  private String userId;

  private String name;  

}

标注完之后,在mapper层继承MppBaseMapper

interface UserMapper extends MppBaseMapper<User>{

}

然后UserMapper就会多以下的方法deleteByMultiIdselectByMultiIdupdateByMultiId,方法调用只需要传入对应的实体类,给联合主键赋值就可以了,关于Service层的操作可以查看官方文档 文档

然后这个时候又有了一种需求,就是某些数据返回的时候前端需要另外一张表的额外数据,比如现在有一张Adress表来记录地址,

class Adress {

  private Long id;
  
  private String adress;
  
  private String userId;
}

Adress记录了所关联的 User的id,然后这个时候前端所需要的数据是这个样子的

class UserVo {

  private String org;
  
  private String userId;

  private String name;  

  // 来自于Adress表的数据
  private String adress
}

这个时候使用基础mp的话,就需要编写对应的xml来达成查询效果,但是我还是不想写xml,所以今天引入了今天的第二位主角,就是MyBatis-Plus-Join文档


只需要在项目中引入对应的依赖

<dependency>
    <groupId>com.github.yulichang</groupId>
    <artifactId>mybatis-plus-join-boot-starter</artifactId>
    <version>1.5.2</version>
</dependency>

文章篇幅有限,我只使用mapper层的方法,引入依赖后,我们需要mapper继承他的mapper-->MPJBaseMapper

interface UserMapper extends MPJBaseMapper<User> {

}

我们的基础Mapper就会拥有对应的多表联查的方法,具体的使用方法可以查看官方的文档

这个时候我们项目里面有3种Mapper,一种是mp的BaseMapper,一种是plus-plus的MppBaseMapper,和MPJBaseMapper,Mpp和Mpj都继承了BaseMapper,这个时候我们可以自己创建一个新的Mapper来继承这两个,让我们的Mapper拥有这两个的所有函数

interface SuperMapper<T> extends MPJBaseMapper<T>,MppBaseMapper<T>{

}

这样一来,我们的mapper只需要继承SuperMapper就可以使用多表联查和多主键查询的功能了,但是如果只这样配置会调用方法的时候大概率会出现Invalid bound statement (not found)的问题,这是因为这两个库都重写了SqlInjector,并且都继承了DefaultSqlInjector,我们需要重写sqlSessionFactory,然后将两个的方法都注入进去,首先我们先写sql注入器

@Component
public class MySqlInjector extends MPJSqlInjector {
    // 我们继承连表查询的注入器,然后将多主键的方法注入进去
    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
        List<AbstractMethod> methodList = super.getMethodList(mapperClass, tableInfo);
        //添加多主键的方法
        methodList.add(new SelectByMultiIdMethod());
        methodList.add(new UpdateByMultiIdMethod());
        methodList.add(new DeleteByMultiIdMethod());
        return methodList;
    }
}

然后自己自定义SqlSessionFactory

    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        // 关联SqlSessionFactory与GlobalConfig
        GlobalConfig globalConfig = new GlobalConfig();
        globalConfig.setSqlInjector(new MySqlInjector());
        sessionFactory.setGlobalConfig(globalConfig);
        // 添加拦截器 MPJInterceptor需要放在最后面
        // 如果项目没有使用拦截器, 只需要添加MPJ拦截器sessionFactory.setPlugins(new MPJInterceptor());
        sessionFactory.setPlugins(mybatisPlusInterceptor(), new MPJInterceptor());
        // 其他配置 略
        return sessionFactory.getObject();
    }

然后就能愉快的玩耍了

posted @ 2024-12-05 19:01  pf666nb  阅读(3261)  评论(1)    收藏  举报