第 3 章 核心处理层(中)
3.3 ResultSetHandler
MyBatis 将结果集按照映射配置文件中定义的映射规则,例如节点、resultType 属性等,映射成相应的结果对象。这一过程是由 ResultSetHandler 完成的。
public interface ResultSetHandler {
// 处理结果集,生成相应的结果对象集合
<E> List<E> handleResultSets(Statement stmt) throws SQLException;
// 处理结果集,返回相应的游标对象
<E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
// 处理存储过程的输出参数
void handleOutputParameters(CallableStatement cs) throws SQLException;
}
DefaultResultSetHandler 是 MyBatis 提供的 ResultSetHandler 的唯一实现。
3.3.1 handleResultSets()方法
通过 select 语句查询数据库得到的结果集由 handleResultSets() 方法进行处理。
3.3.2 ResultSetWrapper
DefaultResultSetHandler 在获取 ResultSet 对象之后,将其封装成 ResultSetWrapper 对象进行处理。
3.3.3 简单映射
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
// 默认的 ResultContext 对象
DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
ResultSet resultSet = rsw.getResultSet();
// 根据 RowBounds 中的 offset 定位到指定的记录
skipRows(resultSet, rowBounds);
// 检测已经处理的行数是否达到上限(RowBounds 中的 limit)以及 ResultSet 中是否还有更多的记录
while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
// 根据该行记录以及 ResultMap.discriminator 中的配置,解析出对应的 ResultMap
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
// 根据最终确定的 ResultMap,对该行记录进行映射,得到映射结果
Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
// 将映射结果存储到 resultHandler.resultList 中
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
}
3.3.4 嵌套映射
private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
// 创建 DefaultResultContext 对象
final DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
ResultSet resultSet = rsw.getResultSet();
// 根据 RowBounds 中的 offset 定位到指定的记录
skipRows(resultSet, rowBounds);
Object rowValue = previousRowValue;
// 检测 ResultSet 中是否还有更多的记录
while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
// 通过 resolveDiscriminatedResultMap() 方法解析出最终确定的 ResultMap
final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
// 为当前行记录创建 CacheKey 对象
final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);
// 从 nestedResultObjects 中获取该行记录对应的嵌套映射的映射结果
Object partialObject = nestedResultObjects.get(rowKey);
// 检测 resultOrdered 是否为 true
if (mappedStatement.isResultOrdered()) {
if (partialObject == null && rowValue != null) {
// 主结果对象发生变化
// 清空 nestedResultObjects 集合
nestedResultObjects.clear();
// 保存主结果对象,也就是嵌套的外层对象
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
// 完成该行记录的映射,将映射结果存储到 nestedResultObjects 集合中
rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
} else {
// 完成该行记录的映射,将映射结果存储到 nestedResultObjects 集合中
rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
if (partialObject == null) {
// 保存结果对象
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
}
}
// 对 resultOrdered 为 true 的情况进行特殊处理,调用 storeObject() 方法保存结果对象
if (rowValue != null && mappedStatement.isResultOrdered() && shouldProcessMoreRows(resultContext, rowBounds)) {
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
previousRowValue = null;
} else if (rowValue != null) {
previousRowValue = rowValue;
}
}
3.3.5 嵌套查询&延迟加载
“延迟加载”是指嵌套查询时,作为被嵌套属性的某个对象,直到真正被使用时,才会执行数据库操作加载到内存中。一个属性是否延迟加载,有两个地方配置:
-
中明确地配置了【fetchType】
<resultMap id="detailedBlogResultMap" type="Blog"> <!-- 对象属性的映射,同时也是一个嵌套映射 --> <association property="author" resultMap="authorResultMap" fetchType="eager"/> <!-- 集合属性的映射,也是一个匿名的嵌套映射 --> <collection property="posts" ofType="Post" fetchType="lazy"> <id column="post_id" property="id"/> <result column="post_content" property="content"/> </collection> </resultMap>
-
mybatis-config.xml 配置文件
<settings> <!-- 打开延迟加载的功能 --> <setting name="lazyLoadingEnabled" value="true"/> <!-- 将积极的延迟加载改变为消极的加载模式 --> <setting name="aggressiveLazyLoading" value="false"/> </settings>
MyBatis 中延迟加载是通过动态代理实现的,但是由于被代理对象通过是普通的 JavaBean,没有实现任何接口,所以无不使用 JDK 动态代理。MyBatis 提供了另外两种可以为普通 JavaBean 动态生成代理对象的方式。
1. cglib
cglib 采用字节码技术实现动态代理功能,其原理是通过字节码技术为目标类生成一个子类,并在该子类中采用方法拦截的方式拦截父类方法的调用,从而实现代理的功能。
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxy implements MethodInterceptor {
// cglib 中的 Enhancer 对象
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class<?> clazz) {
// 设置创建子类的类
enhancer.setSuperclass(clazz);
// 设置回调
enhancer.setCallback(this);
// 创建子类对象代理
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("前置处理");
// 调用父类方法
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("后置处理");
return result;
}
}
public class CgLibTest {
/**
* 目标方法
* @param str
* @return
*/
public String method(String str) {
System.out.println(str);
return "CgLibTest method(): " + str;
}
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy();
CgLibTest proxyImp = (CgLibTest) proxy.getProxy(CgLibTest.class);
String result = proxyImp.method("test");
System.out.println(result);
/**
* 前置处理
* test
* 后置处理
* CgLibTest method(): test
*/
}
}
2. Javassist
Javassist 是一个开源的生成 Java 字节码的类库,其主要优点在于简单、快速,直接使用 API 就能动态修改类的结构,或是动态地生成类。
public class JavassistMain {
public static void main(String[] args) throws Exception {
// 创建类池
ClassPool cp = ClassPool.getDefault();
// 要生成的类名为 com.example.chapter3.section3.JavassistTest
CtClass clazz = cp.makeClass("com.example.chapter3.section3.JavassistTest");
StringBuffer body;
// 创建字段、指定了字段名和字段类型、字段所属的类
CtField field = new CtField(cp.get("java.lang.String"), "prop", clazz);
// 指定该字段为 private
field.setModifiers(Modifier.PRIVATE);
// 设置字段的getter和setter方法
clazz.addMethod(CtNewMethod.setter("setProp", field));
clazz.addMethod(CtNewMethod.getter("getProp", field));
// 设置 prop 字段的初始化值,并将字段添加到类中
clazz.addField(field, CtField.Initializer.constant("MyName"));
// 创建构造方法,指定了构造方法的参数、构造方法所属的类
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
// 设置构造方法的方法体
body = new StringBuffer();
body.append("{\n prop=\"MyName\";\n}");
constructor.setBody(body.toString());
// 将构造方法添加到类中
clazz.addConstructor(constructor);
// 创建 execute方法,指定了方法的返回值类型、方法名、方法所属的类
CtMethod ctMethod = new CtMethod(CtClass.voidType, "execute", new CtClass[]{}, clazz);
// 设置方法的访问修饰符
ctMethod.setModifiers(Modifier.PUBLIC);
// 设置方法体
body = new StringBuffer();
body.append("{\n System.out.println(\"execute(): \" + this.prop);\n}");
ctMethod.setBody(body.toString());
// 将方法添加到类中
clazz.addMethod(ctMethod);
// 生成的类写入文件
clazz.writeFile("D:\\Gitee\\notes\\MyBatis\\MyBatis技术内幕\\MyBatis-Tec-Inside\\src\\main\\java");
// 加载 clazz 类,并创建对象
Class<?> c = clazz.toClass();
Object obj = c.newInstance();
// 调用 execute 方法
Method method = obj.getClass().getMethod("execute", new Class[]{});
method.invoke(obj, new Object[]{});
}
}
执行上述代码后,在指定目录下可以找到生成的 JavassistTest.class 文件
Javassist 也是通过创建目标类的子类方式实现动态代理功能的。
public class JavassistMain2 {
public static void main(String[] args) throws Exception {
ProxyFactory proxyFactory = new ProxyFactory();
// 指定父类,ProxyFactory 会动态生成一个子类
proxyFactory.setSuperclass(JavassistTest.class);
// 设置过滤器,判断哪些方法调用需要被拦截
proxyFactory.setFilter(m -> {
// 拦截 execute 方法
if (m.getName().equals("execute")) {
return true;
}
return false;
});
// 设置拦截处理
proxyFactory.setHandler((self, thisMethod, proceed, params) -> {
System.out.println("前置处理");
Object result = proceed.invoke(self, params);
System.out.println("执行结果:" + result);
System.out.println("后置处理");
return result;
});
// 生成代理类
Class<?> c = proxyFactory.createClass();
JavassistTest javassistTest = (JavassistTest) c.newInstance();
// 调用 execute 方法,会被拦截
javassistTest.execute();
System.out.println(javassistTest.getProp());
}
}
完整代码
3.3.6 多结果集处理
3.4 KeyGenerator
默认情况下,insert
语句并不会返回自动生成的主键,而是返回插入记录的条数。MyBatis 提供 KeyGenerator 接口来获取插入记录时产生的自增主键。
public interface KeyGenerator {
// 在执行 insert 之前执行,设置属性 order="BEFORE"
void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
// 在执行 insert 之后执行,设置属性 order="AFTER"
void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
}
3.4.1 Jdbc3KeyGenerator
Jdbc3KeyGenerator 用于取回数据库生成的自增 id,它对应于 mybatis-config.xml 配置文件中的 useGeneratedKeys 全局配置,以及映射配置文件中 SQL 节点的 useGeneratedKeys 属性。
3.4.2 SelectKeyGenerator
SelectKeyGenerator 用于不支持自动生成自增主键的数据库。
3.5 StatementHandler
StatementHandler 接口是 MyBatis 的核心接口之一,它的功能很多,例如创建 Statement 对象,为 SQL 语句绑定实参,执行 select、insert、update、delete 等多种类型的 SQL 语句,批量执行 SQL 语句,将结果集映射成结果对象。
public interface StatementHandler {
// 从连接中获取一个 Statement
Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException;
// 绑定 statement 执行时所需的实参
void parameterize(Statement statement) throws SQLException;
// 批量执行 SQL 语句
void batch(Statement statement) throws SQLException;
// 执行 update/insert/delete 语句
int update(Statement statement) throws SQLException;
// 执行 select 语句
<E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException;
<E> Cursor<E> queryCursor(Statement statement) throws SQLException;
BoundSql getBoundSql();
ParameterHandler getParameterHandler();
}
3.5.1 RoutingStatementHandler
RoutingStatementHandler 会根据 MappedStatement 中指定的 statementType 字段,创建对应的 StatementHandler 接口实现。
3.5.2 BaseStatementHandler
BaseStatementHandler 是一个实现了 StatementHandler 接口的抽象类,它只提供了一些参数绑定相关的方法,并没有实现操作数据库的方法。
3.5.3 ParameterHandler
ParameterHandler 只定义了一个 setParameters() 方法,主要负责调用 PreparedStatement.set*() 方法为 SQL 语句绑定实参。
3.5.4 SimpleStatementHandler
SimpleStatementHandler 继承了 BaseStatementHandler 抽象类。它底层使用 java.sql.Statement 对象来完成数据库的相关操作。
3.5.5 PreparedStatementHandler
PreparedStatementHandler 底层依赖于 java.sql.PreparedStatement 对象来完成数据库的相关操作。