我们目前在Mybatis中,我们知道Mybatis的Mapper是一个接口,而不是一个实体类。在Java中接口是没有办法运行的。那么它是怎么运行的呢?它是通过动态代理运行。
一、MyBatis是怎么实现这个动态代理的
[java] view plain copy-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 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;
- }
- }
这便是mybaitis处理对象的方法,我们可以看到invoke方法。我们知道一旦mapper是一个代理对象,那么它就会运行到invoke方法里面,invoke首先判断是否一个类,显然这里mapper是一个接口,不是类所以判定失败。那么跟着就会生成MapperMethod对象,它是通过cachedMapperMethod方法对其初始化的,然后执行execute方法,把sqlSession和当前运行的参数传递进去。于是让我们看看这个execute方法的源码:
[java] view plain copy- package org.apache.ibatis.binding;
-
-
- public class MapperMethod {
-
- private final SqlCommand command;
- private final MethodSignature method;
-
- public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
- this.command = new SqlCommand(config, mapperInterface, method);
- this.method = new MethodSignature(config, method);
- }
-
- public Object execute(SqlSession sqlSession, Object[] args) {
- Object result;
- if (SqlCommandType.INSERT == command.getType()) {
- Object param = method.convertArgsToSqlCommandParam(args);
- result = rowCountResult(sqlSession.insert(command.getName(), param));
- } else if (SqlCommandType.UPDATE == command.getType()) {
- Object param = method.convertArgsToSqlCommandParam(args);
- result = rowCountResult(sqlSession.update(command.getName(), param));
- } else if (SqlCommandType.DELETE == command.getType()) {
- Object param = method.convertArgsToSqlCommandParam(args);
- result = rowCountResult(sqlSession.delete(command.getName(), param));
- } else if (SqlCommandType.SELECT == command.getType()) {
- if (method.returnsVoid() && method.hasResultHandler()) {
- executeWithResultHandler(sqlSession, args);
- result = null;
- } else if (method.returnsMany()) {
- <span style="color:#FF0000;">result = executeForMany(sqlSession, args);
- } else if (method.returnsMap()) {
- result = executeForMap(sqlSession, args);
- } else {
- Object param = method.convertArgsToSqlCommandParam(args);
- result = sqlSession.selectOne(command.getName(), param);
- }
- } else if (SqlCommandType.FLUSH == command.getType()) {
- result = sqlSession.flushStatements();
- } else {
- 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;
- }
- ........
-
- 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 {
- <span style="color:#FF0000;">result = sqlSession.<E>selectList(command.getName(), param);</span>
- }
-
- if (!method.getReturnType().isAssignableFrom(result.getClass())) {
- if (method.getReturnType().isArray()) {
- return convertToArray(result);
- } else {
- return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
- }
- }
- return result;
- }
- .......
- }
好这里我们看到,MapperMethod 类采用命令模式运行,然后根据上下文跳转可能跳转到许多方法中取,我们不需要全部明白,我们可以看到里面的executeForMany方法,再看看它的实现,实际上它最后就是通过sqlSession对象去运行对象的SQL而已。至此,相信大家已经了解了Mybatis为什么只用mappper接口便能够运行sql,因为mapperd的xml文件的命名空间对应的便是这个接口全路径,那么它根据全路径和方法名,便能够绑定起来,通过动态代理技术,让这个接口跑起来。
二、SqlSession下面的四大对象之一-----执行器(executor)
首先我先解释一下标题 四大对象是指:executor, statementHandler,parameterHandler,resultSetHandler对象。(为了方便下面的文章说道四大对象就专指它们)
它们都是sqlSession的底层类实现,也是插件能够拦截的四大对象。所以这里已经触及了Mybatis的底层,动态代理,反射随时可以看到,了解他们的协作,是插件编写的基础之一,所以这是十分的重要。
Executor在sqlSession中的应用
一个mapper被执行是通过动态代理来完成的,然后进入到了sqlSession的方法中去。这个并不难理解,但是sqlSession内部是怎么运行的呢?答案四大对象的协作。在SqlSession它还是一个接口,mybatis内部是通过DefaultSqlSession这个实现类为我们提供服务的,它比较长,但是我们不需要全部看到,我们只看到很常用的selectList方法便可以了。
[java] view plain copy- package org.apache.ibatis.session.defaults;
- public class DefaultSqlSession implements SqlSession {
-
- private Configuration configuration;
- private Executor executor;
-
- private boolean autoCommit;
- private boolean dirty;
-
- .......
- @Override
- public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
- try {
- MappedStatement ms = configuration.getMappedStatement(statement);
- return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
- } catch (Exception e) {
- throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
- } finally {
- ErrorContext.instance().reset();
- }
- }
- ......
- }
我们可以看到它是通过executor去执行方法来完成查询的。初认Executor
那么我们对executor就很感兴趣,于是我们看看executor是怎么样的,首先在MyBATIS中有三种executor:
SimpleExecutor -- SIMPLE 就是普通的执行器。
ReuseExecutor -执行器会重用预处理语句(prepared statements)
BatchExecutor --它是批量执行器
这些就是mybatis的三种执行器。你可以通过配置文件的settings里面的元素defaultExecutorType,配置它,默认是采用SimpleExecutor如果你在spring运用它,那么你可以这么配置它:
[html] view plain copy- <bean id="sqlSessionTemplateBatch" class="org.mybatis.spring.SqlSessionTemplate">
- <constructor-arg index="0" ref="sqlSessionFactory" />
-
- <constructor-arg index="1" value="BATCH"/>
- </bean>
这样,它便是一个批量的执行器。mybatis的三个executor都有一个共同的父类——BaseExecutor。Executor初始化
首先我们先了解一下mybatis是怎么样生成executor的。我们看到生成Executor的地方(org.apache.ibatis.session.Configuration):
[java] view plain copy- 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;
- }
这里大部分都很好理解,但是有个地方就好不好理解,它就是:[java] view plain copy- executor = (Executor) interceptorChain.pluginAll(executor);
这是一段非常重要的代码,它是采用责任链模式,来产生代理对象。我们需要再深入理解它,打开它具体的pluginAll方法:[java] view plain copy- public Object pluginAll(Object target) {
- for (Interceptor interceptor : interceptors) {
- target = interceptor.plugin(target);
- }
- return target;
- }
我们这里先介绍一下这段代码:Interceptor它是mybatis拦截器必须要实现的接口,换句话说,这个遍历就是遍历mybatis的拦截器。
然后调用plugin方法,这个方法是为了生成代理对象(占位)的。于是可以想象我们的插件的代理对象将会是一层层的嵌套,所以当中任何一个插件(Interceptor)都有机会拦截这个真是的服务对象(executor),则便是责任链模式,我们完全可以提供插件(Interceptor),进入到代理对象的invoke方法里面,来改变executor的行为和方法。
但是我要在这里强调,当你使用插件的时候,你将改变mybatis的executor内容实现,你必须慎重的使用它。如果要我们自己编写动态的代理,那么工作量可不小,好在mybatis为我们提供了Plugin.Java类来完成我们所需要的功能。[java] view plain copy- public class Plugin implements InvocationHandler {
-
- private Object target;
- private Interceptor interceptor;
- private Map<Class<?>, Set<Method>> signatureMap;
-
- private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
- this.target = target;
- this.interceptor = interceptor;
- this.signatureMap = signatureMap;
- }
-
- public static Object wrap(Object target, Interceptor interceptor) {
- Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
- Class<?> type = target.getClass();
- Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
- if (interfaces.length > 0) {
- return Proxy.newProxyInstance(
- type.getClassLoader(),
- interfaces,
- new Plugin(target, interceptor, signatureMap));
- }
- return target;
- }
-
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- try {
- Set<Method> methods = signatureMap.get(method.getDeclaringClass());
- if (methods != null && methods.contains(method)) {
- return interceptor.intercept(new Invocation(target, method, args));
- }
- return method.invoke(target, args);
- } catch (Exception e) {
- throw ExceptionUtil.unwrapThrowable(e);
- }
- }
- ......
- }
这里有一个wrap方法:它会为我们生成代理对象。一旦我们的插件和它绑定,那么我们可以想到就会进入invoke方法里面。invoke方法:很简单,它运行首先通过class和method的过滤,看看是否需要拦截这个方法,如果被拦截,那么它就运行interceptor的intercept方法。所以当我们配置了签名,就能够拦截我们的方法。
我们先讨论那么多,我们知道后面讲插件的时候我们还会提及它,这里我们知道它会根据插件的个数生成一层层的代理对象就可以了。
executor的执行
executor的执行是依赖于Statement对象来操作的,让我们以SimpleExecutor的doQuery方法为例子:
[java] view plain copy- public class SimpleExecutor extends BaseExecutor {
- ......
-
- @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);
- }
- }
-
- ......
- private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
- Statement stmt;
- Connection connection = getConnection(statementLog);
- stmt = handler.prepare(connection);
- handler.parameterize(stmt);
- return stmt;
- }
-
- }
很显然这里调度的是一个查询方法首先它先生成StatementHandler对象。
通过prepareStatement方法调用prepare方法初始化参数。
然后使用parameterize方法设置参数到运行环境。
然后便通过handler.<E>query(stmt, resultHandler);方法来完成结果组装。
于是我们的焦点就集中在了StatementHandler对象上。
三、SqlSession下面的四大对象之一-----statementHandler
讲到statementHandler,毫无疑问它是我们四大对象最重要的一个,它的任务就是和数据库对话。在它这里会使用parameterHandler和ResultSetHandler对象为我们绑定SQL参数和组装最后的结果返回。首先我们先来看看statementHandler接口的定义:
[java] view plain copy- public interface StatementHandler {
-
- Statement prepare(Connection connection)
- throws SQLException;
-
- void parameterize(Statement statement)
- throws SQLException;
-
- void batch(Statement statement)
- throws SQLException;
-
- int update(Statement statement)
- throws SQLException;
-
- <E> List<E> query(Statement statement, ResultHandler resultHandler)
- throws SQLException;
-
- BoundSql getBoundSql();
-
- ParameterHandler getParameterHandler();
-
- }
这里有几个重要的方法,prepare,parameterize和query,update,他们的作用是不一样的。
在MyBatis实现了statementHandler的有四个类:
RoutingStatementHandler,这是一个封装类,它不提供具体的实现,只是根据Executor的类型,创建不同的类型StatementHandler。
SimpleStatementHandler,这个类对应于JDBC的Statement对象,用于没有预编译参数的SQL的运行。
PreparedStatementHandler 这个用于预编译参数SQL的运行。
CallableStatementHandler 它将实存储过程的调度。
在MyBatis中,Configuration对象会采用new RoutingStatementHandler()来生成StatementHandler对象,换句话说我们真正使用的是RoutingStatementHandler对象,然后它会根据Executor的类型去创建对应具体的statementHandler对象(SimpleStatementHandler,PreparedStatementHandler和CallableStatementHandler)。
然后利用具体statementHandler的方法完成所需要的功能。那么这个具体的statementHandler是保存在RoutingStatementHandler对象的delegate属性的,所以当我们拦截statementHandler的时候就要常常访问它了。
首先prepare方法是用来编译SQL的,让我们看看它的源码实现。这里我们看到了BaseStatementHandler对prepare方法的实现.[java] view plain copy- @Override
- public Statement prepare(Connection connection) throws SQLException {
- ErrorContext.instance().sql(boundSql.getSql());
- Statement statement = null;
- try {
- statement = instantiateStatement(connection);
- setStatementTimeout(statement);
- 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);
- }
- }
-
- protected abstract Statement instantiateStatement(Connection connection) throws SQLException;
显然我们通过源码更加关注抽象方法instantiateStatement是做了什么事情。它依旧是一个抽象方法,那么它就有其实现类。那就是之前说的那几个具体的StatementHandler对象,让我们看看PreparedStatementHandler:[java] view plain copy- @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);
- }
- }
好这个方法非常简单,我们可以看到它主要是根据上下文来预编译SQL,这是我们还没有设置参数。设置参数的任务是交由,statement接口的parameterize方法来实现的。上面我们在prepare方法里面预编译了SQL。那么我们这个时候希望设置参数。在Statement中我们是使用parameterize方法进行设置参数的。让我们看看PreparedStatementHandler中的parameterize方法:
[java] view plain copy- @Override
- public void parameterize(Statement statement) throws SQLException {
- parameterHandler.setParameters((PreparedStatement) statement);
- }
我们用了prepare方法预编译了SQL,用了parameterize方法设置参数,那么我们接下来肯定是想执行SQL,而SQL无非是两种:
一种是进行查询——query,另外就是更新——update。
这些方法都很简单,让我们看看PreparedStatementHandler的实现:
[java] view plain copy- @Override
- public int update(Statement statement) throws SQLException {
- PreparedStatement ps = (PreparedStatement) statement;
- ps.execute();
- int rows = ps.getUpdateCount();
- Object parameterObject = boundSql.getParameterObject();
- KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
- keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
- return rows;
- }
-
- ......
- @Override
- public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
- PreparedStatement ps = (PreparedStatement) statement;
- ps.execute();
- return resultSetHandler.<E> handleResultSets(ps);
- }
我们可以看到如果是进行update的,它将会执行生成主键的操作(插入数据要自动生成主键的时候),然后就返回影响行数。如果是进行query的就更加简单了,它就是执行SQL语句,然后讲结果使用resultHandler的handleResultSets去完成我们的结果组装。至于resultsetHandler的内部实现还是很复杂的,值得期待哦。这里我们暂且不讲等待下一章吧。
StatementHandler是MyBatis四大对象里面最重要的对象,它的方法是十分重要的,也是我们插件的基础。
当我们需要改变sql的时候,显然我们要在预编译SQL(prepare方法前加入修改的逻辑)。
当我们需要修改参数的时候我们可以在调用parameterize方法前修改逻辑。或者使用ParameterHandler来改造设置参数。
我们需要控制组装结果集的时候,也可以在query方法前后加入逻辑,或者使用ResultHandler来改造组装结果。
懂的这些方法,才能理解我需要拦截什么对象,如何处理插件,这是MyBatis的核心内容。
四、SqlSession下面的四大对象之一-----parmeterHandler和resultsetHandler
我们知道ParameterHandler是用来设置参数规则的。当StatementHandler使用prepare()方法后,接下来就是使用它来设置参数,让我们看看它的定义:[java] view plain copy- package org.apache.ibatis.executor.parameter;
-
- import java.sql.PreparedStatement;
- import java.sql.SQLException;
-
-
-
-
-
-
- public interface ParameterHandler {
-
- Object getParameterObject();
-
- void setParameters(PreparedStatement ps)
- throws SQLException;
-
- }
十分简单getParameterObject()是获取参数的,而setParameters()是设置参数的,相当于对一条sql所有的参数都执行ps.setXXX(value);让我们看看它的实现类:DefaultParameterHandler
[java] view plain copy-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 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)) {
- 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);
- }
- }
- }
- }
- }
-
- }
这里重点是setParameters(),首先它读取了ParameterObject参数对象,然后用typeHandler对参数进行设置,而typeHandler里面需要对jdbcType和javaType进行处理,然后就设置参数了。也很好理解。所以当我们使用TypeHandler的时候完全可以控制如何设置SQL参数。ResultSetHandler要比Parameter复杂得多,对其原理要讲清楚真的很困难,但是我们一般在插件里面使用不多。所以我就不分析太详细了,大致讲讲它的大概原理。让我们看看ResultSetHandler的接口定义:[java] view plain copy- package org.apache.ibatis.session;
-
-
-
-
- public interface ResultHandler<T> {
-
- void handleResult(ResultContext<? extends T> resultContext);
-
- }
接口就只有一个方法,但是内部真的包罗万象,要读懂源码真的很不容易,而且在插件中使用得不算太多,所以我就不带大家读了。大概的道理是,首先要考虑是否延迟加载,如果是使用延迟加载就只记录其主要的关联信息,否则就用TypeHandler读取出resultSet的返回的记录,然后通过ObjectFactory组装结果集。因为掌握好四大对象的基本方法,了解他们的作用是我们插件编写的根本,没有掌握它们你是没有办法准确的去编写你的插件的。
尤其是对StatmentHanlder的掌握尤其重要。