一般拿到源码会无从下手,我的基本思路一般就是根据一个基本的helloWorld Debug下去,把主线先大概理一遍,然后再具体分析细节,没有必要一个类一个类细看,看了也会忘掉。自己理源码的时候看不下去时,可以结合网上的分析文章,一边看别人的解析,一边自己对照源码。了解框架设计原理,以后项目中出了问题可以更容易定位。再往上一层面,以后自己可以根据需求扩展框架。
先执行个HelloWorld
去github上 clone Mybatis代码,然后再其测试源码里添加如下代码
示例代码,里面未贴出来的类自行补全。
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession(true);
try {
//后面介绍mybatis通过动态代理来避免手工调用session,直接调用dao接口;
//BlogDao mapper = session.getMapper(BlogDao.class);
//List<Blog> blogs= mapper.selectAll();
List<Blog>blogs= session.selectList("org.apache.ibatis.robin.BlogDao.selectAll");
System.out.println(blogs);
} finally {
session.close();
}
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/BlogMapper.xml"/>
</mappers>
</configuration>
BlogMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.apache.ibatis.robin.BlogDao">
<resultMap type="org.apache.ibatis.robin.Blog" id="blog">
<result property="id" column="id"/>
<result property="context" column="context"/>
<result property="dateCreate" column="date_create"/>
</resultMap>
<insert id="insert" parameterType="org.apache.ibatis.robin.Blog">
insert into blog(id,context,date_create)
values (#{id},#{context},now())
</insert>
<select id="selectAll" resultMap="blog" flushCache="true" >
SELECT * from blog
</select>
</mapper>
注意xml文件放到项目的resource位置,可以通过ide来设置,否则程序会获取不到。
一步一步DEBUG
解析配置,构建SqlSessionFactory
从上面的代码可以看到mybatis重要的执行顺序:输入配置流,由SqlSessionFacotryBuilder来根据输入的配置构建出来一个SqlSessionFactory。
//简略代码
//解析配置
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
//根据配置返回SqlSessionFactory
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
构建打开SqlSession (executor生成,plugin织入,缓存开闭)
获取到SqlSessionFactory后,下一步肯定是获得sqlSession。
sqlSessionFactory.openSession(true);
//这里的true 表示是否自动commit
//debug 进去 这部分是openSession的大体过程;
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
//先通过configuration获取我们再xml中配的environment,里面包含事务和DataSource;
final Environment environment = configuration.getEnvironment();
//这一步是获取JDBC事务
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//打开Executor过程,见下段落的分析
final Executor executor = configuration.newExecutor(tx, execType);
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();
}
}
打开Executor的过程,Executor默认的类型是SIMPLE
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);
}
//如果在配置文件settings中打开 <setting name="cacheEnabled" value="true" />
// cacheEnabled 着为true,开启全局缓存,也就是二级缓存;
//下面查询具体分析CachingExecutor执行查询过程
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
//如果自定义了Plugin拦截器,在xml通过plugins配置后,这一步会通过JDK动态代理织入到executor中,
//生成一个带了拦截器方法功能的Executor
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
在根BaseExecutor中可以看到 ,Executor不仅包含了事务,还同时加入localCache ,也就是Session级别的缓存,也是大家常叫的一级缓存,这里提一下一级缓存是默认的,如果非要去掉只能通过在select 语句配置中 flushCache="true"。
protected BaseExecutor(Configuration configuration, Transaction transaction) {
this.transaction = transaction;
this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>();
this.localCache = new PerpetualCache("LocalCache");
this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
this.closed = false;
this.configuration = configuration;
this.wrapper = this;
}
Executor组装好之后通过new DefaultSqlSession返回我们的SqlSession;
new DefaultSqlSession(configuration, executor, autoCommit);
执行具体查询(重要概念MappedStatement)
SqlSession是直接对数据库发号施令的组件。通过发起下面一个SQL查询,继续Debug进去
List<Blog>blogs= session.selectList("org.apache.ibatis.robin.BlogDao.selectAll");
先mybatis自动补全一些默认参数(rowBounds主要是指代返回的行数限制)后,进去下面的代码
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//MappedStatement是Mybatis的精髓,如果Bean对Spring是一样的道理;这里我们要重点介绍下
//属性MappedStatement 是一个SQL执行语句的包装,里面的属性有SqlSource(指代SQL具体的语句);
//属性StatementType 如果是PREPARED表明执行预编译执行,这样不仅防止SQL注入,而且还能避免SQL重复解析;
//属性id 指代我们唯一确定MappedStatement唯一标识;
//还有ResultMap 和ParameterMap等等,这里就不解释来;
//可以说MappedStatement是发起SQL请求的所需的必备数据;
MappedStatement ms = configuration.getMappedStatement(statement);
//进行query查询,转下段分析
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();
}
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//获取SQL
BoundSql boundSql = ms.getBoundSql(parameterObject);
//通过参数,sql,和rowBounds一起拼装出cacheKey
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
//进入到查询
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
//先判断是否有缓存中去取
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, parameterObject, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
//缓存中取不到,直接执行query,这里delegate指代我们前面生成的SimpleExecutor;
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
//跳转到下面的方法
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
//清掉cache
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//跳到下面到数据库中去查询数据
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
//从这个方法可以明显看出里面的localCache,指代前面说的Session缓存,即一级缓存
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
//这一步主要是获取Connection,然后准备PreparedState,执行查询返回结果
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
//把新结果缓存起来
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
至此我们的查询SQL就执行完了。
这一路涉及了 Configuration --->SqlSessionFactory------>SqlSession(里面又包装了 Executor,Cache,MappedStatement) 这几个重要概念;
下节主要讲Mybatis通过代理 把原先自己需要直接调用 sqlSession来执行 改成只需调用相应dao接口类,在实际项目中省掉大量代码,以及与spring结合的实现原理;
本文链接 http://my.oschina.net/robinyao/blog/645263