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就会多以下的方法deleteByMultiId,selectByMultiId,updateByMultiId,方法调用只需要传入对应的实体类,给联合主键赋值就可以了,关于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();
}
然后就能愉快的玩耍了