Mybatis教程 | 第七篇:Mybatis的事务管理和缓存机制

本文介绍了Mybatis的事务管理,包括事务的概念、Mybatis的两种事务管理机制及其配置和使用。接着,文章详细讲解了一级缓存和二级缓存的工作原理、注意事项以及如何配置和使用。一级缓存存在于SqlSession作用域,而二级缓存为mapper级别,可跨SqlSession共享,但需要手动管理数据实时性以避免脏读问题。
摘要由CSDN通过智能技术生成

Mybatis的事务管理

1、事务的概念

在我们开发过程中,几乎每个业务逻辑都离不开对数据库的操作。那么对数据的的单个操作(单个CRUD)或者多个操作(多个CRUD)绑定在一起,就称为事务。

单个事务是一个最小的逻辑执行单元,整个事务不能分开执行,要么同时成功,要么同时失败。(PS:初次接触事务概念的朋友,也许会难以理解最小逻辑执行单元,反正当初我是误解了,误解的地方还真有点描述不出来,原谅我词穷,下面举个例子领悟吧)。

场景一:

在我们的service层(业务逻辑层)要执行一个转账操作(A账户转账到B账户,转100元)。那么简单的做法就应该是A的账户执行一个update操作减去100,然后B的账户也执行一个update操作增加100。这两个操作绑定到一起,就称为事务。这个事务是不能分开执行的,要么同时成功(转账成功),要么同时失败(转账失败)。不能A账户少了100,B账户没增加100,那这100归我(哈哈)?这样就破坏了数据的完整性了。

场景二:

将一批数据插入到数据库中,如果要求同时成功或者同时失败,当然是不可靠的,假如每次在执行过程中程序都出现意外呢(^_^),那么就需要将每次插入(insert操作)作为一个事务(这也说明,多个事务是可以同时存在的,这也是容易误解的地方)。(反例:当然,我们将所有的insert操作绑定在一起也是一个事务,那么就要求,要么全部失败,要么全部成功,是不能分开执行的)。

这两个场景在我们的业务逻辑层具体怎么实现,体现的就是事务的传播行为(后面我们单独开一篇来讲事务)。

通常来讲,事务具备4个特性,就是经常听人提起的(ACID):原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。具体说明见下表:

特性说明
原子性(Atomicity)也就是说事务是应用中的最小逻辑执行单元,就像自然界中的原子一样,不能再被拆分,也就是再次强调,单个事务是不能够分开执行的(哪怕你单个事务中包含了n多个CRUD,都是要么全部成功,要么全部失败)。
一致性(Consistency)事务的执行结果,必须使数据库从一种一致性状态变成另一种一致性状态。就比如上面的场景一,A、B之间不管转账几次,A、B的账户总额一定是不变的,A、B的账户和总额之间的关系始终是一致的。如果A减少100,B没增加,那么它们之间的一致性就被破坏了。因此,一致性是通过原子性保证的。
隔离性(Isolation)各个事务之间的执行是互不干扰的,比如上面的场景二,单个事务的insert插入,并不不能影响下一个事务的insert操作成功与否,再比如我和你同时访问CSDN(文章评论的业务逻辑肯定只有一个),我对一篇文章的评论能否提交成功,并不能影响你对一篇文章的评论能否提交成功。所以,事务也是并发的。
持久性(Durability)我们单个事务一旦提交,那么对数据所做的操作都是永远保存在数据库中的,直到下一个事务再对其进行更改

 

那么,在我们去实现事务的过程中,就应该包含以下几个操作:创建(create)、提交(commit)、回滚(rollback)、关闭(close)。

回到我们上面的场景一就是:创建一个事务分别包含A和B的update操作,如果两个update操作都成功了,则commit,如果在两个update执行过程中,断网或者断电了,再或者B被匪徒劫持了导致A取消转账,则rollback,最后close事务。

2、Mybatis的事务管理

在Mybatis中,事务的管理分为两种形式:

>使用JDBC的事务管理机制: 就是在没使用ORM框架之前,基于jdbc中java.sql.Connection对象里面的commit()、rollback()、close()方法实现。

>使用MANAGED的事务管理机制。对于这种机制,Mybatis自身不会去实现对事务的管理,而是让容器去实现,比如我们常用的Spring。那么,我们在使用Spring+Mybatis整合的开发方式时,如果将事务交给Spring去管理,就应该配置Mybatis为MANAGED的事务机制。当然,现在一般都是整合开发,所以都是选择这种方式。

