Spring @Transactional
屬性propagation
設為Propagation.REQUIRES_NEW
的效果如下。
範例環境:
- Spring Boot 2.3.2.RELEASE
- Spring Data JPA
- H2 Database
- Lombok
Spring @Transactional
的propagation
如未指定則預設為Propagation.REQUIRED
,作用為如果目前已經處在一個交易,則使用原本的交易,沒有的話則建立新的交易。
若把propagation
設為Propagation.REQUIRES_NEW
,則效果為如果已經有交易存在則先暫停並另開新的交易處理,不使用原本的交易。
例如下面Employee
是交易存取的entity。
Employee
package com.abc.demo.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.io.Serializable;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
public class Employee implements Serializable {
private static final Long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
EmployeeRepository
為Spring Data JPA的repository,用來存取Employee
。
EmployeeRepository
package com.abc.demo.repository;
import com.abc.demo.entity.Employee;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}
DemoDao
呼叫EmployeeRepository
新增及修改。
注意update2()
的@Transactional
屬性propagation
為Propagation.REQUIRES_NEW
,且故意拋出RuntimeException
模擬例外發生的情況。
DemoDao
package com.abc.demo.dao;
import com.abc.demo.entity.Employee;
import com.abc.demo.repository.EmployeeRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Repository
public class DemoDao {
@Autowired
private EmployeeRepository employeeRepository;
/** 新增 */
@Transactional
public void insert(String name) {
Employee employee = Employee.builder()
.name(name)
.build();
employeeRepository.save(employee);
}
/** 修改1 */
@Transactional
public void update1(long id, String name) {
employeeRepository.findById(id).ifPresent(e -> {
e.setName(name);
employeeRepository.save(e);
});
}
/** 修改2 */
@Transactional(propagation = Propagation.REQUIRES_NEW) // 另外產生新的交易,所以丟出例外時僅此方法的異動回滾
public void update2(long id, String name) {
try {
employeeRepository.findById(id).ifPresent(e -> {
e.setName(name);
employeeRepository.save(e);
});
throw new RuntimeException("Database error."); // 模擬交易中發生錯誤
} catch (Exception e) {
e.printStackTrace();
}
}
}
DemoService
的modify()
中呼叫了DemoDao
的三個方法,順序為:
- 新增一個
Employee
名為Ivy
為第一筆資料,id
為1
。 - 修改
Employee
id
為1的名稱為John
。 - 修改
Employee
id
為1的名稱為Kenny
。
DemoService
package com.abc.demo.service;
import com.abc.demo.dao.DemoDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class DemoService {
@Autowired
private DemoDao demoDao;
@Transactional
public void modify() {
demoDao.insert("Ivy"); // { "id"=1, "name"="Ivy" }
demoDao.update1(1L, "John"); // Ivy -> John
demoDao.update2(1L, "Kenny"); // John -> Kenny
}
}
則執行DemoService.modify()
時,在update2()
發生錯誤造成回滾,由於update2()
設定了Propagation.REQUIRES_NEW
,也就是新建一個交易,所以回滾範圍限於update2()
內,前面已執行insert()
及update1()
的異動沒有回滾,最終Employee
的名稱為John
。
DemoServiceTests
測試。
DemoServiceTests
package com.abc.demo.service;
import com.abc.demo.entity.Employee;
import com.abc.demo.repository.EmployeeRepository;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class DemoServiceTests {
@Autowired
private DemoService demoService;
@Autowired
private EmployeeRepository employeeRepository;
@Test
void modify_nameIsJohn() {
demoService.modify();
String name = employeeRepository.findById(1L)
.map(Employee::getName).orElse("");
Assertions.assertEquals("John", name); // pass
}
}
參考github。
@Service
回覆刪除public class DemoService {
@Autowired
private DemoDao demoDao;
@Transactional
public void modify() {
demoDao.insert("Ivy"); // { "id"=1, "name"="Ivy" }
demoDao.update1(1L, "John"); // Ivy -> John
demoDao.update2(1L, "Kenny"); // John -> Kenny
}
}
你這樣寫會有問題
方便解釋我把@Transactional分成以下四個
主 @Transactional
insert @Transactional
update1 @Transactional
update2 @Transactional
因為 insert、update1 @Transactional沒有寫propagation ,所以默認使用REQUIRED,全部加入到主 @Transactional 裡
1.第一個有問題地方是update2 裡面加了try catch,這會導致異常被捕獲,@Transactional失效,回滾失敗
2.第二個問題是,應該要在 Service 那邊加上try catch,不然update2 的異常會出去影響到主 @Transactional,導致加入主 @Transactional的insert、update1 @Transactional也一起觸發回滾,
簡單講就是,會全部回滾