面试官问:mybatis工作原理!你分析过吗?有就留下,没有那就是悲伤更悲伤的故事了

Mybatis工作原理也是面试的一大考点,必须要对其非常清晰,这样才能怼回去。本文建立在Spring+SpringMVC+Mybatis整合的项目之上。

我将其工作原理分为六个部分:

读取核心配置文件并返回InputStream流对象。

根据InputStream流对象解析出Configuration对象,然后创建SqlSessionFactory工厂对象

根据一系列属性从SqlSessionFactory工厂中创建SqlSession

从SqlSession中调用Executor执行数据库操作&&生成具体SQL指令

对执行结果进行二次封装

提交与事务

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

/**

* 图书实体

*/

publicclassBook{

privatelongbookId;// 图书ID

privateString name;// 图书名称

privateintnumber;// 馆藏数量

getterandsetter ...

}

1. 读取核心配置文件

1.1 配置文件mybatis-config.xml

PUBLIC "-//mybatis.org//DTD Config 3.0//EN"

"http://mybatis.org/dtd/mybatis-3-config.dtd">

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

1.2 BookMapper.xml

PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"

"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

insert into book (name,number) values (#{name},#{number})

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

1.3 Main方法

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

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

publicclassMain{

publicstaticvoidmain(String[] args)throwsIOException{

// 创建一个book对象

Book book =newBook();

book.setBookId(1006);

book.setName("Easy Coding");

book.setNumber(110);

// 加载配置文件 并构建SqlSessionFactory对象

String resource ="mybatis-config.xml";

InputStream inputStream = Resources.getResourceAsStream(resource);

SqlSessionFactory factory =newSqlSessionFactoryBuilder().build(inputStream);

// 从SqlSessionFactory对象中获取 SqlSession对象

SqlSession sqlSession = factory.openSession();

// 执行操作

sqlSession.insert("insert", book);

// 提交操作

sqlSession.commit();

// 关闭SqlSession

sqlSession.close();

}

}

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

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

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

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

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

我们只看getResourceAsStream方法:

publicstaticInputStreamgetResourceAsStream(String resource)throwsIOException{

returngetResourceAsStream((ClassLoader)null, resource);

}

getResourceAsStream调用下面的方法:

publicstaticInputStreamgetResourceAsStream(ClassLoader loader, String resource) throws IOException{

InputStreamin= classLoaderWrapper.getResourceAsStream(resource, loader);

if(in==null) {

thrownewIOException("Could not find resource "+ resource);

}else{

returnin;

}

}

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

InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {

ClassLoader[] arr$ = classLoader;

int len$ = classLoader.length;

for(int i$ =0; i$ < len$; ++i$) {

ClassLoader cl = arr$[i$];

if(null != cl) {

InputStream returnValue = cl.getResourceAsStream(resource);

if(null == returnValue) {

returnValue = cl.getResourceAsStream("/"+ resource);

}

if(null != returnValue) {

returnreturnValue;

}

}

}

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

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

publicSqlSessionFactoryBuilder(){

}

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

publicSqlSessionFactory build(InputStream inputStream) {

returnthis.build((InputStream)inputStream, (String)null, (Properties)null);

}

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

publicSqlSessionFactorybuild(InputStream inputStream, String environment, Properties properties){

SqlSessionFactory var5;

try{

// 进行XML配置文件的解析

XMLConfigBuilder parser =newXMLConfigBuilder(inputStream, environment, properties);

var5 =this.build(parser.parse());

}catch(Exception var14) {

throwExceptionFactory.wrapException("Error building SqlSession.", var14);

}finally{

ErrorContext.instance().reset();

try{

inputStream.close();

}catch(IOException var13) {

;

}

}

returnvar5;

}

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

publicSqlSessionFactorybuild(Configuration config){

returnnewDefaultSqlSessionFactory(config);

}

publicDefaultSqlSessionFactory(Configuration configuration){

this.configuration = configuration;

}

3. 创建SqlSession

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

publicSqlSession openSession() {

returnthis.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null,false);

}

调用自身的openSessionFromDataSource方法:

getDefaultExecutorType()默认是SIMPLE。

注意TX等级是 Null, autoCommit是false。

privateSqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {

Transaction tx =null;

DefaultSqlSession var8;

try{

Environment environment =this.configuration.getEnvironment();

// 根据Configuration的Environment属性来创建事务工厂

TransactionFactory transactionFactory =this.getTransactionFactoryFromEnvironment(environment);

// 从事务工厂中创建事务,默认等级为null,autoCommit=false

tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);

// 创建执行器

Executor executor =this.configuration.newExecutor(tx, execType);

// 根据执行器创建返回对象 SqlSession

var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);

}catch(Exception var12) {

this.closeTransaction(tx);

throwExceptionFactory.wrapException("Error opening session.  Cause: "+ var12, var12);

}finally{

ErrorContext.instance().reset();

}

returnvar8;

}

构建步骤:

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

其中,Environment是Configuration中的属性。

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

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

publicintinsert(String statement, Object parameter){

returnthis.update(statement, parameter);

}

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

publicint update(String statement, Object parameter) {

int var4;

try{

this.dirty =true;

MappedStatement ms =this.configuration.getMappedStatement(statement);

// wrapCollection(parameter)判断 param对象是否是集合

var4 =this.executor.update(ms,this.wrapCollection(parameter));

}catch(Exception var8) {

throwExceptionFactory.wrapException("Error updating database.  Cause: "+ var8, var8);

}finally{

ErrorContext.instance().reset();

}

returnvar4;

}

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

