ResultHandler字面上意思就是对查询结果处理。
-->mybatis官网
想要理解ResultHandler需要知道一下源码,查询过程是怎么跑的
ResultHandler: 参数允许你按你喜欢的方式处理每一行。你可以将它添加到 List 中、创建 Map 和 Set,或者丢弃每个返回值都可以,它取代了仅保留执行语句过后的总结果列表的死板结果。你可以使用 ResultHandler 做很多事,并且这是 MyBatis 自身内部会使用的方法,以创建结果集列表。
由于网上对ResultHandler使用的文章介绍过少,于是我就自己去摸索了。
1.探索之前准备好一个小例子
我准备了一张user表
2.自己搭建好一个简单的mybatis框架
3.先看看官方文档
官方告诉了我们SqlSession这个类里面应该只有select方法才有ResultHandler参数
然后我们再去看myabtis里面的SqlSession这个类
果然只有select方法才会对ResultHandler进行处理
对比之下selectList是没有的
接下来需要了解mybatis查询在源码当中是经历了什么过程才能知道我们查询的时候什么时候才会去走select方法.
接下来我大致讲下源码怎么走,关键是走到哪里才会运用到ResultHandler
String resource = "mybatis-cfg.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
// 获取mapper,返回的mapper是经过jdk动态代理的
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
Optional<User> user = userMapper.selectOneById(1);
System.out.println(user.get().getName());
sqlSession.close();
先从sqlsession.getMapper(UserMapper.class)
代码说起,它是调用了DefaultSqlSession里面的getMapper方法,我们继续追踪,追踪到MapperRegistry这个类里面的getMapper方法。
public class MapperRegistry {
private final Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
public MapperRegistry(Configuration config) {
this.config = config;
}
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
我们再继续点进去mapperProxyFactory.newInstance(sqlSession);
这个方法
接下来我们进入到MapperProxyFactory 这个类
最终返回的UserMapper是经过MapperProxy这个代理类的类
接下来我们得到UserMapper就要跑Optional<User> user = userMapper.selectOneById(1);
这个代码。运行这行代码前,因为userMapper是被代理过的类。那么selectOneById之前会先去调用MapperProxy这个类的invoke方法.
我们去到MapperProxy会看到下面的invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
最后实际调用查询方法是mapperMethod.execute(sqlSession, args);
我们点execute追踪下去来到MapperMethod这个类的execute方法
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
这部分代码意思是你进行增删改查,它会根据条件去匹配方法。
重点来了
我们着重去看SELECT部分,根据最上面文档我们知道调用SqlSession的select方法才会去用到ResultHandler,我们继续SELECT里面一个一个方法去看,最终发现executeWithResultHandler(sqlSession, args);
这个方法里面才调用select方法。
当然我是怎么知道的呢,因为我按照文档写了一个方法试着传了ResultHandler进去发现ResultHandler并没有用。
public interface UserMapper {
User selectByPrimaryKey(Integer id, ResultHandler<User> resultHandler);
}
UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xiaowan.UserMapper">
<sql id="Base_Column_List" >
id, name, age
</sql>
<select id="selectByPrimaryKey" resultType="com.xiaowan.User">
select
<include refid="Base_Column_List" />
from user
where id = #{id}
</select>
</mapper>
简单的查询方法,对了我们需要自己新建一个自己的ResultHandler类去实现ResultHandler
/**
* @Description 为了测试,结果处理类的作用只是简单的将名字+上hello
* @Author wbm
* @Date 2019/10/11 0011 下午 04:48
**/
public class MyResultHandler implements ResultHandler<User> {
private User result = null;
@Override
public void handleResult(ResultContext<? extends User> resultContext) {
User user = resultContext.getResultObject();
user.setName(user.getName() + "hello");
result = user;
}
public User getReuslt(){
return result;
}
}
MyResultHandler 我主要只是简单将返回的结果的名字加上hello,实际根据需求去变换
为什么我传入的ResultHandler并没有产生作用呢,debug追踪,发现
我的代码并有去走
executeWithResultHandler(sqlSession, args);
这个方法,我们目的是要让它走executeWithResultHandler(sqlSession, args);
这个方法,它才会去调用我们的ResultHandler类。为什么一定要走这个方法,这个需要自己去深入看看这个executeWithResultHandler(sqlSession, args);
方法了。
没走进去,就说明判断条件不成立,我们看看判断条件
method.returnsVoid()
:返回类型是void
method.hasResultHandler()
:是否拥有ResultHandler这个参数
根据判断条件,我们需要修改一下我们的UserMapper方法
将原来的User返回类型改为void
public interface UserMapper {
void selectByPrimaryKey(Integer id, ResultHandler<User> resultHandler);
}
接下来修改下原来的查询方法,见下图
这样就成功了。
注意点
有时候简单的语句我们会直接在方法上面注解@Select然后写sql
public interface UserMapper {
@Select("select * from user where id=#{id}")
void selectByPrimaryKey(Integer id, ResultHandler<User> resultHandler);
}
会出现以上错误,是我们使用注解sql的时候,还必须得使用@ResultType声明返回类型,将上面的UserMapper修改为如下
public interface UserMapper {
@ResultType(User.class)
@Select("select * from user where id=#{id}")
void selectByPrimaryKey(Integer id, ResultHandler<User> resultHandler);
}
细节点:当结果是返回多条的时候
我们在UserMapper定义个按年龄(qryUserList)查询,这样会返回多条数据
public interface UserMapper {
@ResultType(User.class)
@Select("select * from user where id=#{id}")
void selectByPrimaryKey(Integer id, ResultHandler<User> resultHandler);
@ResultType(User.class)
@Select("select * from user where age = #{age} order by id asc")
void qryUserList(int age, ResultHandler<User> handler);
}
MyResultHandler :resultContext.getResultObject()这个方法里面只是会储存一条数据,意思假设你搜索出来的结果有3条分别是A,B,C,那么它就会跑3次MyResultHandler
public class MyResultHandler implements ResultHandler<User> {
private List<User> result = new ArrayList<>(10);
@Override
public void handleResult(ResultContext<? extends User> resultContext) {
User user = resultContext.getResultObject();
user.setName(user.getName() + "hello");
result.add(user);
}
public List<User> getReuslt(){
return result;
}
}
resultContext.stop(); 这个方法作用,当你找出3条数据ABC,
但是你根据条件匹配到只要A的数据,那么在第一次对比A的时候你发现正确,不想继续匹配BC数据,就可以用这个方法