问题1:多表关联的条件查询、分页、排序
在使用mybatis-plus自带的分页查询时,我们只能查询出实体类的相关信息。但是如果需要同时查出其他表的信息,那么就得查两次。在没有其他限制条件的情况下,查两次也没有什么问题。但是如果加上排序以及分页,那么两次查询就不能满足需求了。
example
我们有三张表 A学生表 B学生在课程下的排名 C课程表
我们要查符合某些条件的学生的信息以及在某门课程下的排名信息,并且根据排名来排序,最后在加上分页查。这显然是一个比较复杂的查询了。(如何在业务逻辑去处理??)
1、根据B表来分页查出10条数据(假设分页大小就是10)
2、用10条数据再去查A表的基本信息
3、根据条件再去筛选查出来的A
到了这一块,可能筛选过后,我们返回的数据就不够10条了。那么该如何处理?
并且如果排序不是B表,而是根据A表的某些字段去排序,那么业务代码又该怎么去写?
所以我们可以使用mybatis+mybatis-plus来使用xml去写sql来实现这个功能,这样就可以轻松实现这个需求
problem
sql应该怎么写?resultMap应该怎么映射?
resultType的使用
因为这里我们不会涉及到typeHandler的类型处理,也不会涉及到List 等复杂类型的处理,所以我们就不需要自定义resultMap,直接只用resultType就好,mybatis会将我们的结果封装到我们给定的resultType类型的bean中
sql的编写
select A.*, B.* from A left join B
on A.id = B.A_ID
<where>
<if test = "A.name!= null and A.name != ''">
A.name = #{A.name} and
</if>
...... to Many if 判断 注意!最后一个 if 判断里面不能加 and
group by A.id
<chose>
<when test = "sortField!= null and sortField!= ''">
order by ${sortField}
<chose>
<when test = "sortMethod != null and sortMethod != ''">
${sortMethod}
</when>
</chose>
</when>
</chose>
</where>
这样,我们可以实现分页查询的功能。分页的参数在哪里传递呢?
在我们的mapper接口里面只需要将page对象传递进去。mybatisPlus会帮我们实现分页功能
page(@Param("field") String field, Page page);
稍微提一点!我们在xml中取值的时候用的是#{}, 但是对于前端给我们传的排序的字段以及排序的规则都是字符串,这个时候我们就得使用 ${}去取值。
问题2:对于createTime以及updateTime的自动处理
在这一块,我们既可以使用mysql的触发器来帮我们实现,也可以使用mybatis的自动填充功能去实现
mybatis-plus自动填充功能参考文档:mybatis-plus字段自动填充
mysql的触发器:
ALTER TABLE table_name
ADD COLUMN create_time timestamp NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN update_time timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP AFTER create_time;
这里简单使用mybatis的自动填充功能来填充时间
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
//这里会给createTime与updateTime自动填充值,无需我们在代码里再去处理
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill ....");
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐使用)
// 或者
this.strictUpdateFill(metaObject, "createTime", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐)
// 或者
this.fillStrategy(metaObject, "createTime", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug)
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill ....");
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐)
// 或者
this.strictUpdateFill(metaObject, "updateTime", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐)
// 或者
this.fillStrategy(metaObject, "updateTime", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug)
}
}
最后得在字段上标注填充的类型即可 不然框架不会对这些字段去处理的
public class User {
// 注意!这里需要标记为填充字段
@TableField(.. fill = FieldFill.INSERT)
// 时间序列化与反序列化机制
@JsonSerialize
@JsonDeserialize
private LocalDateTime createTime;
....
}
问题3:如何根据多个字段的集合去查询多个数据
在工作时,碰到了这样一个问题:
我的入参是list,我需要根据list中的对象Student的两个字段去判断这个对象是不是在数据库中。
如果在的话,我需要更新这个对象,不在的话,我需要插入这个对象。
List list = new ArrayList();
前提条件是不能循环去调用数据库。要是数据量小的话还行,数据量大的话,这个循环调用会出大问题的。
循环调用会消耗大量的数据库连接资源,并且如果sql有关联查询的话,查询会锁表,导致写操作无法进行,从而影响其他业务的速度. 所以能避免就不要循环调!
解决办法:
1、将对象中的多个字段处理成map,并且收集起来
List<Map<String,Object>> list = Lists.newArrayListWithExpectedSize(list.size());
list.forEach(student -> {
Map<String,Object> paramMap = new HashMap<>(字段的数量);
paramMap.put("field1", student.getField1());
......to many field put
paramMapList.add(paramMap);
)
2、mapper接口
list(@Param("paramMapList") List<Map<String,Object>> paramMapList);
3、xml文件的编写
select * from student where
<foreach collection = "paramMapList" item = "paramMap" open = "(" close = ")" separator = "or">
field1 = #{paramMap.field1}, field2 = #{paramMap.field2} to many field ...
</foreach>
4、@Transaction注解与批量、非批量的之间的冲突
接着我们第三个问题讨论:
在我们有一大批数据需要处理的时候,我们首先需要考虑的是将其作为一个批量操作。
但是这样在某些情况下会有问题:
(1)、mybatis-plus的批量操作不会返回主键id
有的时候,我们需要批量将数据导入数据库。如果导入的数据它存在一个相互的关联关系:比如将以下数据导入数据库
[
{"field1":"field1",
"field2":"field2",
......to many field
"list":[
{"subField1":"subField1",...to many field}
]
},
{"field1":"field1",
"field2":"field2",
......to many field
"list":[
{"subField1":"subField1",...to many field}
]
},
]
可以看到,保存的数据是一个集合,集合中的对象可能会存在一个集合字段。
那么在保存对象的list字段的时候,我们需要知道对象的id,这样才会将对象与自己的list字段建立起关联关系。
而外层集合的批量保存不会生成主键id,那么list字段保存的时候就没法得知外层对象的id。
(2)、外层使用循环解决问题
所以,我们将外层对象的保存设置为一个循环,那么在保存过后我们就可以知道他的主键id。从而将其与list字段建立关系。
那么在保存list字段的时候 ,我们就可以得知与他相关联的是哪一个对象了。这时候只需要直接保存就好。
所以list字段我们就可以使用批量保存了。因为我们也不需要去获取其他的信息了。
(3)、@Transaction注解的加入
通常情况下,我们的这种关联操作要么执行成功,要么就得数据回滚,所以我们需要加入@Transaction注解来保证数据的一致性。
但是在我们加入@Transaction注解的时候,会发现保存list字段的时候就会报错!
Cannot change the ExecutorType when there is an existing transaction
如果有事务的情况下:我们外层对象是循环插入,那么在处理list的时候就不能使用批量操作的方法。
而list的插入也使用循环的话,那么这个插入的循环次数将是很恐怖的n*n。
所以需要使用xml文件,自己去写sql来实现批量操作,不能使用mybatis-plus的批量操作
同样,对于外层的循环应该怎么解决?
5、根据map参数去查询
某些情况下,我们需要去批量更新数据,如果存在的数据则更新,不存在的插入。而有时候这一批数据可能没有主键id,我们需要根据某两个或者三个字段去看数据库中是否存在。这时候,需要将这些数据收集起来去做查询,进而分别来更新或者插入即可。
(1)、根据两个字段检查是是否存在
如果这一批数据需要查询的两个字段中有一个肯定不会重复,那么我们可以将其封装成map来查
将需要查的封装成map
我这里的keyWord肯定不会重复,所以我是用它作为map的key type作为value
Map<String, TypeEnum> keywordTypeMap = keywordReqs.stream()
.collect(Collectors.toMap(KeywordReq::getKeyWord, HmKeywordEntity::getType));
查数据,这时候返回值肯定是一个list
期望sql应该是这个样子的
select * from tableName
where (key_word = ? and type = ?) or (key_word = ? and type = ?)......
再来写mapper文件
这个collection中的值就是我们mapper接口里面的入参,别忘记标注@Param注解!
index就是map的key
item就是map的value
select * from tableName
<where>
<foreach collection="keywordTypeMap" index="key" item="value" separator="or">
(key_word = #{key} and type = #{value})
</foreach>
</where>
否则使用下面的方法去查
(2)、根据两个以上字段检查是否存在
这个请看标题三即可!