前情提要:
在工作上的时候遇到一个情况,一个实体类没有唯一主键而是由两到三个字段组成的复合主键比如:
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层的操作可以查看官方文档 文档。