在讲述原理之前,我们需要了解一些基础的知识,Java的代理技术,如果不理解为什么的话,我们想一下,为什么mapper仅仅是一个姐恶口,而不是一个包含逻辑的实现类,一个借口是没有办法去执行的,那么是怎么执行的呢,所以有必要理解一下动态代理。
构建SqlSessionFactory的过程:
SqlSessionFactory是mybatis的核心类之一,起追重要的功能就是提供mybatis的核心接口SqlSession,所以,我么和你首先要创建SqlSessionFactory为我们提供配置文件和相关的参数,其构造的方式为Builder够造方式,可以通过SqlSessionFactoryBuilder去构建,分为两步:
第一步:通过ConfigBuilder类解析配置文件,读出参数的配置,并将读取的数据存入到Configuration类中,这个类包含了几乎所有的mybatis的相关的配置。
第二步:使用Configuration对象创建SqlSessionFactory。Mybatis中的SqlSessionFactory是一个接口,这里采用的是抽象工厂,为此,我们需要找到其实现类,一般的实现类为默认的DefaultSqlSessionFactory类,大部分情况下没有必要自己去创建一个新的SqlSessionFactory的实现类。Builder构造模式对于复杂的对象而言直接构造是困难的,而且写出来的代码很不容易维护,而且弹性比较差,对此我们可以使用builder构建模式一步步有秩序的构建,例如全局的配置参数等,交给了Configuration类,DefaultSqlSessionFactory类就可以构建一个复杂的SqlSessionFactory类。
1.构建COnfiguration
configuration类的作用是很重要的,它的作用有以下几个方面:
1.基础的为读入配置的xml文件和mapper的映射的xml文件。
2.初始化基础的配置,比如mybatis的别名,类型转换等,一些重要的对象,插件,映射器等等。创建为单例,为后续创建SqlSessionFactory提供了配置的参数。
3.执行一些重要的对象方法,初始化配置信息。一般其初始化的内容有:
properties全局参数;settings设置;typeAliases别名;type Handler类型处理器;ObjectFactory对象;plugin插件;environment环境;DataBaseProvider 数据库标识;Mapper映射器;
2.剖析映射器内部组成
一般的,一个映射器有三部分组成:
1.MappedStatement,它保存映射器的一个节点,(如selectinsert|delete|update)包括许多我饿们配置的SQL和sql的id,缓存信息、resultmap、paramterType,resultType等的重要的配置内容。
2.SqlSource,它是提供BoundSql对象的地方,它是MappedStatement的一个属性。
3.BoundSql,它是建立SQL和参数的地方。有三个常用的属性,SQL,parameterObject、parameterMappings。
MappedStatement对象涉及的东西较多,一般不去修改它,因为容易产生不必要的错误,S起来Source是一个接口,他的主要作用是根据参数和其它的规则组装拼接Sql,包括动态的sql语句,
这些都很复杂。对于参数和SQL而言,主要的规则都反映在BoundSql类对象上,在插件中往往需要拿到它进而可以拿到它运行当前的sql和参数以及参数的规则,作出适当的修改。
对于BoundSql,其有三个主要的属性,parameterMappings,parameterObject和sql。
其中parameterObject为参数本身,可以通过传递对象,pojo,map或者@Param注解的参数。
(1)传递简单对象:比如当我们传递int类型时,Mybatis会把参数变为Integer对象进行传递,类似的如long,float等也是如此。
(2)如果传递的是pojo或者是map,那么这个parameterObject旧手机我们传入的pojo或者是map不变。
(3)当传递多个参数如果没有@Param注解,那么mybatis就会把parameterObject变为一个Map<String,Object>对象,其键值的关系是按照吮吸来规划的,所以在编写的时候我们可以使用#{param1}或者#{1}进行引用第一个参数。
(4)如果使用了@Param注解,那么mybatis也会把parameterObject转换为一个Map<String,Object>对象,只是将数字键值改会为注解的值。
parameterMappings是一个List,每一个元素都是ParameterMapping的对象,这个对象会描述我们的参数,参数包括属性,名称,表达式,javaType,jdbcType等重要的信息,我们一般不需要去修改这里,其可以实现参数和SQL的结合,以便于PreparedStatement能够通过它找到parameterObject对象的属性并设置参数,确保程序能够正常运行。
sql 属性就是我们书写在映射器里面的一条sql语句,大多数时候我许修改这个属性,只有在使用插件的情况下,我们可以进行改写。这个一定要谨慎,如果操作不当,会使得整个框架瘫痪。
3.构建SqlSessionFactory:
当我们有了Configuration对象之后,就可以较为轻松的构建SqlSessionFactory了,我们可以通过下面的方式来构建:
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);这样的化,mybatis会根据Configuration类中的配置的信息构建一个SqlSessionFactory对象。
4.SqlSession运行过程:
SqlSession是一个接口,它的使用并不复杂,构建的SqlSessionFactory可以轻易的拿到SqlSession,SqlSession给出了查询,插入,更新和删除的方法,在旧版的iBatis中,常常使用这些接口,而在新版的Mybatis 中建议我们使用Mapper。(1)映射的动态代理:
其是通过JDK的动态代理来实现的(代码在org.appache.ibatis.binding包中):
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); } }这里看到动态代理对接口的绑定,将代理的方法放置到代理类MapperProxy类中:
public class MapperProxy<T> implements InvocationHandler, Serializable { ```````` } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, 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; } ``````````` }虽与上面的invoke方法,一旦mapper是一个代理对象,那么它就会运行到invoke方法里面,首先会判断是不是一个类,这里显然Mapper是一个接口而不是一个类,所以判定失败,就会生成MapperMethod对象,他是通过cachedMapperMethod方法对其进行初始化的,然后执行execute方法,把sqlSession和当前的参数传递进去。让我们看一下这个execute方法:
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; }可以看到,通过获取commandType类型的命令的类型,结合switch选择来执行对应的处理,我们通过其中的一个例子来分析,我们分析一下返回多条记录的过程,也就是select情况下的executeForMany方法执行的过程:
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; }可以看到,传入的参数为SqlSession的类型,看到实现之后可以理解,实际上最后通过sqlSession对象去运行对象的SQL,因为映射器的xml文件的命名空间对应的就是这个接口的路径,那么根据全路径和方法的名称便可以绑定起来,通过动态代理的技术,然这个接口运行起来,之后使用命令模式,使得SqSession能够执行对应的方法,有了这层封装,我们仅需要提供一个接口,不必再去实现这个接口了。
(2)SqlSession下的四个重要的对象:
其实Mapper的执行过程是通过Executor、StatementHandler、ParammeterHandler和ResultHandler等来执行对应的sql。StatementHandler的作用是使用数据库的Statement(PreparedStatementHandler)执行操作,他起到了一个承上启下的作用,是四大对象的核心。而ParameterHandler用于SQL对参数的处理,ResultHandler是进行最后数据集的封装返回处理。
1.执行器
其是一个真正和数据库进行交互的部件,在mybatis的三种执行器中,我们可以在mybatis的配置文件中进行选择,可以通过settings元素的属性进行配置,配置defaultExecutorType来完成。
SIMPLE,简易执行器。REUSE,是一种执行器重用预处理语句。BATCH执行器重用语句和批量更新,针对批量转用的执行器。
首先通过源码看一下Mybatis是如何生成的:
在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; }Mybatis将根据配置的类型去确定需要的创建的三种执行器的哪一种,在创建对象后会执行如下代码:
interceptorChain.pluginAll(executor);这里将mybatis构建一层层的动态代理对象,在调度真实的WExecutor之前,执行配置插件的代码可以被修改。下面以SIMPLE执行器SimpleExecutor的查询方法作为例子进行分析:
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; } }显然可以看出,Mybatis根据Configuration来构建StatementHandler,然后使用prepareStatement方法,对SQL编译并对参数进行初始化,我们看它实现的过程,它调用了StaementHandler的prepare()进行了预编译和基础设置,然后通过StatementHandler的额parammeterize()方法来设置参数并执行,resultHandler在组装查询结果返回给调用者来完成一次查询。其中发挥主要作用的时StatementHandler。
2.数据库会话器
看一下数据库会话器是如何再Configuraiton类里面生成的:
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不是真正的服务对象,看源码会发现,其实通过适配器模式进行调度的,其对应Executor‘的三种模式,分别有SimpleStatementHandler,PreparedStatementHandler,CallableStatementHandler三种。看一下源码:
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()); } } @Override public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException { return delegate.prepare(connection, transactionTimeout); } @Override public void parameterize(Statement statement) throws SQLException { delegate.parameterize(statement); } @Override public void batch(Statement statement) throws SQLException { delegate.batch(statement); } @Override public int update(Statement statement) throws SQLException { return delegate.update(statement); } @Override public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { return delegate.<E>query(statement, resultHandler); } @Override public <E> Cursor<E> queryCursor(Statement statement) throws SQLException { return delegate.queryCursor(statement); } @Override public BoundSql getBoundSql() { return delegate.getBoundSql(); } @Override public ParameterHandler getParameterHandler() { return delegate.getParameterHandler(); } }可以看到,对象的适配器对象为delegate,其是一个StatementHandler的接口对象,构造方法根据配置来适配对应的StatementHanler对象,它的作用是给实现类对象的使用提供一个统一的,简易使用的适配器,此为对象的适配器模式。之后看一下是怎么执行查询的?
我们以PreparedStatementHandler为例,看一下prepare,parameterize和query方法是如何调度的。
prepare方法:
@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); } }除了一些基本的配置,如设置超时,获取最大行数设置外,instancetiateStatement()方法是对SQL进行 预编译,然后调用了prepareStatementHandler类里面的上述的预编译方法,其中其调用了java.sql中的prepareStatement方法:
@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); } }
然后执行器Executor就会调用parameterize()方法器设置参数,它的代码如下:
@Override public void parameterize(Statement statement) throws SQLException { parameterHandler.setParameters((PreparedStatement) statement); }这个时候,它调用的ParameterHandler去完成的,那么看一下其是怎么完成查询的,不过在这里先了解一下StatementHandler中的查询方法:
@Override public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); return resultSetHandler.<E> handleResultSets(ps); }
在执行参数和SQL都被prepare()方法预编译,参数在parameterize()方法上已经进行了设置,所以到这里已经很简单了,我们只要执行SQL,然后返回结果就可以了,执行之后我们看到了ResultSetHandler对结果进行封装和返回。
所以,我们再来梳理一遍这个过程哈:
首先,Executor会调用StatementHandler的prepare()方法预编译sql语句,同时设置一些基本的运行参数,然后用prepareize()方法启动ParameterHandler设置参数,完成预编译,跟着就是执行查询,而update()也是这样的,如果需要查询,我们就会用ResultSetHandler封装返回结果给调用者。
3.参数处理器
首先看一下参数处理器声明的接口,其功能就是完成对预编译参数的设置:
public interface ParameterHandler { Object getParameterObject(); void setParameters(PreparedStatement ps) throws SQLException; }其中,第一个方法的作用是返回参数对象,setParameters()方法的作用是设置预编译SQL语句的参数,其实现类为一个默认的DefaultParameterHandler,看一下第二个方法的实现:
@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进行参数处理,如果有设置在配置文件中,那么就会根据签名住v额的typeHandler对参数进行预处理,而其也是在mybatis初始化的时候注册在Configuration类中的,当我们需要的的时候就可以完成参数的设置了。
4.结果处理器
根据上面的分析可以了解到,它是用来组装结果集进行返回的,我们再来看看结果处理器(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; }其中,handleOutputParameters()方法是处理存储过程输出参数的,我们暂时不需要管他,重点看一下handleResultSet方法,他是包装结果集的,Mybatis同样为我们提供一个DefaultResultSetHandler类,在默认的情况下都是通过这个类进行处理的,这个类较为复杂,它涉及使用JAVASSIST和CGLIB作为延迟加载,然后通过typeHandler和ObjectFactory进行组装结果再返回,基本上改动这一部分的概率很小。
以上就是大体的运行流程的浅析,如果想要了解具体是怎么运行,怎么设计的,还需要我们返回我们的代码中继续理解。里面一些优秀的设计模式,一些优秀的设计理念是很值得我们去学习的。