mybatis分页与collection的冲突解决
背景
某个查询,是流水-发券类的查询,查询流水的同时,想要取得关联的卡券,关系是一对多的关系。希望返回给前端的结果如下:
{
"orderId":123,
"num":1,
"cardList": [
{
"id":0001,
"cardCode":ASDAS15QWE,
}
]
}
问题点:
- 由于是订单流水,考虑数量比较大的情况,所以必须使用分页,且是使用服务器端的分页。
- 为了便于前端展示,最好使用[]的数组形式进行显示
原本的处理(存在bug)
数组的返回结果使用了resultMap的collection来进行处理,分页是mybatis-plus,其分页的本质是在sql 里加limit。
本来数据少的时候没发现什么问题,造了点数据,数据多了就发现,单页存在card数组内容不全,重复等问题。
比如假设数据:
订单id | 数量 |
---|---|
001 | 1 |
002 | 2 |
003 | 3 |
004 | 4 |
005 | 5 |
卡券id | 订单id | 卡号 |
---|---|---|
1 | 001 | c921dd66f922 |
2 | 002 | 1991f9230af2 |
3 | 002 | bdf214812363 |
4 | 003 | f4b43652fc91 |
5 | 004 | d1663166f32c |
6 | 004 | 17a22b31fe7a |
7 | 004 | 0c9b35eda5e1 |
如果订单分页限制是5条,正常的时候应该活全部取出,但是使用collection是,sql类似是如下:
SELECT 订单id,数量,卡券id,卡号 FROM 订单 JOIN 卡券 ON 卡券.订单id = 订单.订单id
以上sql 的result结果是8条,如果使用sql的limit,限制5条的话,后三条就无法取得。
初版解决:
卡券的select另写sql的mapper,使用单独查询,比如:
<collection column="{条件}" select="另一sql"/>
但是,这种情况,是mybatis在取得查询结果之后,对每条单独查询。比如一次查询取得5条件结果,则以上sql会执行5次。效率太低。
最后解决(GROUP_CONCAT + 自定义typeHandler):
首先,使用GROUP_CONCAT转换成String形式的json数据,如下:
SELECT 订单id,
数量,
(SELECT CONCAT('[',IFNULL(GROUP_CONCAT(CONCAT('{ 卡券id:',卡券id,',卡号:',卡号,'}') Separator ','),''),']') FROM 卡券 WHERE 卡券.订单id = 订单.订单id) AS JSON
FROM 订单
以上的sql的查询结果是[]形式的json数据。这样的数据其实返回给前端,让前端JSON.parse(返回数据)的方式进行处理也是可以的。但为了符合需求,使用mybatis自定义typeHandler的方法,给前端返回json的数据。
相关代码如下:(json使用hutool)
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONException;
import cn.hutool.json.JSONUtil;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JsonArrayTypeHandler extends BaseTypeHandler {
private static final Log log = LogFactory.get();
// 核心的转换处理
private JSONArray parse(String json) {
try {
if (json == null || json.length() == 0) {
return null;
}
return JSONUtil.parseArray(json);
}catch (JSONException e){
log.error(json);
log.error(e);
return null;
}
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i,parameter.toString());
}
@Override
public JSONArray getNullableResult(ResultSet rs, String columnName) throws SQLException {
return parse(rs.getString(columnName));
}
@Override
public JSONArray getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return parse(rs.getString(columnIndex));
}
@Override
public JSONArray getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return parse(cs.getString(columnIndex));
}
}