面试官:你分析过mybatis工作原理吗?

2019/3/22 10:25:44 来源:Java的小本家 浏览:

先给大家看看我的实体类:

 1 /**
2 * 图书实体
3 */
4 public class Book {
5
6 private long bookId;// 图书ID
7
8 private String name;// 图书名称
9
10 private int number;// 馆藏数量
11
12 getter and setter ...
13 }

如果想学习Java工程化、高性能及分布式、深入浅出。微服务、Spring,MyBatis,Netty源码分析的朋友可以加我的Java高级交流:787707172,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给大家。

1. 读取核心配置文件

1.1 配置文件mybatis-config.xml

 1 <?xml version="1.0" encoding="UTF-8" ?>
2 <!DOCTYPE configuration
3 PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
4 "http://mybatis.org/dtd/mybatis-3-config.dtd">
5 <configuration>
6 <environments default="development">
7 <environment id="development">
8 <transactionManager type="JDBC"/>
9 <dataSource type="POOLED">
10 <property name="driver" value="com.mysql.jdbc.Driver"/>
11 <property name="url" value="jdbc:mysql://xxx.xxx:3306/ssm" />
12 <property name="username" value="root"/>
13 <property name="password" value="root"/>
14 </dataSource>
15 </environment>
16 </environments>
17 <mappers>
18 <mapper resource="BookMapper.xml"/>
19 </mappers>
20 </configuration>

当然,还有很多可以在XML 文件中进行配置,上面的示例指出的则是最关键的部分。要注意 XML 头部的声明,用来验证 XML 文档正确性。environment 元素体中包含了事务管理和连接池的配置。mappers 元素则是包含一组 mapper 映射器(这些 mapper 的 XML 文件包含了 SQL 代码和映射定义信息)。

1.2 BookMapper.xml

 1 <?xml version="1.0" encoding="UTF-8"?>
2 <!DOCTYPE mapper
3 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
4 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
5 <mapper namespace="Book">
6 <!-- 目的:为dao接口方法提供sql语句配置 -->
7 <insert id="insert" >
8 insert into book (name,number) values (#{name},#{number})
9 </insert>
10 </mapper>

就是一个普通的mapper.xml文件。

1.3 Main方法

从 XML 文件中构建 SqlSessionFactory 的实例非常简单,建议使用类路径下的资源文件进行配置。但是也可以使用任意的输入流(InputStream)实例,包括字符串形式的文件路径或者 file:// 的 URL 形式的文件路径来配置。

MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,可使从 classpath 或其他位置加载资源文件更加容易。

 1 public class Main {
2 public static void main(String[] args) throws IOException {
3 // 创建一个book对象
4 Book book = new Book();
5 book.setBookId(1006);
6 book.setName("Easy Coding");
7 book.setNumber(110);
8 // 加载配置文件 并构建SqlSessionFactory对象
9 String resource = "mybatis-config.xml";
10 InputStream inputStream = Resources.getResourceAsStream(resource);
11 SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
12 // 从SqlSessionFactory对象中获取 SqlSession对象
13 SqlSession sqlSession = factory.openSession();
14 // 执行操作
15 sqlSession.insert("insert", book);
16 // 提交操作
17 sqlSession.commit();
18 // 关闭SqlSession
19 sqlSession.close();
20 }
21 }

这个代码是根据Mybatis官方提供的一个不使用 XML 构建 SqlSessionFactory的一个Demo改编的。

注意:是官方给的一个不使用 XML 构建 SqlSessionFactory的例子,那么我们就从这个例子中查找入口来分析。

2. 根据配置文件生成SqlSessionFactory工厂对象

2.1 Resources.getResourceAsStream(resource);源码分析

Resources是mybatis提供的一个加载资源文件的工具类。

我们只看getResourceAsStream方法:

1 public static InputStream getResourceAsStream(String resource) throws IOException {
2 return getResourceAsStream((ClassLoader)null, resource);
3 }

getResourceAsStream调用下面的方法:

1 public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {
2 InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
3 if (in == null) {
4 throw new IOException("Could not find resource " + resource);
5 } else {
6 return in;
7 }
8 }

获取到自身的ClassLoader对象,然后交给ClassLoader(lang包下的)来加载:

 1 InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
2 ClassLoader[] arr$ = classLoader;
3 int len$ = classLoader.length;
4
5 for(int i$ = 0; i$ < len$; ++i$) {
6 ClassLoader cl = arr$[i$];
7 if (null != cl) {
8 InputStream returnValue = cl.getResourceAsStream(resource);
9 if (null == returnValue) {
10 returnValue = cl.getResourceAsStream("/" + resource);
11 }
12
13 if (null != returnValue) {
14 return returnValue;
15 }
16 }
17 }

值的注意的是,它返回了一个InputStream对象。

2.2 new SqlSessionFactoryBuilder().build(inputStream);源码分析

1 public SqlSessionFactoryBuilder() { 2 }

所以new SqlSessionFactoryBuilder()只是创建一个对象实例,而没有对象返回(建造者模式),对象的返回交给build()方法。

1 public SqlSessionFactory build(InputStream inputStream) {
2 return this.build((InputStream)inputStream, (String)null, (Properties)null);
3 }

这里要传入一个inputStream对象,就是将我们上一步获取到的InputStream对象传入。

 1 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
2 SqlSessionFactory var5;
3 try {
4 // 进行XML配置文件的解析
5 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
6 var5 = this.build(parser.parse());
7 } catch (Exception var14) {
8 throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
9 } finally {
10 ErrorContext.instance().reset();
11
12 try {
13 inputStream.close();
14 } catch (IOException var13) {
15 ;
16 }
17
18 }
19
20 return var5;
21 }

