系列文章:
文章 | 状态 | 时间 | 描述 |
---|---|---|---|
(一)Mybatis 基本使用 | 已复习 | 2022-12-14 | 对Mybtais的基本使用,能够开发 |
(二)Mybatis-config.xml的初始化 | 已复习 | 2023-02-10 | 对我们编写的mybatis配置文件的解析 |
(三)SqlSessionFactory的初始化 | 已复习 | 2023-02-11 | SqlSession会话工厂的初始化 |
(四)Mapper文件的解析 | 已复习 | 2023-02-12 | 主要对我们编写的Mapper.xml进行解析 |
(五)SqlSession的创建 | 已复习 | 2023-02-13 | 主要介绍构建DefaultSqlSessionFactory |
(六)Mapper的接口代理 | 已复习 | 2023-02-14 | 如何通过动态代理来执行我们编写的方法 |
(七)MapperMethod的INSERT分析 | 已复习 | 2023-02-15 | 通过代理对象来执行Insert语句,返回结果 |
(八)MapperMethod的Select分析 | 已复习 | 2023-02-16 | 通过代理对象来执行Select语句,返回结果 |
(九)Mybatis的PreparedStatement | 已复习 | 2023-02-17 | 预处理语句的常见,以及与数据库打交道 |
(十)Mybatis的结果隐射 | 已复习 | 2023-02-18 | 数据库结果与实体类对象的转换 |
(十一)Mybatis的一级缓存与二级缓存 | 已复习 | 2023-02-24 | Mybatis中一级缓存与二级缓存 |
(十二)Mybatis的插件开发及原理分析 | 已复习 | 2023-02-25 | Mybatis中的插件运行机制与开发 |
(十三)Mybatis中的四大组件梳理 | 计划中 | – | Mybatis中的四大组件的梳理 |
(十四)Mybatis中的设计模式梳理 | 计划中 | – | Mybatis中设计模式的整理 |
(十五)Spring-Mybatis整理 | 计划中 | – | Spring与Mybatis整合 |
(十六)Mybatis疑惑总结 | 计划中 | – | 我遇到的疑惑与问题 |
设计模式参考网站:设计模式目录:22种设计模式:
前面介绍了Mybatis的基本知识,我们通过源码可以发现,优秀的框架少不了设计模式的运用,下面我们来梳理一下Mybatis的设计模式
工厂方法模式是一种创建型设计模式, 其在父类中提供一个创建对象的方法, 允许子类决定实例化对象的类型。
简单来说,抽离出共同特性,创造出不同实现,我们我们以一个案例来说明,我们现在有一个家具工厂,但是需要生成出不同风格的家具,但是生产的标准是不会变动的,下面我们来看看这个案例
/**
* @description: 抽象工厂方法
* @author: shu
* @createDate: 2022/9/10 12:13
* @version: 1.0
*/
public interface FurnitureFactory {
/**
* 生成椅子
* @return
*/
Chair creatChair();
/**
* 生成办公桌
* @return
*/
CoffeeTable creatCoffeeTable();
/**
* 生成沙发
* @return
*/
Sofa creatSofa();
}
现代风格
/**
* @description: 现代风格
* @author: shu
* @createDate: 2022/9/10 12:25
* @version: 1.0
*/
public class ModernFurnitureFactory implements FurnitureFactory{
/**
* 生成椅子
*
* @return
*/
@Override
public Chair creatChair() {
return new Chair(180,150,"现代风格");
}
/**
* 生成办公桌
*
* @return
*/
@Override
public CoffeeTable creatCoffeeTable() {
return new CoffeeTable(180,150,"现代风格");
}
/**
* 生成沙发
*
* @return
*/
@Override
public Sofa creatSofa() {
return new Sofa(180,150,"现代风格");
}
}
维多利亚 风格
**
* @description: 维多利亚
* @author: shu
* @createDate: 2022/9/10 12:23
* @version: 1.0
*/
public class VictorianFurnitureFactory implements FurnitureFactory{
/**
* 生成椅子
*
* @return
*/
@Override
public Chair creatChair() {
return new Chair(180,150,"维多利亚");
}
/**
* 生成办公桌
*
* @return
*/
@Override
public CoffeeTable creatCoffeeTable() {
return new CoffeeTable(180,150,"维多利亚");
}
/**
* 生成沙发
*
* @return
*/
@Override
public Sofa creatSofa() {
return new Sofa(180,150,"维多利亚");
}
}
我们可以看到他有不同风格的实现,两种工厂互不干扰,互不影响,在实际开发中会有很多这样的情况,统一返回接口是一样的,但是不同类型的事务的具体处理过程是不一样的,这时我们就可以用工厂模式来简化开发,下面我们来看看Mybatis的工厂模式
首先让我们来看看我们的测试类
@Test
public void SelectById(){
// 第一阶段:MyBatis的初始化阶段
String resource = "mybatis-config.xml";
// 得到配置文件的输入流
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
// 得到SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 第二阶段:数据读写阶段
try (SqlSession session = sqlSessionFactory.openSession()) {
// 找到接口对应的实现
UserMapper userMapper = session.getMapper(UserMapper.class);
// 调用接口展开数据库操作
User user = userMapper.queryById(1);
System.out.println(user);
}
}
我们重点来关注下SqlSessionFactory 的创建,前面我们详细的讲过他的源码,这里我们只是重点针对设计模式进行分析
首先我们来看看接口方法
我们可以发现接口的返回结果都是一致的,但是传入的参数不同,我们再来看看他是决定如何用哪一个实现类的
DefaultSqlSessionFactory
/**
* 从数据源中获取SqlSession对象
* @param execType 执行器类型
* @param level 事务隔离级别
* @param autoCommit 是否自动提交事务
* @return SqlSession对象
*/
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 找出要使用的指定环境
final Environment environment = configuration.getEnvironment();
// 从环境中获取事务工厂
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 从事务工厂中生产事务
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 创建执行器
final Executor executor = configuration.newExecutor(tx, execType);
// 创建DefaultSqlSession对象
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
该⽅法先从configuration读取对应的环境配置,然后初始化TransactionFactory 获得⼀个 Transaction 对象,然后通过 Transaction 获取⼀个 Executor 对象,最后通过configuration、Executor、是否autoCommit三个参数构建了 SqlSession,Mybatis还有很多地方也使用了M工厂模式,暂不分析了。
单例模式是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。
单例模式(Singleton)是一种非常简单且容易理解的设计模式。顾名思义,单例即单一的实例,确切地讲就是指在某个系统中只存在一个实例,同时提供集中、统一的访问接口,以使系统行为保持协调一致。singleton一词在逻辑学中指“有且仅有一个元素的集合”,这非常恰当地概括了单例的概念,也就是“一个类仅有一个实例”。但是单例模式的实现有很多种,这里我就不一一介绍了,我们来介绍一下懒汉式单例模式。
全世界只有一个太阳?
package com.shu;
/**
* @description: 太阳类,世界上只有一个太阳,既然太阳系里只有一个太阳,我们就需要严格把控太阳实例化的过程。
* @author: shu
* @createDate: 2022/9/8 16:06
* @version: 1.0
*/
public class Sun {
// static关键字确保太阳的静态性
private static Sun sun;
// 调用该方法进行类的初始化,
private static Sun getInstance(){
if(sun==null){
sun=new Sun();
}
return sun;
}
}
在Mybatis中有两个地方用到单例模式,ErrorContext和LogFactory,其中ErrorContext是用在每个线程范围内的单例,用于记录该线程的执行环境错误信息,而LogFactory则是提供给整个Mybatis使用的日志工厂,用于获得针对项目配置好的日志对象。
ErrorContext
public class ErrorContext {
// 获得当前操作系统的换行符
private static final String LINE_SEPARATOR = System.getProperty("line.separator","\n");
// 将自身存储进ThreadLocal,从而进行线程间的隔离
private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<>();
// 存储上一版本的自身,从而组成错误链
private ErrorContext stored;
// 下面几条为错误的详细信息,可以写入一项或者多项
private String resource;
private String activity;
private String object;
private String message;
private String sql;
private Throwable cause;
private ErrorContext() {
}
/**
* 从ThreadLocal取出已经实例化的ErrorContext,或者实例化一个ErrorContext放入ThreadLocal
* @return ErrorContext实例
*/
public static ErrorContext instance() {
ErrorContext context = LOCAL.get();
if (context == null) {
context = new ErrorContext();
LOCAL.set(context);
}
return context;
}
/**
* 创建一个包装了原有ErrorContext的新ErrorContext
* @return 新的ErrorContext
*/
public ErrorContext store() {
ErrorContext newContext = new ErrorContext();
newContext.stored = this;
LOCAL.set(newContext);
return LOCAL.get();
}
/**
* 剥离出当前ErrorContext的内部ErrorContext
* @return 剥离出的ErrorContext对象
*/
public ErrorContext recall() {
if (stored != null) {
LOCAL.set(stored);
stored = null;
}
return LOCAL.get();
}
}
ErrorContext在Mybatis的很多地方都有使用,使Mybatis问题的定位十分容易,这也主要取决于ErrorContext的成员变量
记住重要的一点,为其他对象提供一个代理以控制对某个对象的访问。代理类主要负责为委托了(真实对象)预处理消息、过滤消息、传递消息给委托类,代理类不现实具体服务,而是利用委托类来完成服务,并将执行结果封装处理。
在我们的现实生活中其实有很多这样的案例,不如商品经销商与厂家的关系,出租房的人与中介。
package com.shu.agency;
/**
* @author shu
* @version 1.0
* @date 2022/7/20 16:00
* @describe 代理类和委托代理类都要实现的接口
*/
public interface Sell {
/**
* 销售产品
*/
void market();
/**
* 生成产品
*/
void add();
}
package com.shu.agency;
/**
* @author shu
* @version 1.0
* @date 2022/7/20 16:03
* @describe 生产厂家
*/
public class Production implements Sell{
@Override
public void market() {
System.out.println("生产厂家销售产品了哦!!!");
}
@Override
public void add() {
System.out.println("生产厂家销售产品了哦!!!");
}
}
package com.shu.agency;
/**
* @author shu
* @version 1.0
* @date 2022/7/20 16:05
* @describe 商家代销产品
*/
public class Merchant implements Sell{
Sell production;
public Merchant(Production production) {
this.production = production;
}
@Override
public void market() {
production.market();
}
@Override
public void add() {
production.add();
}
}
/**
* 调用处理程序
*/
public interface InvocationHandler {
Object invoke(Object proxy, Method method, Object[] args);
}
定义中介类
package com.shu.agency;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @author shu
* @version 1.0
* @date 2022/7/20 19:33
* @describe
*/
public class DynamicProxy implements InvocationHandler {
//obj为委托类对象;
private Object obj;
public DynamicProxy(Object obj) {
this.obj = obj;
}
/**
* 通过反射来执行真正的方法
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(obj, args);
return result;
}
}
测试
package com.shu.agency;
import java.lang.reflect.Proxy;
/**
* @author shu
* @version 1.0
* @date 2022/7/20 19:35
* @describe
*/
public class ProxyTest {
public static void main(String[] args) {
//创建中介类实例
DynamicProxy inter = new DynamicProxy(new Production());
//加上这句将会产生一个$Proxy0.class文件,这个文件即为动态生成的代理类文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
//获取代理类实例sell
Sell sell = (Sell)(Proxy.newProxyInstance(Sell.class.getClassLoader(), new Class[] {Sell.class}, inter));
sell.market();
sell.add();
}
}
让我们来看看Mybatis中的动态代理模式吧,一个很显著的地方接口方法的隐射,我们编写的接口方法Mybatis是如何调用的
public <T> void addMapper(Class<T> type) {
// 要加入的肯定是接口,否则不添加
if (type.isInterface()) {
// 加入的是接口
if (hasMapper(type)) {
// 如果添加重复
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
MapperProxyFactory
public class MapperProxyFactory<T> {
// 对应SQL的java接口类
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
/**
* MapperProxyFactory构造方法
* @param mapperInterface 映射接口
*/
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
// 三个参数分别是:
// 创建代理对象的类加载器、要代理的接口、代理类的处理器(即具体的实现)。
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
MapperRegistry
/**
* 找到指定映射接口的映射文件,并根据映射文件信息为该映射接口生成一个代理实现
* @param type 映射接口
* @param sqlSession sqlSession
* @param 映射接口类型
* @return 代理实现对象
*/
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 找出指定映射接口的代理工厂
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 通过mapperProxyFactory给出对应代理器的实例
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
MapperProxy
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
// 该Map的键为方法,值为MapperMethod对象。通过该属性,完成了MapperProxy内(即映射接口内)方法和MapperMethod的绑定
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 继承自Object的方法
if (Object.class.equals(method.getDeclaringClass())) {
// 直接执行原有方法
return method.invoke(this, args);
} else if (method.isDefault()) { // 默认方法
// 执行默认方法
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 找对对应的MapperMethod对象
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 调用MapperMethod中的execute方法
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
throws Throwable {
final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
.getDeclaredConstructor(Class.class, int.class);
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
final Class<?> declaringClass = method.getDeclaringClass();
return constructor
.newInstance(declaringClass,
MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
.unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
}
}
通过这种方式,我们只需要编写Mapper.java接口类,当真正执行一个Mapper接口的时候,就会转发给MapperProxy.invoke方法,而该方法则会调用后续的sqlSession.cud>executor.execute>prepareStatement等一系列方法,完成SQL的执行和返回,注意动态代理模式在Mybatis中有很多运行,我就不一一介绍了,比如:PooledConnection,Plugin等等
package com.shu;
/**
* @description: 建筑
* @author: shu
* @createDate: 2022/9/10 14:20
* @version: 1.0
*/
import java.util.ArrayList;
import java.util.List;
public class Building {
// 用List来模拟建筑物组件的组装
private List<String> buildingComponents = new ArrayList<>();
/**
* 地皮
* @param basement
*/
public void setBasement(String basement) {// 地基
this.buildingComponents.add(basement);
}
/**
* 墙
* @param wall
*/
public void setWall(String wall) {// 墙体
this.buildingComponents.add(wall);
}
/**
* 吊顶
* @param roof
*/
public void setRoof(String roof) {// 屋顶
this.buildingComponents.add(roof);
}
@Override
public String toString() {
String buildingStr = "";
for (int i = buildingComponents.size() - 1; i >= 0; i--) {
buildingStr += buildingComponents.get(i);
}
return buildingStr;
}
}
组建专业的建筑施工团队对建筑工程项目的实施至关重要,于是地产开发商决定通过招标的方式来选择施工方。招标大会上有很多建筑公司来投标,他们各有各的房屋建造资质,有的能建别墅,有的能建多层公寓,还有能力更强的能建摩天大楼,建造工艺也各有区别。但无论如何,开发商规定施工方都应该至少具备三大组件的建造能力,于是施工标准公布出来了。
package com.shu;
/**
* @description: 建造标准
* @author: shu
* @createDate: 2022/9/10 14:22
* @version: 1.0
*/
public interface Builder {
/**
* 建筑地基
*/
void buildBasement();
/**
* 建筑墙体
*/
void buildWall();
/**
* 建筑屋顶
*/
void buildRoof();
/**
* 得到最后的建筑
* @return
*/
Building getBuilding();
}
A公司
package com.shu;
/**
* @description: A施工队
* @author: shu
* @createDate: 2022/9/10 14:26
* @version: 1.0
*/
public class HouseBuilder implements Builder {
private Building house;
public HouseBuilder() {
this.house = new Building();
}
/**
* 建筑地基
*/
@Override
public void buildBasement() {
System.out.println("挖土方,部署管道、线缆,水泥加固,搭建围墙、花园。");
house.setBasement("╬╬╬╬╬╬╬╬\n");
}
/**
* 建筑墙体
*/
@Override
public void buildWall() {
System.out.println("搭建木质框架,石膏板封墙并粉饰内外墙。");
house.setWall("|田|田 田|\n");
}
/**
* 建筑屋顶
*/
@Override
public void buildRoof() {
System.out.println("建造木质屋顶、阁楼,安装烟囱,做好防水。");
house.setRoof("╱◥███◣\n");
}
/**
* 得到最后的建筑
*
* @return
*/
@Override
public Building getBuilding() {
return house;
}
}
B公司
package com.shu;
/**
* @description:
* @author: shu
* @createDate: 2022/9/10 14:30
* @version: 1.0
*/
public class ApartmentBuilder implements Builder {
private Building apartment;
public ApartmentBuilder() {
this.apartment = new Building();
}
/**
* 建筑地基
*/
@Override
public void buildBasement() {
System.out.println("深挖地基,修建地下车库,部署管道、线缆、风道。");
apartment.setBasement("╚═════════╝\n");
}
/**
* 建筑墙体
*/
@Override
public void buildWall() {
System.out.println("搭建多层建筑框架,建造电梯井,钢筋混凝土浇灌。");
for (int i = 0; i < 8; i++) {// 此处假设固定8层
apartment.setWall("║ □ □ □ □ ║\n");
}
}
/**
* 建筑屋顶
*/
@Override
public void buildRoof() {
System.out.println("封顶,部署通风井,做防水层,保温层。");
apartment.setRoof("╔═════════╗\n");
}
/**
* 得到最后的建筑
*
* @return
*/
@Override
public Building getBuilding() {
return apartment;
}
}
package com.shu;
/**
* @description: 监工
* @author: shu
* @createDate: 2022/9/10 14:32
* @version: 1.0
*/
public class Director {
public Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
public void setBuilder(Builder builder) {
this.builder = builder;
}
/**
* 监工规定施工步骤,工程总监并不关心是哪个施工方来造房子,更不关心施工方有什么样的建造工艺,
* 但他能保证对施工工序的绝对把控,也就是说,工程总监只控制施工流程。
* @return
*/
public Building direct(){
builder.buildBasement();
builder.buildWall();
builder.buildRoof();
return builder.getBuilding();
}
}
测试
package com.shu;
/**
* @description:
* @author: shu
* @createDate: 2022/9/10 14:36
* @version: 1.0
*/
public class Client {
public static void main(String[] args) {
Director director = new Director(new HouseBuilder());
Building direct = director.direct();
System.out.println(direct);
System.out.println("----------------------------------------");
Director director1 = new Director(new ApartmentBuilder());
Building direct1 = director1.direct();
System.out.println(direct1);
}
}
在Mybtais中建造者模式的实现主要集中于在对配置文件进行解析的时候,比如:
SqlSessionFactoryBuilder,MappedStatement,等等,下面我们以SqlSessionFactoryBuilder来进行分析
SqlSessionFactoryBuilder即根据不同的输入参数来构建SqlSessionFactory这个工厂对象。
人们总是惊叹女生们魔法师一般的化妆技巧,可以从素面朝天变成花容月貌(如图9-2所示),化妆前后简直判若两人,这正是装饰器的粉饰效果在发挥作用。
化妆接口
package com.shu;
/**
* @description:
* @author: shu
* @createDate: 2022/9/11 11:46
* @version: 1.0
*/
public interface Showable {
// 展示
public void show();
}
素颜
package com.shu;
/**
* @description:
* @author: shu
* @createDate: 2022/9/11 11:47
* @version: 1.0
*/
public class Girl implements Showable{
@Override
public void show() {
System.out.println("女生的素颜");
}
}
抽象类
package com.shu;
/**
* @description:
* @author: shu
* @createDate: 2022/9/11 11:48
* @version: 1.0
*/
public abstract class Decorator implements Showable{
private Showable showable;
public Decorator(Showable showable) {
this.showable = showable;
}
@Override
public void show() {
showable.show();
}
}
粉底
package com.shu;
/**
* @description: 粉底
* @author: shu
* @createDate: 2022/9/11 12:06
* @version: 1.0
*/
public class FoundationMakeup extends Decorator {
public FoundationMakeup(Showable showable) {
super(showable);
}
@Override
public void show() {
System.out.println("打粉底了");
super.show();
}
}
口红
package com.shu;
/**
* @description:
* @author: shu
* @createDate: 2022/9/11 12:07
* @version: 1.0
*/
public class Lipstick extends Decorator{
public Lipstick(Showable showable) {
super(showable);
}
@Override
public void show() {
System.out.println("涂口红");
super.show();
}
}
测试
package com.shu;
/**
* @description:
* @author: shu
* @createDate: 2022/9/11 11:49
* @version: 1.0
*/
public class Client {
public static void main(String[] args) {
new Lipstick(new FoundationMakeup(new Girl())).show();
}
}
我们来看看执行器的装饰器模式
CachingExecutor使用了装饰器模式来给具体的Executor添加缓存功能,他实现了Executor接口,同时内部持有某一个Executor的具体实现类(4.3中的某一个),在主流程方法的执行过程中添加缓存相关的读取流程。CachingExecutor再实现的缓存是二级缓存,因此在整个Mybatis的查询过程中,是先查询二级缓存的再查询一级缓存的,因为二级缓存是通过这个装饰器类实现的,那么会在真正的Executor查询之前访问二级缓存,如果没有命中,那么就会会真正的走Executor的查询流程(比如SimpleExecutor的),在SimpleExecutor里面才会再去查询一级缓存(流程是在BaseExecutor中实现的)。下面是CachingExecutor代码,具体的过程可以参考前面的一级缓存与二级缓存
CachingExecutor
/**
* 更新数据库数据,INSERT/UPDATE/DELETE三种操作都会调用该方法
* @param ms 映射语句
* @param parameterObject 参数对象
* @return 数据库操作结果
* @throws SQLException
*/
@Override
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
// 根据要求判断语句执行前是否要清除二级缓存,如果需要,清除二级缓存
flushCacheIfRequired(ms);
return delegate.update(ms, parameterObject);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
@Override
public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
flushCacheIfRequired(ms);
return delegate.queryCursor(ms, parameter, rowBounds);
}
/**
* 查询数据库中的数据
* @param ms 映射语句
* @param parameterObject 参数对象
* @param rowBounds 翻页限制条件
* @param resultHandler 结果处理器
* @param key 缓存的键
* @param boundSql 查询语句
* @param 结果类型
* @return 结果列表
* @throws SQLException
*/
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// 获取MappedStatement对应的缓存,可能的结果有:该命名空间的缓存、共享的其它命名空间的缓存、无缓存
Cache cache = ms.getCache();
// 如果映射文件未设置或则,此处cache变量为null
if (cache != null) { // 存在缓存
// 根据要求判断语句执行前是否要清除二级缓存,如果需要,清除二级缓存
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) { // 该语句使用缓存且没有输出结果处理器
// 二级缓存不支持含有输出参数的CALLABLE语句,故在这里进行判断
ensureNoOutParams(ms, boundSql);
// 从缓存中读取结果
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) { // 缓存中没有结果
// 交给被包装的执行器执行
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);
}
在我们Mybatis中组合模式的运用,主要在针对动态Sql的SqlNode解析
处理动态Sql节点信息
DynamicSqlSource
/**
* 获取一个BoundSql对象
* @param parameterObject 参数对象
* @return BoundSql对象
*/
@Override
public BoundSql getBoundSql(Object parameterObject) {
// 创建DynamicSqlSource的辅助类,用来记录DynamicSqlSource解析出来的
// * SQL片段信息
// * 参数信息
DynamicContext context = new DynamicContext(configuration, parameterObject);
// 这里会逐层(对于mix的node而言)调用apply。最终不同的节点会调用到不同的apply,完成各自的解析
// 解析完成的东西拼接到DynamicContext中,里面含有#{}
// 在这里,动态节点和${}都被替换掉了。
rootSqlNode.apply(context);
// 处理占位符、汇总参数信息
// RawSqlSource也会焦勇这一步
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
// 使用SqlSourceBuilder处理#{},将其转化为?
// 相关参数放进了context.bindings
// *** 最终生成了StaticSqlSource对象,然后由它生成BoundSql
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
// 把context.getBindings()的参数放到boundSql的metaParameters中进行保存
context.getBindings().forEach(boundSql::setAdditionalParameter);
return boundSql;
}
rootSqlNode.apply(context),方法通过组合模式来解析动态Sql
/**
* @author Clinton Begin
* 在我们写动态的SQL语句时, 这些就是sqlNode
*/
public interface SqlNode {
/**
* 完成该节点自身的解析
* @param context 上下文环境,节点自身的解析结果将合并到该上下文环境中
* @return 解析是否成功
*/
boolean apply(DynamicContext context);
}
当然Mybatis中还有其他设计模式,后面有时间在再补充