第07章 SpringBoot集成MyBatis(3)

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值