如何解析的就大概说下,通过Document对象来解析,然后返回InputStream对象,然后交给XMLConfigBuilder构造成org.apache.ibatis.session.Configuration对象,然后交给build()方法构造程SqlSessionFactory:

1 public SqlSessionFactory build(Configuration config) {
2 return new DefaultSqlSessionFactory(config);
3 }
4 public DefaultSqlSessionFactory(Configuration configuration) {
5 this.configuration = configuration;
6 }

3. 创建SqlSession

SqlSession 完全包含了面向数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。

1 public SqlSession openSession() {
2 return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
3 }

调用自身的openSessionFromDataSource方法:

  1. getDefaultExecutorType()默认是SIMPLE。
  2. 注意TX等级是 Null, autoCommit是false。
 1 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
2 Transaction tx = null;
3
4 DefaultSqlSession var8;
5 try {
6 Environment environment = this.configuration.getEnvironment();
7 // 根据Configuration的Environment属性来创建事务工厂
8 TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
9 // 从事务工厂中创建事务,默认等级为null,autoCommit=false
10 tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
11 // 创建执行器
12 Executor executor = this.configuration.newExecutor(tx, execType);
13 // 根据执行器创建返回对象 SqlSession
14 var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
15 } catch (Exception var12) {
16 this.closeTransaction(tx);
17 throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
18 } finally {
19 ErrorContext.instance().reset();
20 }
21 return var8;
22 }

构建步骤:

Environment>>TransactionFactory+autoCommit+tx-level>>Transaction+ExecType>>Executor+Configuration+autoCommit>>SqlSession

其中,Environment是Configuration中的属性。

4. 调用Executor执行数据库操作&&生成具体SQL指令

在拿到SqlSession对象后,我们调用它的insert方法。

1 public int insert(String statement, Object parameter) {
2
3 return this.update(statement, parameter);
4
5 }

它调用了自身的update(statement, parameter)方法:

