在 Spring 中集成 MyBatis 进行数据库操作,在项目中很常见,那么当我们需要使用到事务时,他们是如何配合进行的呢?
使用 Spring 的注解 @Transational(或其他的配置形式)就可以在 Java 代码层面声明一个事务了,但是,事务这个东西明明是属于数据库的啊,这个注解是如何做到控制事务的呢?
一、连接池 与 DB 之间的关系
首先,Java 中如无意外,应该所有的数据库操作都是基于 JDBC 进行的,而 MyBatis 也是对 JDBC 做了简单的封装而已。在 JDBC 中操作数据库可分为如下几个步骤:
1、加载驱动,比如使用 MySQL 的话驱动一般是 com.mysql.jdbc.Driver,代码示例为:
Class.forName("com.mysql.jdbc.Driver");
2、获取连接,使用方法 getConnection (url, user, pwd),代码示例为:
Connection conn = (Connection) DriverManager.getConnection(url, username, password);
获取了该连接并持有它的实例,这是才是真正的建立了与数据库的联系;
3、执行 PreparedStatement ,更多细节,略。
其中第二个步骤,如果我们每次需要操作数据库时都去获取一个连接的话,会存在以下问题:1、浪费创建步骤(用完丢弃,GC 回收);2、创建慢(因为每次都是现创现用的);3、连接数不可控(在 DB 端的体现就是:一直都在创建线程,虽然服务器都是支持多线程的,但实际能够并行的线程数是有限的,一般等于或略大于 CPU 的核芯数,而超出这个限度的线程只不过是 CPU 玩的魔术:不断地在多个线程之间切换上下文。简单说就是:创建不受限的线程并不会真的提高总的产出,反而是增加了过多的上下文切换的负担。)
所以,更合理的做法是创建一个不会无限创建连接(DB 端的线程)的连接池 —— 这就是连接池的意义。 连接池的配置上我们也应该严格遵循上述的原则,比如最大连接数不要配置一个特别大的数字(诸如 1000、200 等等),在考虑长事务的情况下,最大连接数设为 DB 端 CPU 核芯数的两倍就是一个相对合理的数字了(比如 DB 端的 CPU 是 8 核的,最大连接数就设为 16 即可。当然这个具体的数字也要看具体的场景,这并非唯一的标准,要讨论这个问题则需要更多的篇幅,这里暂且到此为止)。
这时,我们拥有了一个存放着有限数量连接实例的连接池,1. 当我们需要执行一个 SQL 语句时,就到这个池里面去拿一个 connection;2. 如果没有可用的 connection 就看最大连接数是否满了,2.1. 满了就等待其他线程释放 connection 再获取,直到成功或超时,2.2. 没满就创建一个 connection;3. 用完再把 connection 放回去。
小结
这就是连接池与 DB 的关系,它起到优化连接数据库的过程的作用,而要用好连接池,还必须根据实际场景去配置合适的最大连接数,否则等同于没有连接池。至于连接池的其他配置项本文不继续讨论。
二、Spring 事务 与 MyBatis SqlSession 之间的关系
前面说到,MyBatis 也是对 JDBC 做了简单的封装,但是 MyBatis 每次执行一个 SQL 语句时,并不是直接对应一个 connection,而是每次都要创建一个叫做 SqlSession 的类的对象,每个 sqlSession 对象都对应一个 connection,如果连接池中的 connection 需要等待时,sqlSession 对象也是在等待 connection 的获取的。
这时就到了 Spring 事务是如何控制的了,在 Spring 中,如果存在事务,每个线程都会缓存当前创建的 sqlSession 对象,不同线程之间的 sqlSession 对象是完全隔离的。而 MyBatis 每创建一个 sqlSession 对象前,都会先判断当前是否存在事务(也就是 Spring 中声明的事务),1. 没有事务时就是正常创建即可,并且这样创建的 sqlSession 对象都会在执行完 SQL 语句后自动 commit 和 close 的;2. 有事务时 MyBatis 创建 sqlSession 对象前先查看当前线程是否存在缓存的 sqlSession 对象,2.1. 不存在 sqlSession 对象,创建并缓存到当前线程,2.2. 存在 sqlSession 对象,不再创建,直接使用缓存中的 sqlSession 对象;并且这时的 sqlSession 对象执行完之后是不会进行 commit 与 close 的,这些操作交给了 Spring 事务来完成(至于 Spring 是如何完成 commit 与 close 的,那自然是利用了 AOP 的切面技术了)。
这样,如果一个事务中存在多个 SQL 语句需要执行,那么在这个事务中,自始至终都是只有一个 sqlSession 对象的,并且只对应一个 connection,全部语句都执行完后再 commit 与 close,如果出现异常(或手动回滚)就回滚事务。
这整个过程都是基于 JDBC,以及 Spring 与 MyBatis 的配合。
小结
Spring 事务说到底只是一套标准,在这套标准下,其他的 ORM 框架(不限于 MyBatis,其他 ORM 框架的实现思路也大同小异)配合完成对 JDBC 的 connection 的控制即可起到对数据库事务的控制了。
总结
Spring 事务、MyBatis SqlSession、连接池、DB 之间的关系便是:Spring 指定事务标准,MyBatis 配合并实现 Spring 标准,最后通过 JDBC 实现对数据库的操作。而连接池仅仅起到优化和管理连接数据库这个过程而已。