在完成一个需求的时候,意外发现了resultSets
这一属性,以前都没有接触过,现在在学习经历写下了做一次总结与分享
除了resultType,resultMap做映射外,Mybatis还提供了开放的API来做映射的处理。
resultType
和resultMap
首先来说说MyBatis的resultType和resultMp,这个两个实际上就是对数据库中的列名和我们的Java实体中做了一对一的映射处理,使得数据库中的字段可以被赋值到我们的Java实体对象中,熟悉JDBC的童鞋肯定知道JDBC的结果集映射是多么烦,只不过这些繁琐的操作由Mybatis这个半自动ORM框架帮我们做了。
resultSets
这个属性其实更像我们的JDBC,使用JDBC定制地去操作实体的映射
话不多讲,先看代码
-- 表名做了处理
<select id="selectItemExportByQueryInfo" resultSets="financeInvoiceItemResultMapper">
SELECT bii.item_id id, bli.ln_std_name stdName, bli.id_card idCard, bli.std_no stdNo, bu.unvs_name unvsName, bli.grade, bup.pfsn_level pfsnLevel,
bup.pfsn_name pfsnName, bup.teach_method teachMethod, bli.std_stage stdStage, bli.inclusion_status inclusionStatus, bli.mobile, CONCAT(bia.province,bia.city, bia.district, bia.street, bia.receive_address) address,
oc.campus_name campusName, bii.item_code itemCode, bia.apply_time applyTime, bso.fee_amount feeAmount, bso.zm_scale zmScale, bso.coupon_scale couponScale,
(bso.pay_amount+bso.demurrage_scale) AS actualAmount, bii.status, IF(bsc.new_learn_id,1,0) AS hasChange, bii.export_status exportStatus
FROM
bms.m bii
LEFT JOIN bms.a bia ON bia.apply_id = bii.apply_id
LEFT JOIN bms.i bli ON bli.learn_id = bii.sub_learn_id
LEFT JOIN bms.u bu ON bu.unvs_id = bli.unvs_id
LEFT JOIN bms.p bup ON bup.pfsn_id = bli.pfsn_id
LEFT JOIN bms.s oc ON oc.campus_id = recruit_campus_id
LEFT JOIN pay.r bso ON bso.sub_order_no = bii.sub_order_no
LEFT JOIN bms.e bsc ON bsc.new_learn_id = bli.learn_id
</select>
这里用到了resultSets="financeInvoiceItemResultMapper"
实际上,这个是我们公司里重新定制地一个MyBatis插件,在这个插件里面进行了一些操作,等下直接上代码,首先来看看,MyBtis的dtd文件里,是有resultSets
给我们使用的,这一块是原生MyBatis自带的,而原生的MyBatis是使用DefaultResultSetHandler
来进行处理,那resultSets
究竟是做什么用的呢?
先看看那官方文档 http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html
通过描述,我们可以大概猜到,这个肯定是用来做映射用的(这不是废话吗),而且可以映射多个结果集。
那么,我们再看看是怎么使用的,还是在文档的底部,我们找到了一些使用案例。
也就是说,我们在对多个结果集做映射的场景的时候,我们是可以指定分别映射的,也是通过association
来进行额外映射。
好,原生的我们就先说到这里,毕竟我也没有深究过哈哈。
现在来看看我们公司客制版的resultSets
处理
resultSets
@Intercepts(@Signature(method = "handleResultSets", type = ResultSetHandler.class, args = { java.sql.Statement.class }))
/**
* @desc 对结果集处理
*/
public class ResultHandlerInterceptor implements Interceptor {
private static Logger logger = LoggerFactory.getLogger(ResultHandlerInterceptor.class);
@Override
public Object intercept(Invocation invocation) throws Throwable { //获取参数值,调用execHandlerResult方法进行处理
ResultSetHandler resultSetHandler = (ResultSetHandler) invocation.getTarget();
MappedStatement mappedStatement = (MappedStatement) ReflectionUtils.getFieldValue(resultSetHandler,
"mappedStatement");
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
if (sqlCommandType == SqlCommandType.SELECT) {
String mapper = StringUtil.join(mappedStatement.getResultSets());
logger.info("ResultHandlerInterceptor.intercept,mapperRow:{}", mapper);
if (!StringUtil.isEmpty(mapper)) {
Statement statement = (Statement) invocation.getArgs()[0];
return execHandlerResult(mapper, statement.getResultSet());
}
}
return invocation.proceed();
}
/**
* @dsec 客制化处理resultSet结果集
* @param rs
*/
private Object execHandlerResult(String mapper, ResultSet rs) throws SQLException {
try {
ResultMapper resultMapper = ApplicationContextUtil.getBeanIgnoreEx(mapper);
if (resultMapper != null) {
return resultMapper.handler(rs);
}
} finally {
if (rs != null) {
rs.close();
}
}
return null;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
在execHandlerResult
方法中,获取了ResultMapper
这个类,这个也是我们自己的定义的一个接口
/**
*
* @desc RowMapper 基类处理
* @author lingdian
*
* @param
*/
@FunctionalInterface
public interface ResultMapper {
/**
* @desc 处理整个ResultSet
* @param rs
* @return
* @throws SQLException
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
default Object handler(ResultSet rs) throws SQLException {
List result = Lists.newArrayList();
int row = 1;
while (rs != null && rs.next()) {
result.add(handlerRow(rs, row));
row++;
}
return result;
}
/**
* @desc 处理当条ResultSet
* @param rs
* @return
* @throws SQLException
*/
Object handlerRow(ResultSet rs, int rowNum) throws SQLException;
}
通过这个抽象出来的ResultMapper
接口,我们可以自己定义我们的handler方法。
好了,下面到了我要写这篇博客的目的了,就是我在其中感受到了代码的智慧哈哈哈
首先看看我实现ResultMapper的FinanceInvoiceItemResultMapper
类
@Component("financeInvoiceItemResultMapper")
public class FinanceInvoiceItemResultMapper implements ResultMapper {
@Override
public Object handler(ResultSet rs) throws SQLException {
BaseExportList<BdInvoiceExport> list = new BaseExportList<>();
int row = 1;
while(rs != null && rs.next()){
list.add(rs.getLong(1)+"",(BdInvoiceExport)handlerRow(rs,row));
row++;
}
return list;
}
@Override
public Object handlerRow(ResultSet resultSet, int i) throws SQLException {
BdInvoiceExport bdInvoiceExport = new BdInvoiceExport();
bdInvoiceExport.setId(resultSet.getLong(1));
bdInvoiceExport.setName(resultSet.getString(2));
... // 这里省去了其他的子段,实际上有很多
return bdInvoiceExport;
}
}
这里着重看handler
方法,重写的这个方法,我们主要是改变原来的List,将实际类型替换为BaseExportList
,这个list是继承了ArrayList的
public class BaseExportList<T> extends ArrayList<T> {
private Map<String, T> data = Maps.newHashMap();
public boolean add(String id, T e) {
data.put(id, e);
return super.add(e);
}
public T get(String id) {
return data.get(id);
}
public Map<String, T> getData() {
return data;
}
public Set<String> getKeys() {
return data.keySet();
}
}
通过观察这个List,我们可以很快的感受,用这个List我可以不通过循环,直接拿到我查询到的结果的id集合!
接着看回handler
方法,剩下的也没什么了,就是调用BaseExportList的add方法,在add的时候就将id分好了。
p.s. 这里有个细节,就是BaseExportList的get方法是里面的参数是String类型的,并不是int类型。
而下面的handlerRow
方法只是一个个取出来映射进去而已。
在我这个场景中,利用resultSets的意义就在于我在导出数据时,我需要将这些数据改变导出的状态,一般做法是循环遍历导出的数据,然后获取这些id再批量修改状态,而利用的这个resultSets
,则不再需要循环遍历了。
/*运用场景*/
BaseExportList<BdInvoiceExport> list = bdInvoiceMapper.selectItemExportByQueryInfo(bdInvoiceQuery);
// ......
Set<String> keys = list.getKeys();
bdInvoiceMapper.updateExportStatus(keys); //修改状态
// ......
实际上,在看到以前同事用BaseExportList
这个类的时候,我并不知道这个是和我们resultSets
属性体系绑定使用了,我还是像原来一样写resultType,我认为是MyBatis在后面进行了转换类型,并调用了BaseExportList
的add方法来插值,后面发现,在调用用list.getKeys()
时拿不到值,于是开始debug MyBatis,这一过程也学习到了很多,以后有机会另写一篇博客来总结,这次debug最后得出的结论是,MyBatis一开始是将数据保存在ArrayList中,后面确实会转换list类型,但是它是通过反射,来创建一个用户声明的类型,这里是BaseExportList
,然后再调用这个对象addAll()
方法将原来ArrayList的数据保存在BaseExportList中。
后面和同事讨论了一下,因为BaseExportList是直接继承ArrayList的,而ArrayList的addAll方法是通过System.arraycopy
来实现的,那么如果我们重写了BaseExportList
的addAll
方法,在里面使用add
来设值,是不是也可以达到相同的效果嘞,嘿嘿,以后有机会试下