说明:
(1)在【Spring JDBC与事务管理5:Spring编程式事务;】已知,编程式事务虽然易于理解,但是由于程序员水平问题或疏忽问题,可能会出现忘记编写事务控制代码的情况;为此,声明式事务应运而生;
(2)这一点要明确:声明式事务不是新技术,而是【Spring AOP面向切面编程】一个典型的应用场景而已;
(3)【声明式事务】把事务控制的工作,转移到了applicationContext.xml配置文件中;这样以后,在编写业务代码的时候,程序员就不需要再设置事务了;这种策略,既安全又高效;
目录
三:初始代码:创建项目s02;(这儿不是本篇博客的核心,快速浏览就行。)
(3)applicationContext.xml配置文件:
2.在applicationContext.xml中配置TransactionManager事务管理器对象;(第一步)
3.在applicationContext.xml中:引入【tx】命名空间和约束;【aop】命名空间和约束;
4.在applicationContext.xml中:事务通知配置:决定哪些方法使用事务,哪些方法不使用事务;(核心!)(第二步;第三步)
一:声明式事务简介
(1)声明式事务不是新技术;而是,【Spring AOP面向切面编程】一个典型的应用场景而已;
(2)利用【Spring AOP】中的环绕通知,可以轻松解决这个需求。即,【声明式事务】就是通过【Spring AOP的环绕通知】,在不修改源代码的情况下,完成程序的扩展,实现事务的控制;
(3)关于【Spring AOP 环绕通知】可以快速参考下【Spring AOP面向切面编程7:AOP相关概念五:AOP通知之:Around Advice 环绕通知;】 的内容;
二:声明式事务配置过程
因为,【声明式事务】是通过【Spring AOP的环绕通知】来实现的;所以,【声明式事务】的整个配置过程,都是在applicationContext.xml配置文件中完成的,不需要修改源代码;
(1)首先,需要在applicationContext.xml中配置TransactionManager对象。
前面已经知道,TransactionManager对象的作用是提交或回滚事务;【编程式事务】和【声明式事务】都需要TransactionManager事务管理器对象;
(2)然后,给不同的方法,配置事务通知和事务属性;
在实际开发中,有的方法(比如新增、修改、删除方法)需要事务控制,有的方法(比如查询方法)不需要事务控制。需要根据不同的情况,进行不同的配置;
(3)最后,为事务绑定【划定范围的切点】;
这儿需要利用【AOP】的切点表达式来划定一个范围;只有在这个范围内的,且满足(2)中的条件的方法,才会开启事务控制;
三:初始代码:创建项目s02;(这儿不是本篇博客的核心,快速浏览就行。)
s02的内容和前面几篇博客的代码基本一致,只是去除了编程式事务的有关代码而已;
(1)pom.xml:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.imooc.spring</groupId> <artifactId>jdbc</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.6.RELEASE</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.16</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.6.RELEASE</version> </dependency> <!--logback日志组件,Spring框架默认集成--> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> </dependencies> </project>
说明:
(1)没什么好说的,目前在pom.xml中,引入了【Spring Context模块】,【Spring JDBC模块】,【MySQL JDBC驱动】,【junit测试依赖】,【Spring Test模块】,【logback日志依赖】这几个;
(2)Employee实体类:
package com.imooc.spring.jdbc.entity; import java.util.Date; public class Employee { private Integer eno; private String ename; private Float salary; private String dname; private Date hiredate; public Integer getEno() { return eno; } public void setEno(Integer eno) { this.eno = eno; } public String getEname() { return ename; } public void setEname(String ename) { this.ename = ename; } public Float getSalary() { return salary; } public void setSalary(Float salary) { this.salary = salary; } public String getDname() { return dname; } public void setDname(String dname) { this.dname = dname; } public Date getHiredate() { return hiredate; } public void setHiredate(Date hiredate) { this.hiredate = hiredate; } @Override public String toString() { return "Employee{" + "eno=" + eno + ", ename='" + ename + '\'' + ", salary=" + salary + ", dname='" + dname + '\'' + ", hiredate=" + hiredate + '}'; } }
说明:
(0)Employee实体类很简单,没什么好说的;而且,在前面几篇博客中已经介绍过了;
(1)因为Employee实体类的作用,就是用来承载和存储数据库employee表的查询内容的;所以Employee类的属性,最好严格按照【驼峰命名规则】比照着employee表的字段名去写;
(2)然后,按常规写get和set方法;然后,为了方便观察Employee对象的内容,又重写了toString()方法;
(3)applicationContext.xml配置文件:
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!-- 数据源 --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/springjdbctest?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true"/> <property name="username" value="root"/> <property name="password" value="12345"/> </bean> <!--JdbcTemplate提供数据CRUD的API--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean> <bean id="employeeDao" class="com.imooc.spring.jdbc.dao.EmployeeDao"> <!--为Dao注入JdbcTemplate对象--> <property name="jdbcTemplate" ref="jdbcTemplate"/> </bean> <bean id="employeeService" class="com.imooc.spring.jdbc.service.EmployeeService"> <property name="employeeDao" ref="employeeDao"/> </bean> </beans>
说明:
(1)目前只是引入了【默认命名空间】,【context命名空间】;
(2)这个applicationContext.xml很简单,没什么好说的,就是基本的【Spring JDBC】配置;
(4)EmployeeDao类:
package com.imooc.spring.jdbc.dao; import com.imooc.spring.jdbc.entity.Employee; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import java.util.List; import java.util.Map; public class EmployeeDao { private JdbcTemplate jdbcTemplate; public Employee findById(Integer eno){ String sql = "select * from employee where eno = ?"; //查询单条数据 Employee employee = jdbcTemplate.queryForObject(sql, new Object[]{eno}, new BeanPropertyRowMapper<Employee>(Employee.class)); return employee; } public List<Employee> findByDname(String dname){ String sql = "select * from employee where dname = ?"; //查询复合数据 List<Employee> list = jdbcTemplate.query(sql, new Object[]{dname}, new BeanPropertyRowMapper<Employee>(Employee.class)); return list; } public List<Map<String, Object>> findMapByDname(String dname){ String sql = "select eno as empno , salary as s from employee where dname = ?"; //将查询结果作为Map进行封装 List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql, new Object[]{dname}); return maps; } public void insert(Employee employee){ String sql = "insert into employee(eno,ename,salary,dname,hiredate) values(?,?,?,?,?)"; //利用update方法实现数据写入操作 jdbcTemplate.update(sql,new Object[]{ employee.getEno() , employee.getEname(),employee.getSalary(),employee.getDname() , employee.getHiredate() }); } public int update(Employee employee){ String sql = "UPDATE employee SET ename = ?, salary = ?, dname = ?, hiredate = ? WHERE eno = ?"; int count = jdbcTemplate.update(sql, new Object[]{employee.getEname(), employee.getSalary(), employee.getDname(), employee.getHiredate(), employee.getEno()}); return count; } public int delete(Integer eno){ String sql = "delete from employee where eno = ?"; return jdbcTemplate.update(sql, new Object[]{eno}); } public JdbcTemplate getJdbcTemplate() { return jdbcTemplate; } public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } }
说明:
(1)因为EmployeeDao这个Dao类需要直接操作数据库,所以增加了JdbcTemplate属性;自然再applicationContext.xml中也在EmployeeDao对象中注入了JdbcTemplate对象;
(2)EmployeeDao中都是定义了一些查询、新增、删除、修改方法;而且,在前面几篇博客中已经介绍过了;
(5)EmployeeService类:
package com.imooc.spring.jdbc.service; import com.imooc.spring.jdbc.dao.EmployeeDao; import com.imooc.spring.jdbc.entity.Employee; import java.util.Date; public class EmployeeService { private EmployeeDao employeeDao; public void batchImport() { for (int i = 1; i <= 10; i++) { Employee employee = new Employee(); employee.setEno(8000 + i); employee.setEname("员工" + i); employee.setSalary(4000f); employee.setDname("市场部"); employee.setHiredate(new Date()); employeeDao.insert(employee); } } public EmployeeDao getEmployeeDao() { return employeeDao; } public void setEmployeeDao(EmployeeDao employeeDao) { this.employeeDao = employeeDao; } }
说明:
(1)啰嗦一下吧:因为Service类需要调用Dao类中的方法;所以EmployeeService类中增加了EmployeeDao属性,并且在IoC容器中完成了对象注入;
(2)在EmployeeService中也增加了batchImport()这个业务方法; 然后,我们没有使用【编程式事务控制中的,那些事务控制代码;去控制事务】;
四:需求阐述
已知:EmployeeService中的batchImport()业务方法,是批量更新的操作;在实际开发中,这显然是需要进行事务控制的。
要求:在不修改原始代码的情况下,对系统重要的Service类,配置【声明式事务】;从而,让程序员在编写代码的过程中,不用再考虑事务的问题;(因为,我们已经通过【声明式事务】完成了事务控制的工作;所以,程序员在编写业务代码的时候,就不用再考虑编写事务控制的代码;)
五:【声明式事务】:配置过程;(核心!)
1.在pom.xml中引入aspectj;
2.在applicationContext.xml中配置TransactionManager事务管理器对象;(第一步)
说明:
(1)【编程式事务】和【声明式事务】都需要TransactionManager事务管理器对象;
(2)在【Spring JDBC与事务管理5:Spring编程式事务;】中已经介绍过配置TransactionManager事务管理器对象了;
3.在applicationContext.xml中:引入【tx】命名空间和约束;【aop】命名空间和约束;
说明:
(1)这些命名空间的内容其实很简单,不需要死记硬背;而且,在Spring官网也都有的;
4.在applicationContext.xml中:事务通知配置:决定哪些方法使用事务,哪些方法不使用事务;(核心!)(第二步;第三步)
(1)第二步是:通过<tx:advice>,配置哪些开启事务,以及事务的属性是什么;
(2)第三步是:通过<aop:config>,来划定一个范围;只有在这个范围内的,且满足第二步条件的方法,才会开启事务;
5.实际测试;
(1)首先,有异常的情况:
……………………………………………………
(2)然后,没有异常的情况:
六:【声明式事务】总结;扩展分析;现存问题分析;
1.【声明式事务】summary;
(1)【声明式事务】就是在不修改源代码的情况下,实现事务的控制;
(2)【声明式事务】控制的规则是:如果方法执行成功,则提交事务;如果遇到运行时异常,则回滚事务;
(3)【声明式事务】是【Spring AOP】的一种典型应用;
2.扩展分析;
(1)<tx:method>:通配符映射:匹配多个方法
……………………………………………………
(2)如果某类方法不需要事务控制,如何设置;
……………………………………………………
(3)【其他选项 *】:设置【未被匹配到的方法】;
……………………………………………………
(4)【声明式事务】:在某种程度上也有助于程序的规范;
【声明式事务】让程序员从繁琐的事务控制中摆脱出来,即我们不用再在代码中编写事务控制代码,而是由程序根据自己的执行情况,自己决定提交事务还是回滚事务。
3.现存问题分析:事务传播方式还未了解;
现存问题:【propagation="NOT_SUPPORTED" 】,事务传播行为是什么,然后【NOT_SUPPORTED】和【REQUIRED】区别又是什么。