1. 事务
数据库操作的最基本单元,逻辑上的一组操作,要么都成功,如果一个操作失败那么这组操作都会失败。
1.1 事务的四个特性(ACID)
- 原子性:操作不可分割,要么都成功,要不都失败;
- 一致性:操作前后的总量是不变的;
- 隔离性:同时操作同一条数据不会互相影响;
- 持久性:修改表中数据。
1.2 环境搭建
这里以银行转账为例子来展示Spring事务。
包含两个核心业务,即转款和收款。
- Sql:
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
`id` int(20) NOT NULL AUTO_INCREMENT,
`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`money` int(20) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of t_user
-- ----------------------------
INSERT INTO `t_user` VALUES (1, 'lucy', 200);
INSERT INTO `t_user` VALUES (2, 'mary', 200);
SET FOREIGN_KEY_CHECKS = 1;
- maven:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
</dependencies>
- 数据库配置文件
druid.driverClass = com.mysql.jdbc.Driver
druid.url = jdbc:mysql:///spring220529?useSSL=false
druid.username = root
druid.password = root
- bean配置文件
<?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:property-placeholder location="druid.properties"/>
<bean id="druid" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="driverClassName" value="${druid.driverClass}"/>
<property name="url" value="${druid.url}"/>
<property name="username" value="${druid.username}"/>
<property name="password" value="${druid.password}"/>
</bean>
<context:component-scan base-package="com.jm.spring.*"/>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="druid"/>
</bean>
</beans>
- Dao:
/**
* @Description
* @date 2022/5/29 14:33
*/
public interface UserDao {
/**
* 多钱方法
* @param id
* @param money
* @return
*/
boolean addMoney(Integer id, Integer money);
/**
* 少钱方法
* @param id
* @param money
* @return
*/
boolean reduceMoney(Integer id, Integer money);
}
@Repository
public class UserDaoImpl implements UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public boolean addMoney(Integer id, Integer money) {
String sql = "update t_user set money = money + ? where id = ?";
return jdbcTemplate.update(sql, money, id) > 0;
}
@Override
public boolean reduceMoney(Integer id, Integer money) {
String sql = "update t_user set money = money - ? where id = ?";
return jdbcTemplate.update(sql, money, id) > 0;
}
}
- Service:
/**
* @Description
* @date 2022/5/29 14:34
*/
public interface UserService {
/**
* 转账方法
* @param form 发起人
* @param to 接收人
* @param money 金额
* @return
*/
boolean accountMoney(Integer form, Integer to, int money);
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public boolean accountMoney(Integer form, Integer to, int money) {
return userDao.reduceMoney(form,money) && userDao.addMoney(to,money);
}
}
- 测试类
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("druid-bean.xml");
UserServiceImpl userService = context.getBean("userServiceImpl", UserServiceImpl.class);
userService.accountMoney(1,2,100);
}
}
1.3 事务管理
- 一般在**业务逻辑层(服务层)**中添加事务。
- 分为编程式事务管理(基本不用)和声明式事务管理。
- 声明式事务管理可以通过注解和
xml
配置文件来实现,底层也是使用AOP来实现。 - 通过顶层
PlatformTransactionManager
接口对不同的数据库框架有不同的是实现:
1.4 声明式事务管理——注解
- 配置文件中配置事务管理器:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 指定数据源-->
<property name="dataSource" ref="druid"/>
</bean>
- 配置文件中开启事务管理:
- 修改头文件
<?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/context/spring-tx.xsd">
- 开启事务注解
<tx:annotation-driven transaction-manager="transactionManager"/>
- 给
service
添加事务注解,添加到类上当前类的所有方法都开启事务;如果添加到方法上,只是当前方法开启事务。
@Transactional public class UserServiceImpl implements UserService {
这样添加之后,Spring
会抛出一个新的错误:
Exception in thread "main" org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'userServiceImpl' is expected to be of type 'com.jm.spring.transaction.service.Impl.UserServiceImpl' but was actually of type 'com.sun.proxy.$Proxy26'
即类型强转错误,这里直接不实现接口即可解决问题,并且实现事务的回滚。
public class UserServiceImpl
1.5 事务参数
- propagation,事务传播行为:多事务方法(增、删、改)互相调用,针对事务进行管理的过程。
分为以下七种:
- ioslation,事务隔离级别:事务有隔离性,多事务之间操作不会产生影响。
不存在隔离性造成的三个问题:- 脏读:一个未提交的事务读取到另一个未提交事务数据;
- 不可重复读:一个未提交的事务读取到另一个提交事务修改的数据;
- 幻读:一个未提交事务读取到另一个提交事务添加的数据。
设置隔离级别参数:
- timeout,超时时间:如果在该时间内事务未提交成功,就进行回滚操作。默认**-1**,单位是秒。
- readOnly,是否只读:默认
false
,即可以进行任何操作,如果设置true
,就只能进行查询。 - rollbackFor,回滚:设置出现某个异常进行回滚;
- noRollbackFor,不回滚:设置出现某个异常不进行回滚。