Mybatis源码阅读之一——工厂模式与SqlSessionFactory
文章目录
- 一. 启动demo
- 二. 项目结构
- 三. 工厂模式与SqlSessionFactory
- 工厂模式
- 简单工厂模式
- 工厂方法
- SqlSessionFactory的实现
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开发者知识点
Mybatis源码阅读之一——工厂模式与SqlSessionFactory相关推荐
- mybatis源码阅读(一):SqlSession和SqlSessionFactory
转载自 mybatis源码阅读(一):SqlSession和SqlSessionFactory 一.接口定义 听名字就知道这里使用了工厂方法模式,SqlSessionFactory负责创建SqlSe ...
- Glide源码阅读之工厂模式2【DiskCache.Factory】
参考阅读: Glide多种组合使用方式记录–没有全部亲测,大家可以根据实际需要选用 Glide设计模式之工厂模式1[ModelLoaderFactory] Glide设计模式之工厂模式2[DiskCa ...
- Mybatis源码阅读之二——模板方法模式与Executor
[系列目录] Mybatis源码阅读之一--工厂模式与SqlSessionFactory 文章目录 一. 模板方法模式 二. 同步回调与匿名函数 三. Executor BaseExecutor与其子 ...
- mybatis源码阅读(七) ---ResultSetHandler了解一下
转载自 mybatis源码阅读(七) ---ResultSetHandler了解一下 1.MetaObject MetaObject用于反射创建对象.反射从对象中获取属性值.反射给对象设置属性值,参 ...
- mybatis源码阅读(五) ---执行器Executor
转载自 mybatis源码阅读(五) ---执行器Executor 1. Executor接口设计与类结构图 public interface Executor {ResultHandler NO_ ...
- Mybatis源码阅读(一):Mybatis初始化1.1 解析properties、settings
*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如 ...
- mybatis源码阅读(八) ---Interceptor了解一下
转载自 mybatis源码阅读(八) ---Interceptor了解一下 1 Intercetor MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用.默认情况下,MyBatis允许 ...
- mybatis源码阅读(六) ---StatementHandler了解一下
转载自 mybatis源码阅读(六) ---StatementHandler了解一下 StatementHandler类结构图与接口设计 BaseStatementHandler:一个抽象类,只是实 ...
- mybatis源码阅读(四):mapper(dao)实例化
转载自 mybatis源码阅读(四):mapper(dao)实例化 在开始分析之前,先来了解一下这个模块中的核心组件之间的关系,如图: 1.MapperRegistry&MapperPro ...
- mybatis源码阅读(三):mybatis初始化(下)mapper解析
转载自 mybatis源码阅读(三):mybatis初始化(下)mapper解析 MyBatis 的真正强大在于它的映射语句,也是它的魔力所在.由于它的异常强大,映射器的 XML 文件就显得相对简单. ...
最新文章
- 这个神了,一目了然,确实好,看小姐姐用动图展示10大Git命令
- 开源医学图像数据集(资源整合)
- oracle返回表id,在Oracle的函数中,返回表类型的语句
- poi控制简单的word
- matlab信息隐藏算法,实验四--基于DCT域的信息隐藏算法
- java getstringarray_Java AnnotationAttributes.getStringArray方法代碼示例
- 机器学习之Fisher线性分类器实现样本分类
- RT-Thread使用ENV生成工程时自己添加的文件被清掉的解决方法
- opencv waitKey() 函数理解及应用
- matlab+awgn和wgn,噪聲強度(噪聲功率) 噪聲方差到底有什么關系? matlab中的awgn函數...
- wkhtmltopdf中文显示空白或者乱码方框
- 矩池云上使用nvidia-smi命令教程
- 使用PowerPoint
- C语言 分数加减运算
- 东海学计算机,田东海_北京理工大学计算机学院
- IBM面试题:海盗分金算法及其思想
- 《数据时代 2025》报告-2017年版
- TCP/IP层次安全性
- 2019.10.8 多校赛Day1【including 流量,个人练习生,假摔
- 热门软件中文在线文档
热门文章
- 不要老盯着存储,存储的价值在于数据流:Filenet
- ffmpeg bt709 to bt601
- scanner读取带空格字符串_Scanner类提供了输入字符出的方法,下面哪个方法可以实现字符串的输入且该串可以含有空格()。-智慧树JAVA程序设计(山东联盟-山东农业大学)章节答案...
- Web前端开发工程师需要掌握哪些核心技能?
- 环境土壤物理模型HYDRUS1D/2D/3D实践技术
- 测试工具apipost postman jmeter
- 横向对比22款思维导图工具,最好用的我觉得是这款!
- 一文读懂电子罗盘的原理、校准和应用
- adc0808模数转换实验报告_ADC0808模数转换显示 单片机程序
- leach算法的实现过程_LEACH算法源代码