如果想学习Java工程化、高性能及分布式、深入浅出。微服务、Spring,MyBatis,Netty源码分析的朋友可以加我的Java高级交流:787707172,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给大家。

 1 public int update(String statement, Object parameter) {
2 int var4;
3 try {
4 this.dirty = true;
5 MappedStatement ms = this.configuration.getMappedStatement(statement);
6 // wrapCollection(parameter)判断 param对象是否是集合
7 var4 = this.executor.update(ms, this.wrapCollection(parameter));
8 } catch (Exception var8) {
9 throw ExceptionFactory.wrapException("Error updating database. Cause: " + var8, var8);
10 } finally {
11 ErrorContext.instance().reset();
12 }
13
14 return var4;
15 }

mappedStatements就是我们平时说的sql映射对象.

源码如下:

protected final Map<String, MappedStatement> mappedStatements;

可见它是一个Map集合,在我们加载xml配置的时候,mapping.xml的namespace和id信息就会存放为mappedStatements的key,对应的,sql语句就是对应的value.

然后调用BaseExecutor中的update方法:

 1 public int update(MappedStatement ms, Object parameter) throws SQLException {
2 ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
3 if (this.closed) {
4 throw new ExecutorException("Executor was closed.");
5 } else {
6 this.clearLocalCache();
7 // 真正做执行操作的方法
8 return this.doUpdate(ms, parameter);
9 }
10 }

doUpdate才是真正做执行操作的方法:

 1 public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
2 Statement stmt = null;
3
4 int var6;
5 try {
6 Configuration configuration = ms.getConfiguration();
7 // 创建StatementHandler对象,从而创建Statement对象
8 StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, (ResultHandler)null, (BoundSql)null);
9 // 将sql语句和参数绑定并生成SQL指令
10 stmt = this.prepareStatement(handler, ms.getStatementLog());
11 var6 = handler.update(stmt);
12 } finally {
13 this.closeStatement(stmt);
14 }
15
16 return var6;
17 }

先来看看prepareStatement方法,看看mybatis是如何将sql拼接合成的:

1 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
2 Connection connection = this.getConnection(statementLog);
3 // 准备Statement
4 Statement stmt = handler.prepare(connection);
5 // 设置SQL查询中的参数值
6 handler.parameterize(stmt);
7 return stmt;
8 }

来看看parameterize方法:

1 public void parameterize(Statement statement) throws SQLException {
2
3 this.parameterHandler.setParameters((PreparedStatement)statement);
4
5 }

这里把statement转换程PreparedStatement对象,它比Statement更快更安全。

这都是我们在JDBC中熟用的对象,就不做介绍了,所以也能看出来Mybatis是对JDBC的封装。

从ParameterMapping中读取参数值和类型,然后设置到SQL语句中:

 1 public void setParameters(PreparedStatement ps) {
2 ErrorContext.instance().activity("setting parameters").object(this.mappedStatement.getParameterMap().getId());
3 List<ParameterMapping> parameterMappings = this.boundSql.getParameterMappings();
4 if (parameterMappings != null) {
5 for(int i = 0; i < parameterMappings.size(); ++i) {
6 ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i);
7 if (parameterMapping.getMode() != ParameterMode.OUT) {
8 String propertyName = parameterMapping.getProperty();
9 Object value;
10 if (this.boundSql.hasAdditionalParameter(propertyName)) {
11 value = this.boundSql.getAdditionalParameter(propertyName);
12 } else if (this.parameterObject == null) {
13 value = null;
14 } else if (this.typeHandlerRegistry.hasTypeHandler(this.parameterObject.getClass())) {
15 value = this.parameterObject;
16 } else {
17 MetaObject metaObject = this.configuration.newMetaObject(this.parameterObject);
18 value = metaObject.getValue(propertyName);
19 }
20
21 TypeHandler typeHandler = parameterMapping.getTypeHandler();
22 JdbcType jdbcType = parameterMapping.getJdbcType();
23 if (value == null && jdbcType == null) {
24 jdbcType = this.configuration.getJdbcTypeForNull();
25 }
26
27 try {
28 typeHandler.setParameter(ps, i + 1, value, jdbcType);
29 } catch (TypeException var10) {
30 throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + var10, var10);
31 } catch (SQLException var11) {
32 throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + var11, var11);
33 }
34 }
35 }
36 }
37
38 }

