1 概念
- 事务是数据库操作最基本的单元,逻辑上的一组操作,要么全部成功,要么全部失败。
2 事务的特性
-
原子性
-
一致性
-
隔离性
-
持久性
3 准备工作
- account.sql
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '姓名',
`money` double NULL DEFAULT NULL COMMENT '账户余额',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
INSERT INTO `account` VALUES (1, 'lucy', 1000);
INSERT INTO `account` VALUES (2, 'marry', 1000);
- 导入相关jar包的Maven坐标:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.23</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
- Account.java
package top.open1024.spring.domain;
/**
* 账户
*/
public class Account {
/**
* 主键
*/
private Long id;
/**
* 姓名
*/
private String name;
/**
* 账户余额
*/
private Double money;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
- db.properties
jdbc.url=jdbc:mysql://192.168.1.107:33060/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
jdbc.driverClass=com.mysql.cj.jdbc.Driver
jdbc.username=root
jdbc.password=123456
- applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<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="http://www.springframework.org/schema/beans"
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="top.open1024.spring"></context:component-scan>
<!-- 导入数据库连接信息 -->
<context:property-placeholder location="db.properties"></context:property-placeholder>
<!-- 配置数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClass}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 配置jdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
4 事务操作介绍
4.1 概述
-
事务添加到JavaEE三层结构中的Service层(业务逻辑层)。
-
在Spring中进行事务管理操作有两种方式:
-
编程式事务管理。
-
声明式事务管理(经常使用)。
-
-
声明式事务管理:
-
基于注解方式(经常使用)。
-
基于XML配置文件方式。
-
-
Spring事务管理API
- Spring提供了一个接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类。
-
- @Transaction注解可以添加在类上面,也可以放在方法上面。
- 如果添加在类上面,这个类里面的所有的方法都添加了事务。
- 如果添加在方法上面,则这个方法添加了事务。
- 源码:
- @Transaction注解可以添加在类上面,也可以放在方法上面。
package org.springframework.transaction.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
//传播行为
Propagation propagation() default Propagation.REQUIRED;
//事务隔离级别
Isolation isolation() default Isolation.DEFAULT;
//超时时间
int timeout() default -1;
//是否只读
boolean readOnly() default false;
//回滚
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
//不回滚
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
-
-
- propagation:事务的传播行为。
- 多事务方法(对数据库表数据进行变化的操作)直接进行调用,这个过程中事务是如何进行管理的。
- propagation:事务的传播行为。
-
@Transactional
public void add(){
//调用update方法
update();
}
public void update(){}
-
-
-
-属性:
-
REQUIRED:
- 如果add方法本身有事务,则调用update方法之后,update使用当前add方法里面的事务。
- 如果add方法本身没有事务,调用update方法之后,会创建新的事务。
-
REQUIRED_NEW:
- 使用add方法调用update方法,无论add是否有事务,都会创建新的事务。
-
isolation:事务隔离级别。
- 多事务操作之间不会产生影响。
- 如果不考虑隔离性,会产生脏读、不可重复读、虚读。
- 脏读:一个未提交的事务读到另一个事务未提交的事务(针对update)。
- 不可重复读:一个未提交的事务读到另一个提交事务的修改数据(针对update),前后多次读取,导致数据内容不一致。
- 幻读:一个未提交的事务读到了另一个提交事务的添加数据(针对insert),前后多次读取,数据总量不一致。
-
通过设置事务的隔离级别,可以解决脏读问题。
- READ UNCOMMITTED:会出现脏读、不可重复读、幻读。
- READ COMMITTED:会出现不可重复读、幻读。
- REPEATABLE READ:会出现幻读。
- SERIALIZABLE:不会出现问题。
-
timeout:超时时间。
- 事务需要在一定时间内进行提交,如果不提交就进行回滚。
- 在Spring中,默认值是-1(不回滚),设置时间以秒单位进行计算。
-
readOnly:是否只读。
- 默认值是false,表示可以查询、添加、修改、删除操作。
- 如果设置readOnly为true,就只能查询。
-
rollbackFor:回滚。
- 设置出现了那些异常进行事务回滚。
-
noRollbackFor:不回滚。
- 设置那些异常不进行事务回滚。
-
-
4.2 基于注解方式的声明式事务管理
- 在Spring的配置文件applicationContext.xml中配置事务管理器
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据源 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
- 在Spring的配置文件applicationContext.xml中,开启事务注解
<!-- 开启事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
- 完整的applicationContext.xml文件信息
<?xml version="1.0" encoding="UTF-8"?>
<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="http://www.springframework.org/schema/beans"
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="top.open1024.spring"></context:component-scan>
<!-- 导入数据库连接信息 -->
<context:property-placeholder location="db.properties"></context:property-placeholder>
<!-- 配置数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClass}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 配置jdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<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"/>
</beans>
- AccountDao.java
package top.open1024.spring.dao;
import top.open1024.spring.domain.Account;
public interface AccountDao {
/**
* 转入
*
* @param account
* @param money
*/
void addMoney(Account account, Double money);
/**
* 转出
*
* @param account
* @param money
*/
void reduceMoney(Account account, Double money);
}
- AccountDaoImpl.java
package top.open1024.spring.dao.impl;
import top.open1024.spring.dao.AccountDao;
import top.open1024.spring.domain.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class AccountDaoImpl implements AccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void addMoney(Account account, Double money) {
jdbcTemplate.update(" update account set money = money + ? where username = ? ", money, account.getName());
}
@Override
public void reduceMoney(Account account, Double money) {
jdbcTemplate.update(" update account set money = money - ? where username = ? ", money, account.getName());
}
}
- AccountService.java
package top.open1024.spring.service;
public interface AccountService {
/**
* 转账
*
* @param from
* @param target
* @param money
*/
void transfer(String from, String target, Double money);
}
- AccountServiceImpl.java
package top.open1024.spring.service.impl;
import top.open1024.spring.dao.AccountDao;
import top.open1024.spring.domain.Account;
import top.open1024.spring.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Transactional
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Override
public void transfer(String from, String target, Double money) {
Account targetAccount = new Account();
targetAccount.setName(target);
accountDao.addMoney(targetAccount, money);
int i = 100 / 0;
Account fromAccount = new Account();
fromAccount.setName(from);
accountDao.reduceMoney(fromAccount, money);
}
}
- 测试:
package top.open1024.spring;
import top.open1024.spring.service.AccountService;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringTest {
private AccountService accountService = null;
@Before
public void before(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
accountService = context.getBean(AccountService.class);
}
@Test
public void test(){
accountService.transfer("lucy","marrry",100.00);
}
}
4.3 基于XML配置文件方式的声明式事务管理
- 在Spring的配置文件applicationContext.xml中配置事务管理器
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据源 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
- 在Spring的配置文件applicationContext.xml中配置通知
<!-- 配置通知 -->
<tx:advice id="txAdvice">
<!-- 配置事务参数 -->
<tx:attributes>
<!-- 指定那种规则的方法上面添加事务 -->
<tx:method name="add*" />
<tx:method name="update*" />
<tx:method name="delete*" />
</tx:attributes>
</tx:advice>
- 在Spring的配置文件applicationContext.xml中配置切入点和通知
<!-- 配置切入点和切面 -->
<aop:config>
<!-- 配置切入点 -->
<aop:pointcut id="pointcut" expression="execution(* top.open1024.spring.service.impl.AccountServiceImpl.*(..))"/>
<!-- 配置切面 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"></aop:advisor>
</aop:config>
- 完整的applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<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="http://www.springframework.org/schema/beans"
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="top.open1024.spring"></context:component-scan>
<!-- 导入数据库连接信息 -->
<context:property-placeholder location="db.properties"></context:property-placeholder>
<!-- 配置数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClass}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 配置jdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据源 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置通知 -->
<tx:advice id="txAdvice">
<!-- 配置事务参数 -->
<tx:attributes>
<!-- 指定那种规则的方法上面添加事务 -->
<tx:method name="add*"/>
<tx:method name="update*"/>
<tx:method name="delete*"/>
</tx:attributes>
</tx:advice>
<!-- 配置切入点和切面 -->
<aop:config>
<!-- 配置切入点 -->
<aop:pointcut id="pointcut" expression="execution(* top.open1024.spring.service.impl.AccountServiceImpl.*(..))"/>
<!-- 配置切面 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"></aop:advisor>
</aop:config>
</beans>
4.4 完全注解方式的声明式事务管理
-
SpringConfig.java
package top.open1024.spring.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.TransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; import javax.sql.DataSource; @Configuration @ComponentScan(basePackages = "top.open1024.spring") @EnableTransactionManagement //开启事务 public class SpringConfig { /** * 连接池 * * @return */ @Bean public DataSource dataSource() { DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); druidDataSource.setUrl("jdbc:mysql://192.168.134.100:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true"); druidDataSource.setUsername("root"); druidDataSource.setPassword("123456"); return dataSource(); } /** * JdbcTemplate * * @return */ @Bean public JdbcTemplate jdbcTemplate() { JdbcTemplate jdbcTemplate = new JdbcTemplate(); jdbcTemplate.setDataSource(dataSource()); return jdbcTemplate; } /** * 事务管理器 * * @return */ @Bean public TransactionManager transactionManager() { DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource()); return transactionManager; } }
评论区