社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
事务是区分文件存储系统与Nosql数据库重要特性之一,其存在的意义是为了保证即使在并发情况下也能正确的执行crud操作。怎样才算是正确的呢?这时提出了事务需要保证的四个特性即ACID:
在高并发的情况下,要完全保证其ACID特性是非常困难的,除非把所有的事务串行化执行,但带来的负面的影响将是性能大打折扣。很多时候我们有些业务对事务的要求是不一样的,所以数据库中设计了四种隔离级别,供用户基于业务进行选择。
隔离级别 | 脏读(Dirty Read) | 不可重复读(NonRepeatable Read) | 幻读(Phantom Read) |
---|---|---|---|
未提交读(Read uncommitted) | 可能 | 可能 | 可能 |
已提交读(Read committed) | 不可能 | 可能 | 可能 |
可重复读(Repeatable read) | 不可能 | 不可能 | 可能 |
可串行化(SERIALIZABLE) | 不可能 | 不可能 | 不可能 |
CREATE TABLE `account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`accountName` varchar(255) COLLATE utf8_bin NOT NULL,
`user` varchar(255) COLLATE utf8_bin NOT NULL,
`money` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=122 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8_bin NOT NULL,
`createTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=33 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
public static Connection openConnection() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection(DbConstant.DB_URL, DbConstant.USERNAME, DbConstant.PASSWORD);
conn.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
return conn;
}
SELECT @@tx_isolation
spring 事务是在数据库事务的基础上进行封装扩展 其主要特性如下
spring 提供了三个接口供使用事物:
package com.myx.db;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* 描述:编程示事物示例
* @author: myx
* @date: 2019/1/11
* Copyright © 2018-hotpot. All rights reserved.
*/
public class SpringTransactionExample {
public static void main(String[] args) {
final DataSource ds = new DriverManagerDataSource(DbConstant.DB_URL, DbConstant.USERNAME, DbConstant.PASSWORD);
final TransactionTemplate template = new TransactionTemplate();
template.setTransactionManager(new DataSourceTransactionManager(ds));
template.execute(new TransactionCallback<Object>() {
@Override
public Object doInTransaction(TransactionStatus status) {
Connection conn = DataSourceUtils.getConnection(ds);
Object savePoint = null;
try {
{
// 插入
PreparedStatement prepare = conn.
prepareStatement("insert INTO account (accountName,user,money) VALUES (?,?,?)");
prepare.setString(1, "111");
prepare.setString(2, "aaaa");
prepare.setInt(3, 10000);
prepare.executeUpdate();
}
// 设置保存点
savePoint = status.createSavepoint();
{
// 插入
PreparedStatement prepare = conn.
prepareStatement("insert INTO account (accountName,user,money) VALUES (?,?,?)");
prepare.setString(1, "222");
prepare.setString(2, "bbb");
prepare.setInt(3, 10000);
prepare.executeUpdate();
}
{
// 更新
PreparedStatement prepare = conn.
prepareStatement("UPDATE account SET money= money+1 where user=?");
prepare.setString(1, "asdflkjaf");
//TODO:异常
int i=1/0;
}
} catch (SQLException e) {
e.printStackTrace();
} catch (Exception e) {
System.out.println("更新失败");
if (savePoint != null) {
status.rollbackToSavepoint(savePoint);
} else {
status.setRollbackOnly();
}
}
return null;
}
});
}
}
我们前面是通过调用API来实现对事物的控制,这非常的繁琐,与直接操作JDBC事物并没有太多的改善,所以Spring提出了声明示事物,使我们对事物的操作变得非常简单,甚至不需要关心它。
<properties>
<spring.version>5.0.2.RELEASE</spring.version>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
</properties>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.35</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
spring-tx.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:tx="http://www.springframework.org/schema/tx"
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/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.myx.spring.service.**"/>
<bean class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- similarly, don't forget the PlatformTransactionManager -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- don't forget the DataSource -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<constructor-arg name="url" value="jdbc:mysql://140.143.193.83:3306/spring-demo"/>
<constructor-arg name="username" value="iot"/>
<constructor-arg name="password" value="123456"/>
</bean>
<tx:annotation-driven transaction-manager="txManager"></tx:annotation-driven>
</beans>
import com.myx.spring.pojo.Account;
import com.myx.spring.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
/**
* 描述:
* @author: myx
* @date: 2019/1/11
* Copyright © 2018-hotpot. All rights reserved.
*/
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
JdbcTemplate jdbcTemplate;
@Override
@Transactional
public void addAccount(String name, int initMoney) {
String accountid = new SimpleDateFormat("yyyyMMddhhmmss").format(new Date());
jdbcTemplate.update("insert INTO account (accountName,user,money) VALUES (?,?,?)", accountid, name, initMoney);
// 人为报错
int i = 1 / 0;
}
}
import com.myx.spring.service.AccountService;
import org.junit.jupiter.api.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* 描述:声明示事务测试
* @author: myx
* @date: 2019/1/11
* Copyright © 2018-hotpot. All rights reserved.
*/
public class AccountServiceImplTest {
@Test
public void addAccount() {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("spring-tx.xml");
AccountService service = context.getBean(AccountService.class);
service.addAccount("myx", 10000);
}
}
@Transactional 存在时数据库不会新增数据。
类别 | 事物传播类型 | 说明 |
---|---|---|
支持当前事物 | PROPAGATION_REQUIRED(必须的) | 如果当前没有事物,就新建一个事物,如果已经存在一个事物中,加入到这个事物中。这是最常见的选择。 |
PROPAGATION_SUPPORTS(支持) | 支持当前事物,如果当前没有事物,就以非事物方式执行。 | |
PROPAGATION_MANDATORY(强制) | 使用当前的事物,如果当前没有事物,就抛出异常。 | |
不支持当前事物 | PROPAGATION_REQUIRES_NEW(隔离) | 新建事物,如果当前存在事物,把当前事物挂起。 |
PROPAGATION_NOT_SUPPORTED(不支持) | 以非事物方式执行操作,如果当前存在事物,就把当前事物挂起。 | |
PROPAGATION_NEVER(强制非事物) | 以非事物方式执行,如果当前存在事物,则抛出异常。 | |
套事物 | PROPAGATION_NESTED(嵌套事物) | 如果当前存在事物,则在嵌套事物内执行。如果当前没有事物,则执行与PROPAGATION_REQUIRED类似的操作。 |
创建用户时初始化一个帐户,表结构和服务类如下:
表结构 | 服务类 | 功能描述 |
---|---|---|
user | UserSerivce | 创建用户,并添加帐户 |
account | AccountService | 添加帐户 |
UserSerivce.createUser(name) 实现代码
@Transactional
public void createUser(String name) {
// 新增用户基本信息
jdbcTemplate.update("INSERT INTO `user` (name) VALUES(?)", name);
//调用accountService添加帐户
accountService.addAccount(name, 10000);
}
AccountService.addAccount(name,initMoney) 实现代码(方法的最后有一个异常)
@Transactional(propagation = Propagation.REQUIRED)
public void addAccount(String name, int initMoney) {
String accountid = new SimpleDateFormat("yyyyMMddhhmmss").format(new Date());
jdbcTemplate.update("insert INTO account (accountName,user,money) VALUES (?,?,?)", accountid, name, initMenoy);
// 出现分母为零的异常
int i = 1 / 0;
}
实验预测一
createUser | addAccount(异常) | 预测结果 | |
---|---|---|---|
场景一 | 无事物 | required | user (成功) Account(不成功) |
场景二 | required | 无事物 | user (不成功) Account(不成功) |
场景三 | required | not_supported | user (不成功) Account(成功) |
场景四 | required | required_new | user (不成功) Account(不成功) |
场景五 | required(异常移至createUser方法未尾) | required_new | user (不成功) Account(成功) |
场景六 | required(异常移至createUser方法未尾)(addAccount 方法称至当前类) | required_new | user (不成功) Account(不成功) |
在同一个类中出现两个带事务的方法调用,事务是失效的,原因在于spring 声明示事物使用动态代理实现,而当调用同一个类的方法时,是会不会走代理逻辑的,自然事物的配置也会失效。
如何解决:
如果业务当中上真有这种场景该如何实现呢?在spring xml中配置 暴露proxy 对象,然后在代码中用AopContext.currentProxy() 就可以获当前代理对象
<!-- 配置暴露proxy -->
<aop:aspectj-autoproxy expose-proxy="true"/>
// 基于代理对象调用创建帐户,事物的配置又生效了
((UserSerivce) AopContext.currentProxy()).addAccount(name, 10000);
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!