Mybatis中的设计模式

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,它是一种对象结构模式,代理模式分为静态代理和动态代理,本次介绍动态代理

  • 例如:

    1. 创建一个抽象类,Person接口,使其拥有一个没有返回值的doSomething方法
    /**
     * @Author: wangcong
     * @Date: 2021/6/8 11:31 下午
     * @Version 1.0
     */
    public interface Person {
        void doSomething();
    }
    
    1. 创建一个名为Bob的Person接口的实现类,使其实现doSomething方法
    /**
     * @Author: wangcong
     * @Date: 2021/6/8 11:32 下午
     * @Version 1.0
     */
    public class Bob implements Person{
        @Override
        public void doSomething() {
            System.out.println("Bob doing something");
        }
    }
    
    1. 创建JDK动态代理类,使其实现InvocationHandler接口。拥有一个名为target的变量,并创建getTarget获取代理对象方法
    /**
     * @Date: 2021/6/8 11:35 下午
     * @Version 1.0
     * JDK动态代理 需要实现 InvocationHandler 接口
     */
    public class JDKDynamicproxy implements InvocationHandler {
    
        //被代理对象
        private Person target;
    
        //JDKDynamicproxy构造函数
        public JDKDynamicproxy(Person target) {
            this.target = target;
        }
    
        //获取代理对象
        public Person getTarget() {
            return (Person) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
        }
        
        //动态代理invoke方法
        @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;
        }
    }
    
    1. 创建JDK动态代理测试类
    
    public class JDKDynamicTest {
        public static void main(String[] args) {
            System.out.println("不使用代理类,调用doSomething方法");
            //不使用代理类
            Person person = new Bob();
            person.doSomething();
            System.out.println("---------------------------");
            System.out.println("使用代理类,调用doSomething方法");
            //获取代理类
            Person target = new JDKDynamicproxy(new Bob()).getTarget();
            target.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, 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的执行和返回。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,525评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,203评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,862评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,728评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,743评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,590评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,330评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,244评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,693评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,885评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,001评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,723评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,343评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,919评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,042评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,191评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,955评论 2 355

推荐阅读更多精彩内容