zoukankan      html  css  js  c++  java
  • 常见设计模式在mybatis中的应用

    请你说说,设计模式在mybatis有哪些具体的应用?

    @


    设计模式分类

    是不是看着头大,哈哈;头大就对了,我们没必要每个都去深究,我们只需要结合例子知道部门设计模式是如何使用的就可以了!接下来我们结合mybatis这个框架,探讨下常用设计的使用吧!

    关于设计模式的示例及讲解可以看C语言中文网中关于设计模式的描述及示例:23种设计模式详解

    那么mybatis中运用的设计模式有哪些呢?

    工厂模式

    简单理解,就是工厂模式就是提供一个工厂类,当客户端需要调用的时候就可以得到想要的结果,而不需要关注内部的实现!就好像买东西,不需要你关心这些东西怎么生产的,给对应的钱就可以了!

    那么工厂模式在MyBatis中具体的应用就是SqlSessionFactory, 通过SqlSessionFactory接口类,可以得到对应的SqlSession接口,我们可以通过该接口执行SQL命令,获取映射器示例和管理事务。

    DefaultSqlSessionFactory是SqlSessionFactory下的一个子类,我们来看下其中的openSessionFromDataSource方法的源码:

    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
      Transaction tx = null;
      DefaultSqlSession var8;
      try {
    ​    // 读取环境配置
    ​    Environment environment = this.configuration.getEnvironment();
    ​    TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
    ​    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    ​    // 跟据对应的执行器类型获取对应的执行器对象
    ​    Executor executor = this.configuration.newExecutor(tx, execType);
    ​    var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
    
     } catch (Exception var12) {
    ​    this.closeTransaction(tx);
    ​    throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
    
     } finally {
    ​    ErrorContext.instance().reset();
     }
      return var8;
    }
    
    

    我们可以看到对应的newExecutor方法可以根据executorType类型,创建不同的Executor对象;这就是标准的工厂模式的应用!

    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    ​    executorType = executorType == null ? this.defaultExecutorType : executorType;
    ​    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    ​    Object executor;
    ​    if (ExecutorType.BATCH == executorType) {
    ​      executor = new BatchExecutor(this, transaction);
       } else if (ExecutorType.REUSE == executorType) {
    ​      executor = new ReuseExecutor(this, transaction);
       } else {
    ​      executor = new SimpleExecutor(this, transaction);
       }
    ​    if (this.cacheEnabled) {
    ​      executor = new CachingExecutor((Executor)executor);
       }
    ​    Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
    ​    return executor;
     }
    
    

    单例模式

    其实单例模式一定程度上违背了“单一职责原则”;为什么呢,因为单例模式即提供了单一的实例,又提供了实例的全局访问;但是,这种模式还是有它的优势和不可替代的地方的,在mybatis框架中的单例的代表实现就是ErrorContext;我们来看下它的源码:

    public class ErrorContext {
      private static final String LINE_SEPARATOR = System.getProperty("line.separator", "
    ");
      private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal();
      private ErrorContext stored;
      private String resource;
      private String activity;
      private String object;
      private String message;
      private String sql;
      private Throwable cause;
    
      private ErrorContext() {
    
     }
    
     public static ErrorContext instance() {
    ​    ErrorContext context = (ErrorContext)LOCAL.get();
    ​    if (context == null) {
    ​      context = new ErrorContext();
    ​      LOCAL.set(context);
       }
    ​    return context;
     }
      //...其他代码省略
    }
    
    

    从上面代码我们可以看到,ErrorContext私有了构造器,并且私有了一个LOCAL 线程来保存ErrorContext对象,以此来保证每个线程都有一个独立的ErrorContext对象,在初始化的instance()方法时,将ErrorContext对象 set到LOCAL中!

    建造者模式

    简而言之,就是将一个对象的构建过程拆分,通过多个模块一步步实现,根据模块的多少从而达到不同级别的实现!举个例子,你要组装台电脑,那么你可以根据你的需求,你可以分为:开发,办公,影音聊天,打游戏等这些需求。那么根据不同的需求,你可以选购不同级别的内存,显卡,显示屏,硬盘等。那么电脑组装人员,根据你不同的需求组装对应电脑的过程就是建造者模式!

    那么建造者模式在mybatis是怎么应用的呢?典型的就是各种builder,我们结合SqlSessionFactoryBuilder类来分析下:

    public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
      SqlSessionFactory var5;
      try {
    ​    // 读取xml配置
    ​    XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
    ​    // 构建SqlSessionFactory
    ​    var5 = this.build(parser.parse());
      } catch (Exception var14) {
    ​    throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
    
      } finally {
    ​    // 将单例的重置放到finally 中
    ​    ErrorContext.instance().reset();
    ​    try {
    ​      reader.close();
    ​    } catch (IOException var13) {
    
    ​    }
      }
      return var5;
    }
    
    

    //...省略其他方法

    public SqlSessionFactory build(Configuration config) {
      return new DefaultSqlSessionFactory(config);
    }
    

    通过源码的阅读,我们知道SqlSessionFactoryBuilder类提供了各种build方法,通过读取解析配置,然后通过反射生成对象,最后将对象放入了缓存,然后一步步构建返回SqlSessionFactor对象;

    适配器模式

    适配器模式,其实很好理解;它的运用只为了兼容,通过适配器模式,我们可以将一个不兼容的接口转换成兼容的接口,从而使不兼容的类可以协调一起工作!通俗的讲,就是一个转接头的概念,不论你用啥类型的线,我给你加个转接头,你都能玩,没有什么是加一层不能的解决的嘛!

    例如,mybatis的日志模块适配了多种日志类型,包括:SLF4J,Log4j2,JDK loggiing等;

    我们来看下Log接口:

    public interface Log {
    
      boolean isDebugEnabled();
    
      boolean isTraceEnabled();
    
      void error(String var1, Throwable var2);
    
      void error(String var1);
    
      void debug(String var1);
    
      void trace(String var1);
    
      void warn(String var1);
    
    }
    

    我们来看下Log4j2的实现类Log4j2Impl:

    import org.apache.ibatis.logging.Log;
    import org.apache.logging.log4j.LogManager;
    import org.apache.logging.log4j.Logger;
    import org.apache.logging.log4j.spi.AbstractLogger;
    
    public class Log4j2Impl implements Log {
      private final Log log;
      public Log4j2Impl(String clazz) {
    ​    Logger logger = LogManager.getLogger(clazz);
    ​    if (logger instanceof AbstractLogger) {
    ​      this.log = new Log4j2AbstractLoggerImpl((AbstractLogger)logger);
       } else {
    ​      this.log = new Log4j2LoggerImpl(logger);
       }
    }
      //...其他代码略
    }
    

    这样做的好处是,当你使用log4j2时,mybatis可以直接使用它打印mybatis的日志!就是说,我是总的规范制定者,不管你们下面再怎么乱, 我把规范制定出来,你们要用的话,就实现我的这些方法就可以了!为了你们使用方便,我把你们都集成到我这里来,你们后面直接用你的东西但是还是可以调用我这边提供的方法!

    代理模式

    哈哈,这个是个比较经典的模式了!最经典的理解就是火车票代售点,它就是个代理火车站卖票的作用!那么mybatis中怎么使用的呢?我们来看下MapperProxyFactory类:

    public class MapperProxyFactory<T> {
    
      private final Class<T> mapperInterface;
      private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap();
      public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
     }
      public Class<T> getMapperInterface() {
    ​    return this.mapperInterface;
     }
      public Map<Method, MapperMethod> getMethodCache() {
    ​    return this.methodCache;
     }
      // 创建代理实例
      protected T newInstance(MapperProxy<T> mapperProxy) {
    ​    return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
     }
      public T newInstance(SqlSession sqlSession) {
    ​    MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
    ​    return this.newInstance(mapperProxy);
     }
    }
    

    模板方法模式

    很多时候,我们做事情是有一套固定的流程模板的,例如:把东西放进冰箱;打开冰箱门->放入东西->关门,唯一不同的就是这个东西是可变的!而模板方法模式就是,规定一套流程,而降放入东西这一具体的实现放入子类中去实现,使得子类不改变整个算法流程的结构,即可以重新定义一个模板,重而达到模板复用的目的!

    mybatis中代表的模板方法的应用是BaseExecutor类,BaseExecutor实现了大部分的SQL的执行逻辑,然后再把方法交给子类来实现,它的继承关系如下所示:

    比如,doUpdate()方法就是交给子类去实现的,在BaseExecutor中定义如下:

    protected abstract int doUpdate(MappedStatement var1, Object var2) throws SQLException;
    

    在SimpleExecutor中的实现如下:

    public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
        Statement stmt = null;
        int var6;
        try {
            Configuration configuration = ms.getConfiguration();
            StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, (ResultHandler)null, (BoundSql)null);
            stmt = this.prepareStatement(handler, ms.getStatementLog());
            var6 = handler.update(stmt);
        } finally {
            this.closeStatement(stmt);
        }
        return var6;
    }
    

    而在BatchExecutor中实现如下:

    public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
        Configuration configuration = ms.getConfiguration();
        StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, (ResultHandler)null, (BoundSql)null);
        BoundSql boundSql = handler.getBoundSql();
        String sql = boundSql.getSql();
        Statement stmt;
        if (sql.equals(this.currentSql) && ms.equals(this.currentStatement)) {
            int last = this.statementList.size() - 1;
            stmt = (Statement)this.statementList.get(last);
            this.applyTransactionTimeout(stmt);
            handler.parameterize(stmt);
            BatchResult batchResult = (BatchResult)this.batchResultList.get(last);
            batchResult.addParameterObject(parameterObject);
        } else {
            Connection connection = this.getConnection(ms.getStatementLog());
            stmt = handler.prepare(connection, this.transaction.getTimeout());
            handler.parameterize(stmt);
            this.currentSql = sql;
            this.currentStatement = ms;
            this.statementList.add(stmt);
            this.batchResultList.add(new BatchResult(ms, sql, parameterObject));
        }
        handler.batch(stmt);
        return -2147482646;
    }
    

    在ReuseExecutor中实现如下:

    public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
        Configuration configuration = ms.getConfiguration();
        StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, (ResultHandler)null, (BoundSql)null);
        Statement stmt = this.prepareStatement(handler, ms.getStatementLog());
        return handler.update(stmt);
    }
    

    可以看出每个子类的实现都不同,SimpleExecutor中使用完Statement后,都在finally中关闭了Statement对象,而BatchExecutor和ReuseExecutor都没有关闭;并且BatchExecutor中doUpdate返回了一个最小的int值,而其他两个则返回的是实际影响的条数!

    装饰模式

    装饰模式又称作装饰器模式,它指的是在不改变原有代码结构情况下,动态给对象添加方法的模式;通俗的来讲,即是我们一般扩展对象的功能一般采用继承的方式,但是继承具有原生属性的特点,耦合度高;随着功能增多,子类也会变得越类越大;这个情况我们使用装饰模式,在不改变原有功能的情况,对原有功能进行一个扩展。

    mybatis采用装饰模式的典型代表就是Cache,Cache除了最基本的存储和缓存的作用外,还附加了其他的Cache类;

    如图,我们可以看到防止并发访问的SynchronizedCache、先进先出的FifoCache、最近最少使用的LruCache、定时清空缓存ScheduledCache、阻塞缓存BlockingCache等;其实如果你细心的话通过命名就知道,PerpetualCache是mybatis的基本实现类,而在包decorator下的都是其装饰模式的扩展类。再比较下这些类,你会发现装饰器的类的在有参构造中进行了方法调用,即原有对象构造时功能进行了扩展!

    小结:关于设计模式,我们基本弄清楚这些就可以了;其他的了解即可,因为处在互联网的时代,个人很难做到面面俱到,有些东西你可以不会但是你不能不知道,或者说你不能不知道学习的路径或方法!

    另外mybatis是一个很经典的框架,特别涉及到缓存方面,面试考点也很多;例如涉及到一级缓存,二级缓存,cachekey的算法实现,BlockingCache解决了缓存穿透和雪崩等;这就必须要求我们去读它的源码了:mybati源码带中文注释阅读地址:https://github.com/tuguangquan/mybatis

    余路那么长,还是得带着虔诚上路...
  • 相关阅读:
    结构型模式代理&适配器
    创建型模式单例&工厂&建造者&原型
    结构型模式装饰者&桥接&门面
    python中列表(list)的使用
    Win2003 域控制器设置和客户端安装
    Python下冒泡排序的实现
    乔布斯在斯坦福大学毕业典礼上的演讲
    字符串替换
    统计文件中某一字符串出现的次数
    [用户 'sa' 登录失败。原因: 该帐户被禁用]的解决方案
  • 原文地址:https://www.cnblogs.com/itiaotiao/p/13463195.html
Copyright © 2011-2022 走看看