源码如下:

protected final Map mappedStatements;

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

然后调用BaseExecutor中的update方法:

publicint update(MappedStatement ms, Object parameter) throws SQLException {

ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());

if(this.closed) {

thrownew ExecutorException("Executor was closed.");

}else{

this.clearLocalCache();

// 真正做执行操作的方法

returnthis.doUpdate(ms, parameter);

}

}

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

publicintdoUpdate(MappedStatement ms, Object parameter)throwsSQLException{

Statement stmt =null;

intvar6;

try{

Configuration configuration = ms.getConfiguration();

// 创建StatementHandler对象,从而创建Statement对象

StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, (ResultHandler)null, (BoundSql)null);

// 将sql语句和参数绑定并生成SQL指令

stmt =this.prepareStatement(handler, ms.getStatementLog());

var6 = handler.update(stmt);

}finally{

this.closeStatement(stmt);

}

returnvar6;

}

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

privateStatementprepareStatement(StatementHandler handler, Log statementLog)throwsSQLException{

Connection connection =this.getConnection(statementLog);

// 准备Statement

Statement stmt = handler.prepare(connection);

// 设置SQL查询中的参数值

handler.parameterize(stmt);

returnstmt;

}

来看看parameterize方法:

publicvoidparameterize(Statement statement)throwsSQLException{

this.parameterHandler.setParameters((PreparedStatement)statement);

}

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

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

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

publicvoidsetParameters(PreparedStatement ps){

ErrorContext.instance().activity("setting parameters").object(this.mappedStatement.getParameterMap().getId());

List parameterMappings =this.boundSql.getParameterMappings();

if(parameterMappings !=null) {

for(inti =0; i < parameterMappings.size(); ++i) {

ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i);

if(parameterMapping.getMode() != ParameterMode.OUT) {

String propertyName = parameterMapping.getProperty();

Objectvalue;

if(this.boundSql.hasAdditionalParameter(propertyName)) {

value=this.boundSql.getAdditionalParameter(propertyName);

}elseif(this.parameterObject ==null) {

value=null;

}elseif(this.typeHandlerRegistry.hasTypeHandler(this.parameterObject.getClass())) {

value=this.parameterObject;

}else{

MetaObject metaObject =this.configuration.newMetaObject(this.parameterObject);

value= metaObject.getValue(propertyName);

}

TypeHandler typeHandler = parameterMapping.getTypeHandler();

JdbcType jdbcType = parameterMapping.getJdbcType();

if(value==null&& jdbcType ==null) {

jdbcType =this.configuration.getJdbcTypeForNull();

}

try{

typeHandler.setParameter(ps, i +1,value, jdbcType);

}catch(TypeException var10) {

thrownewTypeException("Could not set parameters for mapping: "+ parameterMapping +". Cause: "+ var10, var10);

}catch(SQLException var11) {

thrownewTypeException("Could not set parameters for mapping: "+ parameterMapping +". Cause: "+ var11, var11);

}

}

}

}

}

5. 对查询结果二次封装

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

publicint update(Statement statement) throws SQLException {

PreparedStatement ps = (PreparedStatement)statement;

// 执行sql

ps.execute();

// 获取返回值

int rows = ps.getUpdateCount();

Object parameterObject =this.boundSql.getParameterObject();

KeyGenerator keyGenerator =this.mappedStatement.getKeyGenerator();

keyGenerator.processAfter(this.executor,this.mappedStatement, ps, parameterObject);

returnrows;

}

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

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

6. 提交与事务

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

publicvoidcommit(){

this.commit(false);

}

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

publicvoidcommit(booleanforce){

try{

// 是否提交(判断是提交还是回滚)

this.executor.commit(this.isCommitOrRollbackRequired(force));

this.dirty =false;

}catch(Exception var6) {

throwExceptionFactory.wrapException("Error committing transaction.  Cause: "+ var6, var6);

}finally{

ErrorContext.instance().reset();

}

}

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

privatebooleanisCommitOrRollbackRequired(booleanforce){

return!this.autoCommit &&this.dirty || force;

}

调用CachingExecutor的commit方法:

publicvoidcommit(booleanrequired)throwsSQLException{

this.delegate.commit(required);

this.tcm.commit();

}

调用BaseExecutor的commit方法:

publicvoidcommit(booleanrequired)throwsSQLException{

if(this.closed) {

thrownewExecutorException("Cannot commit, transaction is already closed");

}else{

this.clearLocalCache();

this.flushStatements();

if(required) {

this.transaction.commit();

}

}

}

最后调用JDBCTransaction的commit方法:

publicvoid commit() throws SQLException {

if(this.connection !=null&& !this.connection.getAutoCommit()) {

if(log.isDebugEnabled()) {

log.debug("Committing JDBC Connection ["+this.connection +"]");

}

// 提交连接

this.connection.commit();

}

}

Demo参考文档

http://www.mybatis.org/mybatis-3/zh/getting-started.html

最后,给大家推荐一个Java进阶内推交流群851531810,不管你在地球哪个方位,不管你参加工作几年都欢迎你的入驻!(群内会免费提供一些群主收藏的免费学习书籍资料以及整理好的几百道面试题和答案文档!)

你可能感兴趣的:(面试官问:mybatis工作原理!你分析过吗?有就留下,没有那就是悲伤更悲伤的故事了)