平时我们使用的一般是集成了Spring或是Spring Boot的Mybatis,封装了一层,看源码不直接;如下,看看原生的Mybatis使用示例
示例解析通过代码可以清晰地看出,MyBatis的操作主要分为两大阶段:
(资料图片仅供参考)
在第一阶段,最关键的就是SqlSessionFactory
对象。在Spring集成Mybatis的源码中,SqlSessionFactoryBean
也是做这个事情,读取配置并初始化构建SqlSessionFactory
。
public class SqlSessionFactoryBean implements FactoryBean, InitializingBean, ApplicationListener { // Spring Bean的生命周期会调用此方法 public void afterPropertiesSet() throws Exception { this.sqlSessionFactory = this.buildSqlSessionFactory(); } protected SqlSessionFactory buildSqlSessionFactory(){ // 构建Configuration.... Configuration configuration; if (this.configuration != null) { configuration = this.configuration; if (configuration.getVariables() == null) { configuration.setVariables(this.configurationProperties); } else if (this.configurationProperties != null) { configuration.getVariables().putAll(this.configurationProperties); } } else if (this.configLocation != null) { xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), (String)null, this.configurationProperties); configuration = xmlConfigBuilder.getConfiguration(); } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Property `configuration` or "configLocation" not specified, using default MyBatis Configuration"); } configuration = new Configuration(); configuration.setVariables(this.configurationProperties); } /// 其它code... return this.sqlSessionFactoryBuilder.build(configuration); }}
配置文件的解析,最终会生成一个Configuration对象。
private void parseConfiguration(XNode root) { try { Properties settings = this.settingsAsPropertiess(root.evalNode("settings")); this.propertiesElement(root.evalNode("properties")); this.loadCustomVfs(settings); this.typeAliasesElement(root.evalNode("typeAliases")); this.pluginElement(root.evalNode("plugins")); this.objectFactoryElement(root.evalNode("objectFactory")); this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); this.reflectionFactoryElement(root.evalNode("reflectionFactory")); this.settingsElement(settings); this.environmentsElement(root.evalNode("environments")); this.databaseIdProviderElement(root.evalNode("databaseIdProvider")); this.typeHandlerElement(root.evalNode("typeHandlers")); // 解析mappers节点 this.mapperElement(root.evalNode("mappers")); } catch (Exception var3) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3); }}
前期的准备已就绪,关键的配置已解析且构建并初始化了SqlSessionFactory了。接下来就是创建数据库连接并执行业务的CRUD。在第二阶段的OpenSession
方法负责创建并打开数据库链接。
public SqlSession openSession(Connection connection) { return this.openSessionFromConnection(this.configuration.getDefaultExecutorType(), connection);}private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; DefaultSqlSession var8; try { Environment environment = this.configuration.getEnvironment(); TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); Executor executor = this.configuration.newExecutor(tx, execType); var8 = new DefaultSqlSession(this.configuration, executor, autoCommit); } catch (Exception var12) { this.closeTransaction(tx); throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12); } finally { ErrorContext.instance().reset(); } return var8;}
最后就是调用Mapper接口的业务方法,返回业务数据。
//SqlSession.getMapper()public T getMapper(Class type) { return this.configuration.getMapper(type, this);}// configuration.getMapper()public T getMapper(Class type, SqlSession sqlSession) { return this.mapperRegistry.getMapper(type, sqlSession);}// mapperRegistry.getMapper()public T getMapper(Class type, SqlSession sqlSession) { MapperProxyFactory mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } else { try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception var5) { throw new BindingException("Error getting mapper instance. Cause: " + var5, var5); } }}// mapperProxyFactory.newInstance()protected T newInstance(MapperProxy mapperProxy) { return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);}public T newInstance(SqlSession sqlSession) { MapperProxy mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache); return this.newInstance(mapperProxy);}
最后,打完收工,示例代码所涉及到的关键代码就这些。
反思上面的示例是比较简单的,那么其实现思路到底是什么样的?首先就有几个问题:
Mapper接口中的方法没有实现,那客户端调用接口的方法时,返回的数据是从哪来的?Mapper文件与Mapper接口是怎么关联绑定上的?第一个问题,绝对离不开动态代理,因为只有接口的时候,那么一定会有动态代理生成代理类同时有拦截处理器(InvocationHandler)来增强其执行逻辑。
第二个问题,Mapper文件中有一个
节点,其namespace
就是接口的全限定名称,而其下节点都有一个id值,该值与接口的方法是一致的。因此从这里就可以看出来,业务的crud操作节点是通过namespace+id来对应mapper接口及其方法的。那么我们就可以考虑到,在第一个问题中的拦截处理器执行方法method时,我们就可以通过此关联关系找到要执行的SQL。
如上,这么一分析来看,其实大概的实现思路已经出来了。就是动态代理+
节点解析实现了接口方法的调用与业务SQL的执行。
因此在第一阶段的解析时,Mapper文件里的
节点解析出来的对象就起到了关键的作用。如下,有几个关键的抽象:
MapperRegistry
类的knownMappers
属性保存着接口及其代理对象的关系。
// type = interfaceknownMappers.put(type, new MapperProxyFactory(type));
MappedStatement
类对应着
节点下的CRUD节点。
public void parseStatementNode() { String id = this.context.getStringAttribute("id"); if (this.databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { Integer fetchSize = this.context.getIntAttribute("fetchSize"); Integer timeout = this.context.getIntAttribute("timeout"); String parameterMap = this.context.getStringAttribute("parameterMap"); String parameterType = this.context.getStringAttribute("parameterType"); Class> parameterTypeClass = this.resolveClass(parameterType); String resultMap = this.context.getStringAttribute("resultMap"); String resultType = this.context.getStringAttribute("resultType"); // 解析其他属性... this.builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, (KeyGenerator)keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }}
如上是抽象出来整个执行过程的简单流程。实际上还有动态参数绑定与事务等,这些都是在动态代理的拦截处理器中的增强逻辑;下篇再阐述。
标签:
精彩推荐
近日,山东省高级人民法院向社会通报全省法院消费者权益司法保护工作情况及10起典型案例。据了解,五年...
南京市19日通报,公安部门在疫情防控期间依法打击各类涉疫违法犯罪行为,截至3月18日,全市共查处各类涉...
日前,北京市人民政府新闻办公室举行新闻发布会,解读《北京市全民科学素质行动规划纲要(2021—2035年)...
去年下半年以来,受多重因素影响,房地产市场出现下行态势。今年以来,各方共同努力持续稳地价、稳房价...
联合国人权理事会第49届会议新疆经济社会发展与人权保障边会18日在广州举办。会议由中国人权研究会、中...
人力资源和社会保障部近日印发《关于开展技术技能类山寨证书专项治理工作的通知》(以下简称《通知》),...
针对网络消费乱象,最高人民法院近期发布《最高人民法院关于审理网络消费纠纷案件适用法律若干问题的规...
当好农民工的“护薪人” 近日,罗某等7名农民工在收到被拖欠的工资后,纷纷打电话向江西省南昌市...
“通讯录里所有人都知道我欠钱了” □ 本报记者 韩丹东 □ 本报见习记者 张守坤 ...
大连宝马车撞人案肇事司机被判死刑 本报讯 记者韩宇 10月29日,辽宁省大连市中级人民法院一审...
医院财务迷上网络赌博输光5000万元公款 □ 本报记者 马维博 □ 本报通讯员 汪宇堂 曹...
辊环车削 雕琢毫厘(工匠绝活) 【绝活看点】 23年来,雷虎始终扎根一线,改进钢材轧制工艺...
交警严查超标电动自行车挪用“白牌” 截至昨晚6时,处罚电动自行车违法行为共计6585笔;下一步将...
明起寒潮来袭 北方气温普降10℃以上 中央气象台预计,本周日北京平原地区最低气温降至-4℃左右...
多种蔬菜价格降幅达五成 包括菠菜、蒿子秆等 预计本月中旬蔬菜恢复供需平衡 本报讯(记者...
北京周日最低气温或达-4℃ 本报讯(记者 赵婷婷)北京青年报记者昨天从中央气象台获悉,新一股...
昌平一家四口确诊新冠肺炎 天通北苑第二社区升级为中风险地区 朝阳两涉疫校区及16所学校停课 ...
人社部发布通知 事业单位招聘可适当降低学历要求 昨日,人社部发布《关于职业院校毕业生参加...
民警马拓 在地铁里看见人生百态 写下200多个故事 他们积极努力生活的样子 全都是最好的素材 ...
60+达人 通信老兵贺春立:用镜头记录广外“变迁” 【发生地点】北京市西城区广外街道 【事...
资讯News
05-21
05-21
05-21
05-21
05-21
05-21
05-21
05-21
05-21
05-21
05-20
05-20
05-20
05-20
05-20
05-20
05-20
05-20
05-20
05-20
聚焦Policy
当好农民工的“护薪人” 近日,罗某等7名农民工在收到被拖欠的工资后,纷纷打电话向江西省南昌市...
“通讯录里所有人都知道我欠钱了” □ 本报记者 韩丹东 □ 本报见习记者 张守坤 ...
大连宝马车撞人案肇事司机被判死刑 本报讯 记者韩宇 10月29日,辽宁省大连市中级人民法院一审...
医院财务迷上网络赌博输光5000万元公款 □ 本报记者 马维博 □ 本报通讯员 汪宇堂 曹...
辊环车削 雕琢毫厘(工匠绝活) 【绝活看点】 23年来,雷虎始终扎根一线,改进钢材轧制工艺...
交警严查超标电动自行车挪用“白牌” 截至昨晚6时,处罚电动自行车违法行为共计6585笔;下一步将...
明起寒潮来袭 北方气温普降10℃以上 中央气象台预计,本周日北京平原地区最低气温降至-4℃左右...
多种蔬菜价格降幅达五成 包括菠菜、蒿子秆等 预计本月中旬蔬菜恢复供需平衡 本报讯(记者...
北京周日最低气温或达-4℃ 本报讯(记者 赵婷婷)北京青年报记者昨天从中央气象台获悉,新一股...
昌平一家四口确诊新冠肺炎 天通北苑第二社区升级为中风险地区 朝阳两涉疫校区及16所学校停课 ...