Mybtis(四)工作原理
一、Mybatis涉及技术
mybatis运行主要两部分:
(1)读取配置文件换乘到Configuration对象,用以创建SqlSessionFactory
(2)SqlSession执行过程。
SqlSession执行过程涉及技术:
反射技术、动态代理技术。
什么是代理模式?
代理模式就是在原有的服务上多加一个占位,通过占位去控制服务的访问。
为什么要使用代理模式?
通过代理一方面可以控制如何访问真正的服务对象,提供额外服务。另一方面有机会通过重写一些类来满足特定的需要。
一般动态代理分两种:
(A)JDK反射机制提供的代理。必须提供接口。
(B)CGLIB代理。不需要提供接口。 CGlib底层是动态的在内存中生成了目标对象的子类的字节码,并生成相应的对象 。
1、反射简单的示例
package com.ssm.web.demo.test.reflect;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class SayService {
public void sayHello(String someOne){
System.out.println(" hello ," +someOne +" !");
}
public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
//通过反射创建SayService对象
Object service = Class.forName(SayService.class.getName()).newInstance();
//获取服务的方法
Method method = service.getClass().getMethod("sayHello", String.class);
//反射调用服务的方法
method.invoke(service, "zhangxiaocai");
}
}
运行输出:
hello ,zhangxiaocai !
反射的好出是配置性提高,springIOC容器也是使用反射机制实现。
2、JDK动态代理
支持包:java.lang.reflect.*
实现JDK动态代理步骤:
(1)编写服务类和接口,是真正的服务提供者,在JDK代理这接口是必须的。
(2)编写代理类,提供绑定和代理方法。
服务接口类:
package com.ssm.web.demo.test.jdktest;
public interface SayServiceI {
public void sayHello(String someOne);
}
服务实现类:
package com.ssm.web.demo.test.jdktest;
public class SayServiceImpl implements SayServiceI {
@Override
public void sayHello(String someOne) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(" hello ," + someOne + " !");
}
}
服务代理类:
package com.ssm.web.demo.test.jdktest;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class SayServiceProxy implements InvocationHandler {
//需要持有真实的目标服务
private Object target;
public SayServiceProxy(){
}
//构造法绑定委托对象
public SayServiceProxy(Object object){
this.target = object;
}
//绑定委托对象并返回代理类
public Object bind(Object object) {
this.target = object;
//取得代理对象
Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
return proxy;
}
/**
* 通过代理对象调用方法首先会进入此方法
* @param proxy -----代理对象
* @param method ------调用方法
* @param args ----方法参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
//System.out.println("----proxy = " + proxy.toString());
//System.out.println("----proxy = " + proxy.hashCode());
System.out.println("----proxy = " + proxy.getClass().getName());
System.out.println("----method = " + method);
System.out.println("---反射方法执行前调用----");
result = method.invoke(this.target, args);
System.out.println("---反射方法执行前调用----");
return result;
}
}
调用测试:
package com.ssm.web.demo.test.jdktest;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class SayServiceTest {
public static void main(String[] args) {
SayServiceProxy proxyHandle = new SayServiceProxy();
//调用bind绑定的方式
SayServiceI service = (SayServiceI) proxyHandle.bind(new SayServiceImpl());
service.sayHello("zhangxiaocai.cn");
SayServiceI mysay = new SayServiceImpl();
//调用构造方法
InvocationHandler handler = new SayServiceProxy(mysay);
SayServiceI proxyHello = (SayServiceI) Proxy.newProxyInstance(mysay.getClass().getClassLoader(), mysay.getClass().getInterfaces(), handler);
proxyHello.sayHello("zhangxiaocai.cn");
}
}
执行结果如下,两种写法的调用结果一致:
----proxy = com.sun.proxy.$Proxy0
----method = public abstract void com.ssm.web.demo.test.jdktest.SayServiceI.sayHello(java.lang.String)
---反射方法执行前调用----
hello ,zhangxiaocai.cn !
---反射方法执行前调用----
----proxy = com.sun.proxy.$Proxy0
----method = public abstract void com.ssm.web.demo.test.jdktest.SayServiceI.sayHello(java.lang.String)
---反射方法执行前调用----
hello ,zhangxiaocai.cn !
---反射方法执行前调用----
执行过程中如果出现死循环导致栈溢出(SOF),注意下列写法会导致死循环。
result = method.invoke(proxy, args);//第一个参数写出proxy
System.out.println("----proxy = " + proxy.toString());//调用了代理对象的方法
System.out.println("----proxy = " + proxy.hashcode());//调用了代理对象的方法
代理对象是没有自己的方法的,它的所有方法都是基于被代理对象,而调用代理对象方法的时候,都会经过拦截器方法。因此,如果在拦截器中再调用代理对象的方法(如toString,hashcode,equals等),就会再次进入拦截器,这样就形成了死循环。
还可以
在jvm启动时加上-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true参数,保存生成的动态代理类字节码$Proxy0.class
然后再利用反编译工具查看代理类$Proxy0.class源码
关于 newProxyInstance
f方法:
Proxy.newProxyInstance(mysay.getClass().getClassLoader(), mysay.getClass().getInterfaces(), handler);
第一个参数mysay.getClass().getClassLoader(),是类加载器。
第二个参数mysay.getClass().getInterfaces(),是接口,代理对象挂在哪个接口下面。
第三个参数InvocationHandler实例,写在代理类中时直接使用this,表示当前代理类;写在非代理类中则是InvocationHandler子类指向InvocationHandler自己的引用。例子中使用了两种不同的写法。
3、CGLIB动态代理
因JDK动态代理必须提供接口才可以使用,为了解决这个难题,CGLIB出现了。
CGLIB是开源框架,也是比较流行的动态代理。
CGLIB的代理类需要实现org.springframework.cglib.proxy.MethodInterceptor
接口。
上例子中的服务接口类和服务实现类不变,添加CGLIB代理类:
package com.ssm.web.demo.test.jdktest;
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
public class SayServiceCglib implements MethodInterceptor {
//需要持有真实的目标服务
private Object target;
// 创建代理对象
public Object getInstance(Object object) {
this.target = object;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
//设置回调方法
enhancer.setCallback(this);
//创建代理对象并返回
return enhancer.create();
}
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object result = null;
System.out.println("----proxy = " + proxy.getClass().getName());
System.out.println("----method = " + method.toString());
System.out.println("---反射方法执行前调用----");
result = method.invoke(this.target, args);
System.out.println("---反射方法执行前调用----");
return result;
}
}
测试运行类
package com.ssm.web.demo.test.jdktest;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import org.springframework.cglib.proxy.MethodInterceptor;
public class SayServiceCglibTest {
public static void main(String[] args) {
SayServiceI mysay = new SayServiceImpl();
MethodInterceptor handler = new SayServiceCglib();
SayServiceI proxyHello =(SayServiceI) ((SayServiceCglib) handler).getInstance(mysay);
proxyHello.sayHello("zhangxiaocai");
}
}
执行结果
----proxy = com.ssm.web.demo.test.jdktest.SayServiceImpl$$EnhancerByCGLIB$$71af915d
----method = public void com.ssm.web.demo.test.jdktest.SayServiceImpl.sayHello(java.lang.String)
---反射方法执行前调用----
hello ,zhangxiaocai.cn !
---反射方法执行前调用----
Mybatis中在延迟加载时会使用CGLIB动态代理。
二、构建SqlSessionFactory过程
SqlSessionFactory 是Mybatis的核心类之一,它最重要的功能就是提供Mybatis核心接口SqlSession。
1、构建步骤
Mybatis使用构造模式去创建SqlSessionFactory,大致过程分两步:
(1)通过org.apache.ibatis.builder.xml.XMLConfigBuilder
解析配置XML文件读出配置参数,并将读得的数据存入org.apache.ibatis.session.Configuration
类中。(几乎所有配置都在这个Configuration类中)
(2)使用Configuration对象创建SqlSessionFactory 。SqlSessionFactory是接口不能直接使用,一般使用它默认的实现类org.apache.ibatis.session.defaults.DefaultSqlSessionFactory
。
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
这种创建方式就是一种Buidler模式。对于复杂的对象,直接使用构造方法构建比较困难的,这会导致大量的逻辑放在构造方法中,过程及其复杂。这种Builder模式值得我们学习。
构建SqlSessionFactory 过程中,Configuration最重要。
2、Configuration的主要作用:
(A)读入配置文件,包括基础配置XML和映射器XML文件。
(B)初始化基础配置,如别名、映射器、对象工厂、类型转换器等
(C)提供单例,为后续创建SessionFactory服务提供配置参数。
(D)执行重要的对象方法,初始化配置信息。
Configuration源码有八百多行,就不贴了。
具体相关配置可以参考《Mybatis配置大全》
3、映射器组成
映射器主要由三个部分组成:
(1)MappedStatement - 保存映射器的一个节点(select | inset | delete | update)。包括配置的SQL、SQL的id、缓存信息、resultMap、parameterType、resultType、languageDriver等重要配置内容。
(2)SqlSource - 提供BoundSql 对象的地方,是MappedStatement 的一个属性。本质上一个接口,主要作用是根据参数和其他规则组装SQL。
(3)BoundSql - 建立SQL和参数的地方。常用属性有3个:SQL、parameterObject、parameterMapping。
(A)parameterObject 表示参数本身。传递基础类型对象时,Mybatis会把参数变成基础类型对应的包装类型对传递,如传int类型,参数会变成Integer类型。
(B)如果传递的是POJO或Map,那么parameterObject 就传入的POJO或Map。
(C)传递多个参数时,不使用@Param
注解,Mybatis会把parameterObject变成Map<String,Object>对象,Map里的键值关系按照参数顺序存放:
{"1":value1,"2",value2,"3":value3......,"param1":value1,"param2":value2,"param3":value3......}
可以使用时可以通过#{param1}
或 #{1}
取第一个参数。
举个例子如:
//假设传入id的值为 10010, name的值为 zhangxiaocai
List<T> selectList(long id, String name);
则parameterObject变成Map<String,Object>对象后内容为:
{"1":10010,"2","zhangxiaocai","param1":10010,"param2":"zhangxiaocai"}
(D)传递多个参数时,使用@Param
注解,Mybatis也会把parameterObject变成Map<String,Object>对象,Map里的键值关系按照参数顺序存放:
{"key1":value1,"key2",value2,"3":value3......,"param1":value1,"param2":value2,"param3":value3......}
举个例子:
//假设传入id的值为 10010, name的值为 zhangxiaocai
List<T> selectList(@Param("id") long id, @Param("name") String name);
那么对于的parameterObject变成的Map<String,Object>对象为:
{"id":10010,"name","zhangxiaocai","param1":10010,"param2":"zhangxiaocai"}
(E)parameterMappings是由元素为ParameterMapping对象组成的List集合。ParameterMapping对象描述的是参数,参数包括属性、名称、表达式、JavaType、jdbcType、typeHandler等信息。通过ParameterMapping对象实现参数和SQL的结合,方便PreparedStatement能通过ParameterMapping找到parameterObject对象的属性并设置参数。
(F)SQL就是映射器的一条SQL。
三、SqlSession运行过程
有了SqlSessionFactory就可以直接拿到SqlSession。
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
sqlSession = sqlSessionFactory.openSession();
SqlSession可以进行查询、写入、更新、删除等方法,新版Mybatis使用Mapper映射器进行操作。
1、Mapper映射器动态代理
在Mybatis源码org.apache.ibatis.binding.MapperProxy<T>
类中可以看出Mapper是通过动态代理来实现的:
package org.apache.ibatis.binding;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
import org.apache.ibatis.reflection.ExceptionUtil;
import org.apache.ibatis.session.SqlSession;
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
}
但是动态代理的特点是:
(1)实现InvocationHandler的接口,重写invoke方法。
(2)使用绑定或构造方法建立与被代理对象的练习。
(3)Proxy.newProxyInstance(…)取得代理对象。
按以上三点来看,代理类里没有看到Proxy.newProxyInstance的调用,因为Mybatis使用MapperProxyFactory来实现代理类管理的,在这里找到了Proxy.newProxyInstance的调用。
package org.apache.ibatis.binding;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.ibatis.session.SqlSession;
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
一旦Mapper接口调用SQL方法,那么就会运行到invoke方法中,invoke首先判断它是否是一个类,Mapper是接口不是类,所以会走到下面,生成MapperMethod对象,通过cachedMapperMethod方法对其进行初始化,然后执行execute方法,吧SQLSession和当前运行的参数传递进行。
MapperMethod的execute的方法,代码如下:
package org.apache.ibatis.binding;
//MapperMethod 类内容较多,只贴了execute的方法。
public class MapperMethod {
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);
}
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;
}
//查询List的操作
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.<E>selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
}
MapperMethod是采用命令模式运行,根据上下文跳转的。
execute里都是常见的IUDS
操作,根据不同的SQL类型进行处理。
比如SELECT里有个 result = executeForMany(sqlSession, args); 就是一个典型的查询List结果的调用,从源代码里可以看到最后都是通过调用sqlSession去执行的对象的SQL。
Mapper接口能够执行SQL,就是因为映射器的XML文件命名空间对应的是这个接口的全路径,根据全路径和方法进行绑定,通过动态代理技术让这个接口运行起来。然后采用命令模式,根据SQL类型跳转不同方法,但最终还是使用SqlSession接口的方法使用执行对应SQL返回结果。
2、 SqlSession的四大对象
Mapper执行的过程是通过Excutor
、StatementHandler
、ParameterHandler
、ResultHandler
来完成数据库操作和结果返回。
(1)Excutor
代表执行器,负责调度StatementHandler
、ParameterHandler
、ResultHandler
等来执行对应SQL。
(2)StatementHandler
作用是使用数据库的Statement(PreparedStatement)
执行操作,是四大对象的核心,起到承上启下的作用。
(3)ParameterHandler
用于SQL参数的处理。
(4)ResultHandler
是进行最后数据集(ResultSet)的封装返回处理。
1、Excutor执行器
执行器Excutor是真正执行Java和数据交互的东西,主要有三种执行器。可以在配置setting元素的属性defaultExcutorType来进行配置。
(A)SIMPLE - 简单执行器,不配做就是默认执行器。
(B)REUSE - 是一种执行器重用预处理语句。
(C)BATCH - 执行器重用语句和批量更新,是针对批量专用的执行器。
三者都提供了查询和更新的方法,事务方法。
Configuration
中可以看到创建执行器的过程:
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
其中interceptorChain.pluginAll
是Mybatis插件,用来构建动态代理对象,在调度真正的Executor方法执行执行配置插件代码可以修改。
如SimpleExecutor代码:
package org.apache.ibatis.executor;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.transaction.Transaction;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collections;
import java.util.List;
public class SimpleExecutor extends BaseExecutor {
public SimpleExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
@Override
protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>queryCursor(stmt);
}
@Override
public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
return Collections.emptyList();
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
}
从代码可以看到常见的doUpdate和doQuery操作。以doQuery为例,其过程大致如下:
根据Configuration来构建StatementHandler,然后使用prepareStatement方法对象SQL编译并对参数初始化,它的实现过程调用StatementHandler的prepare()进行预编译和基础设置,然后通过根据StatementHandler的parameterize()来设置参数并执行,ResultHandler再组装查询结果返回给调用者来完成查询。
2、数据库会话器
数据库会话器StatementHandler主要负责与数据库会话操作。
若要指定使用,可以在SQL上进行配置:
<!-- statementType (可选配置,默认配置为PREPARED)
STATEMENT,PREPARED 或 CALLABLE 的一个。
分别对应使用 Statement,PreparedStatement 或 CallableStatement -->
<insert id="save" statementType="STATEMENT">
</insert>
Configuration
中可以看到创建数据库会话器的过程:
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
可以看到创建statementHandler的动作是由RoutingStatementHandler来完成,它实现了statementHandler接口,和Executor类似用代理对象进行封装。
在RoutingStatementHandler
的源代码中:
package org.apache.ibatis.executor.statement;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.ExecutorException;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
public class RoutingStatementHandler implements StatementHandler {
private final StatementHandler delegate;
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
//some code ...
}
可以看出RoutingStatementHandler也不是真正服务对象,它是通过适配模式找到对应的StatementHandler来执行。Mybatis中StatementHandler和Executor一样也有三种:
(A)SimpleStatementHandler - 对应JDBC中Statement接口,执行普通SQL
(B)PreparedStatementHandler - 对应JDBC中的PreparedStatement,预编译SQL的接口;
(c)CallableStatementHandler - 对应JDBC中CallableStatement,用于执行存储过程相关的接口;
RoutingStatementHandler中定义了一个delegate,它是一个StatementHandler接口对象,构造方法根据配置来适配对应的StatementHandler对象。
RoutingStatementHandler的作用是给实现类对象的使用提供一个统一简易的使用适配器,这是对象的适配模式,可以让我们使用现有的类和方法对外提供服务,也可以根据实际需求对外屏蔽方法,加入新方法。
如PreparedSatementHandler执行Mybatis执行查询,有三个主要的方法:
prepare、parameterize、query
如下是BaseStatementHandler的prepare方法:
package org.apache.ibatis.executor.statement;
public abstract class BaseStatementHandler implements StatementHandler {
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
statement = instantiateStatement(connection);
setStatementTimeout(statement, transactionTimeout);
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
}
instantiateStatement()方法是对SQL进行预编译。
下面是PreparedSatementHandler里的instantiateStatement()和parameterize()方法:
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() != null) {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
} else {
return connection.prepareStatement(sql);
}
}
@Override
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
}
prepare()方法首先进行一些基础配置,如超时、获取最大行数等设置。然后Executor会调用parameterize()方法设置参数。从代码可以看到设置参数是由ParameterHandler来完成的。
SatementHandler执行查询的方法里,由于执行前参数和SQL都被prepare()方法预编译,参数在parameterize()方法完成了设置,只要执行SQL返回结果即可。执行之后就是ResultHandler对结果的封装和返回。
SQL执行过程总结:
Executor会先调用StatementHandler的prepare()方法预编译SQL语句,并设置基本运行参数。
然后使用parameterize()方法启用ParameterHandler设置成桉树,完成预编译,跟着就是执行SQL操作(query/update)
最后,如果有结果集就使用ResultHandler封装结果返回给调用者。
3、参数处理器
参数处理器ParameterHandler主要作用就是完成对预编译参数的设置。
ParameterHandler源码:
package org.apache.ibatis.executor.parameter;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public interface ParameterHandler {
//返回参数对象
Object getParameterObject();
//设置预编译SQL语句的参数
void setParameters(PreparedStatement ps)throws SQLException;
}
ParameterHandler接口中,getParameterObject()方法的作用是返回参数对象,setParameters()方法的作用是设置预编译SQL语句的参数。
Mybatis为ParameterHandler提供了一个实现类DefaultParameterHandler,setParameters()方法源码:
package org.apache.ibatis.scripting.defaults;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeException;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;
public class DefaultParameterHandler implements ParameterHandler {
private final TypeHandlerRegistry typeHandlerRegistry;
private final MappedStatement mappedStatement;
private final Object parameterObject;
private BoundSql boundSql;
private Configuration configuration;
public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
this.mappedStatement = mappedStatement;
this.configuration = mappedStatement.getConfiguration();
this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
this.parameterObject = parameterObject;
this.boundSql = boundSql;
}
@Override
public Object getParameterObject() {
return parameterObject;
}
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
} catch (SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
}
从代码中可以看到从parameterObject对象获取参数,然后使用类型转换器typeHandler进行参数处理。如果自定义了类型转换器,就会根据签名注册typeHandler对参数进行处理。类型转换器也是在Mybatis初始化的时候注册在Configuration里面。
4、结果处理器
ResultHandler是封装结果集的。
ResultHandler的源码:
package org.apache.ibatis.executor.resultset;
import org.apache.ibatis.cursor.Cursor;
import java.sql.CallableStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
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;
}
其中handleOutputParameters()方法是处理存储过程输出参数。handleResultSets()方法是包装结果集的。
Mybatis提供了一个实现类DefaultResultSetHandler类,默认情况下都是通过这个类进行处理。它的实现涉及JavaSSIST或CGLIB作为延迟加载的,然后通过typeHandler和ObjectFacorty进行组装结果再返回的。
public class DefaultResultSetHandler implements ResultSetHandler {
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<Object>();
int resultSetCount = 0;
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults);
}
}
3、SqlSession运行总结:
SqlSession是通过Executor调度StatementHandler来运行,StatementHandler要结果三步:
(A)prepared预编译SQL。
(B)parameterize设置参数
(C)query或update执行SQL
parameterize是调用ParameterHandler的方法去设置,参数是根据类型处理器typeHandler处理的。query或update方法通过ResultHandler进行处理结果的封装。update返回整数,query返回的结果集则是通过typeHandler处理结果类型,然后使用ObjectFactory提供的规则组装对象,返回给调用者。
相关文章:
文章名称 |
---|
《Mybatis(一)主要组件》 |
《Mybatis(二)配置》 |
《Mybatis(三)动态SQL》 |
《Mybtis(四)工作原理》 |
《Mybtis(五)Mapper映射器》 |
《Mybtis(六)Mapper级联》 |