CategoryResourceRepost/极客时间专栏/设计模式之美/开源与项目实战:开源实战/89 | 开源实战五(下):总结MyBatis框架中用到的10种设计模式.md
louzefeng d3828a7aee mod
2024-07-11 05:50:32 +00:00

679 lines
29 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<audio id="audio" title="89 | 开源实战五总结MyBatis框架中用到的10种设计模式" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/8c/57/8cf480d2711c845dbd7ea62681bebd57.mp3"></audio>
上节课我带你剖析了利用职责链模式和动态代理模式实现MyBatis Plugin。至此我们已经学习了三种职责链常用的应用场景过滤器Servlet Filter、拦截器Spring Interceptor、插件MyBatis Plugin
今天我们再对MyBatis用到的设计模式做一个总结。它用到的设计模式也不少就我所知的不下十几种。有些我们前面已经讲到有些比较简单。有了前面这么多讲的学习和训练我想你现在应该已经具备了一定的研究和分析能力能够自己做查缺补漏把提到的所有源码都搞清楚。所以在今天的课程中如果有哪里有疑问你尽可以去查阅源码自己先去学习一下有不懂的地方再到评论区和大家一起交流。
话不多说,让我们正式开始今天的学习吧!
## SqlSessionFactoryBuilder为什么要用建造者模式来创建SqlSessionFactory
在[第87讲](https://time.geekbang.org/column/article/239239)中我们通过一个查询用户的例子展示了用MyBatis进行数据库编程。为了方便你查看我把相关的代码重新摘抄到这里。
```
public class MyBatisDemo {
public static void main(String[] args) throws IOException {
Reader reader = Resources.getResourceAsReader(&quot;mybatis.xml&quot;);
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession session = sessionFactory.openSession();
UserMapper userMapper = session.getMapper(UserMapper.class);
UserDo userDo = userMapper.selectById(8);
//...
}
}
```
针对这段代码,请你思考一下下面这个问题。
之前讲到建造者模式的时候我们使用Builder类来创建对象一般都是先级联一组setXXX()方法来设置属性然后再调用build()方法最终创建对象。但是在上面这段代码中通过SqlSessionFactoryBuilder来创建SqlSessionFactory并不符合这个套路。它既没有setter方法而且build()方法也并非无参需要传递参数。除此之外从上面的代码来看SqlSessionFactory对象的创建过程也并不复杂。那直接通过构造函数来创建SqlSessionFactory不就行了吗为什么还要借助建造者模式创建SqlSessionFactory呢
要回答这个问题我们就要先看下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(&quot;Error building SqlSession.&quot;, 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(&quot;Error building SqlSession.&quot;, 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);
}
}
```
SqlSessionFactoryBuilder类中有大量的build()重载函数。为了方便你查看以及待会儿跟SqlSessionFactory类的代码作对比我把重载函数定义抽象出来贴到这里。
```
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(Reader reader);
public SqlSessionFactory build(Reader reader, String environment);
public SqlSessionFactory build(Reader reader, Properties properties);
public SqlSessionFactory build(Reader reader, String environment, Properties properties);
public SqlSessionFactory build(InputStream inputStream);
public SqlSessionFactory build(InputStream inputStream, String environment);
public SqlSessionFactory build(InputStream inputStream, Properties properties);
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties);
// 上面所有的方法最终都调用这个方法
public SqlSessionFactory build(Configuration config);
}
```
我们知道如果一个类包含很多成员变量而构建对象并不需要设置所有的成员变量只需要选择性地设置其中几个就可以。为了满足这样的构建需求我们就要定义多个包含不同参数列表的构造函数。为了避免构造函数过多、参数列表过长我们一般通过无参构造函数加setter方法或者通过建造者模式来解决。
从建造者模式的设计初衷上来看SqlSessionFactoryBuilder虽然带有Builder后缀但不要被它的名字所迷惑它并不是标准的建造者模式。一方面原始类SqlSessionFactory的构建只需要一个参数并不复杂。另一方面Builder类SqlSessionFactoryBuilder仍然定义了n多包含不同参数列表的构造函数。
实际上SqlSessionFactoryBuilder设计的初衷只不过是为了简化开发。因为构建SqlSessionFactory需要先构建Configuration而构建Configuration是非常复杂的需要做很多工作比如配置的读取、解析、创建n多对象等。为了将构建SqlSessionFactory的过程隐藏起来对程序员透明MyBatis就设计了SqlSessionFactoryBuilder类封装这些构建细节。
## SqlSessionFactory到底属于工厂模式还是建造器模式
在刚刚那段MyBatis示例代码中我们通过SqlSessionFactoryBuilder创建了SqlSessionFactory然后再通过SqlSessionFactory创建了SqlSession。刚刚我们讲了SqlSessionFactoryBuilder现在我们再来看下SqlSessionFactory。
从名字上你可能已经猜到SqlSessionFactory是一个工厂类用到的设计模式是工厂模式。不过它跟SqlSessionFactoryBuilder类似名字有很大的迷惑性。实际上它也并不是标准的工厂模式。为什么这么说呢我们先来看下SqlSessionFactory类的源码。
```
public interface SqlSessionFactory {
SqlSession openSession();
SqlSession openSession(boolean autoCommit);
SqlSession openSession(Connection connection);
SqlSession openSession(TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType);
SqlSession openSession(ExecutorType execType, boolean autoCommit);
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType, Connection connection);
Configuration getConfiguration();
}
```
SqlSessionFactory是一个接口DefaultSqlSessionFactory是它唯一的实现类。DefaultSqlSessionFactory源码如下所示
```
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(&quot;Error opening session. Cause: &quot; + 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(&quot;Error opening session. Cause: &quot; + e, e);
} finally {
ErrorContext.instance().reset();
}
}
//...省略部分代码...
}
```
从SqlSessionFactory和DefaultSqlSessionFactory的源码来看它的设计非常类似刚刚讲到的SqlSessionFactoryBuilder通过重载多个openSession()函数支持通过组合autoCommit、Executor、Transaction等不同参数来创建SqlSession对象。标准的工厂模式通过type来创建继承同一个父类的不同子类对象而这里只不过是通过传递进不同的参数来创建同一个类的对象。所以它更像建造者模式。
虽然设计思路基本一致但一个叫xxxBuilderSqlSessionFactoryBuilder一个叫xxxFactorySqlSessionFactory。而且叫xxxBuilder的也并非标准的建造者模式叫xxxFactory的也并非标准的工厂模式。所以我个人觉得MyBatis对这部分代码的设计还是值得优化的。
实际上这两个类的作用只不过是为了创建SqlSession对象没有其他作用。所以我更建议参照Spring的设计思路把SqlSessionFactoryBuilder和SqlSessionFactory的逻辑放到一个叫“ApplicationContext”的类中。让这个类来全权负责读入配置文件创建Congfiguration生成SqlSession。
## BaseExecutor模板模式跟普通的继承有什么区别
如果去查阅SqlSession与DefaultSqlSession的源码你会发现SqlSession执行SQL的业务逻辑都是委托给了Executor来实现。Executor相关的类主要是用来执行SQL。其中Executor本身是一个接口BaseExecutor是一个抽象类实现了Executor接口而BatchExecutor、SimpleExecutor、ReuseExecutor三个类继承BaseExecutor抽象类。
那BatchExecutor、SimpleExecutor、ReuseExecutor三个类跟BaseExecutor是简单的继承关系还是模板模式关系呢怎么来判断呢我们看一下BaseExecutor的源码就清楚了。
```
public abstract class BaseExecutor implements Executor {
//...省略其他无关代码...
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity(&quot;executing an update&quot;).object(ms.getId());
if (closed) {
throw new ExecutorException(&quot;Executor was closed.&quot;);
}
clearLocalCache();
return doUpdate(ms, parameter);
}
public List&lt;BatchResult&gt; flushStatements(boolean isRollBack) throws SQLException {
if (closed) {
throw new ExecutorException(&quot;Executor was closed.&quot;);
}
return doFlushStatements(isRollBack);
}
private &lt;E&gt; List&lt;E&gt; queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List&lt;E&gt; 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 &lt;E&gt; Cursor&lt;E&gt; 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&lt;BatchResult&gt; doFlushStatements(boolean isRollback) throws SQLException;
protected abstract &lt;E&gt; List&lt;E&gt; doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException;
protected abstract &lt;E&gt; Cursor&lt;E&gt; doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException;
}
```
模板模式基于继承来实现代码复用。如果抽象类中包含模板方法模板方法调用有待子类实现的抽象方法那这一般就是模板模式的代码实现。而且在命名上模板方法与抽象方法一般是一一对应的抽象方法在模板方法前面多一个“do”比如在BaseExecutor类中其中一个模板方法叫update()那对应的抽象方法就叫doUpdate()。
## SqlNode如何利用解释器模式来解析动态SQL
支持配置文件中编写动态SQL是MyBatis一个非常强大的功能。所谓动态SQL就是在SQL中可以包含在trim、if、#{}等语法标签在运行时根据条件来生成不同的SQL。这么说比较抽象我举个例子解释一下。
```
&lt;update id=&quot;update&quot; parameterType=&quot;com.xzg.cd.a89.User&quot;
UPDATE user
&lt;trim prefix=&quot;SET&quot; prefixOverrides=&quot;,&quot;&gt;
&lt;if test=&quot;name != null and name != ''&quot;&gt;
name = #{name}
&lt;/if&gt;
&lt;if test=&quot;age != null and age != ''&quot;&gt;
, age = #{age}
&lt;/if&gt;
&lt;if test=&quot;birthday != null and birthday != ''&quot;&gt;
, birthday = #{birthday}
&lt;/if&gt;
&lt;/trim&gt;
where id = ${id}
&lt;/update&gt;
```
显然动态SQL的语法规则是MyBatis自定义的。如果想要根据语法规则替换掉动态SQL中的动态元素生成真正可以执行的SQL语句MyBatis还需要实现对应的解释器。这一部分功能就可以看做是解释器模式的应用。实际上如果你去查看它的代码实现你会发现它跟我们在前面讲解释器模式时举的那两个例子的代码结构非常相似。
我们前面提到解释器模式在解释语法规则的时候一般会把规则分割成小的单元特别是可以嵌套的小单元针对每个小单元来解析最终再把解析结果合并在一起。这里也不例外。MyBatis把每个语法小单元叫SqlNode。SqlNode的定义如下所示
```
public interface SqlNode {
boolean apply(DynamicContext context);
}
```
对于不同的语法小单元MyBatis定义不同的SqlNode实现类。
<img src="https://static001.geekbang.org/resource/image/03/9f/0365945b91a00e3b98d0c09b2665f59f.png" alt="">
整个解释器的调用入口在DynamicSqlSource.getBoundSql方法中它调用了rootSqlNode.apply(context)方法。因为整体的代码结构跟[第72讲](https://time.geekbang.org/column/article/225904)中的例子基本一致所以每个SqlNode实现类的代码我就不带你一块阅读了感兴趣的话你可以自己去看下。
## ErrorContext如何实现一个线程唯一的单例模式
在单例模式那一部分我们讲到单例模式是进程唯一的。同时我们还讲到单例模式的几种变形比如线程唯一的单例、集群唯一的单例等。在MyBatis中ErrorContext这个类就是标准单例的变形线程唯一的单例。
它的代码实现我贴到下面了。它基于Java中的ThreadLocal来实现。如果不熟悉ThreadLocal你可以回过头去看下[第43讲](https://time.geekbang.org/column/article/196790)中线程唯一的单例的实现方法。实际上这里的ThreadLocal就相当于那里的ConcurrentHashMap。
```
public class ErrorContext {
private static final String LINE_SEPARATOR = System.getProperty(&quot;line.separator&quot;,&quot;\n&quot;);
private static final ThreadLocal&lt;ErrorContext&gt; LOCAL = new ThreadLocal&lt;ErrorContext&gt;();
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框架。实际上它不只是简单地完成了对象和数据库数据之间的互相转化还提供了很多其他功能比如缓存、事务等。接下来我们再讲讲它的缓存实现。
在MyBatis中缓存功能由接口Cache定义。PerpetualCache类是最基础的缓存类是一个大小无限的缓存。除此之外MyBatis还设计了9个包裹PerpetualCache类的装饰器类用来实现功能增强。它们分别是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&lt;Object, Object&gt; cache = new HashMap&lt;Object, Object&gt;();
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;
}
//省略部分代码...
}
```
这9个装饰器类的代码结构都类似我只将其中的LruCache的源码贴到这里。从代码中我们可以看出它是标准的装饰器模式的代码实现。
```
public class LruCache implements Cache {
private final Cache delegate;
private Map&lt;Object, Object&gt; 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&lt;Object, Object&gt;(size, .75F, true) {
private static final long serialVersionUID = 4267176411845948333L;
@Override
protected boolean removeEldestEntry(Map.Entry&lt;Object, Object&gt; eldest) {
boolean tooBig = size() &gt; 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采用装饰器模式来实现缓存功能是因为装饰器模式采用了组合而非继承更加灵活能够有效地避免继承关系的组合爆炸。关于这一点你可以回过头去看下[第10讲](https://time.geekbang.org/column/article/169593)的内容。
## PropertyTokenizer如何利用迭代器模式实现一个属性解析器
前面我们讲到迭代器模式常用来替代for循环遍历集合元素。Mybatis的PropertyTokenizer类实现了Java Iterator接口是一个迭代器用来对配置属性进行解析。具体的代码如下所示
```
// person[0].birthdate.year 会被分解为3个PropertyTokenizer对象。其中第一个PropertyTokenizer对象的各个属性值如注释所示。
public class PropertyTokenizer implements Iterator&lt;PropertyTokenizer&gt; {
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 &gt; -1) {
name = fullname.substring(0, delim);
children = fullname.substring(delim + 1);
} else {
name = fullname;
children = null;
}
indexedName = name;
delim = name.indexOf('[');
if (delim &gt; -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(&quot;Remove is not supported, as it has no meaning in the context of properties.&quot;);
}
}
```
实际上PropertyTokenizer类也并非标准的迭代器类。它将配置的解析、解析之后的元素、迭代器这三部分本该放到三个类中的代码都耦合在一个类中所以看起来稍微有点难懂。不过这样做的好处是能够做到惰性解析。我们不需要事先将整个配置解析成多个PropertyTokenizer对象。只有当我们在调用next()函数的时候,才会解析其中部分配置。
## Log如何使用适配器模式来适配不同的日志框架
在适配器模式那节课中我们讲过Slf4j框架为了统一各个不同的日志框架Log4j、JCL、Logback等提供了一套统一的日志接口。不过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接口。
<img src="https://static001.geekbang.org/resource/image/95/14/95946f9e9c524cc06279114f7a654f14.png" alt="">
这几个实现类的代码结构基本上一致。我把其中的Log4jImpl的源码贴到了这里。我们知道在适配器模式中传递给适配器构造函数的是被适配的类对象而这里是clazz相当于日志名称name所以从代码实现上来讲它并非标准的适配器模式。但是从应用场景上来看这里确实又起到了适配的作用是典型的适配器模式的应用场景。
```
import org.apache.ibatis.logging.Log;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
public class Log4jImpl implements Log {
private static final String FQCN = Log4jImpl.class.getName();
private final Logger log;
public Log4jImpl(String clazz) {
log = Logger.getLogger(clazz);
}
@Override
public boolean isDebugEnabled() {
return log.isDebugEnabled();
}
@Override
public boolean isTraceEnabled() {
return log.isTraceEnabled();
}
@Override
public void error(String s, Throwable e) {
log.log(FQCN, Level.ERROR, s, e);
}
@Override
public void error(String s) {
log.log(FQCN, Level.ERROR, s, null);
}
@Override
public void debug(String s) {
log.log(FQCN, Level.DEBUG, s, null);
}
@Override
public void trace(String s) {
log.log(FQCN, Level.TRACE, s, null);
}
@Override
public void warn(String s) {
log.log(FQCN, Level.WARN, s, null);
}
}
```
## 重点回顾
好了,今天的内容到此就讲完了。我们一块来总结回顾一下,你需要重点掌握的内容。
今天我们讲到了MyBatis中用到的8种设计模式它们分别是建造者模式、工厂模式、模板模式、解释器模式、单例模式、装饰器模式、迭代器模式、适配器模式。加上上一节课中讲到的职责链和动态代理我们总共讲了10种设计模式。
还是那句老话你不需要记忆哪个类用到了哪个模式因为不管你看多少遍甚至记住并没有什么用。我希望你不仅仅只是把文章看了更希望你能动手把MyBatis源码下载下来自己去阅读一下相关的源码锻炼自己阅读源码的能力。这比单纯看文章效果要好很多倍。
除此之外从这两节课的讲解中不知道你有没有发现MyBatis对很多设计模式的实现都并非标准的代码实现都做了比较多的自我改进。实际上这就是所谓的灵活应用只借鉴不照搬根据具体问题针对性地去解决。
## 课堂讨论
今天我们提到SqlSessionFactoryBuilder跟SqlSessionFactory虽然名字后缀不同但是设计思路一致都是为了隐藏SqlSession的创建细节。从这一点上来看命名有点不够统一。而且我们还提到SqlSessionFactoryBuilder并非标准的建造者模式SqlSessionFactory也并非标准的工厂模式。对此你有什么看法呢
欢迎留言和我分享你的想法。如果有收获,也欢迎你把这篇文章分享给你的朋友。