5. 对查询结果二次封装

在doUpdate方法中,解析生成完新的SQL后,然后执行var6 = handler.update(stmt);我们来看看它的源码。

 1 public int update(Statement statement) throws SQLException {
2 PreparedStatement ps = (PreparedStatement)statement;
3 // 执行sql
4 ps.execute();
5 // 获取返回值
6 int rows = ps.getUpdateCount();
7 Object parameterObject = this.boundSql.getParameterObject();
8 KeyGenerator keyGenerator = this.mappedStatement.getKeyGenerator();
9 keyGenerator.processAfter(this.executor, this.mappedStatement, ps, parameterObject);
10 return rows;
11 }

因为我们是插入操作,返回的是一个int类型的值,所以这里mybatis给我们直接返回int。

如果是query操作,返回的是一个ResultSet,mybatis将查询结果包装程ResultSetWrapper类型,然后一步步对应java类型赋值等…有兴趣的可以自己去看看。

6. 提交与事务

最后,来看看commit()方法的源码。

1 public void commit() {
2
3 this.commit(false);
4
5 }

调用其对象本身的commit()方法:

 1 public void commit(boolean force) {
2 try {
3 // 是否提交(判断是提交还是回滚)
4 this.executor.commit(this.isCommitOrRollbackRequired(force));
5 this.dirty = false;
6 } catch (Exception var6) {
7 throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + var6, var6);
8 } finally {
9 ErrorContext.instance().reset();
10 }
11 }

如果dirty是false,则进行回滚;如果是true,则正常提交。

1 private boolean isCommitOrRollbackRequired(boolean force) {
2 return !this.autoCommit && this.dirty || force;
3 }

调用CachingExecutor的commit方法:

1 public void commit(boolean required) throws SQLException {
2 this.delegate.commit(required);
3 this.tcm.commit();
4 }

调用BaseExecutor的commit方法:

 1 public void commit(boolean required) throws SQLException {
2 if (this.closed) {
3 throw new ExecutorException("Cannot commit, transaction is already closed");
4 } else {
5 this.clearLocalCache();
6 this.flushStatements();
7 if (required) {
8 this.transaction.commit();
9 }
10
11 }
12 }

最后调用JDBCTransaction的commit方法:

1 public void commit() throws SQLException {
2 if (this.connection != null && !this.connection.getAutoCommit()) {
3 if (log.isDebugEnabled()) {
4 log.debug("Committing JDBC Connection [" + this.connection + "]");
5 }
6 // 提交连接
7 this.connection.commit();
8 }
9 }

欢迎工作一到八年的Java工程师朋友们加入Java高级交流:787707172

本群提供免费的学习指导 架构资料 以及免费的解答

不懂得问题都可以在本群提出来 之后还会有直播平台和讲师直接交流噢

看看网友怎么说

一路向前142939631:转发了

徐忠明勿忘初心:转发了

开发工程师1:转发了

优雅的BUG:转发了

深心豪素:转发了

不醉独醒:转发了

ello2248064905:转发了

毓濯:转发了

一个人的黎明与黄昏:转发了

