发布时间:2025-01-23 08:01
MyBatis至今被越来越多的公司青睐,其中的原因是它更加轻量级,可控性更高。许许多多的大中小型公司都喜欢在面试的时候提及MyBatis的原理或者MyBatis中一些重要的组件及实现。而与之增强版本MyBatis-Plus同样在面试上面被提问,懂得MyBatis的离理解MyBatis-Plus就不远了。两者差别在于对SQL的处理方式有所不同而产生了差异。
MyBatis有着不胜枚举的优良地方值得我们学习,在前面的一些文章中笔者也提及了,需要了解的童鞋可以访问主页自行学习,后续也会不断的推送关于MyBatis的解读文章。今天又放大招了。
这里涉及到MyBatis架构里面的基础层以及核心层。在前面的文章中笔者有所提及,感兴趣的读者可以回顾一下。解析配置填充Configuration配置对象字段的过程中使用了建造者模式。重要的事情讲三遍。解析配置填充Configuration配置对象字段的过程中使用了建造者模式。解析配置填充Configuration配置对象字段的过程中使用了建造者模式。看文章一定要反复读而不是一读完就行了,顺着来也会逆着来,抓住重点词汇,包括开发需求也一样的道理。Configuration配置对象就是一个配置数据中心,用于存取数据,就像未经持久化的数据库。关于设计模式,也出了几篇文章,后面还会继续。
资源的读取是由MyBatis的IO包完成的,其中重点职责的类为ClassLoaderWrapper以及Resources,关于它们笔者在前面的文章(MyBatis是如何加载配置文件的2)介绍了,这里就不分析了。
读取资源代码入口:
public static InputStream getResourceAsStream(String resource) throws IOException {
return getResourceAsStream(null, resource);
}
public static InputStream getResourceAsStream(ClassLoader loader,
String resource) throws IOException {
InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
if (in == null) {
throw new IOException("Could not find resource " + resource);
}
return in;
}
解析配置核心类之一是XMLConfigBuilder,解析入口是parse()方法。主要解析mybatis-config.xml(配置文件的样例如笔者前面写的文章MyBatis核心武器的源泉),解析mappers封装到了mapperElement(root.evalNode("mappers"));方法中
// 解析mybatis-config.xml的入口方法
public Configuration parse() {
// 如果已经解析过了,报错
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true; // 标志已解析
// 在mybatis-config.xml配置文件中查找节点,并解析
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
// 解析配置节点
private void parseConfiguration(XNode root) {
try {
// 分步骤解析
//issue #117 read properties first
// 1.properties
propertiesElement(root.evalNode("properties"));
// 2.类型别名
typeAliasesElement(root.evalNode("typeAliases"));
// 3.插件
pluginElement(root.evalNode("plugins"));
// 4.对象工厂
objectFactoryElement(root.evalNode("objectFactory"));
// 5.对象包装工厂
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 6.设置
settingsElement(root.evalNode("settings"));
// read it after objectFactory and objectWrapperFactory issue #631
// 7.环境
environmentsElement(root.evalNode("environments"));
// 8.databaseIdProvider
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 9.类型处理器
typeHandlerElement(root.evalNode("typeHandlers"));
// 10.映射器
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
MyBatis把每个节点的解析都封装成了一个方法,这样职责清晰,方便维护与阅读。
// 10.映射器
// 10.1使用类路径
//
//
//
//
//
//
// 10.2使用绝对url路径
//
//
//
//
//
//
// 10.3使用java类名
//
//
//
//
//
//
// 10.4自动扫描包下所有映射器
//
//
//
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
// 10.4自动扫描包下所有映射器
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) {
// 10.1使用类路径
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
// 映射器比较复杂,调用XMLMapperBuilder
// 注意在for循环里每个mapper都重新new一个XMLMapperBuilder,来解析
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
// 10.2使用绝对url路径
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
// 映射器比较复杂,调用XMLMapperBuilder
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
// 10.3使用java类名
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.");
}
}
}
}
}
SqlSessionFactory的创建使用了建造者模式,由SqlSessionFactoryBuilder创建的。
读取解析配置文件并填充Configuration对象字段后,就创建DefaultSqlSessionFactory对象为SqlSessionFactory类型。
/**
* 第8种方法和第4种方法差不多,Reader换成了InputStream
* @param inputStream 输入数据流
* @param environment 数据库环境
* @param properties 属性
* @return 工厂
*/
public SqlSessionFactory build(InputStream inputStream,
String environment,
Properties properties) {
try {
// 读取资源,解析配置并填充Configuration
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return this.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.
}
}
}
/**
* 最后一个build方法使用了一个Configuration作为参数,并返回DefaultSqlSessionFactory
* @param config 配置
* @return 工厂
*/
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
SqlSession的创建使用了工厂方法模式,由DefaultSqlSessionFactory创建DefaultSqlSession对象为SqlSession类型。创建会话的方法openSession()有多个重载,最终委派给下面两个方法去处理创建DefaultSqlSession。
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);
// 然后产生一个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();
}
}
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("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
从上面创建会话过程中可以看出Transaction是在创建DefaultSqlSession前一点进行创建的,它是由工厂方法模式进行创建的,由TransactionFactory的newTransaction()方法创建的。一种是JdbcTransaction,另一种是ManagedTransaction。但是实际中是由另一个框架管理的。
从上面创建会话过程中可以看出Executor执行器是在创建DefaultSqlSession前一点进行创建的,因为SqlSession是高层接口层,它只是关心操作并不关心实现细节的东西的。
// 产生执行器
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
// 这句再做一下保护,囧,防止粗心大意的人将defaultExecutorType设成null?
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
// 然后就是简单的3个分支,产生3种执行器BatchExecutor/ReuseExecutor/SimpleExecutor
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);
}
// 如果要求缓存,生成另一种CachingExecutor(默认就是有缓存),装饰者模式,所以默认都是返回CachingExecutor
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 此处调用插件,通过插件可以改变Executor行为
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
创建会话后,通过会话对象获取mapper。其中会委派给Configuration对象获取。
@Override
public T getMapper(Class type) {
return configuration.getMapper(type, this);
}
Configuration又会委派给映射器注册机MapperRegistry去获取。
public T getMapper(Class type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
MapperRegistry中通过MapperProxyFactory创建代理mapper的。
@SuppressWarnings("unchecked")
// 返回代理类
public T getMapper(Class type, SqlSession sqlSession) {
// 获取mapper代理工厂
final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
// 在map中找不到则表示没有将mapper类注册进来,抛出BindingException
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 使用工厂进行创建一个实例,本质上是通过代理模式创建了一个代理类,创建过程中出现异常抛出BindingException
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
MapperProxyFactory最终交由JDK动态代理类Proxy创建mapper代理类的。
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader()
, new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy mapperProxy = new MapperProxy<>(sqlSession
, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
创建mapper代理对象,执行方法后会调用如下invoke()方法,整体以插入数据为例
@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 {
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
接下来会执行到MapperMethod的execute()方法
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
// 对用户传入的参数进行转换,下同
Object param = method.convertArgsToSqlCommandParam(args);
// 执行插入操作,rowCountResult 方法用于处理返回值
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
// 根据目标方法的返回类型进行相应的查询操作
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
// 执行查询操作,并返回多个结果
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
// 执行查询操作,并将结果封装在 Map 中返回
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
// 执行查询操作,并返回一个 Cursor 对象
result = executeForCursor(sqlSession, args);
} else {
// 执行查询操作,并返回一个结果
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
// 执行刷新操作
result = sqlSession.flushStatements();
break;
default:
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;
}
你会发现执行的还是sqlSession的insert()方法
执行DefaultSqlSession的insert()方法,最终到如下的update()方法。委托Exexutor继续执行。
@Override
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
来到BaseExecutor的update()方法
@Override
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);
}
@Override
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());
// 最终是一个statement进行处理
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
@Override
public int update(Statement statement) throws SQLException {
//调用PreparedStatement.execute和PreparedStatement.getUpdateCount
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
int rows = ps.getUpdateCount();
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
return rows;
}
底层用的就是PreparedStatement的execute()方法了,来到这里是不是感到很亲切,原来绕了一大圈最终还是要用到底层的东西。
MyBatis的其他查询、修改、删除执行流程都是这个走向,感兴趣的读者可以把它收藏起来仔细研究研究。笔者还有其他关于MyBatis的文章,可以关注一下不迷路,在主页就可以查看哦!后续也会陆陆续续出一些关于MyBatis的文章。最后说一下的是,程序员这条路,最好是有一个引路人。
猿创征文|基于反事实的因果推理Causal inference based on counterfactuals--一万六千字文献详细解读(因果关系的推理应用)【全文总结】
本周四晚19:00知识赋能第3期直播丨OpenHarmony智能家居项目之控制面板功能实现
array函数参数 scala_2018年第47周-scala入门-类型参数
OGC WebGIS 常用服务标准(WMS/WMTS/TMS/WFS)速查
跟着迪哥学python电子书pdf-跟着迪哥学Python数据分析与机器学习实战
【开源】DA14580-SPI教程——疯壳·ARM双处理器开发板系列