事务概念

事务是数据库操作的基本单元,是逻辑上的一组操作,要么都执行成功,如果有一个操作失败则所有操作都不成功执行

事务四个特性(ACID),原子性:事务执行过程不可分割,要么执行成功,要么有一步失败则整个事务执行失败;一致性:事务开始和结束时数据状态保持一致,如A向B转账100这个事务,执行成功后A少100,B多100,绝不能出现A少100,B多150的情况;隔离性:多个事务操作同一数据时互不影响;持久性:事务执行成功,则数据的变化就永久保存到数据库中


搭建事务操作环境

05-事务操作_spring

05-事务操作_spring_02

05-事务操作_mysql_03

package com.atguigu.spring5.service;

import com.atguigu.spring5.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {

@Autowired
private UserDao userDao;

public void accountMoney() {
userDao.reduceMoney();
userDao.addMoney();
}
}
package com.atguigu.spring5.dao;

public interface UserDao {

void addMoney();
void reduceMoney();
}
package com.atguigu.spring5.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class UserDaoImpl implements UserDao {

@Autowired
private JdbcTemplate jdbcTemplate;

// lucy给mary转账100
@Override
public void addMoney() {
String sql = "update t_account set money=money+? where username=?";
jdbcTemplate.update(sql, 100, "mary");
}

@Override
public void reduceMoney() {
String sql = "update t_account set money=money-? where username=?";
jdbcTemplate.update(sql, 100, "lucy");
}
}

bean13.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 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.atguigu"></context:component-scan>

<!-- 创建数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="jdbc:mysql:///user_db" />
<property name="username" value="root" />
<property name="password" value="123456" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>

<!-- 创建JdbcTemplate对象,注入DataSource -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 通过set()注入属性值 -->
<property name="dataSource" ref="dataSource"></property>
</bean>

</beans>

测试

05-事务操作_xml_04

05-事务操作_xml_05


事务场景引入

上面例子中,accountMoney()里减钱和加钱的操作应该是在一个事务里完成,否则可能出现执行完减钱的操作后刚好发生异常导致程序终止,该方法应该是这样

05-事务操作_xml_06


Spring事务管理介绍

事务管理操作一般是加在JavaEE三层结构中的Service层(业务逻辑层),有两种实现方式:编程式事务管理(如上面方法中使用代码实现)和声明式事务管理(较多使用)

声明式事务管理可使用基于注解方式(较多使用)和基于xml配置文件方式实现

Spring进行声明式事务管理,底层使用AOP原理

Spring事务管理的API,有一个代表事务管理器的接口,该接口针对不同的操作数据库的框架提供了不同的实现类。对于JdbcTemplate就是DataSourceTransactionManager

05-事务操作_mysql_07


Spring声明式事务管理

注解方式

bean13.xml

05-事务操作_spring_08

引入名称空间tx

05-事务操作_xml_09

05-事务操作_xml_10

在UserService类中添加事务注解,既可以添加在类上,也可以添加在方法上。添加在类上表示为类中所有方法都添加事务

05-事务操作_spring_11


事务参数(传播行为)

在@Transactional注解里可以配置事务相关参数

propagation:事务传播行为;ioslatoin:事务隔离级别;timeout:超时时间;readOnly:是否只读;rollbackFor:回滚;noRollbackFor:不回滚

事务传播行为指的是多个事务方法之间进行调用时,该过程中事务是如何进行管理的,比如一个添加事务的方法中调用了一个没有添加事务的方法,或是一个没有添加事务的方法调用一个添加了事务的方法

05-事务操作_xml_12

Spring中有7中事务传播行为,以上面为例,REQUIRED:add()本身有事务,调用upate()后,update()使用add()里的事务,如果add()没有事务,调用update()后,创建新事务;REQUIRED_NEW:add()调用update(),无论add()是否有事务,都创建新事务

05-事务操作_spring_13

05-事务操作_spring_14


事务参数(隔离级别)

事务的隔离性指的是多事务操作之间不会互相产生影响,不同的隔离级别会产生不同的问题或现象,脏读(不能接受):一个未提交的事务读到另一个未提交事务的数据;不可重复读:一个未提交事务读取到另一个已提交事务修改的数据;幻读:一个未提交事务读取到另一个已提交事务添加的数据

通过设置隔离级别解决读的问题,MySQL默认隔离级别是可重复读

05-事务操作_spring_15

05-事务操作_mysql_16


事务参数(其他参数)

timeout:超时时间,事务需要在一定时间内进行提交,如果不提交导致超时,则进行回滚操作。默认超时时间是-1,即永不超时,设置时以秒为单位

readOnly:是否只读,读是指查询操作,写是指添加、修改删除操作。默认值为false,表示可查询、可添加修改和删除。若设置为true,则只能查询

rollbackFor:回滚,设置出现哪些异常就进行事务回滚

noRollbackFor:不回滚,设置出现哪些异常不进行事务回滚

05-事务操作_mysql_17


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 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/tx http://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.atguigu"></context:component-scan>

<!-- 创建数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="jdbc:mysql:///user_db" />
<property name="username" value="root" />
<property name="password" value="123456" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>

<!-- 创建JdbcTemplate对象,注入DataSource -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 通过set()注入属性值 -->
<property name="dataSource" ref="dataSource"></property>
</bean>

<!-- 1,创建事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 通过set()注入属性值 -->
<property name="dataSource" ref="dataSource"></property>
</bean>

<!-- 2,配置通知 -->
<tx:advice id="txadvice">
<!-- 配置事务参数 -->
<tx:attributes>
<!-- 指定在哪种规则的方法上添加事务 -->
<tx:method name="accountMoney" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>

<!-- 3,配置切入点和切面 -->
<aop:config>
<aop:pointcut id="pt" expression="execution(* com.atguigu.spring5.service.UserService.*(..))"/>
<aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
</aop:config>
</beans>


完全注解方式

创建配置类,对比bean13.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"
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/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

<!-- 开启组件扫描 -->
<context:component-scan base-package="com.atguigu"></context:component-scan>

<!-- 创建数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="jdbc:mysql:///user_db" />
<property name="username" value="root" />
<property name="password" value="123456" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>

<!-- 创建JdbcTemplate对象,注入DataSource -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 通过set()注入属性值 -->
<property name="dataSource" ref="dataSource"></property>
</bean>

<!-- 创建事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 通过set()注入属性值 -->
<property name="dataSource" ref="dataSource"></property>
</bean>

<!-- 开启事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>
package com.atguigu.spring5.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

@Configuration // 这是一个配置类
@ComponentScan(basePackages = "com.atguigu") // 开启组件扫描
@EnableTransactionManagement // 开启事务
public class TxConfig {

// 创建数据库连接池
@Bean
public DruidDataSource getDruidDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///user_db");
dataSource.setUsername("root");
dataSource.setPassword("123456");
return dataSource;
}

// 创建JdbcTemplate对象,上面已经创建了数据库连接池对象,就会
// 自动到IOC容器中根据类型找到dataSource传入该方法中
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
// 注入dataSource
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}

// 创建事务管理器
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}

测试

05-事务操作_xml_18

使用读取bean13.xml配置文件方式完成上面测试时,先注释掉@Configuration注解,否则报错