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 的执⾏和返回

 

posted @   eagle88  阅读(67)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示