Во время работы с JPA и Hibernate нам часто приходится совершать разные манипуляции с JPA entites. Например, чтобы не копировать уже существующий в памяти приложения объект целиком, можно попробовать для уже сохраненной в БД сущности задать id=null
и пересохранить ее. Но что произойдёт, если у сущности вручную обнулить поле id
, а затем сохранить с помощью JPA Repository? Это будет новый объект, с точки зрения JMM, или тот же самый, но с новым id? Давайте разберёмся, как это работает на практике.
Ситуация:
Вы загрузили сущность из базы данных. У неё уже есть сгенерированный id
. Затем вы задаёте id = null
и вызываете метод save()
JPA Repository для сохранения объекта.
Как это работает?
-
Оригинальный объект: Когда вы загружаете объект через JPA Repository (например, с помощью findById
), он находится в состоянии persistent (привязан к текущей Hibernate-сессии). Поле id
содержит уникальный идентификатор записи в базе данных.
-
Обнуление id: После того как вы присваиваете id = null
, объект в памяти остаётся тем же, но Hibernate больше не сможет однозначно связать его с существующей записью в базе.
-
Сохранение объекта: При вызове метода save()
Hibernate проверяет поле id
. Если id == null
, он считает, что это новый объект (новая сущность), и выполняет запрос INSERT
, а не UPDATE
.
-
Генерация нового id: После сохранения Hibernate присваивает объекту новый уникальный id
, сгенерированный на основании вашей стратегии идентификаторов (например, @GeneratedValue
).
Таким образом:
- Java: Объект в памяти остаётся тем же. Его ссылка не изменится. Однако его состояние (например,
id
) обновляется.
- База данных: Hibernate считает объект новой сущностью и создаёт новую запись в таблице. При этом старая запись остаётся неизменной.
Пример:
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// Геттеры и сеттеры
}
// Загружаем объект
User user = userRepository.findById(1L).orElseThrow();
System.out.println(user.getId()); // 1
// Обнуляем id
user.setId(null);
// Сохраняем
userRepository.save(user);
// Результат: новая запись в таблице User с новым id
Возможные проблемы:
-
Дублирование данных: Если не следить за обнулением идентификаторов, можно случайно создать несколько записей для одной и той же сущности.
-
Управление состоянием: Если вы обнуляете id
у объекта, который уже находится в состоянии persistent, это может привести к путанице. Лучше избегать таких операций в управляемых сущностях.
Как избежать ошибок?
-
Не меняйте id у managed-сущностей: Если объект уже находится в базе, его id
должен оставаться неизменным.
-
Используйте DTO: Для создания новых записей лучше использовать отдельные объекты, которые не связаны с JPA Entity. Это снижает риск случайного дублирования.
-
Добавьте проверки: Перед сохранением объекта убедитесь, что изменения соответствуют бизнес-логике.
Итог:
Если вы обнуляете id
у объекта-сущности и сохраняете его, Hibernate создаёт новую запись в базе данных. Это может быть полезно в некоторых сценариях, но требует внимательного подхода, чтобы избежать непредвиденного дублирования данных.