likes
comments
collection
share

Mybatis 用到的设计模式

作者站长头像
站长
· 阅读数 8

Mybatis 用到的设计模式

在看文章之前,先说一下,其实我们不应该关注设计模式本身;而应该是我们在写代码时候,因为某种场景,考虑到代码的扩展性,适配性,耦合性等问题,自然而然用到了某种设计模式。

源码中,有些切合的场景供我们去引用学习;才是这篇文章的目的。

当然了,想学习设计模式,我们确实可以,在了解一个模式的时候,在自己的项目中,生搬硬造给用让,也是成长。

这些设计模式的基本应用案例应用,不再介绍了,直接看本人源码库 『Mybatis源码库』对应的base包就行:

  • 建造者模式:org.apache.ibatis.builder.base.builderpattern

  • 装饰器模式:org.apache.ibatis.cache.base.decoratorpattern

  • 适配器模式:org.apache.ibatis.logging.base.adapterpattern

  • 责任链模式:org.apache.ibatis.plugin.base.chainresponsibilitypatthern

  • 模板方法模式:org.apache.ibatis.type.base.templatepattern

  • 代理模式:

    • 静态代理:org.apache.ibatis.io.base.staticproxy
    • 动态代理:org.apache.ibatis.logging.base.dynamicproxy

下面章节直接介绍在 Mybatis中应用到的场景。

没看过源码的同学,可能直接看有点懵,正常;多看看源码就行了。

一、建造者模式

应用场景:缓存对象的建造者;构建Cache缓存对象。

可以看下,下面的属性参数都会通过对应的setter将赋值给到CacheBuilder中对应的字段(废话!);

然后并返回 this,即当前 CacheBuilder本身

