前几篇文章从源码角度分析了Mybatis框架,当然,作为一个优秀的框架,设计模式的使用也是必不可少的。
本文,作者便简单介绍下Mybatis设计模式的使用场景
关于设计模式的相关知识,读者可自行查看网上相关资料。推荐下菜鸟教程 http://www.runoob.com/design-pattern/design-pattern-tutorial.html
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 entriesToAddOnCommit;
private Map 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 getMapper(Class type) {
return configuration.getMapper(type, this);
}
// Configuration.getMapper()
public T getMapper(Class type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
// MapperRegistry.getMapper()
public T getMapper(Class type, SqlSession sqlSession) {
final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) 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 mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
可知,我们最终获取的是MapperInterface(也就是本例中的IUser接口)的一个代理类MapperProxy,为什么要费尽心机的得到这么一个MapperProxy呢?
我们知道,IUser只是一个接口而已,定义了一系列方法,本身并没有任何实现,那么应该如何调用呢?我们接着看MapperProxy类
public class MapperProxy 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来处理
参考: http://xpenxpen.iteye.com/blog/1508749