MyBatis 是一款一流的支持自定义SQL、存储过程和高级映射的持久化框架。MyBatis 能够使用简单的XML 格式或者注解进行来配置,能够映射基本数据元素、Map 接口和POJO(普通java 对象)到数据库中的记录。所有的MyBatis 应用都以SqlSessionFactory 实例为中心。SqlSessionFactory 实例通过SqlSessionFactoryBuilder 来获得,SqlSessionFactoryBuilder 能够从XML 配置文件或者通过自定义编写的配置类(Configuration class),来创建一个SqlSessionFactory 实例。在非Spring环境下XML文件的配置如下
上面为Mybatis配置了事务管理器以及数据源(数据库连接池)。一般在Spring环境下数据源以及事务管理器不需要在
由上面的配置可以看出,SqlSessionFactoryBean中注入了第三方dataSource,即dbcp数据源。同时在配置中给出了mybatis配置文件以及Mapper映射文件的路径。这时我们来看看Spring环境下的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);
而在上面的配置文件中有这一句
先来看看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, InitializingBean, ApplicationListener {
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); //这是实际执行SQL语句的执行器
return new DefaultSqlSession(configuration, executor, autoCommit); //可知 这就是我们所用的SqlSession的本来面目
}
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 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 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的构造方法有这么一句:
如果再结合Mybatis几个重要类来分析一下具体Sql语句的生成过程,了解一下MappedStatement、SqlSource等类 相信Mybatis的实现机制应该就比较清楚了。先写到这里了,如果有疏漏错误处,多多指出啊。