第四章 Spring集成JDBC

Spring JDBC就是对JDBC的一个封装而已。Spring JDBC框架的主要封装就是从打开连接,执行SQL语句,处理异常,处理事务,最后关闭连接。这样,我们就不需要编写冗余的代码来处理异常,打开和关闭数据库连接等等操作。Spring JDBC中使用最频繁的就是 JdbcTemplate类。JdbcTemplate类用来执行SQL查询,更新语句和存储过程调用,在ResultSet上执行迭代并提取返回的参数值。我们可以可以配置JdbcTemplate的单个实例,然后将其注入到多个DAO中。DAO代表通常用于数据库交互的数据访问对象。DAO提供读取和写入数据到数据库的方法,并且它们应该通过其他应用程序访问它们的接口来公开此功能。使用JdbcTemplate类时需要在Spring配置文件中配置数据库连接源(DataSource)。它本质就是一个bean类,然后给这个bean类提供数据库连接驱动,URL,账号和密码等信息。但是,我们只需要在XML中进行配置即可,不需要手写完成这个数据库连接源(DataSource)。

一个数据库连接对象对应一个物理数据库连接,每次操作都打开一个物理连接,使用完都关闭连接,这样造成系统的性能低下。因为创建数据库连接操作的系统开销一般高于执行简单SQL语句的性能消耗。数据库连接就是Java应用程序到数据库的一座桥梁,建造这座桥梁的成本非常大。如果是否频繁的建造和拆除一座座的桥梁,对于服务器来讲,是非常消耗性能的。服务器应该把性能提供给业务系统,而不是创建数据库连接。数据库连接池 的解决方案是在应用程序启动时建立足够的数据库连接,并将这些连接组成一个连接池(就是一个集合队列),由应用程序动态地对池中的连接进行申请、使用和释放。对于多于连接池中连接数的并发请求,应该在请求队列中排队等待。并且应用程序可以根据池中连接的使用率,动态增加或减少池中的连接数(池的大小)。连接池技术尽可能多地重用了消耗内存地资源,大大节省了内存,提高了服务器地服务效率,能够支持更多的客户服务。比较经典的数据连接池技术是C3P0,它是一个开源组织提供的一个数据库连接池,速度相对较慢,稳定性还可以。Druid 是阿里提供的数据库连接池,据说是集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池。使用它们的方法也很简单,就是下载相应的依赖包(Jar包),然后再Spring配置文件中直接将其配置为数据库连接源(DataSource)即可。我们的核心代码(使用JdbcTemplate)不会有任何的改动,就可以当前数据库连接转变为数据库连接池技术。

在介绍Spring JDBC的之前,我们需要了解“MySQL数据库的使用”和“SQL查询语言”,这些内容我会在MySQL数据库的课程中详细介绍过。如果不熟悉的同学,可以先回顾一下前面的知识点,然后再学习本章节的内容。链接地址:https://blog.youkuaiyun.com/richieandndsc/category_12596462.html

接下来, 我们在之前“SpringMVCDemo”工程的基础上来演示Spring JDBC的使用。本章节使用的数据库依然是我们之前制作的“student.sql”。首先,我们需要添加Spring对jdbc的包文件:spring-jdbc-5.3.39.jar,spring-tx-5.3.39.jar和spring-orm-5.3.39.jar,还有我们的Mysql驱动Jar包文件:mysql-connector-java-5.1.49.jar

然后,我们要在applicationContext.xml中配置DataSource和JdbcTemplate两个bean。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://www.springframework.org/schema/beans
   https://www.springframework.org/schema/beans/spring-beans.xsd">

	<!-- 配置DataSource -->
	<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<!-- 驱动 -->
		<property name="driverClassName" value="com.mysql.jdbc.Driver" /> 
		<!-- 连接url -->
		<property name="url" value="jdbc:mysql://localhost:3306/student?useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=false"/> 
		<!-- 用户名 -->       
		<property name="username" value="root"/>  
		<!-- 密码 -->
		<property name="password" value="123456" /> 
	</bean>

	<!-- 配置jdbcTemplate -->
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">  
		<property name="dataSource" ref="dataSource" />  
	</bean>

	<bean id="helloDao" class="com.demo.HelloDao"></bean>