最后在调用build()方法,构建Cache对象并返回。

  • Cache:被构建的对象,该类是一个接口,下面有各种类型的 Cache类型,像:TransactionalCache:事务缓存,LoggingCache:日志缓存等。这里先不介绍,在第二节 装饰器模式中介绍,如何将Cache包装成对应类型的缓存再详细介绍。

    public interface Cache {
    
        /**
         * 获取缓存id
         */
        String getId();
    
        /**
         * 向缓存写入一条信息
         */
        void putObject(Object key, Object value);
    
        /**
         * 从缓存中读取一条信息
         */
        Object getObject(Object key);
    
        /**
         * 从缓存中删除一条信息,并返回value
         */
        Object removeObject(Object key);
    
        /**
         * 清空缓存
         */
        void clear();
    
        /**
         * 读取缓存中信息的数目
         */
        int getSize();
    
        default ReadWriteLock getReadWriteLock() {
            return null;
        }
    }
    
  • 构建者

    public class CacheBuilder {
      // Cache的编号
      private final String id;
      // Cache的实现类 (cache.decorators包下的,如:BlockingCache,FifoCache,TransactionalCache...)
      private Class<? extends Cache> implementation;
      // Cache的装饰器列表
      private final List<Class<? extends Cache>> decorators;
      // Cache的大小
      private Integer size;
      // Cache的清理间隔
      private Long clearInterval;
      // Cache是否可读写
      private boolean readWrite;
      // Cache的配置信息
      private Properties properties;
      // Cache是否阻塞
      private boolean blocking;
    
      public CacheBuilder(String id) {
        this.id = id;
        this.decorators = new ArrayList<>();
      }
    
      public CacheBuilder implementation(Class<? extends Cache> implementation) {
        this.implementation = implementation;
        return this;
      }
    
      public CacheBuilder addDecorator(Class<? extends Cache> decorator) {
        if (decorator != null) {
          this.decorators.add(decorator);
        }
        return this;
      }
    
      public CacheBuilder size(Integer size) {
        this.size = size;
        return this;
      }
    
      public CacheBuilder clearInterval(Long clearInterval) {
        this.clearInterval = clearInterval;
        return this;
      }
    
      public CacheBuilder readWrite(boolean readWrite) {
        this.readWrite = readWrite;
        return this;
      }
    
      public CacheBuilder blocking(boolean blocking) {
        this.blocking = blocking;
        return this;
      }
    
      public CacheBuilder properties(Properties properties) {
        this.properties = properties;
        return this;
      }
    
      /**
       * 组建缓存
       * @return 缓存对象
       *
       * ege :
       *    <cache type="PERPETUAL"
       *           eviction="FIFO"
       *           flushInterval="60000"
       *           size="512"
       *           readOnly="true"
       *           blocking="true" >
       *           <!-- 可以增加property节点,将用来直接修改Cache实现类及装饰器类的属性,就是对 properties 属性的设置  -->
       *    </cache>
       */
      public Cache build() {
        // 设置缓存的默认实现、默认装饰器(仅设置,并未装配)
        setDefaultImplementations();
        // 创建默认的缓存
        Cache cache = newBaseCacheInstance(implementation, id);
        // 设置缓存的属性
        setCacheProperties(cache);
        // 缓存实现是PerpetualCache,即不是用户自定义的缓存实现
        if (PerpetualCache.class.equals(cache.getClass())) {
          // 为缓存逐级嵌套自定义的装饰器
          for (Class<? extends Cache> decorator : decorators) {
            // 生成装饰器实例,并装配。入参依次是装饰器类、被装饰的缓存
            // 利用for循环给 cache套娃 (装饰器列表有多少种类型就嵌套多少次)
            cache = newCacheDecoratorInstance(decorator, cache);
            // 为装饰器设置属性
            setCacheProperties(cache);
          }
          // 为缓存增加标准的装饰器
          cache = setStandardDecorators(cache);
        } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
          // 增加日志装饰器
          cache = new LoggingCache(cache);
        }
        // 返回被包装好的缓存
        return cache;
      }
    }
    
  • 应用:

    MapperBuilderAssistant类中【org.apache.ibatis.builder.MapperBuilderAssistant】,org.apache.ibatis.builder.MapperBuilderAssistant#useNewCache该方法,用于构建缓存对象。

    public class MapperBuilderAssistant extends BaseBuilder {
        /**
         * 构建缓存对象
         */
        public Cache useNewCache(Class<? extends Cache> typeClass,
                                 Class<? extends Cache> evictionClass,
                                 Long flushInterval,
                                 Integer size,
                                 boolean readWrite,
                                 boolean blocking,
                                 Properties props) {
            // CacheBuilder 是一个建造者类,用于构建不同的缓存对象
            // 里面的 build()方法最终返回 Cache对象、 里面的.***()方法设置对象属性
            Cache cache = new CacheBuilder(currentNamespace)
                                    .implementation(valueOrDefault(typeClass, PerpetualCache.class))
                                    .addDecorator(valueOrDefault(evictionClass, LruCache.class))
                                    .clearInterval(flushInterval)
                                    .size(size)
                                    .readWrite(readWrite)
                                    .blocking(blocking)
                                    .properties(props)
                                    .build();
            configuration.addCache(cache);
            currentCache = cache;
            return cache;
        }
    }
    
  • 测试:

    在自己的 ***Mapper.xml 中配置:(这种方式的话,就是XML配置文件解析)

      <!--缓存-->
      <cache/>
    

    代码跟踪位置:

    /*
     * 映射文件(***Mapper.xml)的解析
     */
    public class XMLMapperBuilder extends BaseBuilder {
    
        // 解析 ***Mapper.xml文件中的 <cache/>节点
        private void cacheElement(XNode context) {
            if (context != null) {
                String type = context.getStringAttribute("type", "PERPETUAL");
                Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
                String eviction = context.getStringAttribute("eviction", "LRU");
                Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
                Long flushInterval = context.getLongAttribute("flushInterval");
                Integer size = context.getIntAttribute("size");
                boolean readWrite = !context.getBooleanAttribute("readOnly", false);
                boolean blocking = context.getBooleanAttribute("blocking", false);
                Properties props = context.getChildrenAsProperties();
    
                // 这里就是构建 缓存对象的位置, 调用的就是上面的 useNewCache()方法
                builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
            }
        }
    }
    

    我们debug到useNewCache()方法中:

    Mybatis 用到的设计模式

    可以看到:

    • ①:当前命名空间是com.yogurt.example.dao.TestMapperDao,就是我们添加了 节点的Mapper;
    • ②:cache:就是我们构建的缓存对象;只不过Cache类型是 SynchronizedCache(Cache的一个子类)

二、装饰器模式

