網頁

2020/11/25

Spring Data JPA 未呼叫Repository.save()但資料卻更新

最近碰到Spring Data JPA的地雷,明明沒有呼叫CrudRepository.save()但資料卻更新。

原因是在@Transactional的交易中查詢的實體(entity)是被EntityManager管理的狀態,此時對實體資料的異動在離開交易後會被自動提交到資料庫。

例如資料表EMPLOYEE現有資料如下。

+----+------+
| ID | NAME |
+----+------+
|  1 | JOHN |
+----+------+

下面的EmployeeService.queryAndUpdateName()上掛有@Transactional,且其中並未呼叫EmployeeRepository.save()儲存修改,但此方法結束後employee的異動卻仍被更新到映射的資料表中。

EmployeeService

package com.abc.demo.service;

import com.abc.demo.entity.Employee;
import com.abc.demo.repository.EmployeeRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class EmployeeService {

    @Autowired
    private EmployeeRepository employeeRepository;

    @Transactional
    public void queryAndUpdateName(String name) {
        Employee employee = employeeRepository.findById(1L).orElse(null);
        if (employee != null) {
            employee.setName(name); // update to database table
        }
    }

}

測試。

EmployeeServiceTests

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
public class EmployeeServiceTests {

    @Autowired
    private EmployeeService employeeService;

    @Autowired
    private EmployeeRepository employeeRepository;

    @Test
    public void queryAndUpdateName_successUpdate() {
        String name = "Joe";

        System.out.println(employeeRepository.findById(1L).map(Employee::getName).orElse("")); // John

        employeeService.queryAndUpdateName(name);
        String updatedName = employeeRepository.findById(1L)
                .map(Employee::getName)
                .orElse("");

        System.out.println(updatedName); // Joe

        Assertions.assertEquals(name, updatedName);
    }

}

結論是使用@Transactional要非常清楚其作用範圍與效果。

參考github


沒有留言:

張貼留言