1.什么是事务
数据库事务(transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成。
2.事务的特性
A:原子性(Atomicity)
一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行定程中发生错误,会被回滚(Rollback) 到事务开始前的状态,就像这个事务从来没有执行过一样。
C:一致性(Consistency)
事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。
如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态。
如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态。
I:隔离性(Isolation)
指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。
D:持久性(Durability)
指的是只要事务成功结束,它对数据库所做的更新就必须保存下来。即使发生系统崩溃,重新启动数据库系统后数据库还能恢复到事务成功结束时的状态。
3.编程式事务
事务功能的相关操作全部通过自己编写代码来实现。
Connection conn = ...;
try{
//开启事务:关闭事务的自动提交
conn.setAutoCommit(false);
//核心操作
//提交事务
conn.commit();
}catch(Exception e){
//回滚事务
conn.rollback();
}finally{
//释放数据库连接
conn.close();
}
编程式的实现方式存在缺陷:
细节没有被屏蔽:具体操作过程中,所有细节都需要程序员自己来完成,比较繁琐。
代码复用性不高:如果没有有效抽取出来,每次实现功能都需要自己编写代码,代码就没有得到复用。
4.声明式事务
既然事务控制的代码有规律可循,代码的结构基本是确定的,所以框架就可以将固定模式的代码抽取出来,进行相关的封装。
封装起来后,我们只需要在配置文件中进行简单的配置即可完成操作。
好处1:提高开发效率
好处2:消除了几余的代码
好处3:框架会综合考虑相关领域中在实际开发环境下有可能遇到的各种问题,进行了健壮性、性能等各个方面的优化。
总结:
编程式:自己写代码实现功能。
声明式:通过配置让框架实现功能。
5.实现
①基于注解实现
1.配置bean文件
<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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 组件扫描-->
<context:component-scan base-package="com.yogurt.spring6.tx"></context:component-scan>
<!-- 引入外部属性文件,创建数据源对象-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.url}"></property>
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="username" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 创建jdbcTemplate对象,注入数据-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="druidDataSource"></property>
</bean>
</beans>
2.创建表
图书表
create table t_book(
book_id int(11) not null auto_increment comment '主键',
book_name varchar(20) default null comment '图书名称',
price int(11) default null comment '价格',
stock int(10) unsigned default null comment '库存(无符号)',
primary key (book_id))engine=innodb auto_increment=3 default charset=utf8;
用户表
create table t_user(
user_id int(11) not null auto_increment comment '主键',
username varchar(20) default null comment '用户名',
balance int(10) unsigned default null comment '余额(无符号)',
primary key (user_id))engine=innodb auto_increment=2 default charset=utf8;
案例测试:
买书过程(创建Controller、Service、Dao)
Controller
@Controller
public class BookController {
@Autowired
private BookService bookService;
/**
* 买书的方法
* @param bookId
* @param userId
*/
public void buyBook(Integer bookId,Integer userId){
//调用Service方法
bookService.buyBook(bookId,userId);
}
}
Service
@Service
public class BookServiceImpl implements BookService{
@Autowired
private BookDao bookDao;
/**
* 买书的方法
* @param bookId
* @param userId
*/
@Override
public void buyBook(Integer bookId, Integer userId) {
//根据图书id查询图书价格
Integer price = bookDao.getBookPriceByBookId(bookId);
//更新图书库存量 -1
bookDao.updateStock(bookId);
//更新用户表用户余额 -图书价格
bookDao.updateUserBalance(userId,price);
}
}
Dao
@Repository
public class BookDaoImpl implements BookDao{
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 根据id查询图书价格
* @param bookId
* @return
*/
@Override
public Integer getBookPriceByBookId(Integer bookId) {
String sql = "select price from t_book where book_id = ?";
Integer price = jdbcTemplate.queryForObject(sql, Integer.class, bookId);
return price;
}
/**
* 更新库存信息
* @param bookId
*/
@Override
public void updateStock(Integer bookId) {
String sql = "update t_book set stock = stock -1 where book_id = ?";
jdbcTemplate.update(sql,bookId);
}
/**
* 更新用户表用户余额 -图书价格
* @param userId
* @param price
*/
@Override
public void updateUserBalance(Integer userId, Integer price) {
String sql = "update t_user set balance = balance - ? where user_id = ?";
jdbcTemplate.update(sql,price,userId);
}
}
测试
@SpringJUnitConfig(locations = "classpath:bean.xml")
public class TestBookTx {
@Autowired
private BookController bookController;
@Test
public void testBuyBook(){
bookController.buyBook(1,1);
}
}
案例添加事务:
开启事务的注解驱动
引入新的命名空间:
开启注解驱动:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="druidDataSource"></property>
</bean>
<!-- 开启事务的注解驱动
transaction-manager属性的默认值是transactionManager,如果事务管理器bean的id-->
<tx:annotation-driven transaction-manager="transactionManager"/>
添加事务注解 (Service层)
注解标识的位置:
@Transactional标识在方法上,只能影响该方法
@Transactional标识在类上,则会影响类中所有方法
测试(余额不足时):
@SpringJUnitConfig(locations = "classpath:bean.xml")
public class TestBookTx {
@Autowired
private BookController bookController;
@Test
public void testBuyBook(){
bookController.buyBook(1,1);
}
}
结果(数据库数据不改变):
6.事务相关属性
只读
@Transactional(readOnly = true)
超时(三秒未响应就超时)
@Transactional(timeout = 3)
模拟超时效果
回滚策略
@Transactional(noRollbackFor = ArithmeticException.class)
当出现此异常时,不回滚事务
隔离级别
@Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别
@Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交
@Transactional(isolation = Isolation.READ_COMMITTED)//读已提交
@Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读
@Transactional(isolation = Isolation.SERIALIZABLE)//串行化
传播行为
在service类中有a()方法和b()方法,a()方法上有事务,b()方法上也有事务,当a(方法执行过程中调用了b()方法事务是如何传递的?合并到一个事务里?还是开启一个新的事务?这就是事务传播行为。
共有七种传播行为:
REQUIRED:支持当前事务,如果不存在就新建一个(默认)[没有就新建,有就加入]SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行[有就加入,没有就不管了]
MANDATORY:必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常[有就加入,没有就抛异常]
REQUIRES_NEW:开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起[不管有没有,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起]
NOT SUPPORTED:以非事务方式运行,如果有事务存在,挂起当前事务[不支持事务,存在就挂起]。NEVER:以非事务方式运行,如果有事务存在,抛出异常[不支持事务,存在就抛异常]
NESTED:如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚。如果外层事务不存在,行为就像REQUIRED一样。[有事务的话,就在这个事务里再嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚。没有事务就和REQUIRED一样。]
全注解配置事务
1.创建SpringConfig(代替bean文件)
@Configuration //配置类
@ComponentScan("com.yogurt.spring6.tx")
@EnableTransactionManagement //开启事务管理
public class SpringConfig {
@Bean
public DataSource getDateSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword("Mitaowulong");
dataSource.setUrl("jdbc:mysql://localhost:3306/spring");
return dataSource;
}
@Bean(name = "jdbcTemplate")
public JdbcTemplate getJdbcTemplate(DataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}
2.创建接口实现类
@Service
public class CheckoutServiceImpl implements CheckoutService{
//注入BookService
@Autowired
private BookService bookService;
//买多本书的方法
@Transactional
@Override
public void checkout(Integer[] bookIds, Integer userId) {
for(Integer bookId:bookIds){
//调用service方法
bookService.buyBook(bookId,userId);
}
}
}
3.测试
public class TestAnno {
@Test
public void testTxAllAnnotation(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
BookController accountService = applicationContext.getBean("bookController",BookController.class);
Integer[] bookIds = {1,2};
accountService.checkout(bookIds,1);
}
}