1、Spring JdbcTemplate
在spring中为了更加方便的操作JDBC,在JDBC的基础之上定义了一个抽象层,此设计的目的是为不同类型的JDBC操作提供模板方法,每个模板方法都能控制整个过程,并允许覆盖过程中的特定任务,通过这种方式,可以尽可能保留灵活性,将数据库存取的工作量讲到最低。
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.mashibing</groupId> <artifactId>spring_demo</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <!-- https://mvnrepository.com/artifact/org.springframework/spring-context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.3.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/com.alibaba/druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.21</version> </dependency> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!-- https://mvnrepository.com/artifact/cglib/cglib --> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.5</version> </dependency> <!-- https://mvnrepository.com/artifact/aopalliance/aopalliance --> <dependency> <groupId>aopalliance</groupId> <artifactId>aopalliance</artifactId> <version>1.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.3.RELEASE</version> </dependency> </dependencies> </project>
dbconfig.properties
jdbc.username=root123 password=123456 url=jdbc:mysql://localhost:3306/demo driverClassName=com.mysql.jdbc.Driver
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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd "> <context:property-placeholder location="classpath:dbconfig.properties"></context:property-placeholder> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> <property name="url" value="${jdbc.url}"></property> <property name="driverClassName" value="${jdbc.driverClassName}"></property> </bean> </beans>
MyTest.java
import com.alibaba.druid.pool.DruidDataSource; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import java.sql.SQLException; public class MyTest { public static void main(String[] args) throws SQLException { ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml"); DruidDataSource dataSource = context.getBean("dataSource", DruidDataSource.class); System.out.println(dataSource); System.out.println(dataSource.getConnection()); } }
2、给spring容器添加JdbcTemplate
spring容器提供了一个JdbcTemplate类,用来方便操作数据库。
1、添加pom依赖
pom.xml
<!-- https://mvnrepository.com/artifact/org.springframework/spring-orm --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>5.2.3.RELEASE</version> </dependency>
jdbcTemplate.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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd "> <context:property-placeholder location="classpath:dbconfig.properties"></context:property-placeholder> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> <property name="url" value="${jdbc.url}"></property> <property name="driverClassName" value="${jdbc.driverClassName}"></property> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <constructor-arg name="dataSource" ref="dataSource"></constructor-arg> </bean> </beans>
MyTest.java
import com.alibaba.druid.pool.DruidDataSource; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.jdbc.core.JdbcTemplate; import java.sql.SQLException; public class MyTest { public static void main(String[] args) throws SQLException { ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml"); JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class); System.out.println(jdbcTemplate); } }
3、插入数据
MyTest.java
import com.alibaba.druid.pool.DruidDataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.jdbc.core.JdbcTemplate; import java.sql.SQLException; public class MyTest { public static void main(String[] args) throws SQLException { ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml"); JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class); String sql = "insert into emp(empno,ename) values(?,?)"; int result = jdbcTemplate.update(sql, 1111, "zhangsan"); System.out.println(result); } }
4、批量插入数据
MyTest.java
import com.alibaba.druid.pool.DruidDataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.jdbc.core.JdbcTemplate; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; public class MyTest { public static void main(String[] args) throws SQLException { ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml"); JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class); String sql = "insert into emp(empno,ename) values(?,?)"; List<Object[]> list = new ArrayLi st<Object[]>(); list.add(new Object[]{1,"zhangsan1"}); list.add(new Object[]{2,"zhangsan2"}); list.add(new Object[]{3,"zhangsan3"}); int[] result = jdbcTemplate.batchUpdate(sql, list); for (int i : result) { System.out.println(i); } } }
5、查询某个值,并以对象的方式返回
MyTest.java
import com.mashibing.bean.Emp; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import java.sql.SQLException; public class MyTest { public static void main(String[] args) throws SQLException { ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml"); JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class); String sql = "select * from emp where empno = ?"; Emp emp = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Emp.class), 7369); System.out.println(emp); } }
6、查询返回集合对象
MyTest.java
import com.mashibing.bean.Emp; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import java.sql.SQLException; import java.util.List; public class MyTest { public static void main(String[] args) throws SQLException { ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml"); JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class); String sql = "select * from emp where sal > ?"; List<Emp> query = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Emp.class), 1500); for (Emp emp : query) { System.out.println(emp); } } }
7、返回组合函数的值
MyTest.java
import com.mashibing.bean.Emp; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import java.sql.SQLException; import java.util.List; public class MyTest { public static void main(String[] args) throws SQLException { ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml"); JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class); String sql = "select max(sal) from emp"; Double aDouble = jdbcTemplate.queryForObject(sql, Double.class); System.out.println(aDouble); } }
8、使用具备具名函数的JdbcTemplate
jdbcTemplate.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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd "> <context:property-placeholder location="classpath:dbconfig.properties"></context:property-placeholder> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> <property name="url" value="${jdbc.url}"></property> <property name="driverClassName" value="${jdbc.driverClassName}"></property> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <constructor-arg name="dataSource" ref="dataSource"></constructor-arg> </bean> <bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate"> <constructor-arg name="dataSource" ref="dataSource"></constructor-arg> </bean> </beans>
MyTest.java
import com.mashibing.bean.Emp; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import java.sql.SQLException; import java.util.HashMap; import java.util.List; import java.util.Map; public class MyTest { public static void main(String[] args) throws SQLException { ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml"); NamedParameterJdbcTemplate jdbcTemplate = context.getBean("namedParameterJdbcTemplate", NamedParameterJdbcTemplate.class); String sql = "insert into emp(empno,ename) values(:empno,:ename)"; Map<String,Object> map = new HashMap<>(); map.put("empno",2222); map.put("ename","sili"); int update = jdbcTemplate.update(sql, map); System.out.println(update); } }
9、整合EmpDao
jdbcTemplate.xml
<context:component-scan base-package="com.mashibing"></context:component-scan>
EmpDao.java
package com.mashibing.dao; import com.mashibing.bean.Emp; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; public class EmpDao { @Autowired private JdbcTemplate jdbcTemplate; public void save(Emp emp){ String sql = "insert into emp(empno,ename) values(?,?)"; int update = jdbcTemplate.update(sql, emp.getEmpno(), emp.getEname()); System.out.println(update); } }
MyTest.java
import com.mashibing.bean.Emp; import com.mashibing.dao.EmpDao; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import java.sql.SQLException; import java.util.HashMap; import java.util.Map; public class MyTest { public static void main(String[] args) throws SQLException { ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml"); EmpDao empDao = context.getBean("empDao", EmpDao.class); empDao.save(new Emp(3333,"wangwu")); } }
2、声明式事务
1、环境准备
tx.sql
/* Navicat MySQL Data Transfer Source Server : localhost Source Server Version : 50528 Source Host : localhost:3306 Source Database : tx Target Server Type : MYSQL Target Server Version : 50528 File Encoding : 65001 Date: 2020-02-13 19:19:32 */ SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for `account` -- ---------------------------- DROP TABLE IF EXISTS `account`; CREATE TABLE `account` ( `username` varchar(10) NOT NULL DEFAULT '', `balance` double DEFAULT NULL, PRIMARY KEY (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Records of account -- ---------------------------- INSERT INTO account VALUES ('lisi', '1000'); INSERT INTO account VALUES ('zhangsan', '1000'); -- ---------------------------- -- Table structure for `book` -- ---------------------------- DROP TABLE IF EXISTS `book`; CREATE TABLE `book` ( `id` int(10) NOT NULL, `book_name` varchar(10) DEFAULT NULL, `price` double DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Records of book -- ---------------------------- INSERT INTO book VALUES ('1', '西游记', '100'); INSERT INTO book VALUES ('2', '水浒传', '100'); INSERT INTO book VALUES ('3', '三国演义', '100'); INSERT INTO book VALUES ('4', '红楼梦', '100'); -- ---------------------------- -- Table structure for `book_stock` -- ---------------------------- DROP TABLE IF EXISTS `book_stock`; CREATE TABLE `book_stock` ( `id` int(255) NOT NULL DEFAULT '0', `stock` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Records of book_stock -- ---------------------------- INSERT INTO book_stock VALUES ('1', '1000'); INSERT INTO book_stock VALUES ('2', '1000'); INSERT INTO book_stock VALUES ('3', '1000'); INSERT INTO book_stock VALUES ('4', '1000');
BookDao.java
package com.mashibing.dao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; @Repository public class BookDao { @Autowired JdbcTemplate jdbcTemplate; /** * 减去某个用户的余额 * @param userName * @param price */ public void updateBalance(String userName,int price){ String sql = "update account set balance=balance-? where username=?"; jdbcTemplate.update(sql,price,userName); } /** * 按照图书的id来获取图书的价格 * @param id * @return */ public int getPrice(int id){ String sql = "select price from book where id=?"; return jdbcTemplate.queryForObject(sql,Integer.class,id); } /** * 减库存,减去某本书的库存 * @param id */ public void updateStock(int id){ String sql = "update book_stock set stock=stock-1 where id=?"; jdbcTemplate.update(sql,id); } }
BookService.java
package com.mashibing.service; import com.mashibing.dao.BookDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class BookService { @Autowired BookDao bookDao; /** * 结账:传入哪个用户买了哪本书 * @param username * @param id */ public void checkout(String username,int id){ bookDao.updateStock(id); int price = bookDao.getPrice(id); bookDao.updateBalance(username,price); } }
MyTest.java
import com.mashibing.service.BookService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import java.sql.SQLException; public class MyTest { public static void main(String[] args) throws SQLException { ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml"); BookService bookService = context.getBean("bookService", BookService.class); bookService.checkout("zhangsan","1"); } }
总结:在事务控制方面,主要有两个分类:
编程式事务:在代码中直接加入处理事务的逻辑,可能需要在代码中显式调用beginTransaction()、commit()、rollback()等事务管理相关的方法
声明式事务:在方法的外部添加注解或者直接在配置文件中定义,将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。spring的AOP恰好可以完成此功能:事务管理代码的固定模式作为一种横切关注点,通过AOP方法模块化,进而实现声明式事务。
2、声明式事务的简单配置
Spring从不同的事务管理API中抽象出了一整套事务管理机制,让事务管理代码从特定的事务技术中独立出来。开发人员通过配置的方式进行事务管理,而不必了解其底层是如何实现的。
Spring的核心事务管理抽象是PlatformTransactionManager。它为事务管理封装了一组独立于技术的方法。无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。
事务管理器可以以普通的bean的形式声明在Spring IOC容器中。下图是spring提供的事务管理器
1、在配置文件中添加事务管理器
jdbcTemplate.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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd "> <context:component-scan base-package="com.mashibing"></context:component-scan> <context:property-placeholder location="classpath:dbconfig.properties"></context:property-placeholder> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> <property name="url" value="${jdbc.url}"></property> <property name="driverClassName" value="${jdbc.driverClassName}"></property> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <constructor-arg name="dataSource" ref="dataSource"></constructor-arg> </bean> <!--事务控制--> <!--配置事务管理器的bean--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!--开启基于注解的事务控制模式,依赖tx名称空间--> <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven> </beans>
BookService.java
package com.mashibing.service; import com.mashibing.dao.BookDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class BookService { @Autowired BookDao bookDao; /** * 结账:传入哪个用户买了哪本书 * @param username * @param id */ @Transactional public void checkout(String username,int id){ bookDao.updateStock(id); int price = bookDao.getPrice(id); bookDao.updateBalance(username,price); } }
3、事务配置的属性
isolation:设置事务的隔离级别
propagation:事务的传播行为
noRollbackFor:那些异常事务可以不回滚
noRollbackForClassName:填写的参数是全类名
rollbackFor:哪些异常事务需要回滚
rollbackForClassName:填写的参数是全类名
readOnly:设置事务是否为只读事务
timeout:事务超出指定执行时长后自动终止并回滚,单位是秒
4、测试超时属性
BookService.java
package com.mashibing.service; import com.mashibing.dao.BookDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class BookService { @Autowired BookDao bookDao; /** * 结账:传入哪个用户买了哪本书 * @param username * @param id */ @Transactional(timeout = 3) public void checkout(String username,int id){ bookDao.updateStock(id); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } int price = bookDao.getPrice(id); bookDao.updateBalance(username,price); } }
5、设置事务只读
如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持SQL执行期间的读一致性;
如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询SQL必须保证整体的读一致性,否则,在前条SQL查询之后,后条SQL查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持。
对于只读查询,可以指定事务类型为readonly,即只读事务。由于只读事务不存在数据的修改,因此数据库将会为只读事务提供一些优化手段
BookService.java
package com.mashibing.service; import com.mashibing.dao.BookDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class BookService { @Autowired BookDao bookDao; /** * 结账:传入哪个用户买了哪本书 * @param username * @param id */ @Transactional(timeout = 3,readOnly = true) public void checkout(String username,int id){ bookDao.updateStock(id); int price = bookDao.getPrice(id); bookDao.updateBalance(username,price); } }
6、设置哪些异常不回滚
注意:运行时异常默认回滚,编译时异常默认不回滚
BookService.java
package com.mashibing.service; import com.mashibing.dao.BookDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class BookService { @Autowired BookDao bookDao; /** * 结账:传入哪个用户买了哪本书 * @param username * @param id */ @Transactional(timeout = 3,noRollbackFor = {ArithmeticException.class,NullPointerException.class}) public void checkout(String username,int id){ bookDao.updateStock(id); int price = bookDao.getPrice(id); bookDao.updateBalance(username,price); int i = 1/0; } @Transactional(timeout = 3,noRollbackForClassName = {"java.lang.ArithmeticException"}) public void checkout(String username,int id){ bookDao.updateStock(id); int price = bookDao.getPrice(id); bookDao.updateBalance(username,price); int i = 1/0; } }
7、设置哪些异常回滚
BookService.java
package com.mashibing.service; import com.mashibing.dao.BookDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @Service public class BookService { @Autowired BookDao bookDao; /** * 结账:传入哪个用户买了哪本书 * @param username * @param id */ @Transactional(timeout = 3,rollbackFor = {FileNotFoundException.class}) public void checkout(String username,int id) throws FileNotFoundException { bookDao.updateStock(id); int price = bookDao.getPrice(id); bookDao.updateBalance(username,price); // int i = 1/0; new FileInputStream("aaa.txt"); } }
8、设置隔离级别
隔离级别没有接触的同学可以看我之前的事务视频,里面有详细讲解,此处不再赘述。
BookService.java
package com.mashibing.service; import com.mashibing.dao.BookDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @Service public class BookService { @Autowired BookDao bookDao; /** * 结账:传入哪个用户买了哪本书 * @param username * @param id */ @Transactional(timeout = 3,isolation = Isolation.READ_COMMITTED) public void checkout(String username,int id) throws FileNotFoundException { bookDao.updateStock(id); int price = bookDao.getPrice(id); bookDao.updateBalance(username,price); // int i = 1/0; new FileInputStream("aaa.txt"); } }
9、事务的传播特性
事务的传播特性指的是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行?
spring的事务传播行为一共有7种:
10、测试事务的传播特性
BookDao.java
package com.mashibing.dao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; @Repository public class BookDao { @Autowired JdbcTemplate jdbcTemplate; /** * 减去某个用户的余额 * @param userName * @param price */ public void updateBalance(String userName,int price){ String sql = "update account set balance=balance-? where username=?"; jdbcTemplate.update(sql,price,userName); } /** * 按照图书的id来获取图书的价格 * @param id * @return */ public int getPrice(int id){ String sql = "select price from book where id=?"; return jdbcTemplate.queryForObject(sql,Integer.class,id); } /** * 减库存,减去某本书的库存 * @param id */ public void updateStock(int id){ String sql = "update book_stock set stock=stock-1 where id=?"; jdbcTemplate.update(sql,id); } /** * 修改图书价格 * @param id * @param price */ public void updatePrice(int id,int price){ String sql = "update book set price=? where id =?"; jdbcTemplate.update(sql,price,id); } }
BookService.java
package com.mashibing.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @Service public class BookService { @Autowired BookDao bookDao; /** * 结账:传入哪个用户买了哪本书 * @param username * @param id */ @Transactional(propagation = Propagation.REQUIRED) public void checkout(String username,int id) { bookDao.updateStock(id); int price = bookDao.getPrice(id); bookDao.updateBalance(username,price); } @Transactional(propagation = Propagation.REQUIRED) public void updatePrice(int id,int price){ bookDao.updatePrice(id,price); int i = 1/0; } }
MulService.java
package com.mashibing.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class MulService { @Autowired private BookService bookService; @Transactional public void mulTx(){ bookService.checkout("zhangsan",1); bookService.updatePrice(1,1000); } }
MyTest.java
import com.mashibing.service.MulService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class MyTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml"); MulService mulService = context.getBean("mulService", MulService.class); mulService.mulTx(); } }
通过上图的结果发现,如果设置的传播特性是Required,那么所有的事务都会统一成一个事务,一旦发生错误,所有的数据都要进行回滚。
BookService.java
package com.mashibing.service; import com.mashibing.dao.BookDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @Service public class BookService { @Autowired BookDao bookDao; /** * 结账:传入哪个用户买了哪本书 * @param username * @param id */ @Transactional(propagation = Propagation.REQUIRES_NEW) public void checkout(String username,int id) { bookDao.updateStock(id); int price = bookDao.getPrice(id); bookDao.updateBalance(username,price); } @Transactional(propagation = Propagation.REQUIRED) public void updatePrice(int id,int price){ bookDao.updatePrice(id,price); int i = 1/0; } }
通过修改checkout方法的传播特性为Required_new,发现价格进行了回滚,而其他的数据没有进行回滚。
BookService.java
package com.mashibing.service; import com.mashibing.dao.BookDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @Service public class BookService { @Autowired BookDao bookDao; /** * 结账:传入哪个用户买了哪本书 * @param username * @param id */ @Transactional(propagation = Propagation.REQUIRED) public void checkout(String username,int id) { bookDao.updateStock(id); int price = bookDao.getPrice(id); bookDao.updateBalance(username,price); } @Transactional(propagation = Propagation.REQUIRED) public void updatePrice(int id,int price){ bookDao.updatePrice(id,price); } }
MulService.java
package com.mashibing.service; import com.mashibing.bean.Book; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class MulService { @Autowired private BookService bookService; @Transactional public void mulTx(){ bookService.checkout("zhangsan",1); bookService.updatePrice(1,1000); int i = 1/0; } }
将bookservice方法的传播行为为Required,并且将报错设置在MulService中,发现会都进行回滚。
BookService.java
package com.mashibing.service; import com.mashibing.dao.BookDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @Service public class BookService { @Autowired BookDao bookDao; /** * 结账:传入哪个用户买了哪本书 * @param username * @param id */ @Transactional(propagation = Propagation.REQUIRES_NEW) public void checkout(String username,int id) { bookDao.updateStock(id); int price = bookDao.getPrice(id); bookDao.updateBalance(username,price); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void updatePrice(int id,int price){ bookDao.updatePrice(id,price); } }
MulService.java
package com.mashibing.service; import com.mashibing.bean.Book; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class MulService { @Autowired private BookService bookService; @Transactional public void mulTx(){ bookService.checkout("zhangsan",1); bookService.updatePrice(1,1000); int i = 1/0; } }
将bookservice方法的传播行为为Requires_new,并且将报错设置在MulService中,发现都不会进行回滚。
BookService.java
package com.mashibing.service; import com.mashibing.dao.BookDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @Service public class BookService { @Autowired BookDao bookDao; /** * 结账:传入哪个用户买了哪本书 * @param username * @param id */ @Transactional(propagation = Propagation.REQUIRES_NEW) public void checkout(String username,int id) { bookDao.updateStock(id); int price = bookDao.getPrice(id); bookDao.updateBalance(username,price); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void updatePrice(int id,int price){ bookDao.updatePrice(id,price); } @Transactional public void mulTx(){ checkout("zhangsan",1); updatePrice(1,1000); int i = 1/0; } }
如果在bookservice执行的话,会发现刚刚的效果就没有了,原因是外层调用的时候使用的AOP,但是本类方法自己的调用就是最最普通的调用,就是同一个事务。
总结:
1、事务传播级别是REQUIRED,当checkout()被调用时(假定被另一类中commit()调用),如果checkout()中的代码抛出异常,即便被捕获,commit()中的其他代码都会roll back 2、是REQUIRES_NEW,如果checkout()中的代码抛出异常,并且被捕获,commit()中的其他代码不会roll back;如果commit()中的其他代码抛出异常,而且没有捕获,不会导致checkout()回滚 3、是NESTED,如果checkout()中的代码抛出异常,并且被捕获,commit()中的其他代码不会roll back;如果commit()中的其他代码抛出异常,而且没有捕获,会导致checkout()回滚 PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 "内部" 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行. 另一方面, PROPAGATION_NESTED 开始一个 "嵌套的" 事务, 它是已经存在事务的一个真正的子事务. 嵌套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交. 由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于, PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 嵌套事务也会被 commit, 这个规则同样适用于 roll back.
3、基于xml的事务配置
jdbcTemplate.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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd "> <context:component-scan base-package="com.mashibing"></context:component-scan> <context:property-placeholder location="classpath:dbconfig.properties"></context:property-placeholder> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> <property name="url" value="${jdbc.url}"></property> <property name="driverClassName" value="${jdbc.driverClassName}"></property> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <constructor-arg name="dataSource" ref="dataSource"></constructor-arg> </bean> <bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate"> <constructor-arg name="dataSource" ref="dataSource"></constructor-arg> </bean> <!--事务控制--> <!--配置事务管理器的bean--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 基于xml配置的事务:依赖tx名称空间和aop名称空间 1、spring中提供事务管理器(切面),配置这个事务管理器 2、配置出事务方法 3、告诉spring哪些方法是事务方法(事务切面按照我们的切入点表达式去切入事务方法) --> <bean id="bookService" class="com.mashibing.service.BookService"></bean> <aop:config> <aop:pointcut id="txPoint" expression="execution(* com.mashibing.service.*.*(..))"/> <!--事务建议:advice-ref:指向事务管理器的配置--> <aop:advisor advice-ref="myAdvice" pointcut-ref="txPoint"></aop:advisor> </aop:config> <tx:advice id="myAdvice" transaction-manager="transactionManager"> <!--事务属性--> <tx:attributes> <!--指明哪些方法是事务方法--> <tx:method name="*"/> <tx:method name="checkout" propagation="REQUIRED"/> <tx:method name="get*" read-only="true"></tx:method> </tx:attributes> </tx:advice> </beans>