[EN] Hibernate 7 Architecture: Repositories, Transactions and the Entity Lifecycle

Julian | Apr 17, 2026 min read

In the first part we clarified why Hibernate will still be the benchmark for Java developers in 2026. But if you only see Hibernate as a pure SQL generator, sooner or later you will stumble upon unexpected behavior. To truly master Hibernate 7, we need to look under the hood and understand how the architecture and state management work.

The Hierarchy of Power: Factory and Session

In the world of Hibernate 7 (based on Jakarta Persistence 3.2) there is a clear distribution of roles. If you’re coming from other frameworks, this is similar to a connection pool that issues instances for short, isolated units of work.

  1. EntityManagerFactory(SessionFactory): This is the “heavy artillery”. It is created once when the application starts, analyzes your entities and knows the entire database structure. It is thread-safe but expensive to build.
  2. EntityManager (Session): Your daily tool. An EntityManager is lightweight, not thread-safe, and represents a single unit of work. This is where the real magic happens.

Pro tip: In modern Hibernate code, primarily use the EntityManager interface. Hibernate’s native Session is still available, but should only be addressed directly for absolutely special cases (such as specific Hibernate filters).

Clean architecture: separation of concerns

A common mistake in projects is distributing the EntityManager across the service layer. As engineers, we rely on clean separation. We encapsulate data access in repositories and control business logic and transaction boundaries in the service.

The model: The entity

We define a simple User class. Important: Hibernate 7 requires a no-arg constructor (protected is sufficient).

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String username;

    private String email;

    private LocalDateTime lastLogin;

    protected User() {} // Von JPA benötigt

    public User(String username, String email) {
        this.username = username;
        this.email = email;
    }

    public void updateLoginTimestamp() {
        this.lastLogin = LocalDateTime.now();
    }

    // Getter & Setter...
}

The Encapsulation: The Repository

The repository is only responsible for physical access.

@Repository
public class UserRepository {

    @PersistenceContext
    private EntityManager em;

    public void save(User user) {
        em.persist(user);
    }

    public User findById(Long id) {
        return em.find(User.class, id);
    }
}

The entity lifecycle and transaction management

The most important mechanism in Hibernate is the Persistence Context. It acts as a first-level cache and “short-term memory”. Within a transaction, Hibernate observes all loaded objects.

The lifecycle in action

Here we see how an object moves through different states:

@Service
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Transactional
    public void processUserLogin(Long userId) {
        // 1. Managed: Das Objekt wird geladen und ist im Persistence Context bekannt.
        User user = userRepository.findById(userId);

        if (user != null) {
            // 2. Action: Wir ändern ein Feld am Objekt.
            user.updateLoginTimestamp();

            // 3. Dirty Checking: Wir rufen KEIN .save() oder .update() auf!
            // Da das Objekt 'Managed' ist, erkennt Hibernate am Ende der
            // Transaktion automatisch, dass eine Änderung vorliegt.
        }
    } // <-- Transaktions-Commit: Hier generiert Hibernate das SQL UPDATE.
}

The four states of an entity:

  1. Transient: A new Java object (new User()) that Hibernate does not yet know about. It has no equivalent in the database.
  2. Managed (Persistent): The object is associated with an active EntityManager. Changes are automatically synchronized via dirty checking.
  3. Detached: The transaction is completed, the EntityManager is closed. The object still exists in memory, but Hibernate no longer observes it. Changes here do not result in an automatic update.
  4. Removed: The object is marked for deletion and will be removed from the database at the next flush.

Why understanding transactions is crucial

The @Transactional annotation defines the framework of our Persistence Context. Within these limits, we benefit from write-behind: Hibernate does not send SQL statements immediately, but collects them in order to send them to the database in batches at the optimal time (the “flush”).

A common problem is the LazyInitializationException. This occurs when one tries to access a lazy loading relationship (e.g. user.getPosts()) while the object is already in the Detached state. Since the connection to the database has been severed, Hibernate can no longer reload the data.

Conclusion: Architecture before syntax

If you understand that Hibernate manages the state of its objects, you will write more efficient code. We use the framework’s automatisms - such as dirty checking and the first-level cache - instead of working against them. A clean separation between repositories and transactional services is the foundation for maintainable enterprise applications.

In Part 3 of our series, we leave theory and jump into practice: We look at the optimal configuration of Hibernate 7 and which properties make the difference between performance and snail’s pace.