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 的唯一实现。
通过 select 语句查询数据库得到的结果集由 handleResultSets() 方法进行处理。
DefaultResultSetHandler 在获取 ResultSet 对象之后,将其封装成 ResultSetWrapper 对象进行处理。
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);
}
}
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;
}
}
“延迟加载”是指嵌套查询时,作为被嵌套属性的某个对象,直到真正被使用时,才会执行数据库操作加载到内存中。一个属性是否延迟加载,有两个地方配置:
中明确地配置了【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 采用字节码技术实现动态代理功能,其原理是通过字节码技术为目标类生成一个子类,并在该子类中采用方法拦截的方式拦截父类方法的调用,从而实现代理的功能。
<dependency>
<groupId>cglibgroupId>
<artifactId>cglibartifactId>
<version>3.3.0version>
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());
}
}
完整代码
默认情况下,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);
}
Jdbc3KeyGenerator 用于取回数据库生成的自增 id,它对应于 mybatis-config.xml 配置文件中的 useGeneratedKeys 全局配置,以及映射配置文件中 SQL 节点的 useGeneratedKeys 属性。
SelectKeyGenerator 用于不支持自动生成自增主键的数据库。
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();
}
RoutingStatementHandler 会根据 MappedStatement 中指定的 statementType 字段,创建对应的 StatementHandler 接口实现。
BaseStatementHandler 是一个实现了 StatementHandler 接口的抽象类,它只提供了一些参数绑定相关的方法,并没有实现操作数据库的方法。
ParameterHandler 只定义了一个 setParameters() 方法,主要负责调用 PreparedStatement.set*() 方法为 SQL 语句绑定实参。
SimpleStatementHandler 继承了 BaseStatementHandler 抽象类。它底层使用 java.sql.Statement 对象来完成数据库的相关操作。
PreparedStatementHandler 底层依赖于 java.sql.PreparedStatement 对象来完成数据库的相关操作。