MyBatis源码学习环境下载
// 1.获取配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
全局配置文件:mybatis-config.xml
映射文件:mapper/*.xml
// 2.加载解析配置文件并获取 SqlSessionFactory 对象
// SqlSessionFactory 的实例我们没有通过 DefaulSqlSessionFactory 直接来获取
// 而是通过一个 Builder 对象来建造的
// SqlSessionFactory 生产 SqlSession 对象的 SqlSessionFactory 应该是单例
// 全局配置文件和映射文件 也只需要在 系统启动的时候完成加载操作
// 通过建造者模式来 构建复杂的对象: 1.完成配置文件的加载解析 2.完成 SqlSessionFactory 的创建
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
加载解析的相关信息存储在SqlSessionFactory对象的Configuration属性里,
通过工厂得到SqlSession对象
// 3.根据 SqlSessionFactory 对象获取 SqlSession 对象
SqlSession sqlSession = factory.openSession();
// 4.通过sqlSession中提供的API方法来操作数据库
List<User> list = sqlSession.selectList("com.boge.mapper.UserMapper.selectUserList");
// 获取接口的代码对象-得到的其实是 通过JDBC代理模式获取的一个代理对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//5.关闭会话
sqlSession.close();//关闭session 清空一级缓存
全局配置文件的加载解析:Configuration
映射文件的加载解析:Configuration.mappedStatements
/**
* MappedStatement 映射
*
* KEY:`${namespace}.${id}`
*/
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>("Mapped Statements collection");
生产了DefaultSqlsession实例对象,完成了Executor对象的创建,以及二级缓存CachingExecutor的装饰,同时完成了插件逻辑的植入。
selectOne():二级缓存->一级缓存->数据库插入
// 1. 读取配置文件,读成字节输入流,注意:现在还没解析
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
// 2. 解析配置文件,封装Configuration对象 创建DefaultSqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
// 3. 生产了DefaultSqlsession实例对象 设置了事务不自动提交 完成了executor对象的创建
SqlSession sqlSession = sqlSessionFactory.openSession();
// 4.(1)根据statementid来从Configuration中map集合中获取到了指定的MappedStatement对象
//(2)将查询任务委派了executor执行器
User user = sqlSession.selectOne("com.lagou.mapper.IUserMapper.findById",1);
System.out.println(user);
User user2 = sqlSession.selectOne("com.lagou.mapper.IUserMapper.findById",1);
System.out.println(user2);
// 5.释放资源
sqlSession.close();
降低数据源的访问频率,从而提高数据源的处理能力,提高服务器的响应速度。
通过装饰者模式对Cache接口的工作做增强
装饰者 | 作用 |
---|---|
BlockingCache | 阻塞的 Cache 实现类 |
FifoCache | 基于先进先出的淘汰机制的 Cache 实现类 |
LoggingCache | 支持打印日志的 Cache 实现类 |
LruCache | 基于最少使用的淘汰机制的 Cache 实现类 |
ScheduledCache | 定时清空整个容器的 Cache 实现类 |
SerializedCache | 支持序列化值的 Cache 实现类 |
SoftCache | 软引用缓存装饰器 |
SynchronizedCache | 同步的 Cache 实现类 |
TransactionalCache | 支持事务的 Cache 实现类,主要用于二级缓存中 |
WeakCache | 弱引用缓存装饰器 |
<setting name="localCacheScope" value="STATEMENT"/>
二级缓存:SqlSessionFactory级别(工厂/进程级别)
在mybatis配置文件中配置cacheEnabled为true
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
在映射文件中添加cache标签,可以在cache标签中更细致的增加配置
<cache />
命名空间下的所有标签放开二级缓存
可以通过在标签中添加 useCache=false 指定api不走二级缓存
mybatis-config.xml
原因:找到概率更高,性能角度。
一级缓存 | 二级缓存 | |
---|---|---|
作用域 | SqlSession级别 | SqlSessionFactory级别 |
找到概率 | 5% | 90% |
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// 从 MappedStatement 中获取 Cache,注意这里的 Cache 是从MappedStatement中获取的
// 也就是我们上面解析Mapper中 标签中创建的,它保存在Configration中
// 我们在初始化解析xml时分析过每一个MappedStatement都有一个Cache对象,就是这里
Cache cache = ms.getCache();
// 如果配置文件中没有配置 ,则 cache 为空
if (cache != null) {
//如果需要刷新缓存的话就刷新:flushCache="true"
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
// 暂时忽略,存储过程相关
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
// 从二级缓存中,获取结果
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
// 如果没有值,则执行查询,这个查询实际也是先走一级缓存查询,一级缓存也没有的话,则进行DB查询
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 缓存查询结果
tcm.putObject(cache, key, list); // issue #578 and #116
}
// 如果存在,则直接返回结果
return list;
}
}
// 不使用缓存,则从数据库中查询(会查一级缓存)
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
/**
* 记录嵌套查询的层级
*/
protected int queryStack;
/**
* 本地缓存,即一级缓存
*/
protected PerpetualCache localCache;
@SuppressWarnings("unchecked")
@Override
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());
// 已经关闭,则抛出 ExecutorException 异常
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 清空本地缓存,如果 queryStack 为零,并且要求清空本地缓存。
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
// queryStack + 1
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 - 1
queryStack--;
}
if (queryStack == 0) {
// 执行延迟加载
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
// 清空 deferredLoads
deferredLoads.clear();
// 如果缓存级别是 LocalCacheScope.STATEMENT ,则进行清理
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
/**
* 永不过期的 Cache 实现类,基于 HashMap 实现类
*
* @author Clinton Begin
*/
public class PerpetualCache implements Cache {
/**
* 缓存容器
*/
private Map<Object, Object> cache = new HashMap<>();
@Override
public void putObject(Object key, Object value) {
cache.put(key, value);
}
}
创建Cache接口的实现,重写putObject和getObject方法
在mapper映射文件中的cache标签里增加type属性,关联自定义的Cache接口的实现
<cache type="org.mybatis.caches.ehcache.EhcacheCache" />
如果未添加type,会默认读取 PERETUAL (二级缓存)
Cache接口 定义了缓存的基本行为
PerpetualCache基于Cache实现,针对于缓存的功能 1.缓存数据淘汰;2.缓存数据的存放机制;3.缓存数据添加是否同步【阻塞】;4.缓存对象是否同步处理…做了增强处理–>代理模式
以及很多装饰类,灵活增强出适用于不同业务场景的Cache实现
帮助我们适配不同的日志框架
Log接口针对不同日志框架,有不同的实现类,做增强处理
/**
* 构造 SqlSessionFactory 对象
*
* @param reader Reader 对象
* @param environment 环境
* @param properties Properties 变量
* @return SqlSessionFactory 对象
*/
@SuppressWarnings("Duplicates")
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
// 创建 XMLConfigBuilder 对象
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
// 执行 XML 解析
// 创建 DefaultSqlSessionFactory 对象
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
/**
* 1.我们最初调用的build
*/
public SqlSessionFactory build(InputStream inputStream) {
//调用了重载方法
return build(inputStream, null, null);
}
/**
* 解析 XML
*
* 具体 MyBatis 有哪些 XML 标签,参见 《XML 映射配置文件》http://www.mybatis.org/mybatis-3/zh/configuration.html
*
* @param root 根节点
*/
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
// 解析 标签
propertiesElement(root.evalNode("properties"));
// 解析 标签
Properties settings = settingsAsProperties(root.evalNode("settings"));
// 加载自定义的 VFS 实现类
loadCustomVfs(settings);
// 解析 标签
typeAliasesElement(root.evalNode("typeAliases"));
// 解析 标签
pluginElement(root.evalNode("plugins"));
// 解析 标签
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);
}
}
作用:通过相关API来实现对应的数据的操作
SqlSession对象的获取需要通SqlSessionFactory来实现,
作用域是会话级别,当一个新的会话到来的时候,需要新建一个SqlSession对象;当一个会话结束后,需要关闭相关会话资源
处理请求的方式:
1.通过相关crud的API直接处理
2.通过getMapper(xx.xml)来获取相关mapper接口的代理对象来处理
数据库层面SQL:
MySQL:LIMIT
Oracle:rowid
方便开发人员实现对MyBatis功能的增强
设计中MyBatis允许映射语句执行过程中的某一点进行拦截调用,允许使用插件拦截的方法包括:
<plugins>
<plugin interceptor="com.github.pagehelper.PageHelper">
<property name="dialect" value="mysql"/>
plugin>
plugins>
可以相同,每一个映射文件的namespace都会设置为对应的mapper接口的全类路径名称
保证了每个Mapper映射文件的namespace是唯一的。
面向开发者,提供相关API
核心功能的实现:增删改查操作
支撑核心层来完成核心的功能
JDBC | MyBatis | |
---|---|---|
资源 | 频繁的创建和释放数据库的连接对象,造成系统资源的浪费 | 通过全局配置文件设置相关的数据连接池 |
sql维护 | sql语句直接写在了代码中,维护成本高,动态性要求较高 | sql语句写在Mapper映射文件中的标签里 |
参数 | 向SQL中传递参数麻烦,where条件不一定,占位符和参数需要一一对应 | 自动完成Java对象和SQL中参数的映射 |
结果集 | 映射麻烦,需要循环封装,SQL本身的变化会导致解析的难度 | 通过ResultHandler自动将结果集映射到Java对象 |
拓展 | 不支持事务、缓存、延迟加载等功能 | 提供了相关实现 |
性能 | 运行性能更高 | 开发效率更高 |
完成相关标签的解析,存储在configuration中
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
// environment 属性非空,从 default 属性获得
if (environment == null) {
environment = context.getStringAttribute("default");
}
// 遍历 XNode 节点
for (XNode child : context.getChildren()) {
// 判断 environment 是否匹配
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
// 解析 ` ` 标签,返回 TransactionFactory 对象
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
// 解析 ` ` 标签,返回 DataSourceFactory 对象
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
// 创建 Environment.Builder 对象
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
// 构造 Environment 对象,并设置到 configuration 中
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
JDBC:在MyBatis中自己处理事务的管理
Managed:在MyBatis中没有处理任何的事务操作,这种情况下事务的处理会交给Spring容器来管理
SqlSession.commit()
名称需要对应
代理模式的使用
创建动态代理对象
每调用一个mapper接口都需要在命名空间中指向接口路径
反射模块的具体设计:
MyBatis是如何解决Java中的类型和数据库中的字段类型的映射
类型处理器:
写:Java类型 --> JDBC类型
读:JDBC类型 --> Java类型
SQL操作:读 写
本质上执行的JDBC操作:
String sql = "SELECT id,user_name,phone from user where id = ? and user_name = ?";
ps = conn.prepareStatement(sql);
ps.setInt(1,2);
ps.setString(2,"张三");
TypeHandler --> BaseTypeHandler —> 具体的TypeHandler
添加mybatis-spring依赖
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatis-springartifactId>
<version>1.2.2version>
dependency>
在spring配置文件spring-context.xml中添加数据源信息和sqlSessionFactory,将sqlSessionFactory注入容器
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="initialSize" value="${jdbc.pool.init}" />
<property name="minIdle" value="${jdbc.pool.minIdle}" />
<property name="maxActive" value="${jdbc.pool.maxActive}" />
<property name="maxWait" value="300000" />
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="typeAliasesPackage" value="com.hlframe"/>
<property name="typeAliasesSuperType" value="com.hlframe.common.persistence.BaseEntity"/>
<property name="mapperLocations" value="classpath:/mappings/**/*.xml"/>
<property name="configLocation" value="classpath:/mybatis-config.xml">property>
bean>
添加mybatis-plus-boot-starter依赖
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>${mybatis-plus.version}version>
<scope>provided scope>
dependency>
将sqlSessionFactory注入容器