MyBatis 是一款一流的支持自定义SQL、存储过程和高级映射的持久化框架。MyBatis 能够使用简单的XML 格式或者注解进行来配置,能够映射基本数据元素、Map 接口和POJO(普通java 对象)到数据库中的记录。所有的MyBatis 应用都以SqlSessionFactory 实例为中心。SqlSessionFactory 实例通过SqlSessionFactoryBuilder 来获得,SqlSessionFactoryBuilder 能够从XML 配置文件或者通过自定义编写的配置类(Configuration class),来创建一个SqlSessionFactory 实例。在非Spring环境下XML文件的配置如下
<configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <span style="white-space:pre"> </span> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <span style="white-space:pre"> </span> <property name="password" value="${password}"/> </dataSource> </environment> </environments> <mappers> <mapper resource="org/mybatis/example/BlogMapper.xml"/> </mappers> </configuration>上面为Mybatis配置了事务管理器以及数据源(数据库连接池)。一般在Spring环境下数据源以及事务管理器不需要在<configuration />文件中配置,而是借助于mybatis-spring包下的org.mybatis.spring.SqlSessionFactoryBean来注入第三方数据源。mybatis在Spring的 applicationContext.xml 中的配置如下
<!-- 配置mybatis --> <bean id="mSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="mDataSource" /> <property name="configLocation" value="classpath:/mysql-mapper/Configuration.xml" /> <property name="mapperLocations" value="classpath:/mysql-mapper/*Mapper.xml" /> </bean> <!-- 数据源配置, 使用应用中的DBCP数据库连接池 --> <bean id="mDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" > <!-- Connection Info --> <property name="driverClassName" value="${jdbc.driver}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <!-- Connection Pooling Info --> <property name="maxActive" value="${dbcp.maxActive}" /> <property name="maxIdle" value="${dbcp.maxIdle}" /> <property name="defaultAutoCommit" value="false" /> <!-- 连接Idle一个小时后超时 --> <property name="timeBetweenEvictionRunsMillis" value="3600000" /> <property name="minEvictableIdleTimeMillis" value="3600000" /> </bean> <!-- mybatis Dao --> <bean id="mBaseDao" class="com.rfidMidware.dao.BaseDao"> <property name="sqlSessionFactory" ref="mSqlSessionFactory" /> </bean>由上面的配置可以看出,SqlSessionFactoryBean中注入了第三方dataSource,即dbcp数据源。同时在配置中给出了mybatis配置文件以及Mapper映射文件的路径。这时我们来看看Spring环境下的mybatis配置文件是怎么样的
<configuration> <properties resource="jdbc.properties" /> <settings> <setting name="cacheEnabled" value="true" /> <setting name="lazyLoadingEnabled" value="false" /> </settings> <typeAliases> <typeAlias type="com.rfidMidware.model.controlManager.Manager" alias="Manager" /> </typeAliases> </configuration>观察上面的配置,可以看出已经不需要再配置数据源、事务管理器等选项了。顺便说说上面的配置吧,<properties />文件给出了外部属性文件。<settings />很重要,用于改变运行中mybatis的行为。可以看出我开了mybatis的二级缓存。我们知道Mybatis共分两级缓存(不知道的话可以点开链接哟,个人觉得很好的博客的链接)。 一级缓存基于Session ,二级缓存作用域为Mapper的范围。但是由于Spring环境下SqlSession是由Spring容器管理的,mybatis的一级缓存不再有效。<typeAlias />为类全名产生一个简短的别名,方便Mapper文件使用。
配置好了,那么mybatis具体是如何工作的呢,首先,看一下我们在业务层读写数据库时使用的mybatis的接口吧(这只是使用mybatis的一种方式)
public class BaseDao extends SqlSessionDaoSupport { public int save(String key, Object object){ return getSqlSession().insert(key, object); } public int update(String key, Object object){ return getSqlSession().update(key, object); } public int update(String key){ return getSqlSession().update(key); } public int delete(String key, Serializable id) { return getSqlSession().delete(key, id); }.................这里使用的是继承SqlSessionDaoSupport的方法。来看看这个类
public abstract class SqlSessionDaoSupport extends DaoSupport { private SqlSession sqlSession; private boolean externalSqlSession; public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { if (!this.externalSqlSession) { this.sqlSession = new SqlSessionTemplate(sqlSessionFactory); } } public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) { this.sqlSession = sqlSessionTemplate; this.externalSqlSession = true; } /** * Users should use this method to get a SqlSession to call its statement methods * This is SqlSession is managed by spring. Users should not commit/rollback/close it * because it will be automatically done. * * @return Spring managed thread safe SqlSession */ public SqlSession getSqlSession() { return this.sqlSession; } }当我们使用BaseDao接口,调用getSqlSession时会调用其基类SqlSessionDaoSupport相应的方法。而这个SqlSession是其子类SqlSessionTemplate 的一个实例。
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
而在上面的配置文件中有这一句 <property name="sqlSessionFactory" ref="mSqlSessionFactory" /> ,由此处可以看出上句SqlSessionTemplate实例化传入的参数sqlSessionFactory即 org.mybatis.spring.SqlSessionFactoryBean。
先来看看SqlSessionTemplate的真面目
public class SqlSessionTemplate implements SqlSession { private final SqlSessionFactory sqlSessionFactory; private final ExecutorType executorType; private final SqlSession sqlSessionProxy; private final PersistenceExceptionTranslator exceptionTranslator; public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required"); notNull(executorType, "Property 'executorType' is required"); this.sqlSessionFactory = sqlSessionFactory; this.executorType = executorType; this.exceptionTranslator = exceptionTranslator; this.sqlSessionProxy = (SqlSession) newProxyInstance( SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSession.class },new SqlSessionInterceptor()); } .......最重要的是最后一句,使用java的动态代理生成的 SqlSessionProxy来执行具体的 数据库CRUD,JDK的动态代理是基于接口的。该代理实现SqlSession的接口,并使用SqlSessionIntercepter拦截器将mybatis方法引导到正确的由Spring事务管理器产生的SqlSession中去。我们知道动态代理中,当代理类调用相应的类方法时候会调用InvocationHandler中的invoke方法。而SqlSessionIntercepter是InvocationHandler的子类,这个拦截器中最重要的的是invoke方法
private class SqlSessionInterceptor implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
}
拦截器中getSession方法调用org.mybatis.spring.SqlSessionUtils中的 静态 getSession方法;我们再观察这个方法(已精简)
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { //需同步时用holder来管理sqlSession SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); if (holder != null && holder.isSynchronizedWithTransaction()) { if (holder.getExecutorType() != executorType) { throw new TransientDataAccessResourceException("Cannot change the ExecutorType when there is an existing transaction"); } holder.requested(); return holder.getSqlSession(); } SqlSession session = sessionFactory.openSession(executorType); ................ return session; }注意到这一句 SqlSession session = sessionFactory. openSession (executorType); 调用传递过来的sessionFactory来打开一个sqlSession。上文说到Spring注入的sqlSessionFactory是 org.mybatis.spring.SqlSessionFactoryBean。 SqlSessionFactoryBean这个类是用来生成SqlSessionFactory的,这也是一般情况下在Spring环境下生成一个共享的SqlSessionFactory的方法。实际上依赖注入时向基于mybatis的DAO接口(此例中即为BaseDao)注入的是其内部生成的SqlSessionFactory
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> { private static final Log logger = LogFactory.getLog(SqlSessionFactoryBean.class); private Resource configLocation; private Resource[] mapperLocations; private DataSource dataSource; private TransactionFactory transactionFactory; private Properties configurationProperties; private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); private SqlSessionFactory sqlSessionFactory; private String environment = SqlSessionFactoryBean.class.getSimpleName(); // EnvironmentAware requires spring 3.1 ; .............}可以看出SqlSessionFactoryBean中包含了dataSource、sqlSessionFactoryBuilder、sqlSessionFactory以及配置文件Configuration等等的实例,该类使用
this.sqlSessionFactory = buildSqlSessionFactory();
而buildSqlSessionFactory又调用 this.sqlSessionFactoryBuilder.build(configuration);
再来看看这个build方法
public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }联系上文可知为BaseDao注入的SqlSessionFactory即为此处的 DefaultSqlSessionFactroy,同时传入配置文件Configuration的实例作为参数。所以上文调用openSession有两种方法 openSessionFromDataSource 和 openSessionFromConnection
public class DefaultSqlSessionFactory implements SqlSessionFactory { private final Configuration configuration; public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); } private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; final Environment environment = configuration.getEnvironment(); final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); final Executor executor = configuration.newExecutor(tx, execType); //<strong><span style="color:#ff0000;">这是实际执行SQL语句的执行器</span></strong> return new DefaultSqlSession(configuration, executor, autoCommit); //<span style="color:#ff0000;"><strong>可知 这就是我们所用的SqlSession的本来面目</strong></span> } public SqlSession openSession(Connection connection) { return openSessionFromConnection(configuration.getDefaultExecutorType(), connection); }由上可知,我们实际上使用的SqlSession即为其子类 DefaultSqlSession.
public class DefaultSqlSession implements SqlSession { private Configuration configuration; private Executor executor; private boolean autoCommit; private boolean dirty; public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) { this.configuration = configuration; this.executor = executor; this.dirty = false; this.autoCommit = autoCommit; }...............................DefaultSqlSession实现了SqlSession的所有接口,仔细查看源码可以明显看出 实际上负责执行SQL语句的是Executor。
Executor的生成,则是通过 org.apache.ibatis.session.Configuration的 newExecutor方法生成。
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; }可以看出,如果不开启cache的话,创建的Executor只是3中基础类型之一,BatchExecutor专门用于执行批量sql操作,ReuseExecutor会重用statement执行sql操作,SimpleExecutor只是简单执行sql没有什么特别的。开启cache的话(默认是开启的并且没有任何理由去关闭它),就会创建CachingExecutor,它以前面创建的Executor作为唯一参数。CachingExecutor在查询数据库前先查找缓存,若没找到的话调用delegate(就是构造时传入的Executor对象)从数据库查询,并将查询结果存入缓存中。Executor对象是可以被插件拦截的,如果定义了针对Executor类型的插件,最终生成的Executor对象是被各个插件插入后的代理对象。(mybatis的分页是基于内存的逻辑分页,数据量比较大时候往往会占用过多内存,一般提倡的分页方式是物理分页,这就需要自己通过拦截器来实现)。
public class SimpleExecutor extends BaseExecutor { public SimpleExecutor(Configuration configuration, Transaction transaction) { super(configuration, transaction); } 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); } }可以看出,Executor的具体工作都交由 StatementHandler来执行。而StatementHandler也是在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,它只是一个分发者,他一个属性delegate用于指定用哪种具体的StatementHandler。可选的StatementHandler有 SimpleStatementHandler、 PreparedStatementHandler和 CallableStatementHandler三种。选用哪种在mapper配置文件的每个statement里指定,默认的是PreparedStatementHandler。同时还要注意到StatementHandler是可以被拦截器拦截的,和Executor一样,被拦截器拦截后的对像是一个代理对象。由于mybatis没有实现数据库的物理分页,众多物理分页的实现都是在这个地方使用拦截器实现的。
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()); } }StatementHandler创建后需要执行一些初始操作,比如statement的开启和参数设置、对于PreparedStatement还需要执行参数的设置操作等。在具体的 Executor中执行。代码如下:
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; Connection connection = getConnection(statementLog); stmt = handler.prepare(connection); handler.parameterize(stmt); return stmt; }其中handler的参数化方法 此handler即PreparedStatementHandler
public void parameterize(Statement statement) throws SQLException { parameterHandler.setParameters((PreparedStatement) statement); }其中 parameterHandler 在 PreparedStatementHandler的基类中BaseStatementHandler
public abstract class BaseStatementHandler implements StatementHandler { protected final Configuration configuration; protected final ObjectFactory objectFactory; protected final TypeHandlerRegistry typeHandlerRegistry; protected final ResultSetHandler resultSetHandler; protected final ParameterHandler parameterHandler; protected final Executor executor; protected final MappedStatement mappedStatement; protected final RowBounds rowBounds; protected BoundSql boundSql;其中 this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql); 在Configuration中生成
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql); parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler); return parameterHandler; }其中 mappedStatement.getLang()返回languageDriver
public class XMLLanguageDriver implements LanguageDriver {
public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
}
即生成 DefaultParameterHandler
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; } public Object getParameterObject() { return parameterObject; } public void setParameters(PreparedStatement ps) throws SQLException { 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(); typeHandler.setParameter(ps, i + 1, value, jdbcType); } } } } }这里面最重要的一句其实就是最后一句代码,它的作用是用合适的TypeHandler完成参数的设置。那么什么是合适的TypeHandler呢,它又是如何决断出来的呢?BaseStatementHandler的构造方法有这么一句:
this.boundSql= mappedStatement.getBoundSql(parameterObject);
它触发了sql 的解析,在解析sql的过程中,TypeHandler也被决断出来了,决断的原则就是根据参数的类型和参数对应的JDBC类型决定使用哪个TypeHandler。比如:参数类型是String的话就用StringTypeHandler,参数类型是整数的话就用IntegerTypeHandler等。
参数设置完毕后,执行数据库操作(update或query)。如果是query最后还有个查询结果的处理过程。
如果再结合Mybatis几个重要类来分析一下具体Sql语句的生成过程,了解一下MappedStatement、SqlSource等类 相信Mybatis的实现机制应该就比较清楚了。先写到这里了,如果有疏漏错误处,多多指出啊。