介绍SpringJDBC
说明:
(1) 本篇博客是介绍Spring JDBC的基本内容,不涉及具体的编码实现;
(2) JDBC是java程序访问数据库的标准。那么,Spring生态体系,自然也对JDBC进行了支持和封装。
(3) 开始了解【Spring JDBC与事务管理】之前,可以快速浏览下【[MySQL基础】和【JDBC入门】中的内容;
零:【Spring JDBC与事务管理】部分,内容介绍
一:Spring JDBC简介
(1) 原生的JDBC操作起来比较麻烦,比如必须使用繁琐的语句完成查询,必须要时刻留意是否关闭了数据库连接等;Spring公司,就在其Spring框架中,添加了【Spring JDBC】模块,来解决这个问题;
(2.1) 【Spring JDBC】的作用,有点像【Apache Commons DBUtils】,只是一个是由Spring机构开发的,一个是由Apache机构开发的;
(2.2) 由于Spring框架底层是基于IoC容器的,所以【Spring JDBC】基于IoC容器,还可以实现更多的扩展行为;这是【Apache Commons DBUtils】所不具备的;
(3) JdbcTemplate对象,提供了数据增删改查的API方法;
(4) 学习【Spring JDBC】,主要就是学习【如何配置Spring JDBC】和【JdbcTemplate对象中的方法】;
二:已经有【Mybatis】了,为什么还需要【Spring JDBC】?
(0) ORM(object/relation mapping 对象关系映射)目前数据库是关系型数据库 ORM 主要是把数据库中的关系数据映射称为程序中的对象。
(1) 这两者面向的对象不一样;【Mybatis】作为一个ORM框架,其封装程度较高,适合于中小企业进行软件的敏捷开发,让程序员可以快速完成数据库交互工作;但是,【Mybatis】需要进行一系列的配置,也有很多操作细节需要注意,即【Mybatis】的封装程度还是比较高的;封装程度高,就意味着执行效率相对较低;
(2) 对于大的公司,无论是数据量还是并发量都是很高的,此时如果使用【Mybatis】,可能由于微小的性能上的差距,就会导致整体应用变慢;所以,对于很多大公司来说,其很少使用市面上成熟的框架,更多时候是使用像【Spring JDBC】这样的轻量级框架,在这个基础上结合自己企业的特点进行二次封装;
(4) 【Spring JDBC】只是对原始JDBC进行简单封装;和【Mybatis】相比,【Spring JDBC】的执行效率更高;
(5) 同时,由于Spring中【Spring IoC】容器的存在,和使用【原生JDBC】相比,使用【Spring JDBC】程序也变得更容易管理;
(6) 【Spring JDBC】是介于【ORM框架,比如Mybatis和Hibernate】和【原生JDBC】之间的一个折中方案
三:Spring JDBC使用步骤
(1) 【 DataSource数据源】以前介绍过,其作用是指明【到底要连接哪种数据库,哪台服务器,数据库用户名和密码是什么】;
(2) 配置好之后,需要在Dao中初始化【JdbcTemplate对象】,并通过【JdbcTemplate对象】实现增删改查操作;
SpringJDBC配置过程
说明: (1) Spring JDBC配置过程主要为:
● 在pom.xml中引入【Spring Context模块】,【Spring JDBC模块】,【Mysql的JDBC驱动依赖】;
●在applicationContext.xml中配置dataSource数据源对象,jdbcTemplate对象;(这样一来,IoC容器初始化时,就能实例化JdbcTemplate)
●在需要增删改查的Dao类中,去持有JdbcTemplate属性,并为其设置get和set方法(之所以设置get和set方法,是因为后面我们在依赖注入的时候是采用【使用XML方式实现Spring】中的策略来做的);然后,在Dao类的具体增删改查业务代码中,调用JdbcTemplate相应的方法,完成相应的增删改查操作;
● 在ApplicationContext.xml中配置Dao类对象,并完成JdbcTemplate属性注入的工作;
● 在初始化IoC容器后,在具体的业务代码中,从IoC容器中获取Dao类对象,调用对应的业务方法就能完成相应的增删改查操作了;
1.准备数据库数据
(1)创建一个逻辑空间:springjdbctest;
(2)导入数据
2.创建一maven工程
3.pom文件中引入依赖: 【Spring Context模块】,【Spring JDBC模块】,【Mysql的JDBC驱动】;
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>
<repositories>
<repository>
<id>aliyun</id>
<name>aliyun</name>
<url>https://maven.aliyun.com/repository/public</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.9</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.9</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
</dependencies>
</project>
说明:
(1)主要引入了三个依赖: 【Spring Context模块】,【Spring JDBC模块】,【JDBC】;
(2)【JDBC】依赖的几点说明:
● java程序想操作数据库,就需要引入JDBC驱动,我们在前面没有学习框架和maven的时候,如果需要操作数据库的话,都在工程中引入了jdbc的依赖;
● 自然,【Mybatis】,【Spring JDBC】这些操作数据库的框架都是基于jdbc的封装;所以,这儿也需要在pom中引入jdbc依赖;
● 【jdbc驱动的依赖】的版本最好和【数据库版本】保持一致;
4.创建applicationContext.xml配置文件:配置dataSource对象;jdbcTemplate对象;
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>
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
说明:
(1) 这儿只是引入了【默认命名空间】、【cointext命名空间】和相关的schema约束;
(2) 在IoC容器中,配置【DataSource对象】:设置数据源;
(3) 在IoC容器中,配置【JdbcTemplate对象】;
5.创建与数据库表对应的实体类:Employee类,作为承载存储数据库查询结果的实体类;
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 +
'}';
}
}
说明:
(1) 实体类的字段名需要采用驼峰命名方式,与Employee表的字段名保持一致;
(2) Employee类的hiredate类型是java.util包下的Date;以前介绍过,【java.sql下的Date】是【java.util下的Date】的子类;具体可以参考【JDBC中的Date日期的处理方式】;
(3) 添加了set和get方法;重写了toString()方法;
6.创建EmployeeDao类:这个类需要持有JdbcTemplate对象属性;编写业务方法;在IoC容器中配置EmployeeDao对象,并在EmployeeDao对象中注入JdbcTemplate对象;
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;
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 JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}
说明:
(1) 因为我们这儿使用【Spring JDBC】完成数据库的增删改查,所以EmployeeDao在数据操作是需要依赖【JdbcTemplate】对象;所以,EmployeeDao中需要持有JdbcTemplate;同时,需要给JdbcTemplate属性添加get和set方法;
(2) 在EmployeeDao类中,增加了一个查询方法:findById()方法,中间调用了JdbcTemplate对象的queryForObject()方法,完成相应的增删改查操作;
● 只要我们在写Employee实体类时,实体类的属性名严格按照驼峰命名规则,与数据表的字段名保持一致了;那么queryForObject()方法的第三个参数就可以使用【new BeanPropertyRowMapper<Employee(Employee.class)】来完成【从数据库记录到实体类对象的转换】;
● 因为,queryForObject()方法,是查询只有一个返回值的SQL语句,即其最多只会返回一条记录;所以,这儿使用Employee对象去接受方法返回结果就行了;
(3) 又因为我们这个项目是基于Spring的,即需要使用IoC管理对象;所以,我们接下来需要在IoC容器中配置EmployeeDao对象,并在EmployeeDao对象中注入JdbcTemplate对象;
7.创建SpringApplication类:测试;
SpringApplication类:
package com.imooc.spring.jdbc;
import com.imooc.spring.jdbc.dao.EmployeeDao;
import com.imooc.spring.jdbc.entity.Employee;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringApplication {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
EmployeeDao employeeDao = (EmployeeDao)
context.getBean("employeeDao", EmployeeDao.class);
Employee employee = employeeDao.findById(3308);
System.out.println(employee);
}
}
说明:
(1) 代码分析:再啰嗦一下~~
(2) 运行结果:
JDBCTemplate的数据查询方法
说明:
(1) 在【Spring JDBC与事务管理:Spring JDBC配置过程】;本篇博客主要内容是Spring JDBC的核心类→JdbcTemplate类的的查询方法; (2) 本篇博客内容说明:
● 如果查询结果只有一条记录:使用queryForObject()方法;
● 如果查询结果有多条记录:使用query()方法;
● 如果查询结果无法用一个实体类对象去承载:使用queryForList()方法;(这个在实际开发中还是比较常用的)
0.准备:在pom.xml中引入【junit单元测试】,【spring test测试模块】;
说明:
(1) 这儿引入【junit单元测试】,【spring test测试模块】的目的是,可以更灵活的来及时测试JdbcTemplate各个方法的效果;
(2)【junit单元测试】可以快速参考【单元测试与Junit4】;【spring test测试模块】可以快速参考【Spring与JUnit4整合】;
1.JdbcTemplate的【queryForObject()】:查询单条数据;
JdbcTemplateTestor:
import com.imooc.spring.jdbc.dao.EmployeeDao;
import com.imooc.spring.jdbc.entity.Employee;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class JdbcTemplateTestor {
@Resource
private EmployeeDao employeeDao;
@Test
public void testFindById() {
Employee employee = employeeDao.findById(3308);
System.out.println(employee);
}
}
说明:
(1) @RunWith()和@ContextConfiguration()注解,作用是整合【spring test】和【junit】;可以参考【Spring Test测试模块】;
(2) 把IoC容器中的EmployeeDao对象,注入到当前测试类对象中;
(3) 当查询结果只有一条记录时,使用JdbcTemplate的queryForObject()方法;
(4) 运行结果;
2.JdbcTemplate的【query()】:查询多条(复合)数据;
首先,在EmployeeDao类中,增加一个findByDname()方法,该方法会调用JdbcTemplate的query()方法;
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;
public class EmployeeDao {
private JdbcTemplate jdbcTemplate;
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 JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}
说明:
(1) 方法说明:查询结果有多条记录,使用JdbcTemplate的query()方法;
然后,在JdbcTemplateTestor类中创建testFindByDname()方法去测试;
import com.imooc.spring.jdbc.dao.EmployeeDao;
import com.imooc.spring.jdbc.entity.Employee;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class JdbcTemplateTestor {
@Resource
private EmployeeDao employeeDao;
@Test
public void testFindByDname() {
System.out.println(employeeDao.findByDname("研发部"));
}
}
说明:
(1) 运行结果;
**3.JdbcTemplate的【queryForList()】
当没有的实体类对象能够去承载查询结果时,使用 List<Map(String,Object)去承载查询结果
首先,在EmployeeDao类中,增加一个findMapByDname()方法,该方法会调用JdbcTemplate的queryForList()方法;
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 List<Map<String, Object findMapByDname(String dname) {
String sql = "select eno as empno,salary as s from employee where dname = ?";
List<Map<String, Object maps = jdbcTemplate.queryForList(sql,
new Object[]{dname});
return maps;
}
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}
说明:
(1)情况说明: 没有【实体类对象】能够去承载【查询结果】;(即,无法完成实体类和数据表的映射)
(2) 没有【实体类对象】能够去承载【查询结果】时,使用queryForList()方法,使用List<Map()去承载查询结果;
然后,在JdbcTemplateTestor类中创建testFindMapByDname()方法去测试;
import com.imooc.spring.jdbc.dao.EmployeeDao;
import com.imooc.spring.jdbc.entity.Employee;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class JdbcTemplateTestor {
@Resource
private EmployeeDao employeeDao;
@Test
public void testFindMapByDname() {
System.out.println(employeeDao.findMapByDname("研发部"));
}
}
说明:
(1) 运行结果;
(2)分析:通过queryForList()方法,即使没有合适的实体类对象可以去承载查询结果,也可以通过一个List <Map(String,Object)去承载查询结果;这在实际开发中,是比较常见的。
JDBCTemplate数据写入操作
说明:
(1) 本篇博客主要内容是介绍JdbcTemplate的update()方法;在JdbcTemplate中,新增、修改、删除统称为写入操作,都使用update()方法;
1. update()方法:新增;
首先,在EmployeeDao中增加insert()方法;
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 void insert(Employee employee) {
String sql = "insert into employee(eno,ename,salary,dname,hiredate) values(?,?,?,?,?)";
jdbcTemplate.update(sql, new Object[]{employee.getEno(),
employee.getEname(), employee.getSalary(), employee.getDname(),
employee.getHiredate()});
}
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}
说明:
(1) 新增的时候,update()方法只有两个参数,第一个是SQL语句,第二个就是SQL语句中的参数;
然后,在JdbcTemplateTestor中,创建测试方法testInsert();
import com.imooc.spring.jdbc.dao.EmployeeDao;
import com.imooc.spring.jdbc.entity.Employee;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
import java.util.Date;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class JdbcTemplateTestor {
@Resource
private EmployeeDao employeeDao;
@Test
public void testInsert() {
Employee employee = new Employee();
employee.setEno(4100);
employee.setEname("小刘");
employee.setSalary(7895f);
employee.setDname("研发部");
employee.setHiredate(new Date());
employeeDao.insert(employee);
}
}
说明:
(1) 运行结果;
(2) 没什么好说的,很简单~~
2. update()方法:更新;
首先,在EmployeeDao中增加update()方法;
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 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 JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}
说明:
(1) 更新的时候,update()方法只有两个参数,第一个是SQL语句,第二个就是SQL语句中的参数;
(2) 更新的时候,update()方法会返回一个int类型的返回值,代表本次更新的记录数;
然后,在JdbcTemplateTestor中,创建测试方法testUpdate();
import com.imooc.spring.jdbc.dao.EmployeeDao;
import com.imooc.spring.jdbc.entity.Employee;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
import java.util.Date;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class JdbcTemplateTestor {
@Resource
private EmployeeDao employeeDao;
@Test
public void testUpdate() {
Employee employee = employeeDao.findById(4100);
employee.setSalary(employee.getSalary() + 1000);
int count = employeeDao.update(employee);
System.out.println(count);
}
}
说明:
(1) 代码分析
(2) 运行结果;
3. update()方法:删除;
首先,在EmployeeDao中创建delete()方法;
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 int delete(Integer eno) {
String sql = "delete from employee where eno = ?";
int count = jdbcTemplate.update(sql, new Object[]{eno});
return count;
}
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}
说明:
(1) 新增的时候,update()方法只有两个参数,第一个是SQL语句,第二个就是SQL语句中的参数;
(2) update()方法删除时候,返回的是成功删除的记录数;
然后,在JdbcTemplateTestor类中,编写测试方法testDelete();
import com.imooc.spring.jdbc.dao.EmployeeDao;
import com.imooc.spring.jdbc.entity.Employee;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
import java.util.Date;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class JdbcTemplateTestor {
@Resource
private EmployeeDao employeeDao;
@Test
public void testDelete() {
int count = employeeDao.delete(4100);
System.out.println(count);
}
}
说明:
(1) 运行结果;
(2) 第一次执行delete()方法后,eno=4100就已经被删除了;如果再次执行delete()方法:
一旦涉及到写操作(新增、更新、删除), 就必须要考虑到实用性,即必须要考虑事务控制;
Spring编程式事务
说明:
(1) 有关事务内容可以快速参考【事务的基本介绍】及附近文章;【JDBC实现事务】;
(2) 【Spring JDBC模块】中也有事务控制;其中就包括编程式事务和声明式事务;本篇博客介绍编程式事务;
一:编程式事务简介
(1) 【Spring JDBC模块】通过引入TransactionManager对象(事务管理器),来控制事务;
(2) TransactionManager对象提供了两个核心方法:commit()和rollback()方法;(PS:在【[JDBC实现事务】中Connection对象也有自己的commit()和rollback()方法来控制事务;都是相通的)
二:代码演示:没有使用【编程式事务控制】的情况
需求: 公司入职10名新员工,需要把这10名新员工批量导入Employee表中;要求是,要么一次性全部导入,要么一个也不导入;
首先,创建EmployeeService类:
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;
}
}
然后,在applicationContext.xml中配置EmployeeService对象,并注入EmployeeDao:
然后,在JdbcTemplateTestor测试类中编写测试方法:
三:问题分析
虽然,上面写完了,而且导入成功了;但是上面写入操作并没有进行事务控制;
我们要求是,这10名员工,要么一次性全部导入,要么一个也不导入;而上面10次新增操作并不是在一个事务中完成的;可以打印一下该过程的日志来观察一下;
引入logback日志依赖:
说明:
(1) 在【MyBatis日志管理】中介绍过logback这个日志组件;
(2) 当添加logback这个日志依赖后,Spring默认就集成了;即Spring发现当前类路径下引入了logback后,Spring框架就可以默认使用logback将日志打印输出;
通过日志信息,分析【未使用控制事务】时的执行过程:
为了重新测试,将刚才添加的十条数据删除:
然后,重新运行JdbcTemplateTestor测试类中的testBatchImport()方法:观察日志信息
即,可知,在插入10条数据的过程中,一旦在过程中程序出现了中断,肯定会出现【一部分数据插入成功,而另一部分数据没有插入的情况】,而这是和我们的要求相违背的;
四:使用【编程式事务】去控制(核心!)
首先,在applicationContext.xml中配置transactionManager事务管理器对象:
然后,在需要事务控制的类(EmployeeService类)中,添加并注入transactionManager:
这儿,是在EmployeeService类中需要事务控制;
设置好了之后,如何使用?
然后,就是【编程式事务】的实际使用了:
EmployeeService完整代码如下:
package com.imooc.spring.jdbc.service;
import com.imooc.spring.jdbc.dao.EmployeeDao;
import com.imooc.spring.jdbc.entity.Employee;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import
org.springframework.transaction.support.DefaultTransactionDefinition;
import java.util.Date;
public class EmployeeService {
private EmployeeDao employeeDao;
private DataSourceTransactionManager transactionManager;
public void batchImport() {
//定义了事务默认的标准配置
TransactionDefinition definition = new
DefaultTransactionDefinition();
//开始一个事务,返回值是事务状态,事务状态说明当前事务的执行阶段
TransactionStatus transactionStatus =
transactionManager.getTransaction(definition);
try {
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);
}
//如果一切正常,就提交事务
transactionManager.commit(transactionStatus);
} catch (RuntimeException e) {
//如果运行出错,回滚事务
transactionManager.rollback(transactionStatus);
e.printStackTrace();//可以将异常进行打印,这也意味着异常不会向上抛出,在EmployeeService内部就被消化了;
// throw e;//如果,想让异常在外侧(即调用方)处理的话,可以把异常抛出去;
}
}
public EmployeeDao getEmployeeDao() {
return employeeDao;
}
public void setEmployeeDao(EmployeeDao employeeDao) {
this.employeeDao = employeeDao;
}
public DataSourceTransactionManager getTransactionManager() {
return transactionManager;
}
public void setTransactionManager(DataSourceTransactionManager
transactionManager) {
this.transactionManager = transactionManager;
}
}
说明:
(1) 代码分析
测试1:【执行过程中出现异常】的案例:
为了重新测试,将刚才添加的十条数据删除:
日志分析:
可以看到,当使用了编程式事务控制以后,所有的数据操作,都是在一个数据库连接中完成的;然后,已经成功写入的数据,不是直接写到数据库的表中,而是先被放入到了事务区中;
测试2:【执行过程中没有出现异常】的案例:
日志分析:
五:【编程式事务】有缺点,引出【声明式事务】
编程式事务:有优点,有缺点:
优点: 编程式事务的控制,是直接写在代码中的, 人眼阅读比较友好;
缺点: 编程式事务需要我们在代码中去书写事务控制的代码,因为不同的人对Spring掌握的程度不同,或者有的人可能在写代码的时候忘记写如下的代码:
这就可能导致数据的不完整的情况,从而给软件埋下巨大的风险。在一些金融、军事、科学运算等对数据要求很高的系统中,出现不完整的数据是非常严重的错误。所以,编程式事务控制可能存在人为的风险;为此,将介绍【Spring JDBC】提供的一种更加高级的做法:声明式事务。
声明式事务
说明:
(1) 已知,编程式事务虽然易于理解,但是由于程序员水平问题或疏忽问题,可能会出现忘记编写事务控制代码的情况;为此,声明式事务应运而生;
(2) 这一点要明确:声明式事务不是新技术,而是【Spring AOP面向切面编程】一个典型的应用场景而已;
(3) 【声明式事务】把事务控制的工作,转移到了applicationContext.xml配置文件中;这样以后,在编写业务代码的时候,程序员就不需要再设置事务了;这种策略,既安全又高效;
一:声明式事务简介
(1) 声明式事务不是新技术;而是,【Spring AOP面向切面编程】一个典型的应用场景而已;
(2) 利用【Spring AOP】中的环绕通知,可以轻松解决这个需求。即,【声明式事务】就是通过【Spring AOP的环绕通知】,在不修改源代码的情况下,完成程序的扩展,实现事务的控制;
(3) 关于【Spring 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编程式事务】中已经介绍过配置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】区别又是什么。
事务传播方式
说明:
(1) 我们知道propagation属性设置为“REQURIED”就是开启声明式事务;但是,对于propagation属性的详细内容并不了解;所以,本篇博客就来详细介绍下事务传播行为的内容;
(2) 在实际开发中,我们很少去设置事务传播方式(行为);只是,在面试的时候可能会被问到;所以,本篇博客的内容就作为一个扩展知识吧;
一:事务传播行为简介
(1) 事务传播行为:可以使用XML的方式配置;
(2) 事务传播行为:也可以使用注解的方式配置;【使用注解方式,开发声明式事务】在下篇博客中会介绍;
(3)这儿不懂【事务传播行为】没事,接下来的案例会更好的说明;
二:为演示【事务传播行为】,而准备的案例;
(1)创建BatchService类:该类里,添加了两个新增的方法;
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 BatchService {
private EmployeeDao employeeDao;
public void importJob1() {
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 void importJob2() {
for (int i = 1; i <= 10; i++) {
Employee employee = new Employee();
employee.setEno(9000 + i);
employee.setEname("市场部员工" + i);
employee.setSalary(4500f);
employee.setDname("市场部");
employee.setHiredate(new Date());
employeeDao.insert(employee);
}
}
public EmployeeDao getEmployeeDao() {
return employeeDao;
}
public void setEmployeeDao(EmployeeDao employeeDao) {
this.employeeDao = employeeDao;
}
}
说明:
(1) 在实际项目中,业务肯定会很复杂,自然会有很多业务方法;而自然,也会有很多新增、修改删除的业务方法。而这类新增操作的业务方法,都是需要进行事务控制的;
(2) BatchService类的主要的功能就是批量导入;
(3) 比如,BatchService类中的,inportJob1()和importJob2()这两个新增的方法,按理说都是需要进行业务控制的;
(2)修改EmployeeService类:增加BatchService属性,并调用BatchService中的方法;
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;
private BatchService batchService;
public void batchImport() {
for (int i = 1; i <= 10; i++) {
// if (i == 3) {
// throw new RuntimeException("一个意料之外的异常。");
// }
Employee employee = new Employee();
employee.setEno(8000 + i);
employee.setEname("员工" + i);
employee.setSalary(4000f);
employee.setDname("市场部");
employee.setHiredate(new Date());
employeeDao.insert(employee);
}
}
/**
* 调用BatchService这个【批量导入类】的批量导入方法;
*/
public void startImportJob() {
batchService.importJob1();
batchService.importJob2();
}
public EmployeeDao getEmployeeDao() {
return employeeDao;
}
public void setEmployeeDao(EmployeeDao employeeDao) {
this.employeeDao = employeeDao;
}
public BatchService getBatchService() {
return batchService;
}
public void setBatchService(BatchService batchService) {
this.batchService = batchService;
}
}
说明:
(1) 在EmployeeService类中,新增了startImportJob()方法,该方法中,调用了BatchService类中的方法;
(2) 为了能实现(1)中的需求,在EmployeeService类中增加了BatchService属性,并添加对应的get和set方法;
(3)在IoC容器中配置BatchService对象;在EmployeeService对象中注入BatchService对象;设置BatchService两个方法的事务传播方式;
三:初始演示一:【事务传播行为】初体验;
(1)情况说明:
(2)测试、运行
(3)分析
可以发现,上面涉及到了方法的嵌套调用;如果代码正常运行、没有出现问题,其结果看着是OK的;但是,在实际开发中上面这种配置是有问题的;
因为,importJob1()和importJob2(),只需要各自保证自己就行了;这两个方法之间应该互不影响;如下:
即可以发现,importJob1()、importJob2(),startImportJob()三个方法是嵌套调用的;然后,三个方法的事务传播方式均设置成了“REQURIED”;此时,通过运行结果可以发现,importJob1()、importJob2()是一荣俱荣、一损俱损的,二者并没有做到互不影响;
四:【事务传播行为】的七种类型;
1.七种【事务传播行为】;
(1) 【REQURIED】:事务传播行为的默认值:如果我们不设置事务传播方式,那么就默认是【REQURIED】;
● 当【设置了事务的方法】进行嵌套时,如果【外侧方法】有事务,那么【内侧被调用的方法】就会加入到外侧方法的事务中;如下图所示:
● 【REQURIED】详细分析;
(2) 【REQURIES_NEW】:方法会创建新的事务;
● 【REQURIES_NEW】:即便【外侧方法】有事务,【内侧被调用的方法】依旧会给自己创建一个新事务;如下案例演示;
然后,重新运行;
● 【REQURIES_NEW】:详细分析;
(3)【NOT_SUPPORTED】:如果【外侧方法】设置了事务,则把当前事务挂起,以非事务方式执行【内侧被调用的方法】;这种策略无法保证数据的完整性,不太安全;
(4)【SUPPORTS】:如果【外侧方法】设置了事务,【内侧被调用的方法】就用这个事务;如果没有事务,就不使用事务;因为,【新增、修改、删除】不使用事务会存在风险,所以这种无法保证数据的完整性,不太安全;
(5) 【MANDATORY】:如果【外侧方法】设置了事务,【内侧被调用的方法】就用这个事务;如果【外侧方法】没有设置事务,就报错;
PS:mandatory:n:受托者;adj:强制性的; 强制的; 法定的; 义务的;
(6) 【NEVER】:如果【外侧方法】设置了事务,就报错;如果【外侧方法】没有设置事务,【内侧被调用的方法】就以非事务方式执行;这个几乎不用;
(7)【NESTED】:如果【外侧方法】设置了事务,【内侧被调用的方法】就用这个事务;如果【外侧方法】没有设置事务,【内侧被调用的方法】就采用【REQURIED】的方式;
2.summary
● 根据不同的业务需求,设置不同的事务传播行为;在实际开发中,【REQURIED】、【REQURIES_NEW】、【NOT_SUPPORTED】使用较多;
● 日常开发中,大多数我们使用【REQURIED】,因为我们一般要保证一个Service方法中的内容要么全部OK,要么什么也不做;
● 少部分情况下,使用【REQURIES_NEW】;
●【NOT_SUPPORTED】多用于查询方法上;仔细一想,因为【新增、修改、删除】存在出错的风险,一旦出错其事务就会回滚;如果,【新增、修改删除】和【查询】混合在一起时,【NOT_SUPPORTED】可以把【查询】从【整体的潜在的回滚风险】中,拎出来;
注解形式声明式事务
说明:
(1) 同理,Spring这个机构,也提供了【使用注解方式,配置声明式事务】的策略;
(2) 本篇博客的IoC容器也采用了注解方式;
(3) 【注解形式】配置声明式事务很简单,主要包括两部分:
● 在applicationContext.xml中使用 【 <tx:annotation-driven transaction-manager=“transactionManager”/】 :启用注解形式声明事务;
● 然后,在需要事务控制的类、方法上使用【@Transactional注解】;
(5)在实际开发中,声明事务的常用配置和开发习惯,这个需要慢慢积累;
一:【注解形式】配置声明式事务;
(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>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
</dependencies>
</project>
说明:
(1) 没什么好说的,目前在pom.xml中,引入了【Spring Context模块】,【Spring JDBC模块】,【MySQL JDBC驱动】,【junit测试依赖】,【Spring Test模块】,【logback日志依赖】,【Spring AOP底层依赖的aspectjweaver】这几个;
(2)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"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.imooc"/>
<!--数据源-->
<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-->
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--事务管理器-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 启用注解形式声明式事务 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
说明:
(1) 【 <context:component-scan base-package=“com.imooc”/】意思是组件扫描,其作用是:确定扫描的包的范围是什么;(这个在【使用注解方式实现Spring IoC】中第一次接触过);
(2) 即使我们这儿是使用注解方式配置声明式事务;但是,DriverManagerDataSource数据源类,JdbcTemplate核心类,DataSourceTransactionManager事务管理器类这三个系统底层的公用类,还是需要采用XML方式进行配置;
(3) 【<tx:annotation-driven transaction -manager=“transactionManager”/】:启用注解形式声明事务;写了这句话后,就能完成【注解形式声明式事务的底层设置】了;(为了能更好的理解这句话,可以快速参考【基于注解开发Spring AOP】)
(3)EmployeeDao类:一个简单的Dao类;
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 org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
@Repository
public class EmployeeDao {
@Resource
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) 这儿我们也使用注解方式配置IoC容器了;PS:为了少点麻烦,要注意如下这种的名字的对应:
(2) EmployeeDao没什么好说的,就是使用注解形式配置IoC,然后使用注解完成对象的注入;
(4)EmployeeService:一个简单的Service类;使用注解配置了声明式事务;
package com.imooc.spring.jdbc.service;
import com.imooc.spring.jdbc.dao.EmployeeDao;
import com.imooc.spring.jdbc.entity.Employee;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.Date;
@Service
//声明式事务核心注解
//放在类上,将声明式事务配置应用于当前类所有方法,默认事务传播为 REQUIRED
@Transactional
public class EmployeeService {
@Resource
private EmployeeDao employeeDao;
@Resource
private BatchService batchService;
@Transactional(propagation = Propagation.NOT_SUPPORTED , readOnly =
true)
public Employee findById(Integer eno){
return employeeDao.findById(eno);
}
public void batchImport() {
for (int i = 1; i <= 10; i++) {
if(i==3){
throw new RuntimeException("意料之外的异常");
}
Employee employee = new Employee();
employee.setEno(8000 + i);
employee.setEname("员工" + i);
employee.setSalary(4000f);
employee.setDname("市场部");
employee.setHiredate(new Date());
employeeDao.insert(employee);
}
}
public void startImportJob(){
batchService.importJob1();
if(1==1){
throw new RuntimeException("意料之外的异常");
}
batchService.importJob2();
System.out.println("批量导入成功");
}
public EmployeeDao getEmployeeDao() {
return employeeDao;
}
public void setEmployeeDao(EmployeeDao employeeDao) {
this.employeeDao = employeeDao;
}
public BatchService getBatchService() {
return batchService;
}
public void setBatchService(BatchService batchService) {
this.batchService = batchService;
}
}
说明:
(1) 对于EmployeeService,我们也使用注解方式配置其IoC;
(2) 在类上使用【@Transactional注解】,传播方式默认为REQURIED;那么这个在类上的配置,会应用到本类的所有方法上;
(3) 在方法上使用【@Transactional注解】,则该方法优先使用在本方法上配置的事务传播方式;
(4) 即【@Transactional注解】可以用在类上,也可以用在方法上;
● 写在类上的【@Transactional注解】,会作用在本类的所有方法上;
● 写在方法上的【@Transactional注解】, 只对当前方法生效;
● 在加载是时候,写在方法上的【@Transactional注解】比写在类上的【@Transactional注解】优先级高;
(5)BatchService:一个简单的Service类;使用注解配置了声明式事务;
package com.imooc.spring.jdbc.service;
import com.imooc.spring.jdbc.dao.EmployeeDao;
import com.imooc.spring.jdbc.entity.Employee;
import com.sun.xml.internal.ws.developer.Serialization;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.Date;
@Service
@Transactional(propagation = Propagation.NOT_SUPPORTED,readOnly = true)
public class BatchService {
@Resource
private EmployeeDao employeeDao;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void importJob1(){
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);
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void importJob2(){
for (int i = 1; i <= 10; i++) {
Employee employee = new Employee();
employee.setEno(9000 + i);
employee.setEname("市场部员工" + i);
employee.setSalary(4500f);
employee.setDname("市场部");
employee.setHiredate(new Date());
employeeDao.insert(employee);
}
}
public EmployeeDao getEmployeeDao() {
return employeeDao;
}
public void setEmployeeDao(EmployeeDao employeeDao) {
this.employeeDao = employeeDao;
}
}
说明:
(1) 对于BatchService,我们也使用注解方式配置其IoC;
(2) 在类上使用【@Transactional注解】;因为按照业务需求,BatchService中的方法都需要在独立的事务中运行,所以在BatchService的类上使用如下配置: