一、sqlSession运行过程 SqlSession采用了门面模式,提供了API(增删改查),辅助API(提交关闭)。
核心执行组件Executor,执行操作。提供了(改和查的操作),维护缓存,辅助API(提交、关闭执行器、批处理刷新)
映射器的动态代理 Mapper映射是通过动态代理来实现的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class MapperProxyFactory <T > { private final Class<T> mapperInterface; private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap(); public MapperProxyFactory (Class<T> mapperInterface) { this .mapperInterface = mapperInterface; } public Class<T> getMapperInterface () { return this .mapperInterface; } public Map<Method, MapperMethod> getMethodCache () { return this .methodCache; } protected T newInstance (MapperProxy<T> mapperProxy) { return Proxy.newProxyInstance(this .mapperInterface.getClassLoader(), new Class[]{this .mapperInterface}, mapperProxy); } public T newInstance (SqlSession sqlSession) { MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this .mapperInterface, this .methodCache); return this .newInstance(mapperProxy); } }
这里可以看到动态代理对接口的绑定,它的作用是生成动态代理对象 ,代理的方法放入MapperProxy类中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 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; } public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this , args); } else { MapperMethod mapperMethod = this .cachedMapperMethod(method); return mapperMethod.execute(this .sqlSession, args); } } private MapperMethod cachedMapperMethod (Method method) { MapperMethod mapperMethod = (MapperMethod)this .methodCache.get(method); if (mapperMethod == null ) { mapperMethod = new MapperMethod(this .mapperInterface, method, this .sqlSession.getConfiguration()); this .methodCache.put(method, mapperMethod); } return mapperMethod; } }
Mybatis使用的是InvocationHandler,是一个JDK动态代理。上面运用了invoke方法。一旦mapper是一个代理对象,那么它就会运行到invoke方法里面 ,invoke首先判断它是否是一个类,显然这里Mapper是一个接口不是类,所以判定失败。那么就会生成MapperMethod对象,它是通过cachedMapperMethod方法对其初始化的,然后执行execute方法,把sqlSession和当前运行的参数传递进去。
MapperMethod采用命令模式 运行,根据上下文跳转,它可能跳转到许多方法中,我们不需要全部明白。我们可以看到里面的executeForMany方法,再看看它的实现,实际上它最后就是通过sqlSession对象去运行对象的SQL 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private <E> Object executeForMany (SqlSession sqlSession, Object[] args) { Object param = this .method.convertArgsToSqlCommandParam(args); List result; if (this .method.hasRowBounds()) { RowBounds rowBounds = this .method.extractRowBounds(args); result = sqlSession.selectList(this .command.getName(), param, rowBounds); } else { result = sqlSession.selectList(this .command.getName(), param); } if (!this .method.getReturnType().isAssignableFrom(result.getClass())) { return this .method.getReturnType().isArray() ? this .convertToArray(result) : this .convertToDeclaredCollection(sqlSession.getConfiguration(), result); } else { return result; } }
总结
Mybatis映射器的XML文件的命名空间对应的便是这个接口的全路径 ,那么它根据全路径和方法名便能够绑定起来,通过动态代理技术,让这个接口跑起来 。而后采用命令模式,最后还是使用SqlSession接口的方法使得它能够执行查询 ,有了这层封装我们便可以使用接口编程,这样编程就更简单了。
二、SqlSession的四大对象 Mapper执行的过程是通过 Executor、StatementHandler 、ParameterHandler 和 ResultHandler来完成数据库操作和结果返回的。
Executor 代表执行器,由它来调度 StatementHandler、ParameterHandler.ResultHandler等来执行对应的SQL。
StatementHandler的作用是使用数据库的Statement (PreparedStatement)执行操作 ,它是四大对象的核心,起到承上启下的作用。
ParameterHandler 用于SQL对参数的处理 。
ResultHandler是进行最后数据集(ResultSet)的封装返回处理 的。
执行器(Executor) 执行器(Executor)起到了至关重要的作用。它是一个真正执行Java和数据库交互的东西。在 MyBatis中存在三种执行器。我们可以在 MyBatis 的配置文件中进行选择
SIMPLE,简易执行器,不配置它就是默认执行器。
REUSE,是一种执行器重用预处理语句 。
BATCH,执行器重用语句和批量更新 ,它是针对批量专用的执行器。
Configuration类中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public Executor newExecutor (Transaction transaction, ExecutorTypeexecutorType) { 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; }
简单执行器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class SimpleExecutor extends BaseExecutor { ... 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(this , 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; } }
创建Executor需要2个参数configuration和transaction,configuration是从配置文件中获取,由SqlSessionFactory获取,而事务是从connection中获取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private Configuration configuration; private Connection connection; private JdbcTransaction jdbcTransaction; private static final String URL = "jdbc:mysql://localhost:3306/ssm" ; private static final String USERNAME = "root" ; private static final String PASSWORD = "123456" ; @Before public void init () throws IOException, SQLException { SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder(); InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml" ); SqlSessionFactory build = factoryBuilder.build(inputStream); configuration = build.getConfiguration(); connection = DriverManager.getConnection(URL, USERNAME, PASSWORD); jdbcTransaction = new JdbcTransaction(connection); } }
创建Executor之后,就可以通过executor.doQuery()执行具体的查询语句。这里需要5个参数
MappedStatement ms : SQL声明
Object parameter: 参数
RowBounds rowBounds: 行范围,要不要分页
ResultHandler resultHandler: 结果处理器
BoundSql boundSql: 动态SQL语句
1 2 3 4 5 6 7 8 9 10 @Test public void simpleTest () throws SQLException { SimpleExecutor executor = new SimpleExecutor(configuration, jdbcTransaction); MappedStatement ms = configuration.getMappedStatement("com.lq.mybatis.mapper.AccountMapper.selectById" ); List<Object> objects = executor.doQuery(ms, 1 , RowBounds.DEFAULT, SimpleExecutor.NO_RESULT_HANDLER, ms.getBoundSql(10 )); System.out.println(objects); }
简单执行器,无论SQL是否一样,每次都会进行预编译,浪费性能。
1 2 3 4 5 6 7 [com.lq.mybatis.mapper.AccountMapper.selectById]-==> Preparing: select id ,name ,money from account where id = ? [com.lq.mybatis.mapper.AccountMapper.selectById]-==> Parameters: 1(Integer) [com.lq.mybatis.mapper.AccountMapper.selectById]-<== Total: 1 [com.lq.mybatis.mapper.AccountMapper.selectById]-ooo Using Connection [com.mysql.jdbc.JDBC4Connection@5bb21b69] [com.lq.mybatis.mapper.AccountMapper.selectById]-==> Preparing: select id ,name ,money from account where id = ? [com.lq.mybatis.mapper.AccountMapper.selectById]-==> Parameters: 1(Integer) [com.lq.mybatis.mapper.AccountMapper.selectById]-<== Total: 1
批处理执行器 1 2 3 4 5 6 7 8 9 10 11 @Test public void batchTest () throws SQLException { BatchExecutor executor = new BatchExecutor(configuration, jdbcTransaction); MappedStatement ms = configuration.getMappedStatement("com.lq.mybatis.mapper.AccountMapper.selectById" ); List<Object> objects = executor.doQuery(ms, 1 , RowBounds.DEFAULT, SimpleExecutor.NO_RESULT_HANDLER, ms.getBoundSql(10 )); System.out.println(objects); }
但是打印日志还是显示2次
1 2 3 4 5 6 7 [com.lq.mybatis.mapper.AccountMapper.selectById]-==> Preparing: select id ,name ,money from account where id = ? [com.lq.mybatis.mapper.AccountMapper.selectById]-==> Parameters: 1(Integer) [com.lq.mybatis.mapper.AccountMapper.selectById]-<== Total: 1 [com.lq.mybatis.mapper.AccountMapper.selectById]-ooo Using Connection [com.mysql.jdbc.JDBC4Connection@c46bcd4] [com.lq.mybatis.mapper.AccountMapper.selectById]-==> Preparing: select id ,name ,money from account where id = ? [com.lq.mybatis.mapper.AccountMapper.selectById]-==> Parameters: 1(Integer) [com.lq.mybatis.mapper.AccountMapper.selectById]-<== Total: 1
批处理值针对修改操作才执行一次。必须要手动刷新
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Test public void batchUpdateTest () throws SQLException { BatchExecutor executor = new BatchExecutor(configuration, jdbcTransaction); MappedStatement ms = configuration.getMappedStatement("com.lq.mybatis.mapper.AccountMapper.updateAccount" ); Map<Object, Object> map = new HashMap<>(); Account account = new Account(); account.setId(1 ); account.setName("aaaa" ); account.setMoney(2000.0 ); map.put("account" , account); executor.doUpdate(ms, map); executor.doUpdate(ms, map); executor.flushStatements(false ); }
基础执行器 基础执行器中包含了一级缓存,query和update,这两个方法都会调用具体的方法执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 public abstract class BaseExecutor implements Executor { protected Transaction transaction; protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads; protected PerpetualCache localCache; protected PerpetualCache localOutputParameterCache; protected Configuration configuration; protected int queryStack = 0 ; private boolean closed; public <E> List<E> query (MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameter); CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); return query(ms, parameter, rowBounds, resultHandler, key, boundSql); } public <E> List<E> query (MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing a query" ).object(ms.getId()); if (closed) throw new ExecutorException("Executor was closed." ); if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } List<E> list; try { queryStack++; list = resultHandler == null ? (List<E>) localCache.getObject(key) : null ; if (list != null ) { handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { queryStack--; } if (queryStack == 0 ) { for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { clearLocalCache(); } } return list; } }
SimpleExecutor和ReuseExecutor都只会进行一次调用,相同的查询会走缓存的逻辑。
1 2 3 4 5 6 7 8 9 @Test public void baseQueryTest () throws SQLException { SimpleExecutor executor = new SimpleExecutor(configuration, jdbcTransaction); MappedStatement ms = configuration.getMappedStatement("com.lq.mybatis.mapper.AccountMapper.selectById" ); executor.query(ms, 1 , RowBounds.DEFAULT, SimpleExecutor.NO_RESULT_HANDLER); executor.query(ms, 1 , RowBounds.DEFAULT, SimpleExecutor.NO_RESULT_HANDLER); }
CachingExecutor
缓存执行器,二级缓存,装饰者模式装饰了具体的Executor 。二级缓存和一级缓存不同,一级缓存执行过就会缓存。二级缓存必须提交之后才会有缓存。是一个跨线程的缓存。
先走二级缓存,再走一级缓存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Test public void cacheQueryTest () throws SQLException { SimpleExecutor executor = new SimpleExecutor(configuration, jdbcTransaction); CachingExecutor cachingExecutor = new CachingExecutor(executor); MappedStatement ms = configuration.getMappedStatement("com.lq.mybatis.mapper.AccountMapper.selectById" ); cachingExecutor.query(ms, 1 , RowBounds.DEFAULT, SimpleExecutor.NO_RESULT_HANDLER); cachingExecutor.commit(true ); cachingExecutor.query(ms, 1 , RowBounds.DEFAULT, SimpleExecutor.NO_RESULT_HANDLER); cachingExecutor.commit(true ); }
数据库会话器 数据库会话器(StatementHandler)就是专门处理数据库会话 的
JDBC处理器,基于JDBC构建Slatement并设置参数,然后执行Sql。每调用会话当中一次SQL,都会有与之相对应的且唯一的Statemet实例。
Configuration类中:
1 2 3 4 5 6 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; }
RoutingStatementHandler不是我们真实的服务对象,它是通过适配模式找到对应的 StatementHandler 来执行的。在 MyBatis 中,StatementHandler和Executor一样分为三种:普通SimpleStatementHandler、预处理PreparedStatementHandler、存储过程CallableStatementHandler。
在初始化 RoutingStatementHandler对象的时候它会根据上下文环境决定创建哪个StatementHandler对象,我们看看RoutingStatementHandler 的源码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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()); } } }
数据库会话器定义了一个对象的适配器delegate ,它是一个StatementHandler接口对象,构造方法根据配置来适配对应的StatementHandler对象 。它的作用是给实现类对象的使用提供一个统一、简易的使用适配器 。此为对象的适配模式,可以让我们使用现有的类和方法对外提供服务,也可以根据实际的需求对外屏蔽一些方法,甚至是加入新的服务 。 我们现在以最常用的PreparedStatementHandler为例,看看MyBatis是怎么执行查询的。执行器有三个主要的方法,prepare、parameterize和 query ,
先看他们的父类BaseStatementHandler,这个类主要用于处理共性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public abstract class BaseStatementHandler implements StatementHandler { ... 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()方法是对SQL进行了预编译 。首先,做一些基础配置,比如超时,获取的最大行数等的设置。 然后,Executor 会调用parameterize()方法去设置参数。这个时候它是调用ParameterHandler去完成的,这里先看StatementHandler的查询方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class PreparedStatementHandler extends BaseStatementHandler { ... public <E> List<E> query (Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); return resultSetHandler.<E> handleResultSets(ps); } public void parameterize (Statement statement) throws SQLException { parameterHandler.setParameters((PreparedStatement) statement); } @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() == ResultSetType.DEFAULT) { return connection.prepareStatement(sql); } else { return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY); } } }
由于在执行前参数和SQL都被prepare()方法预编译,参数在parameterize()方法上已经进行了设置。所以到这里已经很简单了。我们只要执行SQL,然后返回结果就可以了 。执行之后我们看到了ResultSetHandler对结果的封装和返回 。
一条查询SQL的执行流程:预编译、设置参数、执行、结果集映射
Executor先调用StatementHandler 的 prepare()方法预编译SQL语句 ,同时设置一些基本运行的参数。
然后用parameterize()方法启用ParameterHandler 设置参数,完成预编译。
跟着就是执行查询,而update()也是这样的,最后如果需要查询,我们就用ResultSetHandler封装结果返回给调用者。
参数处理器 完成对预编译参数的设置,javabean转换为jdbc
参数转换
ParamNameResolver帮助我们把参数转换为Object就是查询的参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public Object getNamedParams (Object[] args) { final int paramCount = names.size(); if (args == null || paramCount == 0 ) { return null ; } else if (!hasParamAnnotation && paramCount == 1 ) { return args[names.firstKey()]; } else { final Map<String, Object> param = new ParamMap<>(); int i = 0 ; for (Map.Entry<Integer, String> entry : names.entrySet()) { param.put(entry.getValue(), args[entry.getKey()]); final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1 ); if (!names.containsValue(genericParamName)) { param.put(genericParamName, args[entry.getKey()]); } i++; } return param; } }
参数映射处理
1 2 3 4 5 6 7 public interface ParameterHandler { Object getParameterObject () ; void setParameters (PreparedStatement ps) throws SQLException ;}
看实现类DefaultParameterHandler
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public void setParameters (PreparedStatement ps) throws SQLException { ErrorContext.instance().activity("setting parameters" ).object(mappedStatement.getParameterMap().getId()); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); if (parameterMappings != null ) { MetaObject metaObject = parameterObject == null ? null : configuration.newMetaObject(parameterObject); 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 { value = metaObject == null ? null : metaObject.getValue(propertyName); } TypeHandler typeHandler = parameterMapping.getTypeHandler(); JdbcType jdbcType = parameterMapping.getJdbcType(); if (value == null && jdbcType == null ) jdbcType = configuration.getJdbcTypeForNull(); typeHandler.setParameter(ps, i + 1 , value, jdbcType); } } } }
是从parameterObject对象中取参数,然后使用typeHandler进行参数处理,如果你有设置typeHandler,那么它就会根据签名注册的typeHandler对参数进行处理。而typeHandler也是在 MyBatis初始化的时候,注册在Configuration里面的,我们需要的时候可以直接拿来用。这样就完成了参数的设置。
结果处理器
ResultSetHandler:结果集处理器
ResultContext:结果提出
ResultHandler:结果处理器
三、SqlSession运行总结
SqlSession是通过Executor创建StatementHandler来运行 的,而StatementHandler要经过下 面三步。
prepared 预编译SQL。
parameterize设置参数。
query/update执行SQL。
其中parameterize是调用parameterHandler的方法去设置的,而参数是根据类型处理器.typeHandler去处理的。query/update 方法是通过resultHandler进行处理结果的封装,如果是update的语句,它就返回整数 ,否则它就通过typeHandler 处理结果类型 ,然后用 ObjectFactory提供的规则组装对象 ,返回给调用者。这便是SqlSession 执行的过程。