Training, Workshops, Softwareentwicklung

Hibernate Tutorial

Transactional Cache

Der letzte mögliche Modus ist "transactional". Er stellt einige Anforderungen an den Cache und die Umgebung:

  • Der Cache muss Transaktionen unterstützen (deswegen findet Ihr im Beispiel hier Infinispan als Cache)

  • Man benötigt einen JTA Transaktionsmanager

Transactional
@Entity
@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL)
public class Customer {
    @Id
    private Long id;
    private String firstname;
    private String lastname;

In diesem Modus wird der Cache in einem Two-Phase-Commit zusammen mit der Datenbanktransaktion aktualisiert. Normalerweise findet man diese Voraussetzungen eher in einer JEE Umgebung, mit etwas Mühe lässt sich das aber auch standalone realisieren.

Das wichtigste ist wohl, dass die Transaktion nun von JTA verwaltet wird.

UserTransaction im PersistenceHelper
private static UserTransaction userTransaction;

public static void withTransaction(EntityManager em, Runnable runnable) {
    try {
        userTransaction.begin();
        runnable.run();
        if (userTransaction.getStatus() == Status.STATUS_ACTIVE) {
            userTransaction.commit();
        }
    } catch (Exception x) {
        LOG.error("TX Error", x);
        try {
            if (userTransaction.getStatus() == Status.STATUS_ACTIVE) {
                userTransaction.rollback();
            }
        } catch (SystemException sx) {
            LOG.error("TX Error", sx);
        }
        throw new RuntimeException("TX was rolled back", x);
    }
}

Die Benutzung ist dann eher unauffällig:

Transactional Cache Test
Customer c = new Customer();
c.setId(2L);
c.setFirstname("Alice");
c.setLastname("Rogers");

doInHibernate(em -> withTransaction(em, () -> em.persist(c)));
Customer customerOutside = getCustomerAlice();

// what if customerOutside has changed ?
customerOutside.setFirstname("Buck");
saveCustomer(customerOutside);

doInHibernate(em -> {
    Customer customer = em.find(Customer.class, 2L);
    assertNotNull(customer);
    assertEquals("Buck", customer.getFirstname());
    withTransaction(em, () -> em.remove(customer));
});

assertSelectCount(0);

Es ist allerdings ziemlich mühsam, einen JTA Transaktionsmanager Standalone aufzusetzen, da man auch noch eine Lösung für JNDI benötigt. Im Quellcode findet Ihr ein Beispiel mit Atomikos, das tatsächlich funktioniert…​