通过一次SQL查询,查看mybatis源码记录。
1. MyaBatis原生API使用
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
// 第一步
sqlSessionFactory = builder.build(inputStream);
// 第二步
SqlSession session = sqlSessionFactory.openSession();
// 第三步
BlogMapper mapper = session.getMapper(BlogMapper.class);
// 第四步
Blog blog = mapper.selectBlogById(100001);
// 第五步
session.close();
2. 解析配置文件
(1)解析的时候都做了什么
使用XMLConfigBuilder解析mybtais-config.xml文件。
-
XMLConfigBuilder.java
// mybtais-config 配置文件解析,生成一个configuration对象 public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; parseConfiguration(parser.evalNode("/configuration")); return configuration; } // 从这里开始处理mybatis-config。xml配置文件,从configuration根节点开始 private void parseConfiguration(XNode root) { try { // issue #117 read properties first // properties 标签处理 propertiesElement(root.evalNode("properties")); // settings 设置标签处理 Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); loadCustomLogImpl(settings); // typeAliases 别名标签处理 typeAliasesElement(root.evalNode("typeAliases")); // plugins 插件标签处理 pluginElement(root.evalNode("plugins")); // objectFactory 标签处理 objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory")); // configuration中属性赋值 settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 // 数据库连接信息处理 environmentsElement(root.evalNode("environments")); // 数据库厂商标识 databaseIdProviderElement(root.evalNode("databaseIdProvider")); // 自定义的类型映射处理 typeHandlerElement(root.evalNode("typeHandlers")); // 映射器配置文件处理 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
-
解析Environment配置,创建数据源和事务工厂。environmentsElement()方法
if (isSpecifiedEnvironment(id)) { // 事务控制 JDBC 或者 MANAGED TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager")); // 获取数据源 DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")); DataSource dataSource = dsFactory.getDataSource(); // 创建一个环境信息类 Environment.Builder environmentBuilder = new Environment.Builder(id) .transactionFactory(txFactory) .dataSource(dataSource); // configuration中配置enviroment configuration.setEnvironment(environmentBuilder.build()); }
-
mapper映射器解析,使用XMLMapperBuilder解析映射器文件。
// mapper映射器配置,也是使用package和mapper两种方式配置 package需要配置接口的实现类 private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) { String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else { // 非 package方式配置 ,又分为resource url class 三种配置方式,三种方式是互斥的 String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); if (resource != null && url == null && mapperClass == null) { // 我使用的是resource方式配置 ErrorContext.instance().resource(resource); // resource中配置的是个路径,拿到之后可以读取mapper映射器文件 InputStream inputStream = Resources.getResourceAsStream(resource); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null) { ErrorContext.instance().resource(url); InputStream inputStream = Resources.getUrlAsStream(url); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url == null && mapperClass != null) { Class<?> mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); } else { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } } } } }
-
解析mapper文件
InputStream inputStream = Resources.getResourceAsStream(resource); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); mapperParser.parse();
-
按配置顺序开始解析mapper文件
// 从 mapper 根结点开始解析 private void configurationElement(XNode context) { try { String namespace = context.getStringAttribute("namespace"); if (namespace == null || namespace.isEmpty()) { throw new BuilderException("Mapper's namespace cannot be empty"); } builderAssistant.setCurrentNamespace(namespace); cacheRefElement(context.evalNode("cache-ref")); // 解析cache属性,只有配置这个,才会创建一个Cache对象,这个对象经过了好几层的封装 cacheElement(context.evalNode("cache")); parameterMapElement(context.evalNodes("/mapper/parameterMap")); resultMapElements(context.evalNodes("/mapper/resultMap")); sqlElement(context.evalNodes("/mapper/sql")); buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e); } }
XMLStatementBuilder用来解析mapper文件中配置的增删改查语句。所有的SQL语句都会封装成一个MappedStatement对象。
bindMapperForNamespace();把namespace对应的接口和它对应的MapperProxyFactory绑定到一起。
最终通过new DefaultSqlSessionFactory(config)返回一个DefaultSqlSessionFactory对象。
(2)产生了哪些对象
创建XMLConfigBuilder时同时创建了Configuration对象。专门解析mybatis-config.xml配置文件。
-
typeAliasRegistry 对象保存别名的配置。
数据结构是Map<String, Class<?>> typeAliases = new HashMap<>();
-
typeHandlerRegistry保存类型转换映射。
数据结构是Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();
XMLMapperBuilder专门解析映射器文件。
XMLStatementBuilder专门解析增删改查语句。
MappedStatement用来保存SQL语句的解析结果,由MapperBuilderAssistant创建,并把Cache对象放到MappedStatement中。
Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>()。这是mybtais自己实现的一个map,在保存的时候会把statementId对应的SQL语句按namespace+statementId和statementId两种key,分别保存一次。即一个ms对象在map中用两个长度不同的key保存了两次。
-
mapperRegistry保存mapper接口和对应的工厂类。
数据结构是Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
MapperProxyFactory
DefaultSqlSessionFactory
(3)解析结果是怎么保存的
- Configuration保存了所有的属性。
- typeAliasRegistry 对象保存别名的配置
- typeHandlerRegistry保存类型转换映射
- mapperRegistry保存mapper接口和对应的工厂类
- mappedStatements用来保存ms对象
3. 获取SqlSession
从 sqlSessionFactory.openSession();开始获取到一个sqlSession对象。
DefaultSqlSessionFactory.java
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// mybatis-config.xml中配置的environment对应的对象
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 获取一个事务
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 创建一个执行器
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();
}
}
需要注意一下的是configuration.newExecutor(tx, execType);
这句话去创建一个Executor执行器。
Configuration.java
// 在Configuration类中创建执行器 创建一个executor执行器
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);
}
// 如果二级缓存开关开了,用 CachingExecutor修饰一下当前执行器
// cacheEnabled=true走这一句,默认也是true
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 插件植入操作
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
返回SqlSession
最终返回一个DefaultSqlSession类型的SqlSession。
4. 获取Mapper
BlogMapper mapper = session.getMapper(BlogMapper.class);
session是DefaultSqlSession类型的对象。从DefaultSqlSession中的getMapper方法开始看。
(1)mapper对象是什么
从DefaultSqlSession中的getMapper方法一路往下可以看到,最终从mapperRegistry中获取到了在解析mapper文件时绑定的对应的mapperProxyFactory的工厂类。
mapperProxyFactory.newInstance(sqlSession)
通过这句话就返回了一个mapper对象。mapper对象到底是什么呢?是一个动态代理类。-
MapperProxyFactory.java
// JDK的动态代理 protected T newInstance(MapperProxy<T> mapperProxy) { // jdk动态代理的返回对象类型和 class是保持一致的。因此返回的就是 BlogMapper return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { // 创建一个MapperProxy类,这个类实现了InvocationHandler接口,触发管理类h final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); }
(2)为什么要通过sqlSession去创建
我们已经知道,最终创建的实际上就是BlogMapper的一个代理对象。而这个代理对象的属性包含sqlSession、mapperInterface和methodCache。
5. 执行SQL(mapper方法调用)
所有获得的mapper接口对象,都是JDK动态代理过的对象,在真正进行方法调用的时候是通过invoke()方法对真实方法进行调用,那么这个代理类要做的事情是:
(1)怎么根据方法名找到对应的statement并且去执行它呢?(以查询为例)
a. 从MapperProxy类中的invoke()方法开始执行。return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
b. 执行PlainMethodInvoker中的invoke方法。
c. mapperMethod.execute(sqlSession, args)
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
// 真正开始要执行sql语句了
return mapperMethod.execute(sqlSession, args);
}
d. 一直往下调用,最终又调回到DefaultSqlSession中的SQL执行语句
// 一直往下调用,最终又调回到DefaultSqlSession中的SQL执行语句
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();
}
}
e.executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
使用 Executor 执行对应query方法,这里的Executor一般是CachingExecutor类型
如果有插件拦截,先走插件拦截;如果二级缓存开关没有关,再走CachingExecutor;最后走BaseExecutor中的方法。
f.走到CachingExecutor的query中
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 计算一个cacheKey,判断是否可以从缓存中得到执行结果
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
cacheKey例子:
-1281788015:884735269:com.tomas.mysql.mybatis.mapper.BlogMapper.selectBlogById:0:2147483647:select * from blog where bid = ?:100001:development
g. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
Cache cache = ms.getCache();
// 如果没有配置缓存,就没有获取缓存、更新缓存的操作
if (cache != null) {
// 获取缓存
List<E> list = (List<E>) tcm.getObject(cache, key);
......
// 添加到缓存中,实际上只是到了一个临时map中,结果并没有在缓存对象中。
tcm.putObject(cache, key, list);
}
// 二级缓存是和事务绑定的,只有事务成功执行,才会真正的写数据到二级缓存中,防止因事务执行失败回滚导致缓存脏数据的情况的发生。
h. 最后执行到BaseExcutor类中的query方法。一层一层调用之后,最后调用simpleExecutor类的doQuery()方法。再往下就是调用jdbc的查询了。
(2)它是怎么从invoke方法中去找到真正要执行的SQL呢?
在执行cachedInvoker(method)
的时候把method封装成了MapperMethod对象。MapperMethod中包含sqlCommand属性和MethodSignature属性。最后MapperMethod对象保存在PlainMethodInvoker对象中。
6. 关闭session
session.close()方法。如果用到了缓存,在事务执行成功之后,遍历map,把查询结果放到缓存对象中。
7. 插件可以拦截的类
opensession()时处理Executor
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
执行SQL创建StatementHandler时下面三个一起处理
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
8. 一级缓存和二级缓存
一级缓存
BaseExecutor中有一个PerpetualCache(localCache 一级缓存)
二级缓存
new 一个PerpetualCache,然后用装饰器层层包装。