Mybatis 设计模式
mybaits最少用到了九种设计模式:
设计模式 | mybaits体现 |
---|---|
Builder构建者模式 | SqlSessionFactoryBuilder、Environment |
工厂模式 | SqlSessionFactory、TransactionFactory、LogFactory |
单例模式 | ErrorContex、LogFactory |
代理模式 | MapperProxy、ConnectionLogger、executor.loader |
组合模式 | SqlNode、ChooseSqlNode |
模版方法模式 | BaseExecutor、SimpleExecutor、BaseTypeHandler、IntegerTypeHandler |
适配器模式 | Log的Mybatis接口和它对jdbc、log4j等各种日志框架的适配实现 |
装饰者模式 | Cache包中的cache.decorators子包中等各个装饰者的实现 |
迭代器模式 | 迭代器模式PropertyTokenizer |
构建者模式应用
Builder模式的定义是“将一个复杂对象的构建与他的表示分离,使得同样的构建过程可以创建不同的表示。”,它属于创建类模式,一般来说,如果一个对象的构建比较复杂,超出了构造函数所能包含的范围,就可以使用工厂模式或Builder模式,相对于工厂模式会产出一个完整的产品,Builder应用于更加复杂的对象构建,甚至只会构建产品的一部分。直白来说,就是使用多个简单对象一步一步构建成一个复杂的对象。
例如:使用构建者设计模式来生产computer
- 将需要构建的目标类分成多个部件(主机、显示器、键盘、音箱等)
- 创建构建类
- 依次创建部件
- 将部件组装成目标对象
代码事例:
- 先准备一个需要构建的目标类,这个类有很多属性,其中每个属性都是一个对象,代码为了演示效果,类的成员都采用了String字符串类型。
package com.erxiao.constructor;
public class Computer {
//显示器
private String displayer;
//主机
private String mainUnit;
//鼠标
private String mouse;
//键盘
private String keyboard;
@Override
public String toString() {
return "Computer{" +
"displayer='" + displayer + '\'' +
", mainUnit='" + mainUnit + '\'' +
", mouse='" + mouse + '\'' +
", keyboard='" + keyboard + '\'' +
'}';
}
public String getDisplayer() {
return displayer;
}
public void setDisplayer(String displayer) {
this.displayer = displayer;
}
public String getMainUnit() {
return mainUnit;
}
public void setMainUnit(String mainUnit) {
this.mainUnit = mainUnit;
}
public String getMouse() {
return mouse;
}
public void setMouse(String mouse) {
this.mouse = mouse;
}
public String getKeyboard() {
return keyboard;
}
public void setKeyboard(String keyboard) {
this.keyboard = keyboard;
}
}
- 编写一个构建类,并为构建目标类提供成员属性的设置方法
package com.erxiao.constructor;
public class ComputerBuilder {
//创建目标类对象
private Computer computer = new Computer();
//为目标类成员提供设置方法
public void intallDisplaye(String displaye) {
computer.setDisplayer(displaye);
}
public void intallMainUnit(String mainUnit) {
computer.setMainUnit(mainUnit);
}
public void intallMouse(String mouse) {
computer.setMouse(mouse);
}
public void intallKeyboard(String keyboard) {
computer.setKeyboard(keyboard);
}
//提供获取目标类对象函数
public Computer getComputer() {
return computer;
}
}
- 通过构建类获取目标类对象,完成目标类构建
public static void main(String[] args) {
ComputerBuilder computerBuilder = new ComputerBuilder();
computerBuilder.intallDisplaye("显示器");
computerBuilder.intallMainUnit("主机");
computerBuilder.intallMouse("鼠标");
computerBuilder.intallKeyboard("键盘");
Computer computer = computerBuilder.getComputer();
System.out.println(computer);
}
Mybatis中的体现
SqlSessionFactory的构建过程:
Mybatis的初始化工作非常复杂,不是用一个构造函数就能搞定的。所以使用了建造者模式,使用了大量的Builder,进行分层构造,核心对象Configuration使用了XmlConfigBuilder来进行构造。
image-20210603223420406.png- 使用SqlSessionFactoryBuilder对象,将读取到的SqlMapConfig.xml InputStream流当作参数,调用builder方法,返回一个SqlSessionFactory对象;
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
- SqlSessionFactoryBuilder中的builder方法内部会创建一个XMLConfigBuilder对象,继续向下执行会调用XMLConfigBuilder中parse方法。
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
//创建XMLConfigBuilder对象
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//调用XMLConfigBuilder中的parse函数
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.
}
}
}
- 由于XMLConfigBuilder类继承了BaseBuilder类。BaseBuilder类只提供了有参构造方法,需要将Configuration对象当作参数传递到构造函数中。在XMLConfigBuilder的构造函数中就创建了一个Configuration对象,并调用父类的构造函数。
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
//创建Configuration对象,调用父类构造函数
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
- XMLConfigBuilder调用parse函数解析SqlMapConfig.xml配置文件。parse函数中实际调用的是parseConfiguration函数。
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//调用parseConfiguration函数,解析SqlMapConfig中的元素
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
- parseConfiguration函数根据SqlMapConfig.xml中的标签,解析具体的属性,并设置到对应的对象中,同时会调用mapperElement函数,解析mapper文件。
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
//解析所有的mappers
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
- mapperElement函数中会构建一个XMLMapperBuilder对象,并调用XMLMapperBuilder中的parse函数解析mapper配置文件。XMLMapperBuilder也会使用XMLStatementBuilder来读取和build所有的sql。
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
//创建XMLMapperbuilder对象
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
//调用解析mapper配置文件函数
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
- 最后通过以上步骤构建出来的Configuration对象,并将Configuration当作参数传递给SqlSessionFactoryBuilder的build函数,并将DefaultSqlSessionFactory对象返回。
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
在这个过程中,又一个相似的特点,就是这些Builder会读取文件或者配置,然后做大量的XpathParser解析、配置或语法解析、反射生成对象、存入结果缓存等步骤,这么多的工作都不是一个构造函数所能包括的,因此大量采用了Builder模式来解决。
image-20210604114628624.pngSqlSessionFactoryBuilder类根据不同的输入参数来构建SqlSessionFactory这个工厂对象。
工厂模式应用
mybatis中比如SqlSessionFactory使用的是工厂模式,该工厂没有那么复杂的逻辑,是一个简单工厂模式。简单工厂模式(Simple Factory Pattern):又称为静态工厂方法(Static Factory Method)模式,它属于创建模式。在简单的工厂模式中,可以根据参数返回不同类实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。
-
例如:生产电脑
假设电脑的代工生产商,它目前已经可以代工生产联想电脑了,随着业务的拓展,这个代工厂还要生产惠普的电脑,我们就需要一个单独的类来专门生产电脑,这就用到了简单的工厂模式。
实现简单工厂模式:
-
创建抽象产品类
创建一个电脑的抽象产品类,它有一个抽象方法用于启动电脑:
public abstract class Computer { /** * 产品的抽象方法,由具体的产品提供 */ public abstract void start(); }
-
创建具体产品类
接着创建各个品牌的电脑,他们都继承他们的父类Computer,并实现了父类的start方法:
/** * @Author: wangcong * @Date: 2021/6/5 11:17 下午 * @Version 1.0 */ public class LenovoComputer extends Computer{ @Override public void start() { System.out.println("联想电脑启动......"); } }
public class HpComputer extends Computer{ @Override public void start() { System.out.println("惠普电脑启动......."); } }
-
创建工厂类
接下来创建一个工厂类,它提供了一个静态方法createComputer用来生产电脑。只有传入想生产电脑的品牌,他就会实例化相应品牌的电脑对象。
public class ComputerFactory {
public static Computer createComputer(String type) {
Computer computer = null;
switch (type) {
case "lenovo":
computer = new LenovoComputer();
break;
case "hp":
computer = new HpComputer();
break;
}
return computer;
}
}
-
客户端调用工厂类
客户端调用工厂类,传入“hp”生产出hp电脑并调用该电脑的start方法
public class CreateComputer { public static void main(String[] args) { ComputerFactory.createComputer("hp").start(); } }
Mybatis体现:
Mybatis中执行sql语句,获取Mappers、管理事务的核心接口SqlSession的创建过程使用到了工厂模式。有一个SqlSessionFactory来负责SqlSession创建。
image-20210608225023129.pngSqlSessionFactory可以看到,该Factory的openSession()方法重载了很多个分支,分别支持autoCommit、Executor、Transaction等参数的输入,来构建SqlSession对象。在DefaultSqlSessionFactory的默认工厂实现里,有一个方法可以看出工厂怎么产出一个产品:
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);
//根据参数创建制定类型的executor
final Executor executor = configuration.newExecutor(tx, execType);
//返回的是DefaultSession
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();
}
}
这是一个openSession调用的底层方法,该方法从configuration读取对应的环境配置,然后初始化TransactionFactory获得一个Transaction对象,然后通过Transaction获取一个Executor对象,最后通过Configuration、Excutor、是否autoCommit三个参数构建了SqlSession。
代理模式应用
代理模式(Proxy Pattern):给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式的英文叫做Proxy,它是一种对象结构模式,代理模式分为静态代理和动态代理,本次介绍动态代理
-
例如:
- 创建一个抽象类,Person接口,使其拥有一个没有返回值的doSomething方法
/** * @Author: wangcong * @Date: 2021/6/8 11:31 下午 * @Version 1.0 */ public interface Person { void doSomething(); }
- 创建一个名为Bob的Person接口的实现类,使其实现doSomething方法
/** * @Author: wangcong * @Date: 2021/6/8 11:32 下午 * @Version 1.0 */ public class Bob implements Person{ @Override public void doSomething() { System.out.println("Bob doing something"); } }
- 创建JDK动态代理类,使其实现InvocationHandler接口。拥有一个名为target的变量,并创建getTarget获取代理对象方法
/** * @Date: 2021/6/8 11:35 下午 * @Version 1.0 * JDK动态代理 需要实现 InvocationHandler 接口 */ public class JDKDynamicproxy implements InvocationHandler { //被代理对象 private Person target; //JDKDynamicproxy构造函数 public JDKDynamicproxy(Person target) { this.target = target; } //获取代理对象 public Person getTarget() { return (Person) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } //动态代理invoke方法 @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; } }
- 创建JDK动态代理测试类
public class JDKDynamicTest { public static void main(String[] args) { System.out.println("不使用代理类,调用doSomething方法"); //不使用代理类 Person person = new Bob(); person.doSomething(); System.out.println("---------------------------"); System.out.println("使用代理类,调用doSomething方法"); //获取代理类 Person target = new JDKDynamicproxy(new Bob()).getTarget(); target.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, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
//调用newInstance方法生成一个具体的代理类
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
通过newInstance(SqlSession sqlSession)方法会得到一个MapperProxy对象,然后调用newInstance(MapperProxy mapperProxy)生成代理对象然后返回。查看MapperProxy的代码可以看到如下内容
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
非常典型的,该MapperProxy实现了InvocationHandler接口,并且实现了该接口的invoke方法。通过这种方式,我们只需要编写Mapper.java接口类,当真正执行一个mapper接口的时候,就会转发给MapperProxy.invoke方法,而该方法则会调用后续sqlSession.cud、execute.execute、prepareStatement等一系列方法,完成SQL的执行和返回。
网友评论