← Назад к вопросам

Какие знаешь состояния Entity?

2.2 Middle🔥 201 комментариев
#ORM и Hibernate

Комментарии (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