应用场景:缓存Cache ,被装饰成各种类型的缓存,其实就是为了满足遇到的一些应用场景。

  • 被装饰者:

    public interface Cache {
    
      /**
       * 获取缓存id
       */
      String getId();
    
      /**
       * 向缓存写入一条信息
       */
      void putObject(Object key, Object value);
    
      /**
       * 从缓存中读取一条信息
       */
      Object getObject(Object key);
    
      /**
       * 从缓存中删除一条信息,并返回value
       */
      Object removeObject(Object key);
    
      /**
       * 清空缓存
       */
      void clear();
    
      /**
       * 读取缓存中信息的数目
       */
      int getSize();
    
      default ReadWriteLock getReadWriteLock() {
        return null;
      }
    
    }
    
  • 装饰器:

    我们直接去 org.apache.ibatis.cache.decorators 包中 ,这个里面的都是 Cache的装饰器;

    如:日志装饰器(LoggingCache):为缓存增加日志统计的功能;

    同步装饰器(SynchronizedCache):多个线程同时访问一个缓存的情况;可以进入该类看到,方法都加了synchronized的关键字;等等。

Mybatis 用到的设计模式

  • 我们随便找两个看看;

    • BlockingCache:

      下面我们可以学习到 利用 ConcurrentHashMap 做一个Map,key:缓存的Id,value:锁 ReentrantLock

      这样,我们是不是就学习了 ConcurrentHashMap 和 ReentrantLock 的应用场景,nice呀!~

      /**
       * 阻塞装饰器:
       *        用于解决:当 MyBatis 接收到一条数据库查询请求,而对应的查询结果在缓存中不存在时,MyBatis会通过数据库进行查询。
       *    试想如果在数据库查询尚未结束时,MyBatis又收到一条完全相同的数据库查询请求。
       *
       * 两种解决方案:
       *    (1) 因为缓存中没有对应的缓存结果,因此再发起一条数据库查询请求,这会导致数据库短时间内收到两条完全相同的查询请求。
       *    (2) 虽然缓存中没有对应的缓存结果,但是已经向数据库发起过一次请求,因此缓存应该先阻塞住第二次查询请求。等待数据库查
       *      询结束后,将数据库的查询结果返回给两次查询请求即可。  √ 取第二种方案!!!
       *
       * BlockingCache中的缓存数据读写方法。
       *     在读取缓存中的数据前需要获取该数据对应的锁,如果从缓存中读取到了对应的数据,则立刻释放该锁;
       *     如果从缓存中没有读取到对应的数据,则意味着接下来会进行数据库查询,直到数据库查询结束向缓存中写入该数据时,才会释放该数据的锁。
       */
      public class BlockingCache implements Cache {
      
        // 被装饰对象
        private final Cache delegate;
      
        // 获取锁时的运行等待时间
        private long timeout;
        // 锁的映射表。键为缓存记录的键,值为对应的锁。
        private final ConcurrentHashMap<Object, ReentrantLock> locks;
      
        public BlockingCache(Cache delegate) {
          this.delegate = delegate;
          this.locks = new ConcurrentHashMap<>();
        }
      
        @Override
        public String getId() {
          return delegate.getId();
        }
      
        @Override
        public int getSize() {
          return delegate.getSize();
        }
      
        /**
         * 向缓存写入一条信息
         * @param key 信息的键
         * @param value 信息的值
         */
        @Override
        public void putObject(Object key, Object value) {
          try {
            delegate.putObject(key, value);
          } finally {
            releaseLock(key);  //释放锁
          }
        }
      
        /**
         * 从缓存中读取一条信息
         * @param key 信息的键
         * @return 信息的值
         */
        @Override
        public Object getObject(Object key) {
          acquireLock(key);  // 获取锁
          Object value = delegate.getObject(key);  //从缓存中获取 value
          if (value != null) {
            releaseLock(key);  // 读取到结果后释放锁
          }
          // 如果 if (value != null) 不走,说明缓存中没有对应数据,就不会释放锁;
          // ★ 对应的锁会在从数据库读取了结果并写入到缓存后,在putObject中释放。
          // ??? 这里不是很懂,拿到锁;缓存有没有数据,都返回value,只是锁没有释放。
          //     当 putObject()时,会释放锁,那么怎么有 解决方案(2)的效果???
          //
          //  理解错啦
          //     上面的acquireLock(key);方法不一定能获取到锁,当出现类上注释说的问题的时候,
          //     第二个请求,是不会获取到锁的,会进入到阻塞队列中
          //     所以,应该在 acquireLock(key)这里就阻塞住了;
          //  我敲,这种还是需要场景复现一下!!!
          return value;
        }
      
        @Override
        public Object removeObject(Object key) {
          // despite of its name, this method is called only to release locks
          releaseLock(key);
          return null;
        }
      
        @Override
        public void clear() {
          delegate.clear();
        }
      
        /**
         * 找出指定键的锁
         * @param key 指定的键
         * @return 该键对应的锁
         */
        private ReentrantLock getLockForKey(Object key) {
          // 如果没有找到,就 new一个锁
          return locks.computeIfAbsent(key, k -> new ReentrantLock());
        }
      
        /**
         * 获取某个键的锁
         * @param key 数据的键
         */
        private void acquireLock(Object key) {
          Lock lock = getLockForKey(key); // 找出指定对象的锁
          if (timeout > 0) {  // 如果设置了超时时间,那么持有锁的时间就只有 timeout这么长
            try {
              boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS);
              if (!acquired) {
                throw new CacheException("Couldn't get a lock in " + timeout 
                                         + " for the key " +  key + " at the cache " + delegate.getId());
              }
            } catch (InterruptedException e) {
              throw new CacheException("Got interrupted while trying to acquire lock for key " + key, e);
            }
          } else {
            lock.lock();  //没有设置超时时间就直接加锁
          }
        }
      
        /**
         * 释放锁 释放某个对象的锁
         * @param key 被锁的对象
         */
        private void releaseLock(Object key) {
          // 找出指定对象的锁
          ReentrantLock lock = locks.get(key);
          // 看看是不是当前线程持有的锁,如果是就释放
          if (lock.isHeldByCurrentThread()) {
            lock.unlock();
          }
        }
      
        public long getTimeout() {
          return timeout;
        }
      
        public void setTimeout(long timeout) {
          this.timeout = timeout;
        }
      }
      
    • SynchronizedCache:该类为了解决 多个线程同时访问一个缓存的情况

      /**
       * 该类为了解决 多个线程同时访问一个缓存的情况;
       * 如:多个线程同时调用selectByPrimaryKey方法,则这两个线程会同时访问id为:com.yogurt.example.mapper.TestMapperDao的这个缓存
       *    <mapper namespace="com.yogurt.example.mapper.TestMapperDao">
       *         <cache/>
       *         <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
       *            select <include refid="Base_Column_List" />
       *            from test_mapper
       *            where id = #{id,jdbcType=INTEGER}
       *        </select>
       *     </mapper>
       *
       * 装饰器:(同步装饰器类 SynchronizedCache)
       * delegate 是被装饰者
       *
       * 下面方法加了 synchronized 起线程同步的作用;
       * 其实就是 该类在需要同步的方法上加了 synchronized装饰了 delegate的方法。
       */
      public class SynchronizedCache implements Cache {
      
          // delegate 英 [ˈdelɪɡət , ˈdelɪɡeɪt]  n. 代表
          // 被装饰者
          private final Cache delegate;
      
          // delegate 传入的形参,就是被装饰者
          public SynchronizedCache(Cache delegate) {
              this.delegate = delegate;
          }
      
          @Override
          public String getId() {
              return delegate.getId();
          }
      
          @Override
          public synchronized int getSize() {
              return delegate.getSize();
          }
      
          @Override
          public synchronized void putObject(Object key, Object object) {
              delegate.putObject(key, object);
          }
      
          @Override
          public synchronized Object getObject(Object key) {
              return delegate.getObject(key);
          }
      
          @Override
          public synchronized Object removeObject(Object key) {
              return delegate.removeObject(key);
          }
      
          @Override
          public synchronized void clear() {
              delegate.clear();
          }
      
          @Override
          public int hashCode() {
              return delegate.hashCode();
          }
      
          @Override
          public boolean equals(Object obj) {
              return delegate.equals(obj);
          }
      
      }
      
  • 应用:

    其实就是 上一节,建造者模式中, build()方法 通过获取配置文件中的属性信息,根据属性信息将Cache装饰成不同类型的缓存。

    org.apache.ibatis.mapping.CacheBuilder#build

    我们可以看到,下面的build()方法,会根据配置属性,构建不同的缓存类型。

    public class CacheBuilder {
        // Cache的编号
        private final String id;
        // Cache的实现类 (cache.decorators包下的,如:BlockingCache,FifoCache,TransactionalCache...)
        private Class<? extends Cache> implementation;
    
        // Cache的装饰器列表
        //   Cache类型就是 org.apache.ibatis.cache.decorators 包中的类
        private final List<Class<? extends Cache>> decorators;
    
        // Cache的大小
        private Integer size;
        // Cache的清理间隔
        private Long clearInterval;
        // Cache是否可读写
        private boolean readWrite;
        // Cache的配置信息
        private Properties properties;
        // Cache是否阻塞
        private boolean blocking;
        /**
         * 组建缓存
         * @return 缓存对象
         *
         * ege :
         *    <cache type="PERPETUAL"
         *           eviction="FIFO"
         *           flushInterval="60000"
         *           size="512"
         *           readOnly="true"
         *           blocking="true" >
         *           <!-- 可以增加property节点,将用来直接修改Cache实现类及装饰器类的属性,就是对 properties 属性的设置  -->
         *    </cache>
         */
        public Cache build() {
            // 设置缓存的默认实现、默认装饰器(仅设置,并未装配)
            setDefaultImplementations();
            // 创建默认的缓存
            Cache cache = newBaseCacheInstance(implementation, id);
            // 设置缓存的属性
            setCacheProperties(cache);
            // 缓存实现是PerpetualCache,即不是用户自定义的缓存实现
            if (PerpetualCache.class.equals(cache.getClass())) {
                // 为缓存逐级嵌套自定义的装饰器
                for (Class<? extends Cache> decorator : decorators) {
                    // 生成装饰器实例,并装配。入参依次是装饰器类、被装饰的缓存
                    // 利用for循环给 cache套娃 (装饰器列表有多少种类型就嵌套多少次)
                    cache = newCacheDecoratorInstance(decorator, cache);
                    // 为装饰器设置属性
                    setCacheProperties(cache);
                }
                // 为缓存增加标准的装饰器
                cache = setStandardDecorators(cache);
            } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
                // 增加日志装饰器
                cache = new LoggingCache(cache);
            }
            // 返回被包装好的缓存
            return cache;
        }
    }
    