</beans>

关于DriverManagerDataSource类,我们不详细介绍,从他的属性参数来看,它就是数据连接对象。我们使用JDBC API创建链接的时候,也是需要这四个属性参数的。然后将这个DriverManagerDataSource类注入到JdbcTemplate类中,该类是用来执行SQL语句的重要类。

接下来,我们来介绍JdbcTemplate类的使用,它经常使用的几个方法如下所示:

public <T> List<T> query(String sql, RowMapper<T> rowMapper, @Nullable Object... args)
执行查询语句,返回数据集列表,rowMapper表示记录数据类型,args是预编译参数

public List<T> queryForList(String sql, Class<T> elementType) 
可以将查询结果作为Map进行封装

public <T> T queryForObject(String sql, RowMapper<T> rowMapper, @Nullable Object... args)
执行查询语句,返回单一记录数据对象,rowMapper表示记录数据类型,args是预编译参数

public int update(String sql)              
执行新增、更新、删除SQL语句

public int update(String sql,Object... args)   
执行新增、更新、删除SQL语句,args是预编译参数

public int[] batchUpdate(String sql, List<Object[]> batchArgs, final int[] argTypes)
批量执行新增、更新、删除等语句,argTypes是参数类型,batchArgs是参数

public void execute(String sql)             
执行任意SQL(包括DDL语句)

虽然我们对数据的操作主要分为增,删,改,查四种,但是实际执行SQL语句的时候,我们只有两种,一种是查询语句,返回的结果可以是列表记录,也可以是单一记录;另一种是执行语句,也就是增,删,改的操作,返回结果是影响的记录行数。Spring对JDBC的封装也大概是这样划分的。如果是查询的话,可以使用queryForList方法查询列表记录,或者使用queryForObject查询单一记录,我们的记录数据可以是map形式,也可以是Java类形式。如果是执行的话,可以使用update方法,其方法的参数可以是拼接的SQL语句,也可以是预编译SQL语句。对批量添加数据的话,还提供了一个batchUpdate方法,非常的方便。如果以上两种封装方法都不能满足开发的需求,我们还可以直接使用query和execute方法分别执行查询SQL语句和执行SQL语句。总之,Spring为我们提供了非常全面的数据操作封装,对我们帮助非常大。

接下来,我们以 “student_info” 表的操作来演示以上方法的使用。以下是该表的原始数据

我们上面也提到了,对于这些记录,我们可以使用Map形式进行存储,也可以使用Java类形式。这里还是推荐大家使用Java类,我们就先定义一个记录数据类“StudentData”,使用它来存储表中的一条记录数据。如下所示:

package com.demo;

public class StudentData {

	private int stuId;
	private int classId;
	private String stuName;
	private int stuAge;
	private int stuSex;
	private String addTime;

	// 无参构造方法
	public StudentData() {
		super();
	}

	// 有参构造方法
	public StudentData(int classId, String stuName, int stuAge, int stuSex, String addTime) {
		super();
		this.classId = classId;
		this.stuName = stuName;
		this.stuAge = stuAge;
		this.stuSex = stuSex;
		this.addTime = addTime;
	}

}

以上类中的变量都是按照表“student_info”的字段以及类型定义的。这里需要注意的是类变量的命名规则,如果字段名为“stu_name”的话,那么对应的类变量名为“stuName”。Spring会根据这种方式来将字段值对应到类变量上面。鉴于篇幅原因,我们省略了每一个类变量对于的get/set方法。

接下来,我们来创建一个“StudentDao”模型类,使用它来操作表“student_info”数据。在这个“StudentDao”类中,我们需要使用JdbcTemplate类,因此,我们需要将其注入进来。


