美文网首页Mybatis
Mybatis中的设计模式

Mybatis中的设计模式

作者: Scallion | 来源:发表于2021-07-12 10:19 被阅读0次

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

    相关文章

      网友评论

        本文标题:Mybatis中的设计模式

        本文链接:https://www.haomeiwen.com/subject/fddrpltx.html