1. 事务环境的搭建
以银行转账为例子:
1.创建数据库的表,添加记录
create table bank_account
(
id int auto_increment comment '主键',
username varchar(32) null comment '用户名',
money int null comment '钱数',
constraint bank_account_pk
primary key (id)
);
create unique index bank_account_id_uindex
on bank_account (id);
INSERT INTO lipw_test.bank_account (username, money) VALUES ('zhangsan', 100);
INSERT INTO lipw_test.bank_account (username, money) VALUES ('lisi', 100);
Service
package com.demo.study_spring.bank.service;
import com.demo.study_spring.bank.dao.BankAccountDao;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class BankAccountServiceImpl {
@Resource
private BankAccountDao bankAccountDao;
public void change() {
bankAccountDao.reduceMoney();
bankAccountDao.addMoney();
}
}
Dao
package com.demo.study_spring.bank.dao;
public interface BankAccountDao {
void addMoney();
void reduceMoney();
}
daoImpl
package com.demo.study_spring.bank.dao;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
@Repository
public class BankAccountDaoImpl implements BankAccountDao {
@Resource
private JdbcTemplate jdbcTemplate;
@Override
public void addMoney() {
String sql = "update bank_account set money=money + ? where username = ?";
int query = jdbcTemplate.update(sql, 20, "zhangsan");
System.out.println("addMoney" + query);
}
@Override
public void reduceMoney() {
String sql = "update bank_account set money=money - ? where username = ?";
int query = jdbcTemplate.update(sql, 20, "lisi");
System.out.println("reduceMoney" + query);
}
}
配置文件:
<?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 http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--开启组件扫描-->
<context:component-scan base-package="com.demo.study_spring"></context:component-scan>
<!--数据库连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="url" value="jdbc:mysql://127.0.0.1:3306/lipw_test" />
<property name="username" value="root" />
<property name="password" value="root" />
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
</bean>
<!--创建JdbcTemplate 对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
测试类:
@Test
public void testBank() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
BankAccountServiceImpl bankAccountServiceImpl = context.getBean("bankAccountServiceImpl", BankAccountServiceImpl.class);
bankAccountServiceImpl.change();;
}
测试结果:
2. 如果出现异常(引出事务)
数据库还原
如果在
添加了异常
执行测试方法:
出现数据不符。
为了解决这个问题,就要使用事务。
3. 事务操作
1. 事务一般要添加到service层上(业务逻辑层)
2. 在spring进行事务的管理操作
1.有两种方式:1.编程式事务管理,2.声明式事务管理(一般使用)
3. 声明式事务管理
1. 基于注解方式
2. 基于xml配置文件方式
4.在spring进行声明式事务管理,底层使用AOP原理
5. Spring 事务管理API
1. 提供一个接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类
4. 事务操作注解式事务管理
1. 在spring 配置文件配置事务管理器
2. 在spring配置文件,开启事务注解
<?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 http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--开启组件扫描-->
<context:component-scan base-package="com.demo.study_spring"></context:component-scan>
<!--数据库连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="url" value="jdbc:mysql://127.0.0.1:3306/lipw_test" />
<property name="username" value="root" />
<property name="password" value="root" />
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
</bean>
<!--创建JdbcTemplate 对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--创建实现类-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--开启事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>
3. 在service类上面或者service 类里面的方法 添加上注解。
package com.demo.study_spring.bank.service;
import com.demo.study_spring.bank.dao.BankAccountDao;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
@Service
@Transactional
public class BankAccountServiceImpl {
@Resource
private BankAccountDao bankAccountDao;
public void change() {
try {
// 第一步 开启事务
// 第二步 进行业务操作
bankAccountDao.reduceMoney();
int a = 1/0;
bankAccountDao.addMoney();
// 第三步 事务的提交
} catch (Exception e) {
e.printStackTrace();
// 第四步, 出现异常,事务回滚
}
}
}
1. 在Transaction,这个注解添加到类上面,也可以添加到方法上面
2.如果这个注解添加类上面,这个类里面所有的方法都添加事务
3. 如果把这个注解添加方法上面,为这个方法添加事务
事务操作(声明式事务管理参数)
1. 在service类上面添加注解@Tranactional,在这个注解里面可以配置事务相关参数
/**
* 事务的传播行为.
* <p>
* 多事务方法直接进行调用,这个过程中事务是如何进行管理的
* 事务方法:对数据库表数据进行变化的操作
* Spring 框架事务传播行为有7种
*
* 1. REQUIRED: 如果add 方法本身有事务,调用update方法之后,update使用当前add方法里面的事务
* 2. REQUIRED_NEW: 使用add方法调用update方法,如果add无论是否有事务,都创建新的事务
* 3. SUPPOPTS: 如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中
* 4. NO_SUPPORTE: 当前的方法不应该运行在事务中,如果有运行的事务,将他挂起
* 5. MANDATORY: 当前的方法必须运行在事务内部,如果没有正在运行的事务,就掏出异常
* 6. NEVER: 当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常
* 7. NESTED:如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则,就启动一个新的事务,并在它自己的事务内运行。
*</p>
*/
Propagation propagation() default Propagation.REQUIRED;
/**
*事务的隔离级别
* 1. 事务有特性称为隔离性,多事务操作之间不会产生影响。不考虑隔离性产生很多问题
* 2. 有三个读问题,脏读、不可重复读、虚幻读
* 3. 脏读:一个未提交的事务读取到另一个未提交的事务的数据
* 4. 不可重复读: 一个未提交事务,获取到已提交事务的修改数据
* 5. 虚读:
*/
Isolation isolation() default Isolation.DEFAULT;
/**
* 超时时间
* 1. 事务需要在一定的时间内提交,如果不提交就会回滚
* 2. 默认值是-1,
*
*/
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
String timeoutString() default "";
/**
* 是否只读
* readOnly: 默认值false,表示可以查询,可以添加修改,删除操作
* 设置 readOnly 默认值是true,设置成true之后,只能查询
*
*/
boolean readOnly() default false;
/**
* 回滚
* 1. 设置出现哪些异常进行事务回滚
*
*/
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
/**
* 不回滚
* 1. 设置出现哪些异常不会混滚
*
*/
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
4. 事务操作(XML 声明式事务管理)
1. 在spring配置文件中进行配置
第一步,配置事务管理器
第二步,配置通知
(增强的部分通知,事务也是通知)
第三步,配置切入点,切面
<?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 http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/tx.xsd">
<!--开启组件扫描-->
<context:component-scan base-package="com.demo.study_spring"></context:component-scan>
<!--数据库连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="url" value="jdbc:mysql://127.0.0.1:3306/lipw_test" />
<property name="username" value="root" />
<property name="password" value="root" />
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
</bean>
<!--创建JdbcTemplate 对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--1. 创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--2. 配置通知-->
<tx:advice id ="txadvice">
<!--配置事务参数-->
<tx:attributes>
<!--指定那种规则的方法上面添加事务-->
<tx:method name="change" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--3. 配置切入点和切面-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pt" expression="execution(* com.demo.study_spring.bank.service.BankAccountServiceImpl.*(..))"/>
<!--配置切面-->
<aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
</aop:config>
</beans>
service修改
package com.demo.study_spring.bank.service;
import com.demo.study_spring.bank.dao.BankAccountDao;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
@Service
//@Transactional(readOnly = true,timeout = -1, propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ)
public class BankAccountServiceImpl {
@Resource
private BankAccountDao bankAccountDao;
public void change() {
// try {
// // 第一步 开启事务
// // 第二步 进行业务操作
// bankAccountDao.reduceMoney();
// int a = 1/0;
// bankAccountDao.addMoney();
// // 第三步 事务的提交
// } catch (Exception e) {
// e.printStackTrace();
// // 第四步, 出现异常,事务回滚
// }
bankAccountDao.reduceMoney();
int a = 1/0;
bankAccountDao.addMoney();
}
}
测试方法:
测试前数据库都还原为100
@Test
public void testBankXML() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml");
BankAccountServiceImpl bankAccountServiceImpl = context.getBean("bankAccountServiceImpl", BankAccountServiceImpl.class);
bankAccountServiceImpl.change();;
}
结果:
5. 事务操作(完全注解声明式事务管理)
新增配置文件
package com.demo.study_spring.bank.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.demo.study_spring.bank")
// 开启事务
@EnableTransactionManagement
public class TxConfig {
/**
* 创建数据库连接池
*/
@Bean
public DruidDataSource getDataSource() {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
druidDataSource.setUrl("jdbc:mysql://127.0.0.1:3306/lipw_test");
druidDataSource.setUsername("root");
druidDataSource.setPassword("root");
return druidDataSource;
}
/**
* 创建 JdbcTemplate
*/
@Bean
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;
}
}
编写测试方法
@Test
public void testBankAnno() {
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(TxConfig.class);
BankAccountServiceImpl bankAccountServiceImpl = annotationConfigApplicationContext.getBean("bankAccountServiceImpl", BankAccountServiceImpl.class);
bankAccountServiceImpl.change();;
}