JPA With Hibernate - Transactional data processing

66 阅读6分钟

Ref

Managing data

The persistence life cylcle

Because JPA is a transparent persistence mechanism—classes are unaware of their own persistence capability—it’s possible to write application logic that’s unaware whether the data it operates on represents persistent state or temporary state that exists only in memory.

image.png

  • Transient state:
    • Instances created with the new Java operator are transient, which means their state is lost and garbage-collected as soon as they’re no longer referenced.
  • Persistent state:
    • persistent entity instance has a representation in the database. It’s stored in the database—or it will be stored when the unit of work completes.
  • Removed state
    • You can delete a persistent entity instance from the database in several ways: For example, you can remove it with EntityManager#remove().
    • It may also become available for deletion if you remove a reference to it from a mapped collection with orphan removal enabled
  • Detached state
    • To understand detached entity instances, consider loading an instance. You call EntityManager#find() to retrieve an entity instance by its (known) identifier. Then you end your unit of work and close the persistence context. The application still has a handle—a reference to the instance you loaded. It’s now in the detached state, and the data is becoming stale. You could discard the reference and let the garbage collector reclaim the memory. Or, you could continue working with the data in the detached state and later call the merge() method to save your modifications in a new unit of work.

The persistence context

In a Java Persistence application, an EntityManager has a persistence context. You create a persistence context when you call EntityManagerFactory#createEntityManager(). The context is closed when you call EntityManager#close().

In JPA terminology, this is an application-managed persistence context; your application defines the scope of the persistence context, demarcating the unit of work.

The persistence context allows the persistence engine to perform automatic dirty checking, detecting which entity instances the application modified. The provider then synchronizes with the database the state of instances monitored by a persistence context, either automatically or on demand.

The persistence context acts as a first-level cache; it remembers all entity instances you’ve handled in a particular unit of work.

The persistence context cache is always on—it can’t be turned off. It ensures the following:

  • The persistence layer isn’t vulnerable to stack overflows in the case of circular references in an object graph.
  • There can never be conflicting representations of the same database row at the end of a unit of work. The provider can safely write all changes made to an entity instance to the database.
  • Likewise, changes made in a particular persistence context are always immediately visible to all other code executed inside that unit of work and its persistence context. JPA guarantees repeatable entity-instance reads.

The EntityManager interface

image.png

  • Creating an EntityManager starts its persistence context.
  • Hibernate won’t access the database until necessary;
  • the EntityManager doesn’t obtain a JDBC Connection from the pool until SQL statements have to be executed.
  • You can create and close an EntityManager without hitting the database.
  • Hibernate executes SQL statements when you look up or query data and when it flushes changes detected by the persistence context to the database.
  • Hibernate joins the in-progress system transaction when an EntityManager is created and waits for the transaction to commit. When Hibernate is notified (by the JTA engine) of the commit, it performs dirty checking of the persistence context and synchronizes with the database. You can also force dirty checking synchronization manually by calling EntityManager#flush() at any time during a transaction.

Making data persitent

image.png

image.png

Detecting entity state using the identifier

  • An entity instance is in persistent state if EntityManager#contains(e)returns true.
  • It’s in transient state if PersistenceUnitUtil#getIdentifier(e)returns null.
  • It’s in detached state if it’s not persistent, and Persistence-UnitUtil#getIdentifier(e) returns the value of the entity’s identifier property. You can get to the PersistenceUnitUtil from the EntityManagerFactory.

Retrieving and modifying persitent data

image.png

image.png

You can modify the Item instance, and the persistence context will detect these changes and record them in the database automatically.

image.png

The first find() operation hits the database and retrieves the Item instance with a SELECT statement. The second find() is resolved in the persistence context, and the same cached Item instance is returned.

Getting a reference

If you don’t want to hit the database when loading an entity instance, because you aren’t sure you need a fully initialized instance, you can tell the EntityManager to attempt the retrieval of a hollow placeholder—a proxy:

image.png

Making data transient

To make an entity instance transient and delete its database representation, call the remove() method on the EntityManager:

image.png

image.png

Refreshing data

Item item = em.find(Item.class, ITEM_ID);
item.setName("Some Name");

// Someone updates this row in the database

String oldName = item.getName();
em.refresh(item);
assertNotEquals(item.getName(), oldName);

Most applications don’t have to manually refresh in-memory state; concurrent modifications are typically resolved at transaction commit time. The best use case for refreshing is with an extended persistence context, which might span several request/response cycles and/or system transactions.

Replicating data

Replication takes detached instances loaded in one persis- tence context and makes them persistent in another persistence context.

The replicate() operation is only available on the Hibernate Session API. Here is an example that loads an Item instance from one database and copies it into another:

tx.begin();

EntityManager emA = getDatabaseA().createEntityManager();
Item item = emA.find(Item.class, ITEM_ID);

