我们说了职责链加上动态代理实现的MyBatis Plugin,现在我们对MyBatis用到设计模式做一个总结,其用到的设计模式不少,不下十几种,简单的学习下
SqlSessionFactoryBuilder,为什么要用建造者来创建SqlSessionFactory?
public class MyBatisDemo { public static void main(String[] args) throws IOException { Reader reader = Resources.getResourceAsReader(“mybatis.xml”); SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader); SqlSession session = sessionFactory.openSession(); UserMapper userMapper = session.getMapper(UserMapper.class); UserDo userDo = userMapper.selectById(8); //… } } |
上面中,使用SqlSessionFactoryBuilder来创建对象,但是往常的Builder都是级联一组setXXX()来设置属性,最后掉用build来创建最后的对象,但是,在上面的代码中,使用SqlSessionFactoryBuiler来创建SqlSessionFactory中,我们的build需要直接传入参数,那么,为什么还要使用SqlSessionFatroyBuilder呢,直接使用构造函数不行吗?
我们接下来看了下SqlSessionFactoryBuilder
public class SqlSessionFactoryBuilder { public SqlSessionFactory build(Reader reader) { return build(reader, null, null); } public SqlSessionFactory build(Reader reader, String environment) { return build(reader, environment, null); } public SqlSessionFactory build(Reader reader, Properties properties) { return build(reader, null, properties); } public SqlSessionFactory build(Reader reader, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException(“Error building SqlSession.”, e); } finally { ErrorContext.instance().reset(); try { reader.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } } public SqlSessionFactory build(InputStream inputStream) { return build(inputStream, null, null); } public SqlSessionFactory build(InputStream inputStream, String environment) { return build(inputStream, environment, null); } public SqlSessionFactory build(InputStream inputStream, Properties properties) { return build(inputStream, null, properties); } public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); 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. } } }
public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); } } |
在其中有着大量重载函数,我们之前说过,如果有多重重载的构造函数,同时为了避免构造函数过多,参数列表过长,我们一般使用构造者模式来解决的
但是,MyBatis中的建造者模式并不是标准的建造者模式,而且其内部也定义了足够多的构造函数,这个设计的初衷是为了简化开发,最后也足够的简化了开发工作
SqlSessionFactory
SqlSessionFactoryBuilder创建了SqlSessionFactory,并且利用这个工厂类创建了对应的SqlSession,在这个类上,看起来是融合了工厂模式,但是这并不是一个合格的工厂模式
首先是对应的源码
public class DefaultSqlSessionFactory implements SqlSessionFactory { private final Configuration configuration; public DefaultSqlSessionFactory(Configuration configuration) { this.configuration = configuration; } @Override public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); } @Override public SqlSession openSession(boolean autoCommit) { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit); } @Override public SqlSession openSession(ExecutorType execType) { return openSessionFromDataSource(execType, null, false); } @Override public SqlSession openSession(TransactionIsolationLevel level) { return openSessionFromDataSource(configuration.getDefaultExecutorType(), level, false); } @Override public SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level) { return openSessionFromDataSource(execType, level, false); } @Override public SqlSession openSession(ExecutorType execType, boolean autoCommit) { return openSessionFromDataSource(execType, null, autoCommit); } @Override public SqlSession openSession(Connection connection) { return openSessionFromConnection(configuration.getDefaultExecutorType(), connection); } @Override public SqlSession openSession(ExecutorType execType, Connection connection) { return openSessionFromConnection(execType, connection); } @Override public Configuration getConfiguration() { return configuration; } 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); final Executor executor = configuration.newExecutor(tx, execType); 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(); } } private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) { try { boolean autoCommit; try { autoCommit = connection.getAutoCommit(); } catch (SQLException e) { // Failover to true, as most poor drivers // or databases won’t support transactions autoCommit = true; } final Environment environment = configuration.getEnvironment(); final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); final Transaction tx = transactionFactory.newTransaction(connection); final Executor executor = configuration.newExecutor(tx, execType); return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { throw ExceptionFactory.wrapException(“Error opening session. Cause: ” + e, e); } finally { ErrorContext.instance().reset(); } } //…省略部分代码… } |
从SqlSessionFactory和DefaultSqlSessionFatory两者的源码来看,它的设计非常类似SqlSessionFactoryBuilder,通过重载了多个openSession()函数,,来组合创建SqlSession的对象,只是对象的属性不同,于是,可以认为比起工厂模式,更加像是建造者模式
这两者一个建造者,一个工厂,但都不是严格意义上的工厂类,而是一种类似的设计思路,我们可以参考这种设计思路,将其利用容器进行统一化管理
BaseExecutor:模板模式的使用
我们看出,Executor是一个接口,而具体的执行者BatchExecutor并没有直接实现这个接口,而是先由一个BaseExecutor抽象化实现了,然后具体的执行者去继承这个BaseExecutor
在BseExecutor中,对于Executor的接口进行了简单的实现,称为默认方法,但是在默认方法中,会调用抽象方法doXXX方法().从而实现了模板模式
public abstract class BaseExecutor implements Executor { //…省略其他无关代码…
@Override public int update(MappedStatement ms, Object parameter) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity(“executing an update”).object(ms.getId()); if (closed) { throw new ExecutorException(“Executor was closed.”); } clearLocalCache(); return doUpdate(ms, parameter); } public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException { if (closed) { throw new ExecutorException(“Executor was closed.”); } return doFlushStatements(isRollBack); } private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List<E> list; localCache.putObject(key, EXECUTION_PLACEHOLDER); try { list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { localCache.removeObject(key); } localCache.putObject(key, list); if (ms.getStatementType() == StatementType.CALLABLE) { localOutputParameterCache.putObject(key, parameter); } return list; } @Override public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameter); return doQueryCursor(ms, parameter, rowBounds, boundSql); } protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException; protected abstract List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException; protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException; protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException; } |
SqlNode:利用解释器来解析动态SQL
MyBatis一个重要特性就是动态SQL,MyBatis中的动态sql就是利用判断你的条件来动态的增加Sql条件
<update id=”update” parameterType=”com.xzg.cd.a89.User” UPDATE user <trim prefix=”SET” prefixOverrides=”,”> <if test=”name != null and name != ””> name = #{name} </if> <if test=”age != null and age != ””> , age = #{age} </if> <if test=”birthday != null and birthday != ””> , birthday = #{birthday} </if> </trim> where id = ${id} </update> |
根据mybatis给出的sqEL来动态的,生成真正执行的SQL语句,这就是解释器模式
解释器模式在解释语法的时候,会把规则划分为最小的单元,然后对应的每个单元进行解析,然后拼接在一起,mybatis会把每个语法的小单元叫做sqlNode,sqlNode的定义如下
public interface SqlNode { boolean apply(DynamicContext context); } |
整个解释器的入口在DynamicSqlSource的getBoundSql,其中的SqlNode是一个MixedSqlNode,整体调用结构和SqlNode的类似
ErrorContext:线程内唯一的单例模式
单例模式是进程唯一的,同时可以有线程唯一的,ErrorContext就是标准的线程唯一的单例.利用了ThreadLocal来实现
public class ErrorContext { private static final String LINE_SEPARATOR = System.getProperty(“line.separator”,”\n”); private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>(); 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 = LOCAL.get(); if (context == null) { context = new ErrorContext(); LOCAL.set(context); } return context; } } |
Cache,使用了装饰器模式
Mybatis是一个ORM框架,其中还提供了很多诸如Cache的实现
在Mybatis中,缓存功能由接口Cache定义,PerpetualCache是基础的缓存类,在此基础上,Mybatis还设计了其他的方式,实现了功能增强,分别是FifoCache,LoggingCache,LruCache,ScheduledCache,SerializedCache,SoftCache,SynchronizedCache,WeakCache,TransactionalCache
public interface Cache { String getId(); void putObject(Object key, Object value); Object getObject(Object key); Object removeObject(Object key); void clear(); int getSize(); ReadWriteLock getReadWriteLock(); } public class PerpetualCache implements Cache { private final String id; private Map<Object, Object> cache = new HashMap<Object, Object>(); public PerpetualCache(String id) { this.id = id; } @Override public String getId() { return id; } @Override public int getSize() { return cache.size(); } @Override public void putObject(Object key, Object value) { cache.put(key, value); } @Override public Object getObject(Object key) { return cache.get(key); } @Override public Object removeObject(Object key) { return cache.remove(key); } @Override public void clear() { cache.clear(); } @Override public ReadWriteLock getReadWriteLock() { return null; } //省略部分代码… } |
里面是一个标准的装饰器类,标准的装饰器的实现
其中,我们拿出LruCache的源码,来仔细看下装饰器类的实现方式
public class LruCache implements Cache { private final Cache delegate; private Map<Object, Object> keyMap; private Object eldestKey; public LruCache(Cache delegate) { this.delegate = delegate; setSize(1024); } @Override public String getId() { return delegate.getId(); } @Override public int getSize() { return delegate.getSize(); } public void setSize(final int size) { keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) { private static final long serialVersionUID = 4267176411845948333L; @Override protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) { boolean tooBig = size() > size; if (tooBig) { eldestKey = eldest.getKey(); } return tooBig; } }; } @Override public void putObject(Object key, Object value) { delegate.putObject(key, value); cycleKeyList(key); } @Override public Object getObject(Object key) { keyMap.get(key); //touch return delegate.getObject(key); } @Override public Object removeObject(Object key) { return delegate.removeObject(key); } @Override public void clear() { delegate.clear(); keyMap.clear(); } @Override public ReadWriteLock getReadWriteLock() { return null; } private void cycleKeyList(Object key) { keyMap.put(key, key); if (eldestKey != null) { delegate.removeObject(eldestKey); eldestKey = null; } } } |
之所以Mybatis使用了装饰器模式实现缓存,是因为装饰器模式基于组合,而非继承,更加的灵活,有效的避免继承关系的组合爆炸
PropertyTokenizer:迭代器实现的属性解析器
我们说的,迭代器模式常用语替代For来循环遍历集合的元素,Mybatis的propertyTokenizer实现了Java的iterator接口,是一个迭代器,用来对配置属性来进行解析
// person[0].birthdate.year 会被分解为3个PropertyTokenizer对象。其中,第一个PropertyTokenizer对象的各个属性值如注释所示。? public class PropertyTokenizer implements Iterator<PropertyTokenizer> { private String name; // person private final String indexedName; // person[0] private String index; // 0 private final String children; // birthdate.year public PropertyTokenizer(String fullname) { int delim = fullname.indexOf(‘.’); if (delim > -1) { name = fullname.substring(0, delim); children = fullname.substring(delim + 1); } else { name = fullname; children = null; } indexedName = name; delim = name.indexOf(‘[‘); if (delim > -1) { index = name.substring(delim + 1, name.length() – 1); name = name.substring(0, delim); } } public String getName() { return name; } public String getIndex() { return index; } public String getIndexedName() { return indexedName; } public String getChildren() { return children; } @Override public boolean hasNext() { return children != null; } @Override public PropertyTokenizer next() { return new PropertyTokenizer(children); } @Override public void remove() { throw new UnsupportedOperationException(“Remove is not supported, as it has no meaning in the context of properties.”); } } |
将配置的解析,解析后的元素,迭代器,这三个部分放在三个类的代码进行了集合,集合在了一个类中,这样可以惰性解析,只有我们调用next()函数的时候,才会解析部分配置
Log:使用适配器模式来适配不同的日志框架
Slf4j框架为了统一不同的日志框架,提供了统一的日志接口,不过,Mybatis并没有提供使用Slf4j提供统一的日志规范,而是,自己重复造了轮子,定义了同一的访问接口
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); } |
针对Log接口,Mybatis提供了不同的实现类,对不同的日志框架实现了Log接口
在适配器模式中,传递给适配器构造函数的是被适配的类的对象,然后在这里进行了二次封装,进行了重新的适配作用
本章重点
我们说了Mybatis还涉及的8种设计模式,分别是建造者模式,工厂模式,模板模式,解释器模式,单例模式,装饰器模式,迭代器模式,适配器模式
我们需要对这些涉及的模式好好的看一下,钻研一下
课后讨论
我们说,SqlSessionFactoryBuildler和SqlSessionFacotry都是为了隐藏SqlSession的创建细节而存在的,不过都不是标准的模板实现
设计思想比设计模式更重要,只要符合其设计的本意,没什么大不了的