本文最后更新于:March 18, 2023 pm
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
目录
首先简单的回顾一下MyBatis的初始使用过程:
创建Maven项目并引入Mybatis和MySQL连接依赖。
创建并编写MyBatis主配置文件:mybatis-config.xml,并配置数据源、事务等信息。
创建并编写对应的Mapper文件:xxxxMapper.xml,编写SQL语句。
在主配置文件中声明Mapper文件。
然后主函数中进行使用,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| package com.tothefor.MybatisMember;
import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream; import java.util.List;
public class Main { public static void main(String[] args) throws Exception{ InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); SqlSessionFactory build = sqlSessionFactoryBuilder.build(resourceAsStream); SqlSession session = build.openSession();
int insert = session.insert("insertTest"); System.out.println(insert); session.commit(); } }
|
在这里,我们需要注意MyBatis的几个核心对象:SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession。
面试题:在MyBatis中,负责执行SQL语句的对象叫什么?
但是,如何才能获取到SqlSession呢?要想获取SqlSession对象,需要先获取SqlSessionFactory对象,通过SqlSessionFactory来生产SqlSession;但是,要想获取SqlSessionFactory对象,又需要先获取SqlSessionFactoryBuilder对象,通过SqlSessionFactoryBuilder对象的build方法获取SqlSessionFactory对象。具体代码可见初始示例,核心代码如下:
| InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); SqlSessionFactory build = sqlSessionFactoryBuilder.build(resourceAsStream); SqlSession session = build.openSession();
|
其中,SqlSessionFactoryBuilder对象的build方法需要InputStream流,获取InputStream流的方式很多。上面示例中是通过MyBatis包中的一个Resources工具类进行获取的,当然也可以自行直接进行new,如下:
| SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); InputStream inputStream = new FileInputStream("mybatis-config.xml"); SqlSessionFactory build = sqlSessionFactoryBuilder.build(inputStream); SqlSession session = build.openSession();
|
还有一种获取方式,通过系统的类加载器进行获取,如下所示:
| SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); InputStream resourceAsStream = ClassLoader.getSystemClassLoader().getResourceAsStream("mybatis-config.xml"); SqlSessionFactory build = sqlSessionFactoryBuilder.build(resourceAsStream); SqlSession session = build.openSession();
|
我们对MyBatis提供的Resources工具类进行源码查看可知,其底层实现就是通过调用类加载器。源码如下所示:
| private static ClassLoaderWrapper classLoaderWrapper = new ClassLoaderWrapper();
public static InputStream getResourceAsStream(String resource) throws IOException { return getResourceAsStream((ClassLoader)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); } else { return in; } }
|
其中的classLoaderWrapper的实现方式为:
| public class ClassLoaderWrapper { ClassLoader defaultClassLoader; ClassLoader systemClassLoader;
ClassLoaderWrapper() { try { this.systemClassLoader = ClassLoader.getSystemClassLoader(); } catch (SecurityException var2) { }
} }
|
📚小知识:一般情况下,名称中带resource的都是以根节点(resource目录)作为查找起点的。
事务管理机制
MyBatis的主配置文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "https://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/jwgl?serverTimezone=UTC"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <mappers> <mapper resource="mappers/XsxkbMapper1.xml"/> </mappers> </configuration>
|
在最开始的实例中可以看见,最后一行是session.commit();。那么这是干嘛的呢?是用来提交事务的,因为MyBatis中默认是需要手动进行提交的。
在MyBatis中,通过主配置文件的
| <environment id="development"> <transactionManager type="JDBC"/> ... </environment>
|
进行事务的管理,其中type支持两种值:
如果type的值为JDBC,则表示MyBatis框架自行管理事务,使用原生的JDBC代码进行事务的管理。比如:
| coon.setAutoCommit(false); coon.commit();
|
但是,在MyBatis的使用中,我们发现并没有使用过这两句代码,主要是因为被封装了。
| SqlSession session = build.openSession(); session.commit();
|
提示:以上仅是type为JDBC时,采用了JDBC的事务管理器。
相反,如果type为MANAGED,则表示使用MANAGED事务管理器。也就是表示MyBatis不再负责事务的管理了,而是交给了其他容器来负责,比如:Spring。如果配置值为MANAGED,但又没有其他进行管理,则表示事务没有开启。
源码分析
JDBC
手动提交
首先,我们在openSession()处打一个断点:
| SqlSession session = build.openSession();
|
然后DeBug进去,可以看见如下:
| public SqlSession openSession() { return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false); }
|
这里面,需要注意最后一个参数为false,这也表示是否自动提交。然后再进入openSessionFromDataSource里面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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; }
|
在这里面,我们可以看见一个事务管理器接口:
而该事务管理器接口有两个实现类,刚好对应type的两个值:
| public class JdbcTransaction implements Transaction public class ManagedTransaction implements Transaction
|
然后一步一步往下走,一直走到:
| tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
|
这里就表示创建一个事务管理器。
然后再进入newTransaction:
| public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) { return new JdbcTransaction(ds, level, autoCommit, this.skipSetAutoCommitOnClose); }
|
这里,我们就可以看见,其实是通过JDBC实现的,接下来继续往下走,直到JDBC的构造方法:
| public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit, boolean skipSetAutoCommitOnClose) { this.dataSource = ds; this.level = desiredLevel; this.autoCommit = desiredAutoCommit; this.skipSetAutoCommitOnClose = skipSetAutoCommitOnClose; }
|
在这里,就是将desiredAutoCommit赋值给autoCommit的,表示是否需要自动提交,这里为false。
所以,当type为JDBC时,底层其实就是使用的传统JDBC进行事务的处理。
自动提交
接下来,再看看设置自动提交的源码过程,同时打断点:
| SqlSession session = build.openSession(true);
|
然后同样的方式,一直到:
| public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit, boolean skipSetAutoCommitOnClose) { this.dataSource = ds; this.level = desiredLevel; this.autoCommit = desiredAutoCommit; this.skipSetAutoCommitOnClose = skipSetAutoCommitOnClose; }
|
然后找到该类(JdbcTransaction)中的openConnection()方法:
| protected void openConnection() throws SQLException { if (log.isDebugEnabled()) { log.debug("Opening JDBC Connection"); } connection = dataSource.getConnection(); if (level != null) { connection.setTransactionIsolation(level.getLevel()); } setDesiredAutoCommit(autoCommit); }
|
然后查看:
| setDesiredAutoCommit(autoCommit);
|
源码如下(删除无用代码):
| protected void setDesiredAutoCommit(boolean desiredAutoCommit) { try { if (connection.getAutoCommit() != desiredAutoCommit) { ... connection.setAutoCommit(desiredAutoCommit); } } catch (SQLException e) { ... } }
|
这里非常重要。
首先,看一下判断条件:
| connection.getAutoCommit() != desiredAutoCommit
|
我们知道,这时的desiredAutoCommit是为true的,表示自动提交。那connection.getAutoCommit()的值是什么呢?
首先,我们需要知道,如果在JDBC的代码中没有执行过:connection.setAutoCommit(false)的话,那么默认是为true的。即:JDBC中默认事务是自动提交的。
而且,我们也并没有设置过,所以,此时的 connection.getAutoCommit() 是为true的。那么,
| connection.getAutoCommit() != desiredAutoCommit
|
的结果为false才对。即然为false了,那源码中的:
| connection.setAutoCommit(desiredAutoCommit);
|
就不会被执行。虽然没有被执行,但是默认就是设置的为true,所以,并没有影响。
总结
- 只要setCommit是true,则表示没有开启事务;为false表示开启事务。