package com.demo;

import org.springframework.jdbc.core.JdbcTemplate;

public class StudentDao {

	// 待注入的JdbcTemplate类
	private JdbcTemplate jdbcTemplate;

	// Spring使用该方法将jdbcTemplate实例注入进来
	public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
		this.jdbcTemplate = jdbcTemplate;
	}

}

然后就是我们的applicationContext.xml文件内容,如下所示:

	<!-- 配置模型类:StudentDao -->
	<bean id="studentDao" class="com.demo.StudentDao">
		<property name="jdbcTemplate" ref="jdbcTemplate" /> 
	</bean>

以上配置就完成来的向“StudentDao”类中注入JdbcTemplate类。接下来,我们就可以在“StudentDao”类使用JdbcTemplate类了。首先,我们先查询一条学生记录,如下所示:

	// 根据id查询一条学生记录
	public StudentData getStudent(int id) {

		String sql = "select * from student_info where stu_id = ?";
		return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<StudentData>(StudentData.class), id);
	}

这里我们使用queryForObject方法,该方法第一个参数是SQL语句,第二个参数是我们要返回的StudentData数据类对象,第三个参数是我们预编译SQL语句的数据参数。我们完成的功能,就是根据学生ID来查询学习记录,返回一个StudentData记录数据对象。我们只是给Spring传递了一个“StudentData”类,它就能够将表中字段值对应到类变量中。因此,我们前面也说明了,字段名称和类变量名称有一个对应关系。有的同学可能觉得使用StudentData数据类对象比较麻烦,想直接使用Map存储,我们应该如何写呢?

	// 根据id查询一条学生记录,返回Map形式
	public Map<String, Object> getStudentMap(int id) {

		Object[] args = {id};
		int[] argTypes = {java.sql.Types.INTEGER};
		String sql = "select * from student_info where stu_id = ?";
		return jdbcTemplate.queryForMap(sql, args, argTypes);
	}

这里我们需要借助queryForMap方法(前面没有列举出来),参数分别是SQL语句,预编译参数类型列表以及预编译参数列表。这种方式返回的是Map<String, Object>类型,其中String是字段名,Object是字段值。因为字段类型不确定,因此我们的字段值是Object类型。也就是说,再使用的时候,我们可能需要进行类型转换。

为了验证上述方法的执行,我们需要创建一个“StudentController”控制器来将其调用,如下:

package com.demo;

import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

public class StudentController implements Controller {

	// 需要注入的 StudentDao 类
	private StudentDao studentDao;
	
	public void setStudentDao(StudentDao studentDao) {
		this.studentDao = studentDao;
	}

	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {

		// 实例化模型视图类
		ModelAndView mv = new ModelAndView();

		// 查询id=1的学生
		StudentData data = studentDao.getStudent(1);
		mv.addObject("data", data);

		// 返回 Map 形式
		Map<String, Object> data2 = studentDao.getStudentMap(1);
		mv.addObject("data2", data2);

		// 返回视图
		mv.setViewName("/student.jsp");
		return mv;
	}

}

这个控制器主要完成三件事情,第一是StudentDao的注入,第二就是调用它的两个方法,第三就是向视图student.jsp文件传递id=1的学生记录信息。接下来,我们要做的是完成注入的XML配置,以及视图文件步长。首先,我们先去“springmvc-config.xml”中配置这个StudentController控制器并完成依赖注入。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/mvc
    https://www.springframework.org/schema/mvc/spring-mvc.xsd">

	<!-- 配置StudentController控制器 -->
	<bean name="studentController" class="com.demo.StudentController">
		<property name="studentDao" ref="studentDao"></property>
	</bean>

	<!-- 声明控制器 -->
	<bean name="helloController" class="com.demo.HelloController">
		<property name="helloDao" ref="helloDao"></property>
	</bean>

	<!-- 声明映射器:那个url对应那个控制器 -->
	<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
		<property name="mappings">
		<props>
			<prop key="hello.do">helloController</prop>
			<prop key="student.do">studentController</prop>
		</props>
		</property>
	</bean>

	<!-- 声明视图解析器 -->
	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"></bean>

