目录
- 独立使用Mybatis
- Mybatis执行流程
- SqlSessionFactory\SqlSession
- MapperProxy
- Excutor
独立使用Mybatis
这篇文章主要以分析Mybatis框架执行SQL的流程。
回忆曾经独立使用Mybatis半自动化框架时,我们需要执行以下步骤:
- 读取配置文件(mybatis-config.xml),初始化配置类即configuration;
- 创建SQLSessionFactory;
- 创建SqlSession;
- 执行SQL,处理结果集
对应如下代码:
public class MyBatisUtils {
private final static SqlSessionFactory SQL_SESSION_FACTORY;
static {
String resource = "mybatis-config.xml";
Reader reader = null;
try {
reader = Resources.getResourceAsReader(resource);
} catch (IOException e) {
e.printStackTrace();
}
SQL_SESSION_FACTORY = new SqlSessionFactoryBuilder().build(reader);
}
public static SqlSessionFactory getSqlSessionFactory() {
return SQL_SESSION_FACTORY;
}
}
//单元测试
public class MapperTest {
static SqlSessionFactory sqlSessionFactory = null;
static {
sqlSessionFactory = MyBatisUtils.getSqlSessionFactory();
}
@Test
public void testAdd() {
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setUsername("hello");
user.setAge(5);
userMapper.insert(user);
sqlSession.commit(); // 这里一定要提交,不然数据进不去数据库中
} finally {
sqlSession.close();
}
}
@Test
public void getUser() {
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.selectByPrimaryKey(32L);
System.out.println("name: " + user.getUsername() + "| age: " + user.getAge());
} finally {
}
}
}
Mybatis执行流程
基于上面说明,我们基本了解了执行流程,下面我们从源码层面解释一下流程。
SqlSessionFactory\SqlSession
独立使用Mybatis时,第一步要读取配置文件,因此,我们将从读取配置文件开始。
- SqlSessionFactoryBuilder读取mybatis的配置文件,调用build方法创建DefaultSqlSessionFactory
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//parse()方法XMLConfigBuilder类解析xml文件,同时完成configuration属性的创建
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
- 获取SqlsessionFactory之后,调用openSession()方法创建SQLSession,这里说明一下SQLSession仅仅向外提供了对数据库操作的支持,真正执行对数据库的操作是execute的职责。
DefaultSqlSessionFactory类
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
//通过Confuguration对象去获取Mybatis相关配置信息, Environment对象包含了数据源和事务的配置
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//之前说了,从表面上来看,咱们是用sqlSession在执行sql语句, 实际呢,其实是通过excutor执行, excutor是对于Statement的封装
final Executor executor = configuration.newExecutor(tx, execType);
//创建SqlSession
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
// may have fetched a connection so lets call close()
closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
获取SQLSession之后,对数据进行CRUD操作的准备工作就正式结束。
MapperProxy
主要处理我们Mybatis的映射文件。该类主要负责代理开发中的mapper。那么思考一下该代理对象如何获取呢?
下面我们从SQLSession中跟踪。
DefaultSQLSession类中
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//从缓存中获取该Mapper接口的代理工厂对象
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
//如果该Mapper接口没有注册过,则抛异常
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
/
@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<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
一路跟踪下来,我们发现MapperProxy对象是在MapperProxyFactory里创建完成。
Excutor
上面我们提到Excutor的职责是负责对数据的crud操作,上面的时序图,详细地说明了SQL的执行过程。
对于每一个MapperProxy对应开发人员自定的Mapper(dao)接口,下面我们将从源码追踪,如何实现的。
- MappProxy:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//判断是否为通用类,mybatis使用JDK代理方式,即面向接口,false
//method.getDeclaringClass()返回底层的类对象的class
if (Object.class.equals(method.getDeclaringClass())) {
//如果是类,则利用反射机制,返回目标对象
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
//如果是默认方法,则执行默认方法,java1.8提供;
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
//从缓存中获取MapperMethod对象,如果缓存中没有,则创建一个,并添加到缓存中
final MapperMethod mapperMethod = cachedMapperMethod(method);
//执行方法对应的SQL语句,返回查询的结果
return mapperMethod.execute(sqlSession, args);
}
- MapperMethod:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
//param 为参数名和参数值的对应关系,即map集合
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()) {
//void类型且方法中有ResultHandler参数(特殊参数),调用sqlSession.select执行
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
//返回类型是集合或者数组,调用sqlSession.<E>selectList执行
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
//返回 map ,调用 sqlSession.<K, V>selectMap
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional() &&
(result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
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;
}
本篇文章主要是浅析SQL的执行流程,因此我们建议以selectList为例,带领大家熟悉一下流程,后面会专门介绍
各个步骤的流程。
回归到SQLSession
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();
}
}
- BaseExecutor:
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//获取查询SQL
BoundSql boundSql = ms.getBoundSql(parameter);
//创建缓存的key,即作为HashMap中的key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
//执行查询
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
- SimpleExecutor:
@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.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
- preparedStatement:
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleResultSets(ps);
}
参考资料:Spring源码深度解析第二版