说在前面:首先来看一段JDBC获取数据的代码。
public static void main(String[] args) throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/mybatis_study";
Connection conn = DriverManager.getConnection(url, "root", "123456");
PreparedStatement statement = null;
ResultSet rs = null;
if (conn != null) {
System.out.println("get conn");
String sql = "select id,name from user where id=?";
statement = conn.prepareStatement(sql);
statement.setInt(1, 1);
statement.execute();
rs = statement.getResultSet();
if (rs != null) {
System.out.println("get rs");
User user = new User();
for (; rs.next(); ) {
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
//...
}
System.out.println(JSON.toJSONString(user));
}
}
rs.close();
conn.close();
statement.close();
}
在使用JDBC时,我们所面临的烦恼就是不断的获取Connection、拼接sql、和转换ResultSet。试想一个User有100个字段,那我们的代码就没有可读性了,犯错的概率极大。
mybatis作为一个ORM框架就是为了帮助我们解决这些问题的,包括Connection获取、sql拼接、ResultSet转换为POJO几个方面。接下来的系列文章,会看到mybatis如何处理以上问题的。
分析采用mybati-3.4.1.jar
mybatis的工程结构如图:
与执行流程相关的类主要有:
SqlSessionFactory
SqlSessionFactoryBuilder
DefaultSqlSessionFactory
Configuration
DefaultSqlSession
MapperProxyFactory
MapperProxy
MapperMethod
CachingExecutor
BaseExecutor
SimpleExecutor
RoutingStatementHandler
PreparedStatementHandler
ResultMap
MappedStatement
下面来看我们程序中的调用流程:
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
首先会创建SqlSessionFactory 。在SqlSessionFactoryBuilder调用build(Reader reader)
来创建SqlSessionFactory,并将我们的配置文件(mybatis-config.xml)流包装为Reader传入。
SqlSessionFactoryBuilder.java
public SqlSessionFactory build(Reader reader) {
return this.build((Reader)reader, (String)null, (Properties)null);
}
而build(Reader reader)
调用了build(Reader reader, String environment, Properties properties)
,该方法执行XML文件的解析,并调用build(Configuration config)
创建出默认的SqlSessionFactory 对象。
SqlSessionFactoryBuilder.java
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
SqlSessionFactory var5;
try {
//解析Mybatis-config.xml配置文件中的内容,包括xml文件的校验,以及从标签解析mybatis的各项配置,并返回Configuration对象。解析的过程中对Configuration的许多默认参数做了设置,详情见XMLConfigBuilder类的分析。
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
var5 = this.build(parser.parse()); //parser.parse()返回Configuration实例。
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset(); //TODO 异常类的分析
try {
reader.close();
} catch (IOException var13) {
;
}
}
return var5;
}
//将Configuration实例传给DefaultSqlSessionFactory
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config); //默认创建的SqlSessionFactory为DefaultSqlSessionFactory
}
SqlSessionFactory顾名思义就是用来提供各种方法来获取SqlSession。或配置Configuration。
这里主要用到DefaultSqlSessionFactory,其实现了SqlSessionFactory中的openSession等方法,提供了从数据源和连接中获取Session的方法,并提供获取事务工厂和关闭事务的方法。
SqlSessionFactory的创建主要用到了Builder模式:
Configuration担任导演角色。
再来看openSession()
方法:
public SqlSession openSession() {
return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(),
(TransactionIsolationLevel)null, false);
//可以看到默认的事务隔离级别为空,autoCommit默认为false事务不会自动提交。ExecutorType默认为SIMPLE。
}
可以看出默认是从我们配置的dataSource中获取SqlSession 。
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
DefaultSqlSession var8;
try {
//获取Environment,即我们在配置文件中用标签所指定的内容。
Environment environment = this.configuration.getEnvironment();
//获取TransactionFactory工厂,用来创建Transaction
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
//获取事务
tx = transactionFactory.newTransaction(e.getDataSource(), level, autoCommit);
//Executor 的创建
Executor executor = this.configuration.newExecutor(tx, execType);
//在这里创建了默认的SqlSession
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception var12) {
this.closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
} finally {
ErrorContext.instance().reset();
}
return var8;
}
environment就是我们在mybatis-config.xml中配置在 标签内的东西:断点结果如图。
在这里我们配置的是JDBC的事务管理器,所以使用了JDBC的Transaction。
public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level,
boolean autoCommit) {
return new JdbcTransaction(ds, level, autoCommit);
}
在JdbcTransaction默认使用的是slf4j日志接口,所以我们可以通过配置日志,打印出sql语句。
在Executor executor = this.configuration.newExecutor(tx, execType);
时进行了Executor的创建,类型为CachingExecutor:
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
//两次检查防止executorType为空,默认情况下设置为ExecutorType.SIMPLE
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
//批量 TODO 分析
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); //在这里使用的SimpleExecutor
}
if (cacheEnabled) { //默认缓存是开启的,所以会创建CachingExecutor
executor = new CachingExecutor(executor);
}
//注册拦截器链(其实就是一个ArrayList)
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
因为cacheEnabled
为true
,所以创建的Executor类型为CachingExecutor。而CachingExecutor中,持有一个delegate类型为SimpleExecutor
。
拦截器链的初始化,
executor = (Executor) interceptorChain.pluginAll(executor);
拦截器链是一个ArrayList.
private final List interceptors = new ArrayList();
接下来看SqlSession。SqlSession中定义了操作数据库的base方法以及对事务的提交和回滚等操作,可以看到mybatis非常灵活,我们可以直接在SqlSession拿到Connection。
同时,SqlSession接口继承自Closeable,而 Closeable extends AutoCloseable
,表示session可以自动被关闭。
例如:我们可以通过拿到Connection并获取操作的表名。
Connection conn = sqlSession.getConnection();
log.info(conn.getCatalog());
//在DefaultSqlSession中,持有Configuration,和Executor的引用。
//所以SqlSession才可以委托Executor具体实现类(例如:CachingExecutor、SimpleExecutor)去执行对应的sql语句。
//而Configuration则提供类型转换、结果解析等信息。
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
private Executor executor;
private boolean autoCommit;
private boolean dirty;
private List> cursorList;
...
}
至此,SqlSession创建完成。就可以调用接口中的selectOne()、selectList()去执行查询等操作。
1.首先会读取mybatis-config.xml配置文件及Mapper.xml映射文件。
2.从以上配置文件中解析各个标签中的信息,并将信息注册到Configuration实例中,同时还会初始化默认的参数。
3.通过DefaultSqlSessionFactoryBuilder#build(Configuration config)
实例化DefaultSqlSessionFactory,并将Configuration传递给它。并通过Configuration中的连接池、事务隔离级别,以及executor和autoCommit等参数实例化SqlSession接口的实现类DefaultSqlSession。
4.DefaultSqlSession具体实现了SqlSession中定义的接口。并通过自己持有的Executor接口,委托具体的Executor去执行sql语句完成具体的CRUD操作。