3、事务的配置创建和简单使用

<environments default="mysql">
    <environment id="mysql">
    <!-- 指定事务管理类型,指使用JDBC的提交和回滚设置 -->
      <transactionManager type="JDBC"/>
</environments>

就是在我们的Mybatis配置文件中(传送门:配置文件详解)直接定义如上的信息即可。这里只用了Mybais,所以选择了JDBC的事务管理机制。

剩下的一系列工作,Mybatis都提供了事务工厂(TransactionFactory)为我们创建并管理事务,我们只需要调用方法即可。(这里不分析Mybatis是怎么在操作,可以直接点开TransactionFactory或者在运行过程中debug,只做简单的操作示例,篇幅有限,水平也有限,见谅哦^_^)。

接下来,我们就可以愉快的玩耍了。

创建如下的表并配置好环境(参考:Mybatis第一篇)

20190301230013371.png

public class Test {

	public static void main(String[] args) {
		InputStream is = Test.class.getResourceAsStream("/mybatis/mybatis-config.xml");
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); 
		//注意这里-----
		SqlSession sqlSession = sqlSessionFactory.openSession();
		UserDao userDao = sqlSession.getMapper(UserDao.class);
		//插入两条数据
		User user1 = new User();
		user1.setUserName("孙悟空");
		user1.setUserAge(1000);
		userDao.saveUser(user1);
		User user2 = new User();
		user2.setUserName("猪八戒");
		user2.setUserAge(500);
		userDao.saveUser(user2);
		try {
			int i = 5/0;//假如这里执行其他业务逻辑产生了异常行为
		} catch (Exception e) {
			sqlSession.rollback();//回滚
		}
		sqlSession.close();
	}
}

执行上面的测试代码,会发现,数据库中并没有数据的插入,因为被我们执行回滚了。

注意这里:

SqlSession sqlSession = sqlSessionFactory.openSession();

在获取SqlSession对象的时候,我们可以对其进行一系列的设置。

1、开启批处理(上一篇已经深入了解过);

SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);

2、设置隔离级别(这个需要底层数据库的支持)

SqlSession sqlSession = sqlSessionFactory.openSession(TransactionIsolationLevel.READ_COMMITTED);

3、设置自动提交(commit)

将上面的测试代码,删掉rollback(),不执行commit(),数据库同样会没数据,需要手动执行commit(),假如设置自动提交,就算不执行commit()也会提交。

SqlSession sqlSession = sqlSessionFactory.openSession(true);

还有很多其它的设置,需要我们去发现

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MTM4MTg2Mw==,size_16,color_FFFFFF,t_70

Mybatis的缓存机制

缓存的机制就是,将我们的数据置于内存中,然后我们就可以直接在内存中访问数据,省去了对底层数据库的操作(因为数据库的数据置于硬盘上,所以访问数据需要IO的操作,还有和数据库建立连接等等)。

在现实的互联网产品中,很大部分都是查询功能占主比。所以,通常对数据库查询的性能要求很高。而Mybatis则提供了这样的功能来为我们提高查询性能。

Mybatis的缓存分为一级缓存和二级缓存。一级缓存是SqlSession级别的,也就是只存在于我们当前的SqlSession作用域中,简单的比喻就是,比如两个用户通过Mybatis访问我们的数据,Mybatis则会为两个用户创建不同的SqlSession,那么A的一级缓存,则不能被B共享。二级缓存是Mapper级别的,可以简单理解为,二级缓存存在于Mybatis处理Mapper文件的功能中,是可以多个SqlSession共享的。

1、一级缓存

一级缓存的作用域是SqlSession范围的。实现的原理大致就是,我们构建SqlSession之后,会在SqlSession的作用域中创建一个HashMap用于缓存数据,这个很好理解,就是我们在平时一些编码过程中,也会经常使用HashMap作为数据的容器,但是它会随着当前对象的作用域创建而创建,也会随着当前对象作用域被销毁而销毁。

需要注意的是,如果当前SqlSession执行了DML语句(insert、update、delete),并提交到了数据库,则Mybatis会情况SqlSession中的一级缓存,这样做的目的是为了保证缓存中存储的是最新的信息,避免出现脏读现象(比如A的账户有100元,然后执行了update变为50元,再次查询因为缓存没清空的原因还是100元,这就是脏读现象)。当一个SqlSession关闭后,当前SqlSession中的缓存也是不存在的。

Mybatis是默认存在一级缓存的,不需要进行任何配置,也不能被关闭,所以直接上示例吧。同样是user表

