java代码的行数 九万九千行。
一. 启动demo
现在我们做web开发一般都是springboot, 故而我们印象里面mybatis的配置一般都是在application.yml(/properties)里面去配置。
但是我们既然分析mybatis,那就抛开spring,抛开mybatis-spring,对原汁原味的mybatis进行观赏。
下面是一段不依赖spring等注入框架的原生mybatis使用代码:

- 第一步,加载myabtis配置文件,配置包括必须的数据库连接,mapper文件等信息

- 第二步,通过配置文件生成一个SqlSessionFactory实例,该实例遵循工厂模式(
工厂模式解释见下方)实现,用于生产SqlSession(即,一次次的mysql会话)。 - 第三步,开启一个SqlSession会话,用于与DB进行信息交互。
- 第四步,获取Mapper实例,这里的BlogMapper就是我们业务逻辑自定义的Mapper。
- 第五步,调用Mapper的业务方法,并进行相关处理。
二. 项目结构

mybatis下的包不少,我们简单的分出两个层次。另外一提,test包下的单测真不少,值得学习。
- 基础(业务无关)
| 包 | 说明 |
|---|---|
| exceptions | 异常管理 |
| io | 对资源文件/类加载器的管理, 这里使用了模板方法模式 |
| lang | 包下只有UsesJava7/UsesJava8两个 注解这里的意图应该是单测使用的分 类,想要深追的朋友可以看一下坐着曾 经的pr,链接见表格下[1] |
| logging | 日志文件包,兼容了众多三方日志框架 |
| reflection | 诸多反射相关的实现类 |
| transaction | 事务相关 |
| util | 只有一个MapUtil,值得一提,这里标注了 ConcurrentMap的一个Bug,可以从下面[2]了解 |
[1] : https://github.com/mybatis/mybatis-3/commit/e0682f83820394f4bc2af8c8fd9077f08230aff8#diff-540c3276892273655547976ae0fd3edc4887d0146d413fc5bdb297ab21df7073
[2] : https://bugs.openjdk.java.net/browse/JDK-8161372
- 业务包
| 包 | 说明 |
|---|---|
| annotations | 相关注解 |
| binding | Mapper与xml绑定相关逻辑 |
| builder | xml/注解里面各种元素的解析器的builder |
| cache | 一/二级缓存实现,使用装饰器模式 |
| cursor | sql游标相关 |
| datasource | 数据源相关 |
| executor | 执行器相关,包含对statement,sql入参/返回值等的处理 |
| jdbc | Sql语句构建器,官方说明[1] |
| mapping | 主要解决出入参的映射 |
| parsing | 处理Xpath以及sql占位符 |
| plugin | 开放plugin接口,支持用户自定义插件 |
| scripting | 对sql拼接的处理以及xml中sql相关标签的处理 |
| session | 会话及会话工厂 |
| type | 别名机制以及一些默认的TypeHandler |
[1] : https://mybatis.org/mybatis-3/zh/statement-builders.html
三. 工厂模式与SqlSessionFactory
工厂模式
工厂模式属于三种设计模式类型中的创建型设计模式,除了包括我们常见的简单工厂模式,还有工厂方法模式。
简单工厂模式
很多时候,我们的一个一些实体可能同属于一个接口(类型),比如采购订单(POrder),销售订单(SOrder),它们都属于Order接口的子类。
那么当我们想要在逻辑中区分类型创建订单实例时,会是这样的代码:
public class Business{
public void dealOrder(String type) {
Order order = null;
if("sale".equals(type)){
order = new SOrder();
order.init();
}else if("purchase".equals(type)){
order = new POrder();
order.initP();
}else {
// throw
}
order.deal();
}
}
如上代码是处于业务逻辑中的,这样写的问题:
问题一:是不符合开闭原则,每次增加新的订单类型,业务逻辑都需要感知。
问题二:是臃肿,各自订单初始化所需要的操作不应该体现在业务逻辑中,尤其是当这段逻辑比较复杂时。
针对这二个问题,我们使用简单工厂模式进行改造:
public class OrderFactory{
public static void newInstance(String type) {
Order order = null;
if("sale".equals(type)){
order = new SOrder();
order.init();
order.doElse();
}else if("purchase".equals(type)){
String name = "p001";
order = new POrder(name);
order.init2();
order.doElse();
order.doElse3();
order.doElse4();
}else {
// throw
}
return order;
}
}
public class Business{
public void dealOrder(String type) {
Order order= OrderFactory.newInstance(type);
order.deal();
}
}
看上去好像没什么变化,其实不然,订单的初始化从业务逻辑中解耦,业务系统中需要使用订单的地方只需要从工厂获取,不需要再处处都自己实例化不同类型的订单。
这样看上去似乎不错,但是对于一些场景可能并不合适,比如在上述代码中,order.init()这一步骤如果很繁琐很臃肿,需要十行(十行这个标准是个人审美)以上的逻辑时,建议最好使用工厂方法进行拆分,避免newInstance方法的臃肿。
工厂方法
工厂方法与简单工厂不同,它针对不同类型的订单有对应不同的工厂类,即想要什么类型的订单,就用什么类型的工厂生成。
public interface OrderFactory{
void newInstance();
}
public class SOrderFactory{
public static void newInstance() {
Order order = new SOrder();
order.init();
order.doElse();
return order;
}
}
public class POrderFactory{
public static void newInstance() {
String name = "p001";
order = new POrder(name);
order.init2();
order.doElse();
order.doElse3();
order.doElse4();
return order;
}
}
public class Business{
public void dealOrder(String type) {
OrderFactory orderFactory = null;
if("sale".equals(type)){
orderFactory = new SOrderFactory();
}else if("purchase".equals(type)){
orderFactory = new POrderFactory();
}else {
// throw
}
orderFactory.newInstance().deal();
}
}
大家看到这里可能会有疑惑,这多加了几个工厂实现类,好像和最开始差不多了?
其实不然,工厂方法模式解决的是不同类型实例化(+初始化时)if else中臃肿的问题,这里让我们强调一个code smell:
if else不一定就是ugly的,臃肿的if else才是ugly的,易读性很差的,如果一个if之后是很长的一段逻辑,往往会导致我们读完了这段逻辑时已经忘记这个地方是通过什么条件if进来的。
针对于这种case,我们才需要使用工厂方法这样的方式来优化,反之,if else里面比较干净的话,直接简单工厂模式其实更简洁一些。
如果有强需求去掉if else的,其实可以使用类似策略模式的做法,定义一个全局Map来保存type与对应工厂类实例的对应关系,当type到来,直接从Map中取出对应工厂实例进行生产操作。
SqlSessionFactory的实现
看一下相关类图关系,可以看到,SqlSessionFactory其实有两个实现类,这里就是一个典型的工厂方法模式,而这里两个实现类给自的实现逻辑都比较繁琐,是比较适合用这种模式的。

