Mybatis

Mybatis中的设计模式

2021-07-12  本文已影响0人  Scallion

Mybatis 设计模式

mybaits最少用到了九种设计模式:

设计模式 mybaits体现
Builder构建者模式 SqlSessionFactoryBuilder、Environment
工厂模式 SqlSessionFactory、TransactionFactory、LogFactory
单例模式 ErrorContex、LogFactory
代理模式 MapperProxy、ConnectionLogger、executor.loader
组合模式 SqlNode、ChooseSqlNode
模版方法模式 BaseExecutor、SimpleExecutor、BaseTypeHandler、IntegerTypeHandler
适配器模式 Log的Mybatis接口和它对jdbc、log4j等各种日志框架的适配实现
装饰者模式 Cache包中的cache.decorators子包中等各个装饰者的实现
迭代器模式 迭代器模式PropertyTokenizer

构建者模式应用

Builder模式的定义是“将一个复杂对象的构建与他的表示分离,使得同样的构建过程可以创建不同的表示。”,它属于创建类模式,一般来说,如果一个对象的构建比较复杂,超出了构造函数所能包含的范围,就可以使用工厂模式或Builder模式,相对于工厂模式会产出一个完整的产品,Builder应用于更加复杂的对象构建,甚至只会构建产品的一部分。直白来说,就是使用多个简单对象一步一步构建成一个复杂的对象。

例如:使用构建者设计模式来生产computer

代码事例:

  1. 先准备一个需要构建的目标类,这个类有很多属性,其中每个属性都是一个对象,代码为了演示效果,类的成员都采用了String字符串类型。
package com.erxiao.constructor;

public class Computer {
    //显示器
    private String displayer;
    //主机
    private String mainUnit;
    //鼠标
    private String mouse;
    //键盘
    private String keyboard;

    @Override
    public String toString() {
        return "Computer{" +
                "displayer='" + displayer + '\'' +
                ", mainUnit='" + mainUnit + '\'' +
                ", mouse='" + mouse + '\'' +
                ", keyboard='" + 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;
    }
}

  1. 编写一个构建类,并为构建目标类提供成员属性的设置方法
package com.erxiao.constructor;

public class ComputerBuilder {
    //创建目标类对象
    private Computer computer = new Computer();
        
    //为目标类成员提供设置方法
    public void intallDisplaye(String displaye) {
        computer.setDisplayer(displaye);
    }

    public void intallMainUnit(String mainUnit) {
        computer.setMainUnit(mainUnit);
    }

    public void intallMouse(String mouse) {
        computer.setMouse(mouse);
    }

    public void intallKeyboard(String keyboard) {
        computer.setKeyboard(keyboard);
    }

    //提供获取目标类对象函数
    public Computer getComputer() {
        return computer;
    }
}

  1. 通过构建类获取目标类对象,完成目标类构建
public static void main(String[] args) {
        ComputerBuilder computerBuilder = new ComputerBuilder();
        computerBuilder.intallDisplaye("显示器");
        computerBuilder.intallMainUnit("主机");
        computerBuilder.intallMouse("鼠标");
        computerBuilder.intallKeyboard("键盘");
        Computer computer = computerBuilder.getComputer();
        System.out.println(computer);
    }   

Mybatis中的体现

SqlSessionFactory的构建过程:

Mybatis的初始化工作非常复杂,不是用一个构造函数就能搞定的。所以使用了建造者模式,使用了大量的Builder,进行分层构造,核心对象Configuration使用了XmlConfigBuilder来进行构造。

