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 中的配置,解析出对应的 ResultMapResultMap 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() 方法解析出最终确定的 ResultMapfinal ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);// 为当前行记录创建 CacheKey 对象final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);// 从 nestedResultObjects 中获取该行记录对应的嵌套映射的映射结果Object partialObject = nestedResultObjects.get(rowKey);// 检测 resultOrdered 是否为 trueif (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();}@Overridepublic 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.JavassistTestCtClass clazz = cp.makeClass("com.example.chapter3.section3.JavassistTest");StringBuffer body;// 创建字段、指定了字段名和字段类型、字段所属的类CtField field = new CtField(cp.get("java.lang.String"), "prop", clazz);// 指定该字段为 privatefield.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 {// 从连接中获取一个 StatementStatement 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 对象来完成数据库的相关操作。