EntityManager emB = getDatabaseB().createEntityManager();
emB.unwrap(Session.class)
    .replicate(item, org.hibernate.ReplicationMode.LATEST_VERSION);

tx.commit();
emA.close();
emB.close();

ReplicationMode controls the details of the replication procedure:

  • IGNORE—Ignores the instance when there is an existing database row with the same identifier in the database.
  • OVERWRITE—Overwrites any existing database row with the same identifier in the database.
  • EXCEPTION—Throws an exception if there is an existing database row with the same identifier in the target database.
  • LATEST_VERSION—Overwrites the row in the database if its version is older than the version of the given entity instance, or ignores the instance otherwise.

Caching in the persistence context

  • The persistence context is a cache of persistent instances. Every entity instance in per- sistent state is associated with the persistence context.
  • Hibernate still has to create a snapshot of each instance in the persistence context cache, which can lead to memory exhaustion.
  • The persistence context cache never shrinks automatically. Keep the size of your persistence context to the necessary minimum.
  • Check that your queries return only data you need, and consider the following ways to control Hibernate’s caching behavior.
  • You can call EntityManager#detach(i) to evict a persistent instance manually from the persistence context.
  • You can call EntityManager#clear() to detach all persistent entity instances, leaving you with an empty persistence context.

This disables state snapshots and dirty checking, and Hibernate won’t write modifications to the database: image.png

You can disable dirty checking for a single entity instance:

image.png

A query with the org.hibernate.Query interface can return read-only results, which Hibernate doesn’t check for modifications:

image.png

Thanks to query hints, you can also disable dirty checking for instances obtained with the JPA standard javax.persistence.Query interface:

Query query = em.createQuery(queryString)
    .setHint(
        org.hibernate.annotations.QueryHints.READ_ONLY,
        true
    );

Be careful with read-only entity instances: you can still delete them, and modifications to collections are tricky!

Flushing the persistence context

Hibernate, as a JPA implementation, synchronizes at the following times:

  • When a joined JTA system transaction is committed
  • Before a query is executed—we don’t mean lookup with find() but a query with javax.persistence.Query or the similar Hibernate API
  • When the application calls flush() explicitly

You can control this behavior with the FlushModeType setting of an EntityManager:

image.png

With FlushModeType.COMMIT, you’re disabling flushing before queries, so you may see different data returned by the query than what you have in memory. The synchronization then occurs only when the transaction commits.

Working with detached state

The identity of detached instances

image.png

image.png

Whenever you work with instances in detached state and you test them for equality (usually in hash-based collections), you need to supply your own implementation of the equals() and hashCode() methods for your mapped entity class.

Implementing equality methods

image.png

tx.begin();
em = JPA.createEntityManager();

User a = em.find(User.class, USER_ID);
User b = em.find(User.class, USER_ID);
assertTrue(a == b);
assertTrue(a.equals(b));
assertEquals(a.getId(), b.getId());

tx.commit();
em.close();

image.png

Here are some hints that should help you identify a business key in your domain model classes:

  • Consider what attributes users of your application will refer to when they have to identify an object (in the real world). How do users tell the difference between one element and another if they’re displayed on the screen? This is probably the business key you’re looking for.
  • Every immutable attribute is probably a good candidate for the business key. Mutable attributes may be good candidates, too, if they’re updated rarely or if you can control the case when they’re updated—for example, by ensuring the instances aren’t in a Set at the time.
  • Every attribute that has a UNIQUE database constraint is a good candidate for the business key. Remember that the precision of the business key has to be good enough to avoid overlaps.
  • Any date or time-based attribute, such as the creation timestamp of the record, is usually a good component of a business key, but the accuracy of System.currentTimeMillis() depends on the virtual machine and operating system. Our recommended safety buffer is 50 milliseconds, which may not be accurate enough if the time-based property is the single attribute of a business key.
  • You can use database identifiers as part of the business key. This seems to contradict our previous statements, but we aren’t talking about the database identifier value of the given entity. You may be able to use the database identifier of an associated entity instance. For example, a candidate business key for the Bid class is the identifier of the Item it matches together with the bid amount. You may even have a unique constraint that represents this composite business key in the database schema. You can use the identifier value of the associated Item because it never changes during the life cycle of a Bid—the Bid constructor can require an already-persistent Item.

Detaching entity instances

User user = em.find(User.class, USER_ID);

em.detach(user);

assertFalse(em.contains(user));

Merging entity instance

image.png

In this example, the persistence context is empty; nothing has been loaded from the database. Hibernate therefore loads an instance with this identifier from the database. Then, merge() copies the detached entity instance onto this loaded persistent instance. In other words, the new username you have set on the detached User is also set on the persistent merged User, which merge() returns to you.

Now discard the old reference to the stale and outdated detached state; the detachedUser no longer represents the current state. You can continue modifying the returned mergedUser; Hibernate will execute a single UPDATE when it flushes the persistence context during commit.

image.png