image-20210603223420406.png
  1. 使用SqlSessionFactoryBuilder对象,将读取到的SqlMapConfig.xml InputStream流当作参数,调用builder方法,返回一个SqlSessionFactory对象;
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
  1. SqlSessionFactoryBuilder中的builder方法内部会创建一个XMLConfigBuilder对象,继续向下执行会调用XMLConfigBuilder中parse方法。
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      //创建XMLConfigBuilder对象
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      //调用XMLConfigBuilder中的parse函数
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }
  1. 由于XMLConfigBuilder类继承了BaseBuilder类。BaseBuilder类只提供了有参构造方法,需要将Configuration对象当作参数传递到构造函数中。在XMLConfigBuilder的构造函数中就创建了一个Configuration对象,并调用父类的构造函数。
  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    //创建Configuration对象,调用父类构造函数
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }
  1. XMLConfigBuilder调用parse函数解析SqlMapConfig.xml配置文件。parse函数中实际调用的是parseConfiguration函数。
  public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    //调用parseConfiguration函数,解析SqlMapConfig中的元素
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
  1. parseConfiguration函数根据SqlMapConfig.xml中的标签,解析具体的属性,并设置到对应的对象中,同时会调用mapperElement函数,解析mapper文件。
  private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      //解析所有的mappers
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }
  1. mapperElement函数中会构建一个XMLMapperBuilder对象,并调用XMLMapperBuilder中的parse函数解析mapper配置文件。XMLMapperBuilder也会使用XMLStatementBuilder来读取和build所有的sql。
  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            //创建XMLMapperbuilder对象
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            //调用解析mapper配置文件函数
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }
  1. 最后通过以上步骤构建出来的Configuration对象,并将Configuration当作参数传递给SqlSessionFactoryBuilder的build函数,并将DefaultSqlSessionFactory对象返回。
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

在这个过程中,又一个相似的特点,就是这些Builder会读取文件或者配置,然后做大量的XpathParser解析、配置或语法解析、反射生成对象、存入结果缓存等步骤,这么多的工作都不是一个构造函数所能包括的,因此大量采用了Builder模式来解决。

image-20210604114628624.png

SqlSessionFactoryBuilder类根据不同的输入参数来构建SqlSessionFactory这个工厂对象。

工厂模式应用

mybatis中比如SqlSessionFactory使用的是工厂模式,该工厂没有那么复杂的逻辑,是一个简单工厂模式。简单工厂模式(Simple Factory Pattern):又称为静态工厂方法(Static Factory Method)模式,它属于创建模式。在简单的工厂模式中,可以根据参数返回不同类实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。

实现简单工厂模式:

  1. 创建抽象产品类

    创建一个电脑的抽象产品类,它有一个抽象方法用于启动电脑:

    
    public abstract class Computer {
        /**
         * 产品的抽象方法,由具体的产品提供
         */
        public abstract void start();
    }
    
  2. 创建具体产品类

    接着创建各个品牌的电脑,他们都继承他们的父类Computer,并实现了父类的start方法:

    /**
     * @Author: wangcong
     * @Date: 2021/6/5 11:17 下午
     * @Version 1.0
     */
    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用来生产电脑。只有传入想生产电脑的品牌,他就会实例化相应品牌的电脑对象。


public class ComputerFactory {
    public static Computer createComputer(String type) {
        Computer computer = null;
        switch (type) {
            case "lenovo":
                computer = new LenovoComputer();
                break;
            case "hp":
                computer = new HpComputer();
                break;
        }
        return computer;
    }
}
  1. 客户端调用工厂类

    客户端调用工厂类,传入“hp”生产出hp电脑并调用该电脑的start方法

    
    public class CreateComputer {
        public static void main(String[] args) {
            ComputerFactory.createComputer("hp").start();
        }
    }
    

Mybatis体现:

Mybatis中执行sql语句,获取Mappers、管理事务的核心接口SqlSession的创建过程使用到了工厂模式。有一个SqlSessionFactory来负责SqlSession创建。

image-20210608225023129.png

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);
      //返回的是DefaultSession
      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、Excutor、是否autoCommit三个参数构建了SqlSession。

代理模式应用

代理模式(Proxy Pattern):给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式的英文叫做Proxy,它是一种对象结构模式,代理模式分为静态代理和动态代理,本次介绍动态代理

代理模式是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, 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);
  }
  //调用newInstance方法生成一个具体的代理类
  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}

通过newInstance(SqlSession sqlSession)方法会得到一个MapperProxy对象,然后调用newInstance(MapperProxy mapperProxy)生成代理对象然后返回。查看MapperProxy的代码可以看到如下内容

public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  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 {
      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、execute.execute、prepareStatement等一系列方法,完成SQL的执行和返回。

上一篇 下一篇

猜你喜欢

热点阅读