MyBatis设计模式
# MyBatis设计模式
# 概述
MyBatis源码中使⽤了⼤量的设计模式,⾄少⽤到了以下的设计模式:
设计模式 | MyBatis的体现 |
---|---|
Builder构建模式 | 例如 SqlSessionFactoryBuilder、Environment |
⼯⼚模式 | 例如 SqlSessionFactory、TransactionFactory、LogFactory |
单例模式 | 例如 ErrorContext、LogFactory |
代理模式 | MyBatis实现的核心,例如 MapperProxy、ConnectionLogger,⽤的jdk的动态代理;``还有executor.loader包使⽤了 javassist或者cglib达到延迟加载的效果 |
组合模式 | 例如 SqlNode和各个⼦类ChooseSqlNode等 |
模板⽅法模式 | 例如 BaseExecutor 和 SimpleExecutor,还有 BaseTypeHandler 和所有的⼦类例如IntegerTypeHandler |
适配器模式 | 例如 Log的Mybatis接⼝和它对jdbc、log4j等各种⽇志框架的适配实现; |
装饰者模式 | 例如 Cache包中的cache.decorators⼦包中等各个装饰者的实现; |
迭代器模式 | 例如 迭代器模式PropertyTokenizer |
接下来,结合MyBatis的应用对Builder构建模式、⼯⼚模式、代理模式进⾏解读。
# Builder构建模式
Builder构建模式的定义是将复杂对象的构建过程与其表现分离,以便相同的构建过程可以创建不同的表现。它属于创建类模式,⼀般来说,如果⼀个对象的构建⽐较复杂,超出了构造函数所能包含的范围,就可以使⽤⼯⼚模式和Builder模式,相对于⼯⼚模式会产出⼀个完整的产品,Builder应⽤于更加复杂的对象的构建,甚⾄只会构建产品的⼀个部分,简单来说,Builder构建模式就是使⽤多个简单的对象⼀步⼀步构建成⼀个复杂的对象。
# 构建Computer
下面是一个简单的例子:使⽤构建设计模式来⽣产Computer,更多详细的解读,可见构建模式(Python版)
public class Computer {
private String displayer;
private String mainUnit;
private String mouse;
private String keyboard;
// 略 setter & getter & toString
}
public class ComputerBuilder {
private final Computer target = new Computer();
public ComputerBuilder installDisplayer(String displayer) {
target.setDisplayer(displayer);
return this;
}
public ComputerBuilder installMainUnit(String mainUnit) {
target.setMainUnit(mainUnit);
return this;
}
public ComputerBuilder installMouse(String mouse) {
target.setMouse(mouse);
return this;
}
public ComputerBuilder installKeyboard(String keyboard) {
target.setKeyboard(keyboard);
return this;
}
public Computer build() {
return target;
}
}
public class Main {
public static void main(String[] args) {
ComputerBuilder computerBuilder = new ComputerBuilder();
Computer computer = computerBuilder.installDisplayer("显示器")
.installMainUnit("主机")
.installKeyboard("键盘")
.installMouse("鼠标")
.build();
System.out.println(computer);
}
}
# MyBatis的应用
SqlSessionFactory的构建过程:MyBatis的环境初始化⼯作⾮常复杂,只⽤⼀个构造函数就搞不定的。所以使⽤了构建模式,其中使⽤了⼤量的Builder进⾏分层构造,核⼼对象Configuration使⽤了XmlConfigBuilder来进⾏构建。
在MyBatis环境的初始化过程中,SqlSessionFactoryBuilder会调⽤XMLConfigBuilder读取所有的SqlMapConfig.xml和所有的 *Mapper.xml ⽂件,构建其运⾏的核⼼Configuration对象,然后将该Configuration对象作为参数构建⼀个SqlSessionFactory对象。
// SqlSessionFactoryBuilder.build
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// XMLConfigBuilder是专⻔解析mybatis的配置文件的类
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 又调用了一个重载方法。参数是parser.parse()的返回值,它是Configuration对象
return build(parser.parse());
}
// ...
}
// XMLConfigBuilder.parse调用了parseConfiguration
private void parseConfiguration(XNode root) {
try {
// ... 解析<configuration> 中的各种配置标签, 如<properties />、<settings /> 等等
// ...
// 解析 <mappers /> 标签,这里就会用XMLMapperBuilder解析所有*Mapper文件
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
其中 XMLConfigBuilder在构建 Configuration对象时,也会调⽤ XMLMapperBuilder ⽤于读取*Mapper ⽂件,⽽XMLMapperBuilder会使⽤XMLStatementBuilder来读取和build所有的SQL语句。
// XMLConfigBuilder.mapperElement
private void mapperElement(XNode parent) throws Exception {
// ...
// ...
// resource 方式加载
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// ⽣成XMLMapperBuilder,并执⾏其parse⽅法
mapperParser.parse();
}
}
// 其他方式加载
// ...
// ...
}
在整个过程中,都有⼀个相似的特点:这些Builder会读取⽂件或者配置,然后做⼤量的XpathParser解析、配置或语法的解析、反射⽣成对象、缓存结果等步骤,这么多的⼯作都不是⼀个构造函数所能包括的,因此⼤量采⽤了 Builder模式来解决。
好处显而易见:SqlSessionFactory⼯⼚对象的构建统一由SqlSessionFactoryBuilder类负责,可以根据不同的输⼊参数构建工厂对象,将工厂对象复杂的构建过程与其表现分离。
# ⼯⼚模式
工厂模式是指专门定义一个类来负责创建其他类的实例,根据参数的不同创建不同类的实例,被创建的实例通常具有共同的父类。工厂模式有三种细分的版本:简单工厂模式、工厂方法模式、抽象工厂模式。更多的详细解读见工厂模式(Python版)。
在MyBatis中⽐如SqlSessionFactory使⽤的是简单⼯⼚模式,⼜称为静态⼯⼚⽅法(Static Factory Method)模式,属于创建型模式。该⼯⼚没有那么复杂的逻辑。可以根据参数的不同返回不同类的实例。
# 生产Computer
下面依然用Computer为例,实现简单⼯⼚模式:
- 假设有⼀个电脑的代⼯⽣产商,它⽬前可以代⼯⽣产联想电脑,随着业务的拓展,这个代⼯⽣产商还要⽣产惠普的电脑,我们就需要⽤⼀个单独的类来专⻔⽣产电脑,这就⽤到了简单⼯⼚模式。
// 产品共同的父类
public abstract class Computer {
/**
* 产品抽象方法,由具体的产品类去实现
*/
public abstract void start();
}
// 产品的不同实现
public class LenovoComputer extends Computer {
@Override
public void start() {
System.out.println("联想电脑启动");
}
}
public class HpComputer extends Computer{
@Override
public void start() {
System.out.println("惠普电脑启动");
}
}
// 工厂类负责生产产品
public class ComputerFactory {
public static Computer create(String type) {
Computer computer = null;
switch (type) {
case "lenovo":
computer = new LenovoComputer();
break;
case "hp":
computer = new HpComputer();
break;
}
return computer;
}
}
// 调用
public class Main {
public static void main(String[] args) {
ComputerFactory.create("lenovo").start();
ComputerFactory.create("hp").start();
}
}
# MyBatis的应用
MyBatis中的核⼼接⼝SqlSession,可以执⾏Sql语句、获取Mappers、管理事务等等,它的创建过程使⽤到了⼯⼚模式,由 SqlSessionFactory 来负责 SqlSession的创建,默认实现是DefaultSqlSessionFactory:
可以看到,该Factory的 openSession()
⽅法重载了很多个,分别⽀持autoCommit、Executor、Transaction等参数的输⼊,来构建核⼼的SqlSession对象。在DefaultSqlSessionFactory的默认⼯⼚实现⾥,openSessionFromDataSource
⽅法可以看出⼯⼚怎么产出⼀个产品,它是 openSession
的底层调用:
/**
* 7. 进⼊openSessionFromDataSource
* openSession的多个重载⽅法可以指定获得的SeqSession的Executor类型和事务的处理
* @param execType executor的类型
* @param level 事务隔离级别
* @param autoCommit 是否开启事务
* @return 返回sqlSession
*/
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 从configuration读取对应的环境配置
final Environment environment = configuration.getEnvironment();
// 初始化TransactionFactory
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// congFactory 获得⼀个 Transaction 对象
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 通过Transaction和executor的类型,创建指定类型的Executor
final Executor executor = configuration.newExecutor(tx, execType);
// 返回的是 DefaultSqlSession
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();
}
}
# 代理模式
代理模式是指给某⼀个对象提供⼀个代理,并由代理对象控制对原对象的引⽤。代理模式的英⽂叫做Proxy,它是⼀种对象结构型模式,代理模式分为静态代理和动态代理,我们来介绍动态代理,更多的详细解读见代理模式(Python版)。
# Person动态代理
下面是动态代理的一个例子:
// Person抽象类
public interface Person {
void doSomething();
}
// 创建⼀个名为Jack的实现类
public class Jack implements Person {
@Override
public void doSomething() {
System.out.println("Jack doing something!");
}
}
// 创建JDK动态代理类
public class JDKDynamicProxy implements InvocationHandler {
// 被代理对象
Person target;
public JDKDynamicProxy(Person target) {
this.target = target;
}
public Person getTarget() {
return (Person) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 被代理⽅法前执⾏
System.out.println("JDKDynamicProxy do something before!");
// 执⾏被代理的⽅法
Person result = (Person) method.invoke(target, args);
// 被代理⽅法后执⾏
System.out.println("JDKDynamicProxy do something after!");
return result;
}
}
// 测试类
public class Main {
public static void main(String[] args) {
System.out.println("不使用代理类,调用doSomething方法");
Person person = new Jack();
person.doSomething();
System.out.println("使用代理类,调用doSomething方法");
Person proxyPerson = new JDKDynamicProxy(new Jack()).getTarget();
proxyPerson.doSomething();
}
}
# MyBatis的应用
代理模式可以认为是MyBatis的使⽤的核⼼设计模式,正是由于这个模式,我们只需要编写Mapper.java接⼝,而不需要实现,由MyBatis帮我们完成具体SQL的执⾏。当我们使⽤Configuration的 getMapper
⽅法时,会调⽤mapperRegistry.getMapper
⽅法,⽽该⽅法⼜
会调⽤ mapperProxyFactory.newInstance(sqlSession)
来⽣成⼀个具体的代理:
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethodInvoker> getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
// 创建了JDK动态代理的Handler类
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
// 重载方法
return newInstance(mapperProxy);
}
}
在这⾥,先通过 T newInstance(SqlSession sqlSession)
⽅法会得到⼀个MapperProxy对象,然后调⽤T newInstance(MapperProxy mapperProxy)
⽣成代理对象然后返回。⽽查看MapperProxy的代码,可以看到如下内容:
public class MapperProxy<T> implements InvocationHandler, Serializable {
// ...
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 如果是Object定义的⽅法,直接调⽤
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
// 返回的是PlainMethodInvoker,调用了invoke方法。它是MapperProxy的内部类。
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
return MapUtil.computeIfAbsent(methodCache, method, m -> {
if (m.isDefault()) {
try {
if (privateLookupInMethod == null) {
return new DefaultMethodInvoker(getMethodHandleJava8(method));
} else {
return new DefaultMethodInvoker(getMethodHandleJava9(method));
}
} catch (IllegalAccessException | InstantiationException | InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException(e);
}
} else {
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}
// ...
}
我们可以看到,该MapperProxy类实现了InvocationHandler接⼝,并且实现了该接⼝的invoke⽅法,典型的JDK动态代理。通过这种⽅式,我们只需要编写Mapper.java接⼝类,当真正执⾏⼀个Mapper接⼝的时候,就会转发给 MapperProxy.invoke
⽅法,⽽该⽅法则会调⽤后续的 sqlSession.curd > executor.execute > prepareStatement 等⼀系列⽅法,完成 SQL 的执⾏和返回