mybatis学习第⼗⼀部分:mybatis使用到的设计模式
虽然我们都知道有3类23种设计模式,但是⼤多停留在概念层⾯,Mybatis源码中使⽤了⼤量的设计模
式,观察设计模式在其中的应⽤,能够更深⼊的理解设计模式
Mybatis⾄少⽤到了以下的设计模式的使⽤:
模式 |
mybatis 体现 |
Builder 模式 |
例如SqlSessionFactoryBuilder、Environment; |
⼯⼚⽅ 法模式 |
例如SqlSessionFactory、TransactionFactory、LogFactory |
单例模 式 |
例如 ErrorContext 和 LogFactory; |
代理模 式 |
Mybatis实现的核⼼,⽐如MapperProxy、ConnectionLogger,⽤的jdk的动态代理 还有executor.loader包使⽤了 cglib或者javassist达到延迟加载的效果 |
组合模 式 |
例如SqlNode和各个⼦类ChooseSqlNode等; |
模板⽅ 法模式 |
例如 BaseExecutor 和 SimpleExecutor,还有 BaseTypeHandler 和所有的⼦类例如 IntegerTypeHandler; |
适配器 模式 |
例如Log的Mybatis接⼝和它对jdbc、log4j等各种⽇志框架的适配实现; |
装饰者 模式 |
例如Cache包中的cache.decorators⼦包中等各个装饰者的实现; |
迭代器 模式 |
例如迭代器模式PropertyTokenizer; |
接下来对Builder构建者模式、⼯⼚模式、代理模式进⾏解读,先介绍模式⾃身的知识,然后解读在
Mybatis中怎样应⽤了该模式。
11.1 Builder构建者模式
Builder模式的定义是"将⼀个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表 示。”,它属于创建类模式,⼀般来说,如果⼀个对象的构建⽐较复杂,超出了构造函数所能包含的范 围,就可以使⽤⼯⼚模式和Builder模式,相对于⼯⼚模式会产出⼀个完整的产品,Builder应⽤于更加 复杂的对象的构建,甚⾄只会构建产品的⼀个部分,直⽩来说,就是使⽤多个简单的对象⼀步⼀步构建 成⼀个复杂的对象
例⼦:使⽤构建者设计模式来⽣产computer 主要步骤:
1、将需要构建的⽬标类分成多个部件(电脑可以分为主机、显示器、键盘、⾳箱等部件);
2、 创建构建类;
3、 依次创建部件;
4、 将部件组装成⽬标对象
1. 定义computer
package com.lagou.dao; import org.apache.ibatis.binding.BindingException; import org.apache.ibatis.session.SqlSession; import java.util.Optional; public class Computer { private String displayer; private String mainUnit; private String mouse; private String keyboard; public String getDisplayer() { return displayer; } public void setDisplayer(String displayer) { this.displayer = displayer; } public String getMainUnit() { return mainUnit; } public void setMainUnit(String mainUnit) { this.mainUnit = mainUnit; } public String getMouse() { return mouse; } public void setMouse(String mouse) { this.mouse = mouse; } public String getKeyboard() { return keyboard; } public void setKeyboard(String keyboard) { this.keyboard = keyboard; } @Override public String toString() { return "Computer{" + "displayer='" + displayer + '\'' + ", mainUnit='" + mainUnit + '\'' + ", mouse='" + mouse + '\'' + ", keyboard='" + keyboard + '\'' + '}'; }
ComputerBuilder
public static class ComputerBuilder { private ComputerBuilder target = new ComputerBuilder(); public Builder installDisplayer(String displayer) { target.setDisplayer(displayer); return this; } public Builder installMainUnit(String mainUnit) { target.setMainUnit(mainUnit); return this; } public Builder installMouse(String mouse) { target.setMouse(mouse); return this; } public Builder installKeybord(String keyboard) { target.setKeyboard(keyboard); return this; } public ComputerBuilder build() { return target; } }
调⽤
public static void main(String[]args){ ComputerBuilder computerBuilder=new ComputerBuilder(); computerBuilder.installDisplayer("显万器"); computerBuilder.installMainUnit("主机"); computerBuilder.installKeybord("键盘"); computerBuilder.installMouse("⿏标"); Computer computer=computerBuilder.Builder(); System.out.println(computer); }
Mybatis中的体现
SqlSessionFactory 的构建过程:
Mybatis的初始化⼯作⾮常复杂,不是只⽤⼀个构造函数就能搞定的。所以使⽤了建造者模式,使⽤了
⼤ 量的Builder,进⾏分层构造,核⼼对象Configuration使⽤了 XmlConfigBuilder来进⾏构造
在Mybatis环境的初始化过程中,SqlSessionFactoryBuilder会调⽤XMLConfigBuilder读取所有的
MybatisMapConfig.xml 和所有的 *Mapper.xml ⽂件,构建 Mybatis 运⾏的核⼼对象 Configuration
对 象,然后将该Configuration对象作为参数构建⼀个SqlSessionFactory对象。
private void parseConfiguration(XNode root) { try { //issue #117 read properties first //解析<properties />标签 propertiesElement(root.evalNode("properties")); // 解析 <settings /> 标签 Properties settings = settingsAsProperties(root.evalNode("settings")); //加载⾃定义的VFS实现类 loadCustomVfs(settings); // 解析 <typeAliases /> 标签 typeAliasesElement(root.evalNode("typeAliases")); //解析<plugins />标签 pluginElement(root.evalNode("plugins")); // 解析 <objectFactory /> 标签 objectFactoryElement(root.evaINode("obj ectFactory")); // 解析 <objectWrapper Factory /> 标签 obj ectWrappe rFacto ryElement(root.evalNode("objectWrapperFactory")); // 解析 <reflectorFactory /> 标签 reflectorFactoryElement(root.evalNode("reflectorFactory")); // 赋值 <settings /> 到 Configuration 属性 settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 // 解析 <environments /> 标签 environmentsElement(root.evalNode("environments")); // 解析 <databaseIdProvider /> 标签 databaseldProviderElement(root.evalNode("databaseldProvider")); }
其中 XMLConfigBuilder 在构建 Configuration 对象时,也会调⽤ XMLMapperBuilder ⽤于读取
*Mapper ⽂件,⽽XMLMapperBuilder会使⽤XMLStatementBuilder来读取和build所有的SQL语句。
//解析<mappers />标签 mapperElement(root.evalNode("mappers"));
在这个过程中,有⼀个相似的特点,就是这些Builder会读取⽂件或者配置,然后做⼤量的XpathParser
解析、配置或语法的解析、反射⽣成对象、存⼊结果缓存等步骤,这么多的⼯作都不是⼀个构造函数所
能包括的,因此⼤量采⽤了 Builder模式来解决
SqlSessionFactoryBuilder类根据不同的输⼊参数来构建SqlSessionFactory这个⼯⼚对象
11.2 ⼯⼚模式
在Mybatis中⽐如SqlSessionFactory使⽤的是⼯⼚模式,该⼯⼚没有那么复杂的逻辑,是⼀个简单⼯⼚ 模式。
简单⼯⼚模式(Simple Factory Pattern):⼜称为静态⼯⼚⽅法(Static Factory Method)模式,它属于创 建型模式。
在简单⼯⼚模式中,可以根据参数的不同返回不同类的实例。简单⼯⼚模式专⻔定义⼀个类来负责创建 其他类的实例,被创建的实例通常都具有共同的⽗类
例⼦:⽣产电脑
假设有⼀个电脑的代⼯⽣产商,它⽬前已经可以代⼯⽣产联想电脑了,随着业务的拓展,这个代⼯⽣产 商还要⽣产惠普的电脑,我们就需要⽤⼀个单独的类来专⻔⽣产电脑,这就⽤到了简单⼯⼚模式。
下⾯我们来实现简单⼯⼚模式:
1. 创建抽象产品类 我们创建⼀个电脑的抽象产品类,他有⼀个抽象⽅法⽤于启动电脑:
public abstract class Computer { /** *产品的抽象⽅法,由具体的产品类去实现 */ public abstract void start(); }
2. 创建具体产品类
接着我们创建各个品牌的电脑,他们都继承了他们的⽗类Computer,并实现了⽗类的start⽅法:
public class LenovoComputer extends Computer{ @Override public void start() { System.out.println("联想电脑启动"); }
public class HpComputer extends Computer{ @Override public void start() { System.out.println("惠普电脑启动"); } }
3. 创建⼯⼚类
接下来创建⼀个⼯⼚类,它提供了⼀个静态⽅法createComputer⽤来⽣产电脑。你只需要传⼊你
想⽣ 产的电脑的品牌,它就会实例化相应品牌的电脑对象
import org.junit.runner.Computer; public class ComputerFactory { public static Computer createComputer(String type){ Computer mComputer=null; switch (type) { case "lenovo": mComputer=new LenovoComputer(); break; case "hp": mComputer=new HpComputer(); break; } return mComputer; } }
客户端调⽤⼯⼚类
客户端调⽤⼯⼚类,传⼊“hp”⽣产出惠普电脑并调⽤该电脑对象的start⽅法:
public class CreatComputer { public static void main(String[]args){ ComputerFactory.createComputer("hp").start(); } }
Mybatis 体现:
Mybatis中执⾏Sql语句、获取Mappers、管理事务的核⼼接⼝SqlSession的创建过程使⽤到了⼯⼚模 式。
有⼀个 SqlSessionFactory 来负责 SqlSession 的创建
SqlSessionFactory
可以看到,该Factory的openSession ()⽅法重载了很多个,分别⽀
持autoCommit、Executor、Transaction等参数的输⼊,来构建核⼼的SqlSession对象。
在DefaultSqlSessionFactory的默认⼯⼚实现⾥,有⼀个⽅法可以看出⼯⼚怎么产出⼀个产品:
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); //根据参数创建制定类型的Executor 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(); } }
这是⼀个openSession调⽤的底层⽅法,该⽅法先从configuration读取对应的环境配置,然后初始化
TransactionFactory 获得⼀个 Transaction 对象,然后通过 Transaction 获取⼀个 Executor 对象,最
后通过configuration、Executor、是否autoCommit三个参数构建了 SqlSession
11.3 代理模式
代理模式(Proxy Pattern):给某⼀个对象提供⼀个代理,并由代理对象控制对原对象的引⽤。代理模式
的英⽂叫做Proxy,它是⼀种对象结构型模式,代理模式分为静态代理和动态代理,我们来介绍动态代理
举例:
创建⼀个抽象类,Person接⼝,使其拥有⼀个没有返回值的doSomething⽅法。
/** * 抽象类⼈ */ public interface Person { void doSomething(); }
创建⼀个名为Bob的Person接⼝的实现类,使其实现doSomething⽅法
/** * 创建⼀个名为Bob的⼈的实现类 */ public class Bob implements Person { public void doSomething() { System.out.println("Bob doing something!"); } }
(3) 创建JDK动态代理类,使其实现InvocationHandler接⼝。拥有⼀个名为target的变量,并创建
getTa rget获取代理对象⽅法
/** * JDK动态代理 * 需实现 InvocationHandler 接⼝ */ public class JDKDynamicProxy implements InvocationHandler { //被代理的对象 Person target; // JDKDynamicProxy 构造函数 public JDKDynamicProxy(Person person) { this.target = person; } //获取代理对象 public Person getTarget() { return (Person) Proxy.newProxylnstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } //动态代理invoke⽅法 public Person invoke(Object proxy, Method method, Object[] args) throws Throwable { //被代理⽅法前执⾏ System.out.println("JDKDynamicProxy do something before!"); //执⾏被代理的⽅法 Person result = (Person) method.invoke(target, args); //被代理⽅法后执⾏ System.out.println("JDKDynamicProxy do something after!"); return result; }
创建JDK动态代理测试类J DKDynamicTest
/** * JDK动态代理测试 */ public class JDKDynamicTest { public static void main(String[] args) { System.out.println("不使⽤代理类,调⽤doSomething⽅法。"); //不使⽤代理类 Person person = new Bob(); // 调⽤ doSomething ⽅法 person.doSomething(); System.out.println("分割线-----------"); System.out.println("使⽤代理类,调⽤doSomething⽅法。"); //获取代理类 Person proxyPerson = new JDKDynamicProxy(new Bob()).getTarget(); // 调⽤ doSomething ⽅法 proxyPerson.doSomething(); } }
Mybatis中实现:
代理模式可以认为是Mybatis的核⼼使⽤的模式,正是由于这个模式,我们只需要编写Mapper.java接
⼝,不需要实现,由Mybati s后台帮我们完成具体SQL的执⾏。
当我们使⽤Configuration的getMapper⽅法时,会调⽤mapperRegistry.getMapper⽅法,⽽该⽅法⼜
会调⽤ mapperProxyFactory.newInstance(sqlSession)来⽣成⼀个具体的代理:
public class MapperProxyFactory<T> { private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>(); 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<T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } }
在这⾥,先通过T newInstance(SqlSession sqlSession)⽅法会得到⼀个MapperProxy对象,然后调⽤T
newInstance(MapperProxy mapperProxy)⽣成代理对象然后返回。⽽查看MapperProxy的代码,可
以看到如下内容:
public class MapperProxy<T> implements InvocationHandler, Serializable { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); }
⾮常典型的,该MapperProxy类实现了InvocationHandler接⼝,并且实现了该接⼝的invoke⽅法。通
过这种⽅式,我们只需要编写Mapper.java接⼝类,当真正执⾏⼀个Mapper接⼝的时候,就会转发给
MapperProxy.invoke⽅法,⽽该⽅法则会调⽤后续的
sqlSession.cud>executor.execute>prepareStatement 等⼀系列⽅法,完成 SQL 的执⾏和返回
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构