三、适配器模式

Log 日志适配器

应用场景:Log 日志适配器;目的是为了,将能考虑到的Log日志类型和Mybatis日志适配,即下面的日志Log类:

  • 适配器接口:(其实,这里的Log类实现有点特殊,并没有完全按照所谓的适配器模式的标准去定义,这个Log类,既做了适配器接口,又做了目标类[要被适配的目标类])

    public interface Log {
        boolean isDebugEnabled();
        boolean isTraceEnabled();
        void error(String s, Throwable e);
        void error(String s);
        void debug(String s);
        void trace(String s);
        void warn(String s);
    }
    
  • 适配器接口的子类实现:

    下图是源码中 logging包,像里面的: jdbc、jdk14、log4j、log4j2这些都是 日志类型,适配器的目的,就是为了适配Mybatis日志。

Mybatis 用到的设计模式

  • 举个例子:

    /**
     * 典型的(对象)适配器模式:
     *    ★ log将自身所有的方法都委托给了(org.apache.ibatis.logging.Log)这个对象
     *    1.org.apache.ibatis.logging.Log:是适配器接口
     *    2.JakartaCommonsLoggingImpl:是适配器的具体实现
     *        下面的所有 @Override的实现是 实现的org.apache.ibatis.logging.Log
     *    3.private final Log log:是目标类(要被适配的类)
     *        这个log是当前Mybatis包的Log.
     */
    public class JakartaCommonsLoggingImpl implements org.apache.ibatis.logging.Log {
      // 目标类,要被适配的类
      private final Log log;
    
      public JakartaCommonsLoggingImpl(String clazz) {
        log = LogFactory.getLog(clazz);
      }
    
      @Override
      public boolean isDebugEnabled() {
        return log.isDebugEnabled();
      }
    
      @Override
      public boolean isTraceEnabled() {
        return log.isTraceEnabled();
      }
    
      @Override
      public void error(String s, Throwable e) {
        log.error(s, e);
      }
    
      @Override
      public void error(String s) {
        log.error(s);
      }
    
      @Override
      public void debug(String s) {
        log.debug(s);
      }
    
      @Override
      public void trace(String s) {
        log.trace(s);
      }
    
      @Override
      public void warn(String s) {
        log.warn(s);
      }
    
    }
    
  • 应用:

    会在日志工厂类中org.apache.ibatis.logging.LogFactory应用。

    /**
     *  LogFactory就是制造实现类的工厂。
     *  最终,该工厂会给出一个可用的Log实现类,由它来完成MyBatis的日志打印工作。
     *  其实就是调用 getLog()方法
     */
    public final class LogFactory {
        public static final String MARKER = "MYBATIS";
        private static Constructor<? extends Log> logConstructor;
    
        static {
            // 下面这么多日志类型,只有一种日志类型会实例化成功;
            // 默认是:useSlf4jLogging
            // 可以配置
            tryImplementation(LogFactory::useSlf4jLogging);
            tryImplementation(LogFactory::useCommonsLogging);
            tryImplementation(LogFactory::useLog4J2Logging);
            tryImplementation(LogFactory::useLog4JLogging);
            tryImplementation(LogFactory::useJdkLogging);
            tryImplementation(LogFactory::useNoLogging);
        }
    
        /**
         * 这个方法 就是定义在类中 日志方法
         * @param aClass
         * @return
         */
        public static Log getLog(Class<?> aClass) {
            return getLog(aClass.getName());
        }
    
        // ......省略一万字
    }
    

