概要
想要全面深入的理解mybatis框架,首要前提是学习它的核心组件,我们先来看看MyBatis的“表面现象”——mybatis的核心组件包括:SqlSessionFactoryBuilder(构造器)、SqlSessionFactory(工厂接口)、SqlSession(会话接口)、SQL Mapper(映射器)。通过一张图来整体展示四个核心组件的关系:
构建SqlSessionFactory过程
使用MyBatis首先是使用配置或者代码去生成SqlSessionFactory,而MyBatis提供了构造器SqlSessionFactoryBuilder,它采用Builder模式来负责构建SqlSessionFactory,通过源码分析,该类下提供了多个build的重载方法。
但真正重载build方法只有三种:InputStream(字节流)、Reader(字符流)、Configuration(类)。在MyBatis中既可以通过读取XML文件的形式生成SqlSessionFactory,也可以通过Java代码的形式去生成SqlSessionFactory。一般我们常用的是读取XML配置文件的形式。读取XML配置文件的方式构造SqlSessionFactory,构造过程中注入了Configuration的实例对象,之后Configuration实例对象解析XML配置文件来构建SqlSessionFactory:
SqlSessionFactory sqlSessionFactory = null;
String resource = "mybatis-config.xml";
try {
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (Exception e) {
e.printStackTrace();
}
在MyBatis读取XML配置文件后,通过Configuration类对象构建整个MyBatis的上下文,而Configuration采用的是单例模式 (SqlSessionFactory唯一的作用就是生产SqlSession,所以它的责任是唯一的)几乎所有的MyBatis配置都存放在这个单例对象中,以便后续使用。
在MyBayis中SqlSessionFactory是一个接口,它存在两个实现类:和DefaultSqlSessionFactory。SqlSessionManager多使用在多线程环境中。它的具体实现依靠的是DefaultSqlSessionFactory,它们之间的关系如下:
这种创建方式是一种Builder模式,对于复杂的对象而言,使用构造参数很难实现。这时使用一个类(Configuration)作为统领,一步步地构建所需的内容,然后通过它去创建最终的对象(SqlSessionFactory)。
在构建SqlSessionFactory中,Configuration是最重要的,它的作用是:
- 读入配置文件。
- 初始化一些配置。
- 提工单例,为后续创建SessionFactory服务提供配置参数。
- 执行一些重要对象的初始化方法。
显然Configuration不是一个简单的类,MyBatis的配置信息都来源于此,Configuration是通过XMLConfigBuilder去构建的,它会读出所有的XML配置信息,把它们解析并保存在Configuration单例中。
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse()); // 开始进行解析了 :)
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
}
}
}
当XMLConfigBudiler解析XML时,会将每一个SQL和其配置的内容保存起来,在MyBatis中一条SQL和它相关的配置信息是由3部分组成的,它们分别是MappedStatement、SqlSource和BoundSql。
- MappedStatement MappedStatement维护一条<select|update|delete|insert>节点的封装
- SqlSource 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
- BoundSql 表示动态生成的SQL语句以及相应的参数信息
通过以上分析,知道了MyBatis会根据文件流生成Configuration对象,进而构建SqlSessionFactory对象得到SqlSession对象了。得到SqlSession对象后就是去执行sql语句。
SqlSession构建
以上我们构建了SqlSessionFactory,在MyBtis中SqlSession是其核心接口,它有两个实现类,DefaultSqlSession和SqlSessionManager。DefaultSqlSession是单线程使用的,而SqlSessionManager在多线程环境下使用。SqlSession的作用类似于JDBC中的Connection对象,代表着一个连接资源的启用,它的作用:
- 获取Mapper接口。
- 发送SQL给数据库。
- 控制数据库事物。
前面我们构建了SqlSessionFactory,有了SqlSessionFactory创建的SqlSession就十分简单了。如下:
SqlSession sqlSession= sqlSessionFactory.openSession();
继续往下看,DefaultSqlSessionFactory实现了openSession()方法,之后调用了openSessionFromDataSource() 方法。
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
//传入的configuration获取环境变量对象、Environment可以配置多个环境配置
final Environment environment = configuration.getEnvironment();
//从环境对象中获取事务工厂对象
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
//根据DataSource、事务隔离级别、自动提交创建事务对象
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//新建执行者
final Executor executor = configuration.newExecutor(tx, execType);
//创建默认SqlSession
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
//异常情况关闭事务
closeTransaction(tx); // may have fetched a connection so lets call close() (可能已经获取到数据库连接,因此执行关闭)
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
//重置错误上下文
ErrorContext.instance().reset();
}
}
最后调用了new DefaultSqlSession()创建了默认SqlSession。
SqlSession运行过程
SqlSession是一个接口,使用它并不复杂,它给出了查询、插入、更新、删除的方法。但运行过程是MyBatis中最难理解的部分。
先来看看MyBatis的源码是如何实现getMapper方法的。在MyBatis中,通过MapperProxy动态代理Dao, 也就是说当执行自己写的Dao里面的方法的时候,其实是对应的mapperProxy在代理。
通过SqlSession从Configuration中获取
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
显然应用到了Configuration对象的getMapper方法来获取对应接口对象,所以继续往下看。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
它有应用到了映射器的注册器Mapperregistry来获取对应的接口对象。
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
//关键在这儿
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
这里首先它判断是否注册一个Mapper,如果没有则会抛出异常信息,如果有就会启用MapperProxyFactory工厂来生成一个代理实例。
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
//动态代理我们写的 Dao接口
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);
}
在这里可以看到Mapper映射是通过动态代理来实现对接口的绑定的,它的作用就是生成动态代理对象,而代理的方法则被放到了MapperProxy类中。我们再往下看MapperProxy的源码。
@Override
public Object invoke(Object proxy, Method method, Object