</beans>

就是再原来的基础上增加了StudentController的配置以及注入studentDao,最后不要忘记url映射配置。这里我们希望StudentController可以处理 ”student.do“ 的请求URL。我们首先在"index.jsp"中添加这个请求URL吧。

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

<a href="hello.do">hello</a>
<br />
<a href="student.do">student</a>

</body>
</html>

然后我们需要新创建一个“student.jsp”的视图文件,然后显示id=1的学生信息,如下

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>    
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>student</title>
</head>
<body>

<div>Java数据类格式:${data.stuName} ${data.stuAge}</div>
<div>Map数据格式:${data2.stu_name} ${data2.stu_age}</div>

</body>
</html>

这里请大家注意的是,使用EL表达式读取类对象和Map对象是一样的,但是字段名称是不一样的。如果我们使用Map形式的话,我们要访问原始的字段名称,也就是数据库里面的名称。

我们运行工程查看效果

接下来,我们查询学生列表,

	// 查询学生列表
	public List<StudentData> getStudentList(){

		String sql = "select * from student_info order by stu_id asc";
		return jdbcTemplate.query(sql, new BeanPropertyRowMapper<StudentData>(StudentData.class), null);
	}

	// 查询学生列表,返回Map形式
	public List<Map<String,Object>> getStudentListMap(){

		String sql = "select * from student_info order by stu_id asc";
		return jdbcTemplate.queryForList(sql);
	}

这里我们直接给出两种数据形式的方法,我们没有使用查询参数,所有方法后面的参数为null。

最后,我们再添加一个查询所有学生数量的方法,代码如下:

	// 查询学生总数量
	public int getStudentCount() {

		String sql = "select count(*) from student_info";
		return jdbcTemplate.queryForObject(sql, Integer.class, null);
	}

我们经常在SQL语句中使用聚合函数count,sum,max,min等等,他们返回的基本上都是整数类型。接下来,我们就将上面的三个方法放入到StudentController中,如下代码

		// 实例化模型视图类
		ModelAndView mv = new ModelAndView();

		// 查询id=1的学生
		StudentData data = studentDao.getStudent(1);
		mv.addObject("data", data);

		// 返回 Map 形式
		Map<String, Object> data2 = studentDao.getStudentMap(1);
		mv.addObject("data2", data2);

		// 查询所有学生
		List<StudentData> list = studentDao.getStudentList();
		mv.addObject("list", list);

		// 返回 Map 形式
		List<Map<String,Object>> list2 = studentDao.getStudentListMap();
		mv.addObject("list2", list2);

		// 查询所有学生数量
		int count = studentDao.getStudentCount();
		mv.addObject("count", count);

		// 返回视图
		mv.setViewName("/student.jsp");
		return mv;

接下来,我们需要在“student.jsp”视图文件中显示上面的列表数据,如下所示:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>    
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>student</title>
</head>
<body>

<div>Java数据类格式:${data.stuName} ${data.stuAge}</div>
<div>Map数据格式:${data2.stu_name} ${data2.stu_age}</div>

<div>学生列表如下(数据类格式):</div>
<div>
<c:forEach items="${list}" var="item">
<span>${item.stuName} </span>
</c:forEach>
</div>

<div>学生列表如下(Map格式):</div>
<div>
<c:forEach items="${list2}" var="item">
<span>${item.stu_name} </span>
</c:forEach>
</div>

<div>学生总数量:${count}</div>

</body>
</html>

我们重新运行当前工程查看效果