工厂的实现类有两个:SqlSessionManager与DefaultSqlSessionFactory
会话的实现也有两个:SqlSessionManager与DefaultSqlSession。
-
SqlSession
见下图可知,它的作用就是在一次具体的DB会话连接中,进行CRUD以及事务相关的各种操作。

-
DefaultSqlSession
DefaultSqlSession如何与数据库进行命令发送不在这一层级实现,还需要向下去找Executor(执行器),执行器与configuartion之后再解析。
(管中窥豹,从select函数实现看一下)

-
SqlSessionFactory
这个接口应该没有疑问,获取SqlSession实例。

-
DefaultSqlSessionFactory
默认工厂类实例化SqlSession的方式,大致是通过configuration实例化Executor,进而获取一个SqlSession。

-
SqlSessionManager
SqlSessionManager的实现比较令人困惑,为什么它既实现了工厂接口,又实现了会话接口呢?
实际上它的功能,正如它的名字,它是一个管理器,可以让用户在不接触SqlSessionFactory与SqlSession这两个概念的情况下使用,进行概念上的解耦。
我们来看一下它的使用就清晰了。

第一步,创建一个SqlSessionManager。
第二步,使用manager开启一个会话,即使用内部的工厂创建一个SqlSession并放入threadlocal中(ThreadLocal和JDK动态代理相关知识点不在本章范围,本次不再展开) 。
第三步,使用manager执行select方法,即使用之前创建的threadlocal中的sqlSession进行select操作。
这里还有个细节,第三步中,select方法使用了一个sqlSessionProxy,这里是一个动态代理对象。
主要的代理类逻辑在SqlSessionInterceptor逻辑在中,如下所示,逻辑和我们之前讲的一致,是从threadlocal中取出当前线程所拥有的SqlSession实例。

总结,SqlSessionManager与DefaultSqlSessionFactory相比,主要额外做了一件事——实现SqlSession的复用管理及线程安全。
实现的原理是SqlSessionManager管理了两个成员变量,一个是工厂,一个是会话的代理,工厂用来生成会话,会话代理用来获取threadlocal中的具体会话对象。
欢迎关注微信公众号 【JAVA技术分享官】,公众号首发,持续输出原创高质量JAVA开发者知识点
351

被折叠的 条评论
为什么被折叠?



