万隆的笔记 万隆的笔记
博文索引
笔试面试
  • 在线学站

    • 菜鸟教程 (opens new window)
    • 入门教程 (opens new window)
    • Coursera (opens new window)
  • 在线文档

    • w3school (opens new window)
    • Bootstrap (opens new window)
    • Vue (opens new window)
    • 阿里开发者藏经阁 (opens new window)
  • 在线工具

    • tool 工具集 (opens new window)
    • bejson 工具集 (opens new window)
    • 文档转换 (opens new window)
  • 更多在线资源
  • Changlog
  • Aboutme
GitHub (opens new window)
博文索引
笔试面试
  • 在线学站

    • 菜鸟教程 (opens new window)
    • 入门教程 (opens new window)
    • Coursera (opens new window)
  • 在线文档

    • w3school (opens new window)
    • Bootstrap (opens new window)
    • Vue (opens new window)
    • 阿里开发者藏经阁 (opens new window)
  • 在线工具

    • tool 工具集 (opens new window)
    • bejson 工具集 (opens new window)
    • 文档转换 (opens new window)
  • 更多在线资源
  • Changlog
  • Aboutme
GitHub (opens new window)
  • MyBatis

    • MyBatis 简介
    • MyBatis简单应用
    • MyBatis常⽤配置
    • MyBatis 单表 CRUD 操作
    • MyBatis动态 SQL
    • MyBatis复杂映射
    • MyBatis注解开发
    • MyBatis缓存
    • MyBatis插件
    • ⾃定义持久层框架
    • MyBatis架构原理
    • MyBatis源码剖析
    • MyBatis设计模式
      • 概述
      • Builder构建模式
      • ⼯⼚模式
      • 代理模式
  • Spring-MyBatis

  • MyBatis-Plus

  • MyBatis
  • MyBatis
2022-12-04
目录

MyBatis设计模式

# MyBatis设计模式

# 概述

MyBatis源码中使⽤了⼤量的设计模式,⾄少⽤到了以下的设计模式:

设计模式 MyBatis的体现
Builder构建模式 例如 SqlSessionFactoryBuilder、Environment
⼯⼚模式 例如 SqlSessionFactory、TransactionFactory、LogFactory
单例模式 例如 ErrorContext、LogFactory
代理模式 MyBatis实现的核心,例如 MapperProxy、ConnectionLogger,⽤的jdk的动态代理;``还有executor.loader包使⽤了 javassist或者cglib达到延迟加载的效果
组合模式 例如 SqlNode和各个⼦类ChooseSqlNode等
模板⽅法模式 例如 BaseExecutor 和 SimpleExecutor,还有 BaseTypeHandler 和所有的⼦类例如IntegerTypeHandler
适配器模式 例如 Log的Mybatis接⼝和它对jdbc、log4j等各种⽇志框架的适配实现;
装饰者模式 例如 Cache包中的cache.decorators⼦包中等各个装饰者的实现;
迭代器模式 例如 迭代器模式PropertyTokenizer

接下来,结合MyBatis的应用对Builder构建模式、⼯⼚模式、代理模式进⾏解读。

# Builder构建模式

Builder构建模式的定义是将复杂对象的构建过程与其表现分离,以便相同的构建过程可以创建不同的表现。它属于创建类模式,⼀般来说,如果⼀个对象的构建⽐较复杂,超出了构造函数所能包含的范围,就可以使⽤⼯⼚模式和Builder模式,相对于⼯⼚模式会产出⼀个完整的产品,Builder应⽤于更加复杂的对象的构建,甚⾄只会构建产品的⼀个部分,简单来说,Builder构建模式就是使⽤多个简单的对象⼀步⼀步构建成⼀个复杂的对象。

# 构建Computer

下面是一个简单的例子:使⽤构建设计模式来⽣产Computer,更多详细的解读,可见构建模式(Python版)

public class Computer {

    private String displayer;
    private String mainUnit;
    private String mouse;
    private String keyboard;
	
    // 略 setter & getter & toString
}


public class ComputerBuilder {

    private final Computer target = new Computer();

    public ComputerBuilder installDisplayer(String displayer) {
        target.setDisplayer(displayer);
        return this;
    }
    public ComputerBuilder installMainUnit(String mainUnit) {
        target.setMainUnit(mainUnit);
        return this;
    }
    public ComputerBuilder installMouse(String mouse) {
        target.setMouse(mouse);
        return this;
    }
    public ComputerBuilder installKeyboard(String keyboard) {
        target.setKeyboard(keyboard);
        return this;
    }
    public Computer build() {
        return target;
    }

}

public class Main {

