MyBatis的事物
事物的概念
在Java语言数据库框架中,数据库的事务管理都是很是重要的。
每一个业务逻辑都是由一系列数据库访问完成的,这些访问可能修改多条数据记录,这一系列修改应该是一个总体,绝对不能只修改其中的某几条数据记录。
多个数据库原子访问应该被绑定成一个总体,这就是事物。事务是一步或几步操做组成的逻辑执行单元,这些基本操做做为一个总体执行单元,它们要么所有执行,要么所有取消执行,绝对不能仅仅执行一部分。
一个用户请求对应一个业务逻辑方法,一个逻辑方法每每具备逻辑上的原子性,此时应使用事物。
例如:一个转帐操做,对应修改两个帐户余额,这两个帐户的修改要么同时生效,要么同时取消,同时生效是转帐成功,同时取消是转帐失败;但不可只修改其中一个帐户,那将破坏数据库的完整性。
事物的四个特性
1.原子性:事物是应用中最小的执行单位,就如原子是天然界最小颗粒而不能够再分同样,事物是应用中不可再分的最小逻辑执行体。
2.一致性:事物的执行结果,必须使数据库从一种一致性状态,变为另外一种一致性状态。当数据库只包含事物成功提交的结果时,数据库处于一致性状态。当系统运行发生中断,某个事物还没有完成而被迫中断,而该未完成的事物对数据库所作的修改已被写入数据库,此时,数据库处于不正确的状态。一致性是经过原子性来保证的。
3.隔离性:各个事物的执行互不干扰,任意一个事物的内部操做对其余并发的事物,都是隔离的。
4.持续性:持续性也被称为持久性,指事物一旦提交,对数据所作的任何改变都要记录到用就存储器中,一般是保存到物理数据库。
Transaction接口
对数据库事物而言,应具备:建立、提交、回滚、关闭几个动做,MyBatis的事物设计重点是org.apache.ibatis.transaction.Transaction接口,该接口源码以下:
java
1 public interface Transaction { 2 Connection getConnection() throws SQLException; 3 4 void commit() throws SQLException; 5 6 void rollback() throws SQLException; 7 8 void close() throws SQLException; 9 10 Integer getTimeout() throws SQLException; 11 } 12
Transaction接口有两个实现类:org.apache.ibatis.transaction.jdbc.JdbcTransaction和org.apache.ibatis.transaction.managed.ManagedTransaction。
因此MyBatis的事务管理有两种形式:
1.使用JDBC的事物管理机制,利用java.sql.Connection对象完成对事物的提交、回滚、关闭等操做。
2.使用MANAGED的事物管理机制,MyBatis自身不会去实现事务管理,而是让容器如WebLogic、JBOSS等来实现对事物的管理。
## 事物的建立和使用
在使用MyBatis的时候,会在MyBatis的配置文件mybatis-config.xml中定义,此处使用前文(https://www.jianshu.com/p/063a5ca8874c)配置信息:
mysql
1 <environment id="mysql"> 2 <!--指定事务管理的类型,这里简单使用Java的JDBC的提交和回滚设置--> 3 <transactionManager type="JDBC"></transactionManager> 4 <!--dataSource 指链接源配置,POOLED是JDBC链接对象的数据源链接池的实现--> 5 <dataSource type="POOLED"> 6 <property name="driver" value="com.mysql.jdbc.Driver"></property> 7 <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=UTF-8"></property> 8 <property name="username" value="root"></property> 9 <property name="password" value="****"></property> 10 </dataSource> 11 </environment>
MyBatis经过缓存机制减轻数据压力,提升数据库性能。
一级缓存
在操做数据库时须要构造SqlSession对象,在对象中有一个HashMap用户缓存数据。不一样的SqlSession之间的缓存数据区域(HashMap)是互相不影响的。
一级缓存的做用是SqlSession范围的,当同一个SqlSession中执行两次相同的sql语句时,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次查询时会从缓存中获取数据,再也不去底层数据库查询,提升查询效率。
注意:
若SqlSession执行了DML操做(insert、update和delete),并提交到数据库,MyBatis则会清空SqlSession中的一级缓存,目的是为了保证缓存中存储的是最新的数据,避免脏读现象。
当一个SqlSession结束后,该SqlSession中的一级缓存也就不存在了。
MyBatis默认开启一级缓存,不须要进行任何配置。
一级缓存测试
项目代码使用前文项目(https://www.jianshu.com/p/063a5ca8874c)
如今数据库的tb_user表中插入几条数据:
而后在项目的UserMapper.xml文件中添加查询和删除程序,完整程序以下:
sql
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 3 "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 4 <mapper namespace="com.snow.dcl.mapper.UserMapper"> 5 <!--插入用户数据--> 6 <insert id="saveUser" parameterType="com.snow.dcl.domain.User" useGeneratedKeys="true"> 7 insert into tb_user(name,sex,age) values (#{name},#{sex},#{age}); 8 </insert> 9 <!--根据id查询用户--> 10 <select id="selectUserById" parameterType="int" resultType="com.snow.dcl.domain.User"> 11 select * from tb_user where id=#{id}; 12 </select> 13 <!--查询全部用户--> 14 <select id="selectAllUser" resultType="com.snow.dcl.domain.User"> 15 select * from tb_user; 16 </select> 17 <!--根据id删除用户--> 18 <delete id="deleteUserById" parameterType="int"> 19 delete from tb_user where id=#{id}; 20 </delete> 21 </mapper>
1 public interface UserMapper { 2 //根据id查询用户 3 User selectUserById(Integer id); 4 //查询全部用户 5 List<User> selectAllUser(); 6 //根据id删除用户 7 void deleteUserById(Integer id); 8 }
1 public class FactoryUtil { 2 private static SqlSessionFactory sqlSessionFactory = null; 3 4 static { 5 try { 6 InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml"); 7 sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 8 } catch (IOException e) { 9 e.printStackTrace(); 10 } 11 } 12 13 public static SqlSession getSqlSession(){ 14 return sqlSessionFactory.openSession(); 15 } 16 } 17
在项目的test目录下的java目录下建立OneLevelCacheTest.java测试类文件:
编写以下程序:
数据库
1 public class OneLevelCacheTest { 2 public static void main(String[] args) { 3 OneLevelCacheTest oneLevelCacheTest = new OneLevelCacheTest(); 4 oneLevelCacheTest.cacheOneTest(); 5 } 6 7 public void cacheOneTest(){ 8 SqlSession sqlSession = FactoryUtil.getSqlSession(); 9 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); 10 User user = userMapper.selectUserById(1); 11 System.out.println(user); 12 User anotherUser = userMapper.selectUserById(1); 13 System.out.println(anotherUser); 14 sqlSession.close(); 15 } 16 } 17
执行测试程序OneLevelCacheTest1.java,能够在控制台看到打印结果:
能够看到在第一次执行查询id为1的User对象时,执行了一条select语句,第二次执行查询id为1的User对象时,没有执行select语句,由于此时一级缓存中已经缓存了id为1的User对象,MyBatis直接从缓存中将User对象取出来,并无再次去数据库中查询。
DML操做清空缓存
在项目的test目录下的java目录建立OneLevelCacheTest2测试类文件,编写以下代码:
apache
1 public class OneLevelCacheTest2 { 2 public static void main(String[] args) { 3 OneLevelCacheTest2 oneLevelCacheTest = new OneLevelCacheTest2(); 4 oneLevelCacheTest.cacheOneTest(); 5 } 6 7 public void cacheOneTest(){ 8 SqlSession sqlSession = FactoryUtil.getSqlSession(); 9 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); 10 User user = userMapper.selectUserById(1); 11 System.out.println(user); 12 userMapper.deleteUserById(7); 13 sqlSession.commit(); 14 User anotherUser = userMapper.selectUserById(1); 15 System.out.println(anotherUser); 16 sqlSession.close(); 17 } 18 } 19
执行测试程序OneLevelCacheTest2.java,能够在控制台看到打印结果:
能够看到在第一次执行查询id为1的User对象时,执行了一条select语句,接下来执行了一个delete操做,MyBatis为了保证缓存中存储的是最新的数据,清空了一级缓存,因此第二次执行查询id为1的User对象时,又执行了select语句。
不一样Session对象对一级缓存的影响
在项目的test目录下的java目录建立OneLevelCacheTest3测试类文件,编写以下代码:
缓存
1 public class OneLevelCacheTest3 { 2 public static void main(String[] args) { 3 OneLevelCacheTest3 oneLevelCacheTest = new OneLevelCacheTest3(); 4 oneLevelCacheTest.cacheOneTest(); 5 } 6 7 public void cacheOneTest(){ 8 SqlSession sqlSession = FactoryUtil.getSqlSession(); 9 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); 10 User user = userMapper.selectUserById(1); 11 System.out.println(user); 12 sqlSession.close(); 13 sqlSession = FactoryUtil.getSqlSession(); 14 userMapper = sqlSession.getMapper(UserMapper.class); 15 User anotherUser = userMapper.selectUserById(1); 16 System.out.println(anotherUser); 17 sqlSession.close(); 18 } 19 } 20
执行测试程序OneLevelCacheTest2.java,能够在控制台看到打印结果:
能够看到在第一次执行查询id为1的User对象时,执行了一条select语句,接下来调用了sqlSession.close()关闭了一级缓存,第二次执行查询id为1的User对象时,一级缓存是一个新的对象,缓存中没有缓存任何数据,因此再次执行了select语句。
二级缓存
使用二级缓存时,多个SqlSession使用同一个Mapper的sql语句去操做数据库,获得的数据会存在二级缓存区域,它一样是使用HashMap进行数据存储。相比一级缓存SqlSession,二级缓存的范围更大,多个SqlSession能够共用二级缓存,二级缓存是跨SqlSession的。
二级缓存是多个SqlSession共享的,其做用域是mapper的同一个namespace。不一样的SqlSession两次执行相同namespace下的sql语句,且向sql中传递的参数也相同,即最终执行相同的sql语句,则第一次执行完毕会将数据库中查询的数据写入缓存,第二次查询时会从缓存中获取数据,再也不去底层数据库查询,提升效率。
MyBatis默认没有开启二级缓存,须要在setting全局参数中进行配置,开启二级缓存。
二级缓存测试
在mubatis-config.xml配置文件中开启二级缓存,完整配置文件以下:
安全
1 <configuration> 2 <!-- 指定Mybatis所用日志的具体实现 --> 3 <settings> 4 <!--开启二级缓存--> 5 <setting name="cacheEnabled" value="true"/> 6 <!--开启日志--> 7 <setting name="logImpl" value="Log4J"/> 8 </settings> 9 <!--环境配置,链接的数据库--> 10 <environments default="mysql"> 11 <environment id="mysql"> 12 <!--指定事务管理的类型,这里简单使用Java的JDBC的提交和回滚设置--> 13 <transactionManager type="JDBC"></transactionManager> 14 <!--dataSource 指链接源配置,POOLED是JDBC链接对象的数据源链接池的实现--> 15 <dataSource type="POOLED"> 16 <property name="driver" value="com.mysql.jdbc.Driver"></property> 17 <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=UTF-8"></property> 18 <property name="username" value="root"></property> 19 <property name="password" value="Password@123"></property> 20 </dataSource> 21 </environment> 22 </environments> 23 <mappers> 24 <!--告诉Mybatis持久化类的映射文件路径--> 25 <mapper resource="mapping/UserMapper.xml"></mapper> 26 </mappers> 27 </configuration>
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 3 "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 4 <mapper namespace="com.snow.dcl.mapper.UserMapper"> 5 <!--开启当前mapper的namespace下的二级缓存--> 6 <cache eviction="LRU" flushInterval="20000" size="512" readOnly="true"/> 7 <!--插入用户数据--> 8 <insert id="saveUser" parameterType="com.snow.dcl.domain.User" useGeneratedKeys="true"> 9 insert into tb_user(name,sex,age) values (#{name},#{sex},#{age}); 10 </insert> 11 <!--根据id查询用户--> 12 <select id="selectUserById" parameterType="int" resultType="com.snow.dcl.domain.User"> 13 select * from tb_user where id=#{id}; 14 </select> 15 <!--查询全部用户--> 16 <select id="selectAllUser" resultType="com.snow.dcl.domain.User"> 17 select * from tb_user; 18 </select> 19 <!--根据id删除用户--> 20 <delete id="deleteUserById" parameterType="int"> 21 delete from tb_user where id=#{id}; 22 </delete> 23 </mapper>
1 public class TwoLevelCacheTest1 { 2 public static void main(String[] args) { 3 TwoLevelCacheTest1 twoLevelCacheTest = new TwoLevelCacheTest1(); 4 twoLevelCacheTest.cacheTwoTest(); 5 } 6 7 public void cacheTwoTest(){ 8 SqlSession sqlSession = FactoryUtil.getSqlSession(); 9 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); 10 User user = userMapper.selectUserById(1); 11 System.out.println(user); 12 sqlSession.close(); 13 sqlSession = FactoryUtil.getSqlSession(); 14 userMapper = sqlSession.getMapper(UserMapper.class); 15 User anotherUser = userMapper.selectUserById(1); 16 System.out.println(anotherUser); 17 sqlSession.close(); 18 } 19 } 20
执行测试程序TwoLevelCacheTest.java,能够在控制台看到打印结果:
能够看到在第一次执行查询id为1的User对象时,执行了一条select语句,接下来调用了sqlSession.close()关闭了一级缓存,第二次执行查询id为1的User对象时,一级缓存没有任何对象,但由于启用了二级缓存,第一次查询的数据会缓存在二级缓存中,因此显示命中二级缓存数据,不须要在执行select语句。
注意:
在UserMapper.xml文件的select语句中设置useCache='false',能够禁用当前select语句的二级缓存,即每次都会查询数据库,默认为true,配置内容以下:
服务器
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 3 "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 4 <mapper namespace="com.snow.dcl.mapper.UserMapper"> 5 <!--开启当前mapper的namespace下的二级缓存--> 6 <cache eviction="LRU" flushInterval="20000" size="512" readOnly="true"/> 7 <!--插入用户数据--> 8 <insert id="saveUser" parameterType="com.snow.dcl.domain.User" useGeneratedKeys="true"> 9 insert into tb_user(name,sex,age) values (#{name},#{sex},#{age}); 10 </insert> 11 <!--根据id查询用户--> 12 <select id="selectUserById" parameterType="int" resultType="com.snow.dcl.domain.User" useCache="false"> 13 select * from tb_user where id=#{id}; 14 </select> 15 <!--查询全部用户--> 16 <select id="selectAllUser" resultType="com.snow.dcl.domain.User"> 17 select * from tb_user; 18 </select> 19 <!--根据id删除用户--> 20 <delete id="deleteUserById" parameterType="int"> 21 delete from tb_user where id=#{id}; 22 </delete> 23 </mapper>