一般拿到源码会无从下手,我的基本思路一般就是根据一个基本的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来设置,否则程序会获取不到。
从上面的代码可以看到mybatis重要的执行顺序:输入配置流,由SqlSessionFacotryBuilder来根据输入的配置构建出来一个SqlSessionFactory。
//简略代码 //解析配置 XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); //根据配置返回SqlSessionFactory public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
获取到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);
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结合的实现原理;