Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Состояния Entity в Hibernate/JPA
Универсальность понимания состояний Entity - ключевая компетенция JPA разработчика. Есть 4 основных состояния жизненного цикла.
1. Transient (Новое)
Объект существует только в памяти, НЕ связан с БД и НЕ управляется Hibernate:
User user = new User();
user.setId(null);
user.setName("John");
// Состояние: TRANSIENT
// - Нет в session
// - Нет ID
// - Нет записи в БД
Характеристики:
- Объект был создан но не сохранен
- Нет связи с EntityManager/Session
- При удалении сборщиком мусора - никакие операции не выполнятся
- Можно превратить в PERSISTENT через save()
2. Persistent (Управляемое)
Объект управляется EntityManager и связан с записью в БД:
@Transactional
public void createUser() {
User user = new User();
user.setName("John");
entityManager.persist(user); // Переходит в PERSISTENT
// Состояние: PERSISTENT
// - В session
// - Получил ID
// - Отслеживаются изменения (dirty check)
}
Характеристики:
- Управляется текущей session/EntityManager
- Имеет ID
- Hiberate отслеживает изменения
- При commit() выполняется UPDATE
- При удалении из session - переходит в DETACHED
Пример:
@Transactional
public void updateUser(Long id) {
User user = entityManager.find(User.class, id); // PERSISTENT
user.setName("Jane"); // Изменение отслеживается
// persist() не нужен - выполнится UPDATE автоматически
}
3. Detached (Отсоединённое)
Объект был управляемым, но теперь отсоединён от session:
User user;
@Transactional
public void getUserForDisplay(Long id) {
user = entityManager.find(User.class, id); // PERSISTENT
} // При выходе из @Transactional session закрывается
// Теперь user - DETACHED
user.setName("Bob");
// Изменение НЕ отслеживается!
System.out.println(user.getName()); // "Bob"
Характеристики:
- Имеет ID (был управляемым раньше)
- Не в session
- Изменения не отслеживаются
- При попытке доступа к lazy-loaded полям - LazyInitializationException
- Можно переподсоединить через merge() или update()
Переподсоединение (reattachment):
@Transactional
public void updateDetachedUser(User detachedUser) {
User mergedUser = entityManager.merge(detachedUser);
// Теперь mergedUser - PERSISTENT
mergedUser.setName("Updated");
// При commit() выполнится UPDATE
}
Важно: merge() возвращает ссылку на управляемый объект, а не модифицирует исходный:
User detached = new User();
detached.setId(1L);
detached.setName("Bob");
User managed = entityManager.merge(detached);
// detached всё ещё DETACHED
// managed - PERSISTENT
4. Removed (Удалённое)
Объект помечен на удаление из БД:
@Transactional
public void deleteUser(Long id) {
User user = entityManager.find(User.class, id); // PERSISTENT
entityManager.remove(user); // Переходит в REMOVED
// Состояние: REMOVED
// - В session но помечен на удаление
// - При commit() выполнится DELETE
}
Характеристики:
- Управляется session
- При commit() выполнится DELETE
- После commit() переходит в DETACHED
- Нельзя переподсоединить
Диаграмма переходов состояний
NEW (TRANSIENT)
|
v
persist()
|
v
PERSISTENT <------- merge() ------- DETACHED
| ^
| |
remove() close(session)
| detach() flush()
v |
REMOVED -----> DELETE ------> DETACHED
Практические примеры
Пример 1: Типичный CRUD
@Service
public class UserService {
@Transactional
public User create(CreateUserRequest req) {
User user = new User(); // TRANSIENT
user.setName(req.getName());
entityManager.persist(user); // PERSISTENT
return user;
}
@Transactional(readOnly = true)
public User getById(Long id) {
return entityManager.find(User.class, id); // PERSISTENT в этой транзакции
}
@Transactional
public User update(Long id, UpdateUserRequest req) {
User user = entityManager.find(User.class, id); // PERSISTENT
user.setName(req.getName());
return user; // Выйдет как DETACHED (но изменится в БД)
}
@Transactional
public void delete(Long id) {
User user = entityManager.find(User.class, id); // PERSISTENT
entityManager.remove(user); // REMOVED
}
}
Пример 2: Detached объект из REST контроллера
@RestController
public class UserController {
@PutMapping("/api/users/{id}")
@Transactional
public UserDTO update(@PathVariable Long id, @RequestBody UpdateRequest req) {
// req.user пришёл от клиента - TRANSIENT
User user = entityManager.find(User.class, id); // PERSISTENT
user.setName(req.getName());
user.setEmail(req.getEmail());
// При выходе выполнится UPDATE
return new UserDTO(user);
}
}
// Обновление через merge() (когда приходит DETACHED объект)
@PutMapping("/api/users/{id}")
@Transactional
public UserDTO updateFromDetached(@RequestBody User detachedUser) {
User managed = entityManager.merge(detachedUser); // PERSISTENT
managed.setStatus("UPDATED");
// При выходе выполнится UPDATE
return new UserDTO(managed);
}
Пример 3: Опасность Lazy Loading
public User getUserWithOrders(Long id) {
User user;
@Transactional
public void loadUser() {
user = entityManager.find(User.class, id); // PERSISTENT
}
loadUser(); // Session закрылась
// user теперь DETACHED
user.getOrders().size(); // LazyInitializationException!
}
// Решение:
public User getUserWithOrders(Long id) {
return getUserWithOrdersImpl(id); // Завёртываем в @Transactional
}
@Transactional
public User getUserWithOrdersImpl(Long id) {
User user = entityManager.find(User.class, id);
user.getOrders().size(); // OK - в session
return user; // Вернётся DETACHED
}
Проверка состояния
public void checkState() {
PersistenceUnitUtil util = entityManager.getEntityManagerFactory()
.getPersistenceUnitUtil();
User user = ...;
boolean isManaged = util.isLoaded(user);
if (isManaged) {
System.out.println("PERSISTENT");
} else if (user.getId() != null) {
System.out.println("DETACHED");
} else {
System.out.println("TRANSIENT");
}
}
Ключевые выводы
- TRANSIENT: новый объект, не в session
- PERSISTENT: управляется session, изменения отслеживаются
- DETACHED: был управляемым, теперь отсоединён
- REMOVED: помечен на удаление
- Transitions: persist() -> PERSISTENT, close() -> DETACHED, remove() -> REMOVED
- Понимание состояний - основа для правильного использования Hibernate