接下来,我们完善“StudentDao.java”中的其他操作。


	// 添加一个学生记录
	public boolean insertStudent(StudentData data) {

		String sql = "insert into student_info(class_id,stu_name,stu_age,stu_sex,add_time) value (?,?,?,?,?)";
		int affectRows = jdbcTemplate.update(sql, data.getClassId(),data.getStuName(),data.getStuAge(),data.getStuSex(),data.getAddTime());
		return affectRows > 0 ? true : false;
	}

	// 修改指定id的学生信息
	public boolean updateStudent(StudentData data) {

		String sql = "update student_info set stu_name = ?, stu_age = ? where stu_id = ?";
		int affectRows = jdbcTemplate.update(sql, data.getStuName(),data.getStuAge(),data.getStuId());
		return affectRows > 0 ? true : false;
	}

	// 删除指定id的学生信息
	public boolean deleteStudent(int id) {

		String sql = "delete from student_info where stu_id = ?";
		int affectRows = jdbcTemplate.update(sql, id);
		return affectRows > 0 ? true : false;
	}

虽然上面三个不同的方法执行不同的表操作,但是update用法是一样的,都是传递SQL语句和数据参数。并且他们返回的都是影响的记录行数,这里我们做了一个小小的判断,如果返回行数大于零的话,就返回true,否则返回false。这样,经过我们处理后的数据操作方法,返回的就是布尔类型了。我们直接通过布尔类型的结果来判断我们的SQL语句是否执行成功。接下来就是批量添加方法使用,请注意这个批量添加方法的参数是Object数组类型。

	// 批量添加学生记录
	public boolean insertStudentList(List<StudentData> list) {

		String sql = "insert into student_info(class_id,stu_name,stu_age,stu_sex,add_time) value (?,?,?,?,?)";

	   // 将列表里面的StudentData类数据转换为Object数组
		List objectList = new ArrayList();
		for(int i=0; i<list.size();i++) {
			StudentData temp = list.get(i);
			objectList.add(new Object[] {temp.getClassId(),temp.getStuName(),temp.getStuAge(),temp.getStuSex(),temp.getAddTime()});
		}

		// res中每一个元素代表sql执行影响的行数,里面应该是[1,1,1,1,1,1.....]
		int res[] = jdbcTemplate.batchUpdate(sql, objectList);
		return true;
	}

然后,我们在StudentController中调用上述方法,如下所示:

		// 修改id=1的学生"小明"的姓名为"李四", 年龄31岁
		StudentData stu2 = new StudentData();
		stu2.setStuId(1);
		stu2.setStuName("李四");
		stu2.setStuAge(31);
		studentDao.updateStudent(stu2);

		// 删除id=2的学生"小红"信息
		studentDao.deleteStudent(2);

		// 批量添加一批学生
		List students = new ArrayList();
		students.add(new StudentData(1,"阿华1",21,1,"2022-09-01 09:00:00"));
		students.add(new StudentData(1,"阿华2",22,1,"2022-09-02 09:00:00"));
		students.add(new StudentData(1,"阿华3",23,1,"2022-09-03 09:00:00"));
		students.add(new StudentData(1,"阿华4",24,1,"2022-09-04 09:00:00"));
		students.add(new StudentData(1,"阿华5",25,1,"2022-09-05 09:00:00"));
		studentDao.insertStudentList(students);

我们先对旧的“student_info”表数据做一个截图,如下所示:

接下来,我们重新运行工程

由于新增加的功能不会在视图上改变,因此我们直接查看“student_info”表数据发生了改变,如下所示:

我们发现,新的记录 “id=9的张三" 被添加了进去,“id=1的小明”的数据记录也被修改成了“李四”,主键为2的“小红”也被删除了,最后批量添加的5条“阿华”记录也加进入了。这说明,我们的代码都被正确执行了。

