MyBatis框架中的設計模式
前言:
前幾篇文章從原始碼角度分析了Mybatis框架,當然,作為一個優秀的框架,設計模式的使用也是必不可少的。
本文,作者便簡單介紹下Mybatis設計模式的使用場景
1.工廠模式
網上好多說SqlSessionFactory是工廠模式,但是感覺跟工廠模式還是差了點。個人更覺得以下的模式更像
Configuration.newExecutor()
public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor 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 (cacheEnabled) { executor = new CachingExecutor(executor); } executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
總結:根據使用者輸入的executorType來生成具體的Executor,符合工廠模式
2.單例模式
單例模式是大家都很熟悉的設計模式
public final class LogFactory {
...
private LogFactory() {
// disable construction
}
public static Log getLog(Class<?> aClass) {
return getLog(aClass.getName());
}
總結:跟經典的單例模式還是有點差別,LogFactory沒有實現獲取自身的方式,只是當成一個工具類來用
3.建造模式
我們先來看下下面一段優雅的程式碼
XMLConfigBuilder.environmentsElement()
private void environmentsElement(XNode context) throws Exception { if (context != null) { if (environment == null) { environment = context.getStringAttribute("default"); } for (XNode child : context.getChildren()) { String id = child.getStringAttribute("id"); if (isSpecifiedEnvironment(id)) { TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager")); DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")); DataSource dataSource = dsFactory.getDataSource(); // 看這裡 // Builder是Environment裡的static class , Environment.Builder environmentBuilder = new Environment.Builder(id) .transactionFactory(txFactory) .dataSource(dataSource); // environmentBuilder.build()方法直接返回一個Environment configuration.setEnvironment(environmentBuilder.build()); } } } }
下面讓我們來看下Environment是如何構建的
public final class Environment {
private final String id;
private final TransactionFactory transactionFactory;
private final DataSource dataSource;
...
public static class Builder {
private String id;
private TransactionFactory transactionFactory;
private DataSource dataSource;
public Builder(String id) {
this.id = id;
}
public Builder transactionFactory(TransactionFactory transactionFactory) {
this.transactionFactory = transactionFactory;
return this;
}
public Builder dataSource(DataSource dataSource) {
this.dataSource = dataSource;
return this;
}
public Environment build() {
return new Environment(this.id, this.transactionFactory, this.dataSource);
}
}
...
總結:我們在實際的程式碼中可以用這種方式來構造一個物件,程式碼看起來會非常優雅
4.裝飾器模式
類似於JDK中的InputStream相關實現類,通過裝飾器模式可以動態增加輸入流的功能
org.apache.ibatis.cache.Cache有眾多實現類,我們看一下org.apache.ibatis.cache.decorators.TransactionalCache
public class TransactionalCache implements Cache {
private Cache delegate;//重點在這裡
private boolean clearOnCommit;
private Map<Object, AddEntry> entriesToAddOnCommit;
private Map<Object, RemoveEntry> entriesToRemoveOnCommit;
@Override
public Object getObject(Object key) {
if (clearOnCommit) return null; // issue #146
return delegate.getObject(key);
}
public void commit() {
if (clearOnCommit) {
delegate.clear();
} else {
for (RemoveEntry entry : entriesToRemoveOnCommit.values()) {
entry.commit();
}
}
for (AddEntry entry : entriesToAddOnCommit.values()) {
entry.commit();
}
reset();
}
...
總結:通過對delegate屬性的代理,在實現delegate自有功能之前,增加自己的需求
5.模板模式
模板模式應該是使用的比較廣泛的一個模式,我們在父類中定義演算法骨架,將具體實現交由子類去實現
Mybatis中org.apache.ibatis.executor.BaseExecutor就是一個標準的模板模式
public abstract class BaseExecutor implements Executor {
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);
}
protected abstract int doUpdate(MappedStatement ms, Object parameter)
throws SQLException;
...
比如update()方法,定義了update的骨架方法,真正的執行doUpdate()則由子類(如SimpleExecutor)去實現
// SimpleExecutor.doUpdate
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
總結:我們在寫一個多種實現方式的方法時,可參考該模式
6.動態代理模式
動態代理可以說是Mybatis中最重要的設計模式了,其使用了動態代理優雅的實現了很多功能
我們分析下下面一句程式碼:
IUser mapper = session.getMapper(IUser.class);
User user = mapper.getUser(3);
這是我們常用的一種方式,我們來跟蹤一下原始碼,來看下其是如何實現的,如何獲取對應的Mapper介面的
// DefaultSqlSession.getMapper()
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
// Configuration.getMapper()
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
// MapperRegistry.getMapper()
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null)
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
try {
// 我們繼續追蹤該段程式碼
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
// mapperProxyFactory.newInstance()
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
可知,我們最終獲取的是MapperInterface(也就是本例中的IUser介面)的一個代理類MapperProxy,為什麼要費盡心機的得到這麼一個MapperProxy呢?
我們知道,IUser只是一個介面而已,定義了一系列方法,本身並沒有任何實現,那麼應該如何呼叫呢?我們接著看MapperProxy類
public class MapperProxy<T> implements InvocationHandler, Serializable {
...
// MapperProxy方法呼叫的時候會直接呼叫其invoke方法,
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 針對於Object裡的方法
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 正真實現在這裡
return mapperMethod.execute(sqlSession, args);
}
// mapperMethod.execute()
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
if (SqlCommandType.INSERT == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
} else if (SqlCommandType.SELECT == command.getType()) {
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
// 最終還是使用sqlSession去處理
result = sqlSession.selectOne(command.getName(), param);
}
} else {
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
為什麼要使用MapperProxy?
主要是想在這裡進行統一處理,所有的關於Mapper介面的操作統一交由MapperProxy來處理,MapperProxy最終也是通過sqlSession來處理