網頁

2020/11/25

Spring Data JPA org.springframework.orm.jpa.JpaSystemException: identifier of an instance of Entity was altered from x to y錯誤

Spring Data JPA的org.springframework.orm.jpa.JpaSystemException: identifier of an instance of Entity was altered from x to y
Hibernate的org.hibernate.HibernateException: identifier of an instance of Entity was altered from x to y錯誤原因如下。

當修改實體類的@Id欄位並呼叫Repository儲存時就會出現此錯誤。

例如下面是實體類Employee

Employee

@Entity
public class Employee implements Serializable {
    private static final Long serialVersionUID = 1L;

    @Id
    private long id;

    private String name;

    // getters and setters

}

執行下面程式查詢出資料並修改Employee.id的值並呼叫EmployeeRepository.save()儲存就會發生org.springframework.orm.jpa.JpaSystemException: identifier of an instance of com.abc.demo.entity.Employee was altered from x to y錯誤。

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 Employee alterId() {
        Employee employee = employeeRepository.findById(1L).orElse(null);
        if (employee != null) {
            employee.setId(2L);
            return employeeRepository.save(employee); // 拋錯
        }
        return null;
    }
    
    ...

}

Spring Data JPA / Hibernate不應該去修改實體類的@Id欄位即主鍵(primary key)的值。如果真的有需要用JPA修改則可將原本的資料先刪除後再新增。或是使用原生sql修改。

EmployeeService

package com.abc.demo.service;

import com.abc.demo.entity.Employee;
import com.abc.demo.repository.EmployeeRepository;
import org.springframework.beans.BeanUtils;
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 Employee alterIdByCreateNew() {
        Employee employee = employeeRepository.findById(1L).orElse(null);
        if (employee != null) {
            Employee employeeNew = new Employee();
            BeanUtils.copyProperties(employee, employeeNew);
            employeeNew.setId(2L);
            
            employeeRepository.delete(employee);
            return employeeRepository.save(employeeNew);
        }
        return null;
    }

}


測試。

EmployeeServiceTests

package com.abc.demo.service;

import com.abc.demo.entity.Employee;
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;
import org.springframework.orm.jpa.JpaSystemException;

@SpringBootTest
public class EmployeeServiceTests {

    @Autowired
    private EmployeeService employeeService;

    @Test
    public void alterId_throwJpaSystemException() {
        Assertions.assertThrows(JpaSystemException.class, () -> employeeService.alterId());
    }

    @Test
    public void alterIdByCreateNew_correct() {
        Employee employee = employeeService.alterIdByCreateNew();
        Assertions.assertEquals(2L, employee.getId());
    }

}

參考github



沒有留言:

張貼留言