Mybatis源码分析(1)
Mybatis源码分析(1)
1、Maven
什么是OGNL?
OGNL的三要素
表达式:OGNL表达式是功能强大的表达式语言,何解?在ognl中想要执行取值,赋值,调用方法等等操作,你都需要用表达式表示。通过表达式,底层会解析出来你的想要操作。它支持链式结构
根对象:即root对象,可以理解为OGNL的操作对象,表达式规定做什么,而该对象就指定对谁操作。OGNL叫做对象图导航语言,对象图就是以任意一个对象作为根,通过OGNL可以访问到与这个对象相关的其他对象。底层使用list集合做的。
Context对象:其实就是OGNL的上下文环境。root对象也在OGNL的上下文环境里,底层是一个Map集合。该上下文环境规定了OGNL操作在“哪里进行”,注意访问context对象时候需要在表达式中加上#。
JAVAssist
Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。
reload4j
核心内容如下:
- log4j的1.2版本是一个通用版本,但是由于2022年的log4j漏洞原因,slf4j-log4j模块在build时,会自动重定向至slf4j-reload4j模块。
- 如果你想继续使用log4j 1.x的框架,强烈推荐你使用slf4j-reload4j进行替代。
cglib
基于类的动态代理
HSQLDB
Hsqldb是一个开放源代码的JAVA数据库,其具有标准的SQL语法和JAVA接口,它可以自由使用和分发,非常简洁和快速的
Apache Derby
Apache Derby是一个完全用java编写的数据库
h2
H2是一个短小精干的嵌入式数据库引擎,主要的特性包括: 免费、开源、快速 嵌入式的数据库服务器,支持集群 提供JDBC、ODBC访问接口
什么是Mockito
Mockito是一个非常优秀的模拟框架,可以使用它简洁的API来编写漂亮的测试代码,它的测试代码可读性高同时会产生清晰的错误日志。
velocity简介
Velocity是一个基于Java的模板引擎,可以通过特定的语法获取在java对象的数据 , 填充到模板中,从而实现界面和java代码的分离!
PostgreSQL
PostgreSQL是一种特性非常齐全的自由软件的对象-关系型数据库管理系统(ORDBMS)
mysql-connector-java
java连接mysql数据库
MySql
MySQL 是最流行的关系型数据库管理系统,在 WEB 应用方面 MySQL 是最好的 RDBMS(Relational Database Management System:关系数据库管理系统)应用软件之一
2、从哪里开始?
SqlSessionFactoryBuilder
用于通过字节流|字符流构建
SqlSessionFactory
内部构建过程:
- 通过流获取相应的
XMLConfigBuilder
XMLConfigBuilder
解析返回Configuration
- 根据配置构建出
SqlSessionFactory
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。
SqlSessionFactory
通过一些配置去创建SqlSession,例如:autoCommit|Connection|TransactionIsolationLevel|ExecutorType
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
SqlSession
使用 MyBatis 的主要 Java 接口。通过此接口,您可以执行命令、获取映射器和管理事务
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
// 以下非全部方法,只是作为举例
public interface SqlSession extends Closeable {
<T> T selectOne(String statement, Object parameter);
<E> List<E> selectList(String statement, Object parameter);
<E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);
<T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);
void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
int insert(String statement, Object parameter);
int update(String statement, Object parameter);
int delete(String statement, Object parameter);
void commit(boolean force);
void rollback(boolean force);
<T> T getMapper(Class<T> type);
Connection getConnection();
}
绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession,如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。 下面的示例就是一个确保 SqlSession 关闭的标准模式:
try (SqlSession session = sqlSessionFactory.openSession()) {
// 你的应用逻辑代码
}
所有代码中都遵循这种使用模式,可以保证所有数据库资源都能被正确地关闭。
3、缓存
Cache
Cache:缓存的顶级接口,定义了缓存的增删改查、获取大小,id等行为
PerpetualCache:在MyBatis里面,它是一级缓存、二级缓存的最基本实现 (详细参见),一下是所有特性的缓存模式均在包:org.apache.ibatis.cache.decorators
下:
BlockingCache:读阻塞缓存,对应的key没有被缓存的话,将会阻塞所有需要获取该key数据(也可以设定超时时间)的线程,直到有其他线程在缓存中设置该key数据后,阻塞线程将继续执行后续
FifoCache:固定大小队列缓存,维护了一个队列,可以设置队列大小(默认1024个位置),如果新进来一个key值,使得缓存集合中长度大于设定的长度,则将根据先进先出(first in, first out)原则,删除最先进来的key-value键值对儿,此队列只与put方法超过长度时有关,删除缓存元素不影响队列中的元素
LoggingCache:记录缓存命中率日志缓存,获取缓存的命中率日志(命中缓存key的个数与请求缓存总数的比率)
LruCache:最近最少使用的缓存,内部维护了一个LinkedHashMap,通过removeEldestEntry方法找到最近最老的key进行删除
ScheduledCache:定时清空缓存,在增删改查入口做了校验,该校验用于检查定时时间是否超过设定预期时间,超过则清空缓存数据
SerializedCache:序列化缓存,将key对应的value值以对象流的形式存于缓存中
SoftCache:软引用缓存,每次操作都会对应的将原来已被垃圾收集器收集过的对象数据删除,put操作会put一个软引用数据缓存
SynchronizedCache:同步缓存,内部几乎所有方法均加锁,是一个线程安全的缓存
TransactionalCache:事务性缓存,内部有两个方法,commiit以及rollback,在没有commit之前将所有key-value键值对临时存于一个内部map中,commit之后,将临时map以及get的时候的key空数据存储与真正的缓存中
WeakCache:若引用缓存,没什么可解释的
TransactionalCacheManager
让每一种特性的缓存都实现了事务的功能
内部维护了一个map
private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
put是为传入的类型的缓存设置数据,在commit提交方法时,对每中特性缓存实例中的数据轮循做一次提交
public void putObject(Cache cache, CacheKey key, Object value) {
getTransactionalCache(cache).putObject(key, value);
}
private TransactionalCache getTransactionalCache(Cache cache) {
return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
}
public void commit() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.commit();
}
}
public void rollback() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.rollback();
}
}
CacheBuilder
构造缓存用的,内部维护了一个implementation
(默认是:PerpetualCache),decorators
(默认只有一个LruCache)
public class CacheBuilder {
private final String id;
private Class<? extends Cache> implementation; // 构造缓存时,基于该缓存实例构造的(将该缓存类型传入所有装饰者的构造函数中)
private final List<Class<? extends Cache>> decorators;// 缓存类型的集合
private Integer size;// 设置缓存大小(像fifo缓存以及Lru算法缓存需要根据size这个去判断是否需要删除元素)
private Long clearInterval; // 设置清理间隔(如果有定时清空类型的缓存时)
private boolean readWrite; // 暂时没有用到,Cache顶级类中有一个获取读写锁的方法,可能与这个有关
private Properties properties;// 根据这个属性去填充各个类型缓存的字段数据
private boolean blocking;// 是否阻塞(阻塞式缓存用到的)
public CacheBuilder(String id) {
this.id = id;
this.decorators = new ArrayList<>();
}
}
总结:这里Mybatis在缓存的设计上用了装饰器模式、抽象工厂模式