Spring笔记05-Spring事务

5.1 声明事务管理

ACID 属性:

Atomic原子:All or Nothing

Consistent一致性: DB integrity constraints never violated

Isolated: How transactions see work done by others

Durable: Committed changes are permanent

我们一般是在Service层声明事务。

5.1.1 @Transactional注解

@Transactional注解允许我们管理一个事务管理器。Spring不直接提供事务管理器,它提供一个hook连接到已经存在的事务管理器,比如我们使用关系数据库,那Spring就是使用关系数据库的事务管理器。Spring的Bean和Entity允许我们请求事务管理器,设置属性(比如超时、只读等)。

应用Spring的事务需要两步:

  1. 应用Transactional注解
    a. XML格式
    b. 写代码的方式
  2. 声明Platform Transaction Manager bean
  3. @EnableTransactionManagement

@Transactional注解可以用于方法或者类。如果应用于一个类,那么这个类中的所有方法都应用了了这个注解,对于应用了类的注解,我们对单独的类重新使用@Transactional方法来覆盖。

@Transactional方法提供了一些参数让我们来指定一些配置。

5.1.2 PlatformTransactionManager接口

PlatformTransactionManager用来代表依赖平台的事务管理器。它的实现类有Hibernate, JPA等事务管理器。

5.1.3 案例:账户应用

首先,我们定义entity:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package lx.spring.core.entities;

import java.math.BigDecimal;

public class Account {
private Long id;
private BigDecimal balance;

public Account() {
}

public Account(Long id, BigDecimal balance) {
this.id = id;
this.balance = balance;
}
// getter setter toString equals hashCode等
}

这里我们使用BigDecimal类型来表示余额。

然后我们创建一个Repository,用来表示数据库的操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
package lx.spring.core.repositories;
import lx.spring.core.entities.Account;
import java.math.BigDecimal;
import java.util.List;

public interface AccountRepository {
List<Account> getAccounts();
Account getAccount(Long id);
int getNumberOfCounts();
Long createAccount(BigDecimal initialBalance);
int deleteAccount(Long id);
void updateAccount(Account account);
}

后面我们将看到如何用Spring Data来处理数据。

第一步,我们用@Transactional 实体。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Service @Transactional
@Profile("test")
public class AccountService {
@Autowired
private AccountRepository repository;

public BigDecimal getBalance(Long id) {
return repository.getAccount(id).getBalance();
}

public BigDecimal deposit(Long id, BigDecimal amount) {
Account account = repository.getAccount(id);
BigDecimal newBalance = account.getBalance().add(amount);
account.setBalance(newBalance);
repository.updateAccount(account);
return newBalance;
}

public BigDecimal withdraw(Long id, BigDecimal amount) {
return deposit(id, amount.negate());
}
}

第二步是声明Bean来适应事务,我们打开AppConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

@Configuration
@EnableTransactionManagement
public class AppConfig {

@Bean(name = "transactionManager")
@Profile("test")
public PlatformTransactionManager transactionManagerForTest() {
return new DataSourceTransactionManager(dataSourceForTest());
}

@Bean(name = "dataSource")
@Profile("test")
public DataSource dataSourceForTest() {
DataSource dataSource = new BasicDataSource();
return dataSource;
}
}

注意这里的transactionManagerForTest()是返回事务管理器的,同时,我们用EnableTransactionManagement来指明我们的容器用了事务管理。

5.2 Isolation levels 隔离层级

@Transactional有一个属性是Isolation的,它是一个枚举,它有一下几个值:

  • DEFAULT 使用底层数据存储所用的隔离层级
  • READ_COMMITTED dirty reads are prevented; non-repeatable reads and phantom reads can occur.只有在其他事务提交时在一个事务里才能看到改变后的数据。如果事务需要很久,那就一直锁定直到事务完成。
  • READ_UNCOMMITTED dirty reads, non-repeatable reads and phantom reads can occur. 两个事务同时对于一行操作。如果一个事务读取了行,然后又修改了数据,那另一个事务可以看到其他事务修改后的数据。这个好处是速度非常快,你没有锁定行,问题是它有一定风险,比如你根据没有提交的数据做了一些动作,但是这些改变又回滚了。所以,使用时需要分析一下。READ_UNCOMMITTED是最高效但不是最优的选择。
  • REPEATABLE_READ dirty reads and non-repeatable reads are prevented; phantom reads can occur.如果数据修改后发生了回滚,则回滚前后我们看到的数据都是一样的
  • SERIALIZABLE dirty reads, non-repeatable reads and phantom reads are prevented 当我们查询或修改时,别的事务不能增加或修改表(整表锁定)

一般情况下我们不会修改这个属性。

5.3 Propagation 传播

Propagation说的是当事务执行时,我们的方法是否也是执行的。我们再回到@Transactional注解上来,它有一个属性是Propagation枚举。它有以下值:

  • MANDATORY: 1. tx->join tx; 2. no->throw a TransactionRequiredException
  • NESTED:
  • NEVER: 1. tx-> throw an exception; 2. no->nothing
  • NOT_SUPPORTED: 1. tx->suspend tx; run outside tx; resume tx; 2. no->nothing
  • REQUIRED:最常用,如果一个事务发生,那么我们加入,这是默认的;如果没有,我们创建一个事务。
  • REQUIRES_NEW:1. tx1-> suspend tx1; create and run tx2; resume tx1; 2. no->create and run tx
  • SUPPORTS: 1. tx->join tx; 2. no->nothing

除了NESTED,其它的都是来自于J2EE。

5.4 高效事务管理

Spring提供了两种高效高效管理事务的方法:

  • 使用TransactionTemplate
  • 使用PlatfromTransactionManager实现

5.4.1 TransactionTemplate

TransactionTemplate非常有用。我们在Service的构造方法里有提供PlatformTransactionManager参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.Assert;

public class SimpleService {
private final TransactionTemplate transactionTemplate;

public SimpleService(PlatformTransactionManager transactionManager) {
Assert.notNull(transactionManager, "The 'transactionManager' argument must not be null");
this.transactionTemplate = new TransactionTemplate(transactionManager);
}

public Object someServiceMethod() {
return transactionTemplate.execute((TransactionStatus status) -> {
System.out.println("hello");
return "aaa";
});
}
public void someServiceMethodWithouResult() {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
System.out.println(status);
}
});
}
}

参数TransactionStatussetRollbackOnly()方法,用于回滚事务。

我们可以在构造方法中设置事务:

1
2
3
4
5
6
7
public SimpleService(PlatformTransactionManager transactionManager) {
Assert.notNull(transactionManager, "The 'transactionManager' argument must not be null");
this.transactionTemplate = new TransactionTemplate(transactionManager);
//这里我们可以设置会话
this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
this.transactionTemplate.setTimeout(30); //30秒
}

5.4.2 PlatformTransactionManager

我们也可以使用org.springframework.transaction.PlatformTransactionManager直接管理会话。将PlatformTransactionManager直接传给bean,然后可以使用TransactionDefinistionTransactionStatus来初始化会话、回滚、提交等。

TransactionTemplatePlatformTransactionManager之间有限推荐第一个。