Spring可以支持编程式事务和声明式事务。
Spring框架提供了两种编程式事务管理的方法:
1)使用TransactionTemplate
2)使用TransactionManager
在之前的Spring集成JDBC章节中我们介绍过“DataSourceTransactionManager ”的使用方式。本篇介绍TransactionTemplate类,它简化了事务管理的复杂性。TransactionTemplate主要依赖于execute(TransactionCallback<T> action)方法执行事务管理。这个方法传入的参数有两种选择:
1)TransactionCallback
2)TransactionCallbackWithoutResult
两种区别从命名看就知道了,一个是无返回值,一个是有返回值,并且返回值通过泛型来实现自定义类型的。 接下来,我们就在之前的 “SpringBootMyBatisDemo” 工程中演示TransactionTemplate类的使用。 更多信息请查看前两个章节的内容。
关于“student”数据库的内容请参考: 第04章 SQL语句-优快云博客
首先,我们创建 “ClassData.java” 数据类,里面对应了数据表 “class_info” 字段。
package com.demo.data;
public class ClassData {
private Integer classId;
private String className;
private String addTime;
// 省略get/set方法
}
然后,我们创建 “ClassMap.java” 和 “ClassMap.xml” 两个文件用于写入“class_info”数据表记录
package com.demo.mapper;
import com.demo.data.ClassData;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ClassMap {
int insertClassInfo(ClassData data);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.demo.mapper.ClassMap">
<insert id="insertClassInfo" parameterType="com.demo.data.ClassData" useGeneratedKeys="true" keyProperty="classId">
INSERT INTO CLASS_INFO(class_name, add_time) VALUES (#{className}, #{addTime})
</insert>
</mapper>
然后,我们创建一个 “StudentDao” 类
package com.demo.dao;
import com.demo.data.ClassData;
import com.demo.mapper.ClassMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
@Repository
public class StudentDao {
@Autowired
private TransactionTemplate transactionTemplate;
@Autowired
private ClassMap classMap;
// 事务测试
public boolean test(){
Boolean result = transactionTemplate.execute(new TransactionCallback<Boolean>() {
@Override
public Boolean doInTransaction(TransactionStatus status) {
ClassData data03 = new ClassData();
data03.setClassName("三班");
data03.setAddTime("2022-05-01 09:00:00");
classMap.insertClassInfo(data03);
ClassData data04 = new ClassData();
data04.setClassName("四班");
data04.setAddTime("2022-05-01 09:00:00");
classMap.insertClassInfo(data04);
return true;
}
}) ;
return result;
}
}
接下来,我们在 “StudentController.java” 中调用
@Autowired
private StudentDao studentDao;
@RequestMapping("/trans")
public String trans(Map<String, Object> map){
boolean res = studentDao.test();
System.out.println("res=" + res);
map.put("info", null);
return "student";
}
接下来继续补全 “index.html” 和 "student.html" 中内容
<a href="/student">student</a>
<br />
<a href="/studentinfo?id=1">studentinfo?id=1</a>
<br />
<a href="/studentmap?id=1">studentmap?id=1</a>
<br />
<a href="/search?name=小明">search?name=小明</a>
<br />
<a href="/add">add</a>
<br />
<a href="/edit">edit</a>
<br />
<a href="/del">del</a>
<br />
<a href="/trans">trans</a>
由于我们传递给 "student.html" 是 null 对象,因此不需要修改 "student.html" 了。
我们运行,测试一下。
因为是null对象,所以也没是空的,我们查看控制台输出
显示返回结果为 true,我们去数据库查看一下
两条数据被成功写入了。
接下来,我们修改代码,让其抛出异常
ClassData data03 = new ClassData();
data03.setClassName("三班");
data03.setAddTime("2022-05-01 09:00:00");
classMap.insertClassInfo(data03);
ClassData data04 = new ClassData();
data04.setClassName("四班");
//data04.setAddTime("2022-05-01 09:00:00");
classMap.insertClassInfo(data04);
return true;
我们注释掉 “四班” 的添加时间字段,数据库里面的“add_time”是不允许为空的
我们重新运行,测试一下
页面报错了,我们去控制台查看日志
日志中已经明确显示了错误原因:Column 'add_time' cannot be null
我们再去数据库里面核实一下
数据还是以前的,没有变化。说明两条数据都没有写入进去。
为了不让页面抛出异常,我们直接在“StudentController.java”中捕获一下异常
@RequestMapping("/trans")
public String trans(Map<String, Object> map){
boolean res = false;
try{
res = studentDao.test();
}catch (Exception e){
e.printStackTrace();
}
System.out.println("res=" + res);
map.put("info", null);
return "student";
}
我们重新运行,测试一下
页面友好一些了,我们查看控制台
接下来,我们再来声明式事务,也就是使用注解 “@Transactional” 完成。
我们直接在 “StudentDao.java” 中测试一下
@Transactional
public boolean transTest(){
ClassData data03 = new ClassData();
data03.setClassName("三班");
data03.setAddTime("2022-05-01 09:00:00");
classMap.insertClassInfo(data03);
ClassData data04 = new ClassData();
data04.setClassName("四班");
//data04.setAddTime("2022-05-01 09:00:00");
classMap.insertClassInfo(data04);
return true;
}
接下来,我们在“StudentController.java” 中调用一下
@RequestMapping("/transTest")
public String transTest(Map<String, Object> map){
boolean res = false;
try{
res = studentDao.transTest();
}catch (Exception e){
e.printStackTrace();
}
System.out.println("res=" + res);
map.put("info", null);
return "student";
}
不要忘记添加入口文件链接
<a href="/student">student</a>
<br />
<a href="/studentinfo?id=1">studentinfo?id=1</a>
<br />
<a href="/studentmap?id=1">studentmap?id=1</a>
<br />
<a href="/search?name=小明">search?name=小明</a>
<br />
<a href="/add">add</a>
<br />
<a href="/edit">edit</a>
<br />
<a href="/del">del</a>
<br />
<a href="/trans">trans</a>
<br />
<a href="/transTest">transTest</a>
我们重新运行,测试一下
还是一样的异常,我们数据库核实一下
我们可以恢复之前的注释代码,在测试一下
ClassData data03 = new ClassData();
data03.setClassName("三班");
data03.setAddTime("2022-05-01 09:00:00");
classMap.insertClassInfo(data03);
ClassData data04 = new ClassData();
data04.setClassName("四班");
data04.setAddTime("2022-05-01 09:00:00");
classMap.insertClassInfo(data04);
return true;
我们直接给出数据库截图
成功写入两条记录。
接下来,我们介绍一下MyBatis日志,我们希望能够在控制台打印出执行的SQL日志,方便我们排查问题。默认情况下mybatis是不开启SQL日志输出,需要手动配置 “application.properties” 文件。
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
接下来,我们测试一下
我们查看一下控制台
最后我们介绍一下MyBatis的缓存。
在实际项目开发中,通常对数据库查询的性能要求很高,而MyBatis提供了查询缓存来缓存数据,从而达到提高查询性能的要求。MyBatis的查询缓存分为一级缓存和二级缓存。一级缓存是SqlSession级别的缓存,二级缓存是mapper级别的缓存;一级缓存只供自己SqlSession实例使用,二级缓存是多个SqlSession实例共享的。MyBatis通过缓存机制减轻数据压力,提高数据库性能。
一级缓存的作用域是SqlSession范围的。当同一个SqlSession中执行两次相同的sql语句时候,第一执行完毕会将数据库中的查询数据些到缓存(内存),第二次查询时候会从缓存中获取数据,不再去底层数据库查询,从而提高查询效率。需要注意的是,如果SqlSession执行了insert,update,delete操作的话,MyBatis则会清空SqlSession中的一级缓存,这样做的目的是为了保证缓存是最新的数据,避免出现脏读现象。一级缓存是针对同一个SqlSession实例对象而言的,相当于将查询结果保存到一个变量(每一个SqISession都会存放一个map对象)中,后面的代码在执行同一查询的时候,会直接使用该变量的内容,不在查询数据库。当这个SqlSession实例对象销毁后,对应的变量也就被销毁了。MyBatis默认开启一级缓存,不需要进行任何配置。
MyBatis的二级环是mapper级别的。使用二级缓存时候,多个SqlSession使用同一个Mapper的Sql语句去操作数据时候,得到的数据会存储在二级缓存区域。二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace。也就是说,不同的SqlSession两次执行相同的namespace下的sql语句,且sql中传递的参数也相同,也就是最终执行的SQl语句也是相同的,那么第一次执行完毕后会将数据库些到缓存中(内存)。第二次查询时候会从缓存中直接获取数据,不再去底层数据库查询,从而提高查询效率。
MyBatis默认是没有开启二级缓存的,需要在 mybatis-config.xml 的 settings 元素中设置:
<settings>
<setting name="cacheEnabled" value="true" />
</settings>
另外,我们还需要在mapper(StudentMapper.xml)中进行设置二级缓存:
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
Cache元素用来开启当前mapper的二级缓存。以上配置创建了一个LRU缓存,并每隔60秒刷新,最大存储512个对象,且对象为只读方式。Eviction是收回策略,LRU表示最近最少使用的策略,说白了就是移除最长时间不被使用的对象。FIFO表示先进先出策略,按照对象的进入缓存的顺序来移除它们。SOFT是软引用策略,移除基于垃圾回收器状态和软引用规则的对象。WEAK是弱引用策略,表示基于垃圾回收器状态和弱引用规则移除对象。
关于缓存,我们按照MyBatis的默认配置即可,也就是开启一级缓存,关闭二级缓存。
完整 “SpringBootMyBatisDemo” 工程文件下载: https://download.youkuaiyun.com/download/richieandndsc/89888983