Mybatis 用到的设计模式
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()方法中:
可以看到:
- ①:当前命名空间是
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的关键字;等等。
-
我们随便找两个看看;
-
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日志。
-
举个例子:
/** * 典型的(对象)适配器模式: * ★ 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都是它的子类或间接子类实现)
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操作返回结果对象。
其实,我们可以看源码里面,人家用到设计模式,并不拘泥于形式,生搬硬造的套设计模式,都是自然而然在一些场景下就用到了。
转载自:https://juejin.cn/post/7353075407761735692