20190301233333681.png

public class Test {

	public static void main(String[] args) {
		InputStream is = Test.class.getResourceAsStream("/mybatis/mybatis-config.xml");
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); 
		SqlSession sqlSession = sqlSessionFactory.openSession();
		UserDao userDao = sqlSession.getMapper(UserDao.class);
		User user = userDao.getUserById(1);//查询
		System.out.println(user.toString());
		User user2 = userDao.getUserById(1);
		System.out.println(user2.toString());//再次查询
		sqlSession.close();
	}
}
DEBUG [main] - ==>  Preparing: SELECT * FROM tb_user WHERE user_id = ?; 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
User [userId=1, userName=张三, userAge=20]
User [userId=1, userName=张三, userAge=20]

可以看到只执行了一条SQL语句。关闭SqlSession从新获取SqlSession或者再次执行、或者在两次查询中间执行DML操作,都会清空掉缓存。

比如下面是在两次查询中间假如了一个插入语句后执行的结果

DEBUG [main] - ==>  Preparing: SELECT * FROM tb_user WHERE user_id = ?; 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
User [userId=1, userName=张三, userAge=20]
DEBUG [main] - ==>  Preparing: INSERT INTO tb_user (user_name, user_age) VALUEs (?, ?); 
DEBUG [main] - ==> Parameters: ...(String), 12(Integer)
DEBUG [main] - ==>  Preparing: SELECT * FROM tb_user WHERE user_id = ?; 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
User [userId=1, userName=张三, userAge=20]

很明显执行了两条查询的SQL,中间穿插了一条插入SQL。

2、二级缓存

二级缓存是mapper级别的。与一级缓存不同的是,可以多个SqlSession作用域共享。需要注意的是,使用二级缓存时,需要映射对象实现序列化接口java.io.Serializable,如果存在父类,其每个成员都需要实现序列化接口。二级缓存并不会因为执行了DML操作而更新或失效,所以需要手动设置一些属性,保证数据的实时性,从而最小化的避免脏读现象。

使用二级缓存的步骤

首先,在mybatis的配置文件中,开启二级缓存,该属性配置默认为false(参考传送门:详解Mybatis配置文件)

<settings>
	<setting name="cacheEnabled" value="true"/>
</settings>

然后在需要开启二级缓存的mapper作用域下开启开启二级缓存。即在mapper映射文件下:

<mapper namespace="com.zepal.mybatis.dao.UserDao">
  <cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
</mapper>

也可以在<select>标签上单独为某一个查询开启二级缓存(如下,其它属性配置参考详解mapper文件),如果设置为false,则不会为当前查询开启二级缓存,优先级是高于全局的。

<select useCache="true" id=""></select>

 

这样就成功创建二级缓存了,cache标签用来开启当前mapper的命名空间(namespace)下的二级缓存。

cache标签的详细属性如下:

属性说明
flushInterval缓存刷新间隔。可以使任意的正整数,单位是毫秒(ms)。没有默认值,如果不设置,则没有刷新间隔
size缓存数目。可以设置为任意正整数(注意你的计算机内存容量哦),默认是1024
readOnly是否只读。属性可以被设置为true或false,默认false,只读的缓存会给所有的调用者返回缓存对象的相同实例,因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false。
eviction收回策略,默认为LRU。有如下几种:LRU-最近最少使用策略,移除最长时间不被使用的对象。FIFO-先进先出策略,按对象进行缓存的顺序来移除它们。SOFT-软引用策略,移除基于垃圾回收器状态和软引用规则的对象。WEAK-弱引用策略,更积极的移除基于垃圾回收器状态和弱引用规则的对象。

 

接下来二级缓存的测试就可以和一级缓存一样各种尝试各种进行了。

总结

一个系统的是否健壮,一定要将事务考虑周全,但是直接使用Mybais的JDBC的事务管理机制,总是不那么方便,而且现在几乎都是基于Spring在开发,所以常常使用Mybatis的MANAGED事务管理机制,将事务管理交给其它容器。

缓存对我们的系统性能提升很大,但是Mybatis的缓存机制仅可作为辅助手段,不能作为主要缓存手段(现在几乎都是Redis、Memcached或者Guava Cache的本地缓存),,撇开性能的因素,管理起来也方便得多,Mybatis的二级缓存使用起来,不能手动更新是一大弊端,只能通过设置一些参数,让其自动更新,那么出现缓存脏读的几率就很大。


此篇完结

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值