String resource = "mybatis-config.xml";
Reader reader;
try {
//将XML配置文件构建为Configuration配置类
reader = Resources.getResourceAsReader(resource);
// 通过加载配置文件流构建一个SqlSessionFactory DefaultSqlSessionFactory
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
// 数据源 执行器 DefaultSqlSession
SqlSession session = sqlMapper.openSession();
try {
// 执行查询 底层执行jdbc
User user = (User)session.selectOne("com.tuling.mapper.UserMapper.selectById", 1L);
UserMapper mapper = session.getMapper(UserMapper.class);
System.out.println(mapper.getClass());
User user = mapper.selectById(1L);
session.commit();
System.out.println(user.getUserName());
} catch (Exception e) {
e.printStackTrace();
}finally {
session.close();
}
} catch (IOException e) {
e.printStackTrace();
}
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
这句话主要就是解析我们的xml配置文件生成Configuration对象,构建SqlSessionFactory
我们看下xml怎么变成Configuration的
<?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>
<!--SqlSessionFactoryBuilder中配置的配置文件的优先级最高;config.properties配置文件的优先级次之;properties标签中的配置优先级最低 -->
<properties resource="org/mybatis/example/config.properties">
<property name="username" value="dev_user"/>
<property name="password" value="F2Fa3!33TYyg"/>
</properties>
<!--一些重要的全局配置-->
<settings>
<setting name="cacheEnabled" value="true"/>
<!--<setting name="lazyLoadingEnabled" value="true"/>-->
<!--<setting name="multipleResultSetsEnabled" value="true"/>-->
<!--<setting name="useColumnLabel" value="true"/>-->
<!--<setting name="useGeneratedKeys" value="false"/>-->
<!--<setting name="autoMappingBehavior" value="PARTIAL"/>-->
<!--<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>-->
<!--<setting name="defaultExecutorType" value="SIMPLE"/>-->
<!--<setting name="defaultStatementTimeout" value="25"/>-->
<!--<setting name="defaultFetchSize" value="100"/>-->
<!--<setting name="safeRowBoundsEnabled" value="false"/>-->
<!--<setting name="mapUnderscoreToCamelCase" value="false"/>-->
<!--<setting name="localCacheScope" value="STATEMENT"/>-->
<!--<setting name="jdbcTypeForNull" value="OTHER"/>-->
<!--<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>-->
<!--<setting name="logImpl" value="STDOUT_LOGGING" />-->
</settings>
<typeAliases>
</typeAliases>
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!--默认值为 false,当该参数设置为 true 时,如果 pageSize=0 或者 RowBounds.limit = 0 就会查询出全部的结果-->
<!--如果某些查询数据量非常大,不应该允许查出所有数据-->
<property name="pageSizeZero" value="true"/>
</plugin>
</plugins>
<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://10.59.97.10:3308/windty"/>
<property name="username" value="windty_opr"/>
<property name="password" value="windty!234"/>
</dataSource>
</environment>
</environments>
<databaseIdProvider type="DB_VENDOR">
<property name="MySQL" value="mysql" />
<property name="Oracle" value="oracle" />
</databaseIdProvider>
<mappers>
<!--这边可以使用package和resource两种方式加载mapper-->
<!--<package name="包名"/>-->
<!--<mapper resource="./mappers/SysUserMapper.xml"/>-->
<mapper resource="./mappers/CbondissuerMapper.xml"/>
</mappers>
</configuration>
构建了XMLConfigBuilder 对象用来解析xml
这里面没啥好说的,就是解析xml然后负值到Configuration属性上,包括日志、别名、插件、数据源、数据库厂商、typeHandler、mapper,这样我们Configuration对象就构建好了。
重点看下mapper怎么解析的
构建了XMLMapperBuilder用来解析namespace、参数、resultMap、缓存等
ResultMap
org.apache.ibatis.builder.xml.XMLMapperBuilder#resultMapElement(org.apache.ibatis.parsing.XNode, java.util.List<org.apache.ibatis.mapping.ResultMapping>, java.lang.Class<?>)
这里会构建ResultMapping集合
其实也就是我们一些jdbcType、javaType,对应的列,以及resultMap唯一id
然后放到
protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
id 就是 唯一id
接下来看怎么解析sql 标签
org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode
解析一些缓存、节点名称、节点类型SqlCommandType(后续执行sql 会用到,判断是增删改查)、自增id、解析sql、resultMap 最后封装成MappedStatement当道Configuration的mappedStatements中,key 就是namsespace+MappedStatementId
重点看下sql 怎么解析的
分为动态sql、和静态sql,返回SqlSource 放入我们的MappedStatement
sqlSource 有getBoundSql 方法
然后创建 MapperProxyFactory 放入到
Configuration的MapperRegistry的knownMappers.put(type, new MapperProxyFactory<>(type));
SqlSession session = sqlMapper.openSession();
其实就是构建了我们的DefaultSqlSession,DefaultSqlSession对象中包含Executor和Configuration
我们看下Executor
默认就是SimpleExecutor
如果开启缓存CachingExecutor,然后调用拦截器
UserMapper mapper = session.getMapper(UserMapper.class);
可以看到就是从我们的configuration的mapperRegistry 中拿,看下具体怎么拿的
从knownMappers拿到MapperProxyFactory,MapperProxyFactory创建代理,所以最后返回的对象就是MapperProxy
User user = mapper.selectById(1L);
也就是我们代理对象 的
org.apache.ibatis.binding.MapperProxy#invoke
首先 封装 MapperMethod
通过MappedStatement解析出name和type
接下来执行
其实最后还是调用了sqlSession的方法
sqlSession最后还是调用了executor
拿出sql
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
会解析我们的sql,还记得上面的动态sql吗
如果开启了二级缓存,从二级缓存先取
可以看到二级缓存是装饰器模式,可以实现线程安全、fifo内存淘汰、命中率统计
二级缓存没有,从一级缓存取,一级缓存就是一个map
一级缓存是sqlSession级别的,insert、update 操作会清空缓存,commit 也会,所以同一个sqllSerssion 多次操作 会命中一级缓存,一级缓存默认开启,二级缓存默认关闭,且可以持久化,二级缓存提交的时候才回去更新进去,防止回滚脏读,之前也是暂存在map里
org.apache.ibatis.cache.decorators.TransactionalCache#flushPendingEntries
new RoutingStatementHandler的时候会初始化父类,这个时候会进行
ParameterHandler和ResultSetHandler的拦截
StatementHandler 拦截
通过typeHandler 设置我们的参数
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSet
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleRowValues
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleRowValuesForNestedResultMap
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#applyPropertyMappings
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#getPropertyMappingValue
大概思想就是通过
TypeHandler 对应类型的实现类 从resultSet 中 通过列名 拿到对应的java类型的值,反射放到object上
Plugin
上面说过 ParameterHandler、ResultSetHandler、StatementHandler、Executor实例化后会调用我们的InterceptorChain.pluginAll方法,其实就是调用每个拦截器的plugin方法
所以我们plugin方法就是返回代理对象,默认Plugin.wrap(target, this);
我们看看他做了什么
其实也就是判断当前自定义注解上的type和 目前调用plugin是不是一样,一样就能返回代理对象,那代理对象调用的时候如果方法包含目标方法又回调了我们Interceptor的intercept方法,否则调用本身方法