文章来源网络,版权归属原作者,未注明作者均因传阅太多无从查证。本站为公益性非盈利网站,在本网转载其他媒体稿件是为传播更多的信息,此类稿件不代表本网观点。如果本网转载的稿件涉及您的版权、名益权等问题,请尽快与我们联系,我们将第一时间处理!
  • 中新经纬客户端3月20日电3月20日,财政部条法司公布2019年财政部立法工作安排。安排中指出,力争年内完成增值税法、消费税法、印花税法、城市维护建设税法、土地增值税法、关税法、彩票管理条例(修订)的部内起草工作。财政部指出,以加快建立现代财政制度为目标,增强财政立法的系统性、及时性和有效性,坚持科...

  • 我觉得不能这么看问题,我在北京的第一任老板曾告诉我要跳出来看问题。他说:“当你看问题时,不能只看问题的阳面,还要转过来看问题的阴面,之后再做思考”,我觉得我老板说的非常对。前半辈子玩命挣钱是不假,但挣钱的理由不一定时需要搭上性命。而且玩命挣钱的原因是因为生活所迫,为了生存,当时的三观告诉我们,一定要...

  • 参考消息网3月19日报道美媒称,美国通用汽车公司近期关闭了位于俄亥俄州洛兹敦的制造工厂。美国总统特朗普16日、17日与18日连发数条推特,向通用公司施压,要求通用重开这家工厂。据美国彭博社3月18日报道,特朗普表示,他希望通用汽车公司和全美汽车工人联合工会立即开始谈判。特朗普18日一早就在推特上说:...

  • e公司讯,人民网(603000)3月20日晚间公告,卢新宁因工作原因,现辞去董事长、董事等在公司所任的一切职务。注:国务院昨日任免国家工作人员,任命卢新宁为中央人民政府驻香港特别行政区联络办公室副主任。...

  • 无论如何都是为了孩子,还是随孩子的意吧。我是西红柿在农村,我给你回答一下。因为我们此生无论做什么事,自己成功的同时,也希望孩子过得比我们好。特别是人到60岁以后,都希望自己的孩子比自己有出息。孩子在自己身边,往往不会长大成人。我们总归是要离开人世的人。不希望自己拖累子女。除非万不得已的时候,去敬老院...

  • 到了我们现代,使用的桥梁已经都是用钢筋水泥制成的桥梁了,因为这样的桥梁才够安全,承载力也比那些木头做的桥要大。就好比如港珠澳大桥一样,建造这条大桥时,花费了大量的人力物力,其中铺设的钢筋都是一笔很庞大的开销,毕竟港珠澳大桥需要承载很多车辆的出行,如果承载力不够的话,那么很有可能就会发生事故。而且使用...

  • 【英国19岁少女来华旅游发现商机,4年时间狂赚300多万人民币】据网上新闻报道,英国一名19岁少女4年前来中国旅游,由于她的父亲是在中国工作,因此这名少女在中国旅游期间通过与中国人的接触发现了一个很好的商业机会,那就是给中国家庭的孩子起一个好听的英文名字。回国后,当时年仅15岁的女孩就动手建了一个给...

  • 新京报快讯(记者谢莲)据福克斯新闻报道,美国国防部监察长办公室当地时间周三表示,已经对代理防长帕特里克·沙纳汉展开调查,以明确其是否利用职务之便推广他的老雇主——波音公司。美国代理防长沙纳汉。图/美联社据报道,这一调查缘起于非营利组织“华盛顿公民责任与道德”上周发起的一项申诉,该组织称沙纳汉任职期间...

  • 数据库存储引擎是数据库底层软件组织,数据库管理系统(DBMS)使用数据引擎进行创建、查询、更新和删除数据。不同的存储引擎提供不同的存储机制、索引技巧、锁定水平等功能,使用不同的存储引擎,还可以获得特定的功能。现在许多不同的数据库管理系统都支持多种不同的数据引擎。MySQL的核心就是存储引擎。此图就是...

  • 熬夜对于很多现代人来说,是难以避免的一件事。尽管医生们已经把熬夜的诸多危害已经交代给大家,很多人仍旧不能杜绝熬夜。原因没有别的,就是因为工作性质所致,比如说一些需要值夜班的岗位,张大夫所从事的医疗行业就无法避免夜班。那么,既然我们无法避免熬夜,那能不能有什么办法降低熬夜的危害呢?今天张大夫来说一说#...