    public static void main(String[] args) {
        ComputerBuilder computerBuilder = new ComputerBuilder();
        Computer computer = computerBuilder.installDisplayer("显示器")
                .installMainUnit("主机")
                .installKeyboard("键盘")
                .installMouse("鼠标")
                .build();
        System.out.println(computer);
    }

}

# MyBatis的应用

SqlSessionFactory的构建过程:MyBatis的环境初始化⼯作⾮常复杂,只⽤⼀个构造函数就搞不定的。所以使⽤了构建模式,其中使⽤了⼤量的Builder进⾏分层构造,核⼼对象Configuration使⽤了XmlConfigBuilder来进⾏构建。

builder

在MyBatis环境的初始化过程中,SqlSessionFactoryBuilder会调⽤XMLConfigBuilder读取所有的SqlMapConfig.xml和所有的 *Mapper.xml ⽂件,构建其运⾏的核⼼Configuration对象,然后将该Configuration对象作为参数构建⼀个SqlSessionFactory对象。

// SqlSessionFactoryBuilder.build
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  try {
    // XMLConfigBuilder是专⻔解析mybatis的配置文件的类
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    // 又调用了一个重载方法。参数是parser.parse()的返回值,它是Configuration对象
    return build(parser.parse());
  } 
  // ...
}

// XMLConfigBuilder.parse调用了parseConfiguration
private void parseConfiguration(XNode root) {
  try {
    // ... 解析<configuration> 中的各种配置标签, 如<properties />、<settings /> 等等
		// ...
    // 解析 <mappers /> 标签,这里就会用XMLMapperBuilder解析所有*Mapper文件
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

其中 XMLConfigBuilder在构建 Configuration对象时,也会调⽤ XMLMapperBuilder ⽤于读取*Mapper ⽂件,⽽XMLMapperBuilder会使⽤XMLStatementBuilder来读取和build所有的SQL语句。

// XMLConfigBuilder.mapperElement
private void mapperElement(XNode parent) throws Exception {
  	// ...
       	// ...
        // resource 方式加载
        if (resource != null && url == null && mapperClass == null) {
          ErrorContext.instance().resource(resource);
          try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            // ⽣成XMLMapperBuilder,并执⾏其parse⽅法
            mapperParser.parse();
          }
        } 
  			// 其他方式加载 
        // ...
  // ...
}

在整个过程中,都有⼀个相似的特点:这些Builder会读取⽂件或者配置,然后做⼤量的XpathParser解析、配置或语法的解析、反射⽣成对象、缓存结果等步骤,这么多的⼯作都不是⼀个构造函数所能包括的,因此⼤量采⽤了 Builder模式来解决。

好处显而易见:SqlSessionFactory⼯⼚对象的构建统一由SqlSessionFactoryBuilder类负责,可以根据不同的输⼊参数构建工厂对象,将工厂对象复杂的构建过程与其表现分离。

# ⼯⼚模式

工厂模式是指专门定义一个类来负责创建其他类的实例,根据参数的不同创建不同类的实例,被创建的实例通常具有共同的父类。工厂模式有三种细分的版本:简单工厂模式、工厂方法模式、抽象工厂模式。更多的详细解读见工厂模式(Python版)。

在MyBatis中⽐如SqlSessionFactory使⽤的是简单⼯⼚模式,⼜称为静态⼯⼚⽅法(Static Factory Method)模式,属于创建型模式。该⼯⼚没有那么复杂的逻辑。可以根据参数的不同返回不同类的实例。

# 生产Computer

下面依然用Computer为例,实现简单⼯⼚模式:

  • 假设有⼀个电脑的代⼯⽣产商,它⽬前可以代⼯⽣产联想电脑,随着业务的拓展,这个代⼯⽣产商还要⽣产惠普的电脑,我们就需要⽤⼀个单独的类来专⻔⽣产电脑,这就⽤到了简单⼯⼚模式。
// 产品共同的父类
public abstract class Computer {

    /**
     * 产品抽象方法,由具体的产品类去实现
     */
    public abstract void 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("惠普电脑启动");
    }
}

// 工厂类负责生产产品
public class ComputerFactory {

    public static Computer create(String type) {
        Computer computer = null;
        switch (type) {
            case "lenovo":
                computer = new LenovoComputer();
                break;
            case "hp":
                computer = new HpComputer();
                break;
        }
        return computer;
    }

}

// 调用
public class Main {
    public static void main(String[] args) {
        ComputerFactory.create("lenovo").start();
        ComputerFactory.create("hp").start();
    }
}

# MyBatis的应用