最后,我们再介绍一下Spring JDBC事务支持。这里需要提醒大家,事务的底层是由MySQL数据库来完成的,也就是说,如果使用MySQL来存储数据的话,必须使用InnoDB引擎才能支持事务。Spring提供编程式的事务管理与声明式的事务管理两种方式。编程式事务是指通过代码手动提交或者回滚的事务控制方法;声明式事务就是通过配置自动实现,当目标方法执行成功时,自动提交事务。当目标方法抛出运行时异常时,自动事务回滚。声明式事务管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务管理也有两种常用的方式,一种是基于tx和aop名字空间的xml配置文件,另一种就是基于@Transactional注解。显然基于注解的方式更简单易用。

本章节我们只介绍编程式的事务管理如何使用。SpringJDBC通过TransactionManager事务管理器实现事务控制,它提供commit / rollback方法进行事务提交与回滚。

我们直接在“applicationContext.xml”中将TransactionManager作为一个bean进行配置即可,如下所示:

	<!-- 配置事务管理器 --> 
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource" />
	</bean>

接下来,我们要将其注入到“StudentDao”中,配置如下:

	<!-- 配置模型类:StudentDao -->
	<bean id="studentDao" class="com.demo.StudentDao">
		<property name="jdbcTemplate" ref="jdbcTemplate" />
		<property name="transactionManager" ref="transactionManager" /> 
	</bean>

当然,我们也需要在“StudentDao”中声明transactionManager的类变量,如下:

	// 待注入的事务管理器
	private DataSourceTransactionManager transactionManager;

	public void setTransactionManager(DataSourceTransactionManager transactionManager) {
		this.transactionManager = transactionManager;
	}

接下来,我们继续向“StudentDao”添加一个测试事务的方法,如下:

	// 事务测试
	public boolean transTest() {

		// 定义事务默认标准
		TransactionDefinition definition = new DefaultTransactionDefinition();

		// 开始事务,返回事务状态
		TransactionStatus status = transactionManager.getTransaction(definition);

		// 监听是否发送异常
		try {

			String sql1 = "insert into class_info(class_name, add_time) value (?, ?)";
			jdbcTemplate.update(sql1, "三班", "2022-05-01 09:00:00");
	  
			String sql2 = "insert into class_info(class_name, add_time) value (?, ?)";
			jdbcTemplate.update(sql2, "四班", "2022-05-01 09:00:00");

			// 提交事务
			transactionManager.commit(status);
			System.out.println("*******************提交事务*******************");

		} catch (RuntimeException e) {

			// 回滚事务
			transactionManager.rollback(status);
			System.out.println("*******************回滚事务*******************");
		}

		return true;
	}

我们发现,这种写法和我们之前使用JDBC API的写法是差不多的。以上方法非常简单,就是添加两条班级记录。如果SQL执行没有异常,就提交事务,如果SQL执行发生异常,就回滚事务。写完这个方法后,记得在StudentController中调用一下哦。同时,为了避免StudentController中其他StudentDao调用代码对事务测试方法transTest的影响,我们可以暂时注释掉其他StudentDao方法的调用。

		// 事务测试
		studentDao.transTest();

最后我们就按照正确的方式来Run当前工程,运行之前我们先去查看“class_info”表数据,如下:

运行之后“pku_class_info”表数据内容如下:

同时,我们可以在控制台看到“提交事务”的日志输出:

接下来,我们手动删除已经添加的“三班”和“四班”的数据记录,恢复到原始数据的样子。

然后修改transTest方法

String sql2 = "insert into class_info(class_name, add_time) value (?, ?)";
jdbcTemplate.update(sql2, "四班");
//jdbcTemplate.update(sql2, "四班", "2022-05-01 09:00:00");

我们在添加“四班”的时候,只传递一个名称参数,不传递时间参数,这样肯定会发生异常。因为我们在数据库里面不允许时间为空。我们重新Run当前工程,如下所示:

我们发现数据并没有被写入,同时在控制器也可以看到“回滚事务”的日志:

这说明,我们的事务控制起作用了。

整体项目下载地址: https://download.youkuaiyun.com/download/richieandndsc/89876567

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值