四、责任链模式

应用场景:各种类型的拦截器,添加到连接器列表中,对目标方法拦截;目标方法穿过责任链,经过一遍,符合的拦截器会对目标方法增强。

那么最终, 目标方法被执行的时候,执行的是代理对象终极形态,即穿过了整个拦截器最终的形成的代理类。

五、模板方法模式

应用场景:定义处理 各种类型(Java类型:基础数据类型,引用数据类型等;数据库的:varchar,decimal等)的处理器的模板,然后交由子类实现其抽象出来的模板方法

  • 模板方法:

    public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
        /**
         * 从结果集中读出一个结果
         * @param rs 结果集
         * @param columnName 要读取的结果集的列的名称
         * @return 结果值
         * @throws SQLException
         */
        @Override
        public T getResult(ResultSet rs, String columnName) throws SQLException {
            try {
                // 抽象模板1
                return getNullableResult(rs, columnName);
            } catch (Exception e) {
                throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set.  Cause: " + e, e);
            }
        }
    
        @Override
        public T getResult(ResultSet rs, int columnIndex) throws SQLException {
            try {
                // 抽象模板2
                return getNullableResult(rs, columnIndex);
            } catch (Exception e) {
                throw new ResultMapException("Error attempting to get column #" + columnIndex + " from result set.  Cause: " + e, e);
            }
        }
    
        @Override
        public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
            try {
                // 抽象模板3
                return getNullableResult(cs, columnIndex);
            } catch (Exception e) {
                throw new ResultMapException("Error attempting to get column #" 
                                             + columnIndex + " from callable statement.  Cause: " + e, e);
            }
        }
    
        //  下面这4个,作为BaseTypeHandler的抽象方法,会被下面继承了该类的处理器实现
        //  ★★★就是定义了模板方法,交由子类实现★★★
        //
        /**
         * 向PreparedStatement对象中的指定变量位置写入一个不为null的值
         * @param ps
         * @param i
         * @param parameter
         * @param jdbcType
         * @throws SQLException
         */
        public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
    
        /**
         * 抽象模板1
         * 从ResultSet中按照字段名读出一个可能为null的数据
         */
        public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;
    
        /**
         * 抽象模板2
         * 从ResultSet中按照字段编号读出一个可能为null的数据;
         */
        public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException;
    
        /**
         * 抽象模板3
         * 从CallableStatement中按照字段编号读出一个可能为null的数据。
         */
        public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;
    }
    
  • 子类定义具体的实现:(在 type包下面的 handler都是它的子类或间接子类实现)

Mybatis 用到的设计模式

我们随便找一个看看:
public class BigDecimalTypeHandler extends BaseTypeHandler<BigDecimal> {
    @Override
    public BigDecimal getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return rs.getBigDecimal(columnName);
    }

    @Override
    public BigDecimal getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return rs.getBigDecimal(columnIndex);
    }

    @Override
    public BigDecimal getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return cs.getBigDecimal(columnIndex);
    }
}

六、代理模式

org.apache.ibatis.binding我们找这个包。

就是利用代理模式,实现当我们执行Mapper接口中的方法时,转而由Mybatis执行代理对象,进行一系列的处理,执行SQL操作返回结果对象。


其实,我们可以看源码里面,人家用到设计模式,并不拘泥于形式,生搬硬造的套设计模式,都是自然而然在一些场景下就用到了。