MyBatis中的核⼼接⼝SqlSession,可以执⾏Sql语句、获取Mappers、管理事务等等,它的创建过程使⽤到了⼯⼚模式,由 SqlSessionFactory 来负责 SqlSession的创建,默认实现是DefaultSqlSessionFactory:

sql_session_factory

可以看到,该Factory的 openSession()⽅法重载了很多个,分别⽀持autoCommit、Executor、Transaction等参数的输⼊,来构建核⼼的SqlSession对象。在DefaultSqlSessionFactory的默认⼯⼚实现⾥,openSessionFromDataSource⽅法可以看出⼯⼚怎么产出⼀个产品,它是 openSession的底层调用:

/**
 * 7. 进⼊openSessionFromDataSource
 * openSession的多个重载⽅法可以指定获得的SeqSession的Executor类型和事务的处理
 * @param execType executor的类型
 * @param level 事务隔离级别
 * @param autoCommit 是否开启事务
 * @return 返回sqlSession
 */
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;
  try {
      // 从configuration读取对应的环境配置
      final Environment environment = configuration.getEnvironment();
      // 初始化TransactionFactory
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      // congFactory 获得⼀个 Transaction 对象
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // 通过Transaction和executor的类型,创建指定类型的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();
  }
}

# 代理模式

代理模式是指给某⼀个对象提供⼀个代理,并由代理对象控制对原对象的引⽤。代理模式的英⽂叫做Proxy,它是⼀种对象结构型模式,代理模式分为静态代理和动态代理,我们来介绍动态代理,更多的详细解读见代理模式(Python版)。

# Person动态代理

下面是动态代理的一个例子:

// Person抽象类
public interface Person {
    void doSomething();
}

// 创建⼀个名为Jack的实现类
public class Jack implements Person {
    @Override
    public void doSomething() {
        System.out.println("Jack doing something!");
    }
}

// 创建JDK动态代理类
public class JDKDynamicProxy implements InvocationHandler {
    // 被代理对象
    Person target;

    public JDKDynamicProxy(Person target) {
        this.target = target;
    }

    public Person getTarget() {
        return (Person) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    @Override
    public Object 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;
    }
}

// 测试类
public class Main {
    public static void main(String[] args) {
        System.out.println("不使用代理类,调用doSomething方法");
        Person person = new Jack();
        person.doSomething();
        System.out.println("使用代理类,调用doSomething方法");
        Person proxyPerson = new JDKDynamicProxy(new Jack()).getTarget();
        proxyPerson.doSomething();
    }
}

# MyBatis的应用

代理模式可以认为是MyBatis的使⽤的核⼼设计模式,正是由于这个模式,我们只需要编写Mapper.java接⼝,而不需要实现,由MyBatis帮我们完成具体SQL的执⾏。当我们使⽤Configuration的 getMapper⽅法时,会调⽤mapperRegistry.getMapper⽅法,⽽该⽅法⼜ 会调⽤ mapperProxyFactory.newInstance(sqlSession)来⽣成⼀个具体的代理:

public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethodInvoker> 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) {
    // 创建了JDK动态代理的Handler类
    final MapperProxy<T> mapperProxy = new MapperProxy<>(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 {
      // 如果是Object定义的⽅法,直接调⽤
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else {
        // 返回的是PlainMethodInvoker,调用了invoke方法。它是MapperProxy的内部类。
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

  private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
      return MapUtil.computeIfAbsent(methodCache, method, m -> {
        if (m.isDefault()) {
          try {
            if (privateLookupInMethod == null) {
              return new DefaultMethodInvoker(getMethodHandleJava8(method));
            } else {
              return new DefaultMethodInvoker(getMethodHandleJava9(method));
            }
          } catch (IllegalAccessException | InstantiationException | InvocationTargetException
              | NoSuchMethodException e) {
            throw new RuntimeException(e);
          }
        } else {
          return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
        }
      });
    } catch (RuntimeException re) {
      Throwable cause = re.getCause();
      throw cause == null ? re : cause;
    }
  }
  
  // ...
}

我们可以看到,该MapperProxy类实现了InvocationHandler接⼝,并且实现了该接⼝的invoke⽅法,典型的JDK动态代理。通过这种⽅式,我们只需要编写Mapper.java接⼝类,当真正执⾏⼀个Mapper接⼝的时候,就会转发给 MapperProxy.invoke⽅法,⽽该⽅法则会调⽤后续的 sqlSession.curd > executor.execute > prepareStatement 等⼀系列⽅法,完成 SQL 的执⾏和返回

上次更新: 5/30/2023, 10:53:02 PM
Spring整合MyBatis

Spring整合MyBatis→

最近更新
01
2025
01-15
02
Elasticsearch面试题
07-17
03
Elasticsearch进阶
07-16
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式