JPA használata Java SE alkalmazásban (CDI-vel) – II. rész

Posted by | · · · · | Egy csésze kávé · Szoftverfejlesztés | Nincs hozzászólás a(z) JPA használata Java SE alkalmazásban (CDI-vel) – II. rész bejegyzéshez

A korábbi bejegyzésben eljutottunk odáig, hogy az EntityManager-t injectálni tudjuk. Most pedig azzal foglalkozunk, hogyan kezelhetjük a tranzakciókat egyszerűen.

Tranzakció kezelés

A persistence unit-unkat RESOURCE_LOCAL transaction-type-val konfiguráltuk a persistance.xml-ben, ami többek közt azt jelenti, hogy a tranzakciókat az EntityTransaction API-val kell kezelnünk.

final EntityTransaction transaction = em.getTransaction();
try {
    transaction.begin();
    // Manage entities
    transaction.commit();
} catch (Exception e) {
    try {
        if (transaction.isActive()) {
            transaction.rollback();
        }
   } catch (Exception roolbackException) {
        // Rollback of transaction failed
   }
}

A fenti kódrészlet síkit azért, hogy újrafelhasználhatóvá tegyünk.

I. módszer – TransactionService létrehozása

Vezessünk be egy TransactionService nevű osztályt, amely a tranzakció kezelés elfedéséért lesz felelős.

A TransactionService osztályunk rendelkezzen egy metódussal, amely paraméterként megkapja az EntityManager-t (amelytől el tudja kérni az EntityTransaction-t), és egy callback “metódust”:

import java.util.logging.Level;
import java.util.logging.Logger;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;

public class TransactionService {
    
    private static final Logger LOGGER = Logger.getLogger(TransactionService.class.getName());

    public <T> T execute(EntityManager em, TransactionCallback<T> callback) {
        final EntityTransaction transaction = em.getTransaction();
        
        final T returnValue;
        
        try {
            transaction.begin();
            
            returnValue = callback.doInTransaction();
            
            transaction.commit();
            
            return returnValue;
        } catch (Exception e) {
            try {
                if (transaction.isActive()) {
                    transaction.rollback();
                    LOGGER.log(Level.FINE, "Rolled back transaction");
                }
            } catch (Exception roolbackException) {
                LOGGER.log(Level.WARNING, "Rollback of transaction failed -> {0}", roolbackException);
            }
            throw new TransactionException(e);
        }
    }
}

A callback paraméter egy interface, amely a doInTransaction() metódussal valósítja meg a callback “metódust”:

public interface TransactionCallback<T> {

    T doInTransaction() throws Exception;
}

Így a business kódunkat sokkal tisztábbá tudjuk tenni, elrejtjük a EntityTransaction API használatát.

 

A TransactionService osztály felhasználása:

@Inject
private EntityManager em;
@Inject
private TransactionService ts;

public void doIt(/* params */) {
    ts.execute(em, new TransactionCallback<Void>() {
        @Override
        public Void doInTransaction() throws Exception {
            // ...
            return null;
        }
    });
}

II. módszer – @Transactional annotáció bevezetése

A TransactionService osztály nem rossz, de tovább finomíthatjuk a tranzakció kezelés elfedését: vezessünk be egy @Transactional annotációt. Ezt az annotációt osztályon használva elérhetjük, hogy minden egyes metódus hívás tranzakcióban fusson le.

 

Ez a viselkedés interceptor használatával érhető el. Egy interceptor osztály arra használható, hogy metódus hívásokban közbeléphessünk. Ez annyit tesz, hogy írhatunk olyan kódot, amely specifikus metódusok (általunk meghatározott) hívása előtt vagy után fut le.

 

Elsőként egy interceptor binding type-ot kell létrehoznunk, ez lesz a @Transactional annotáció:

@Inherited
@InterceptorBinding
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Transactional {

}

Ezen annotációt használva jelezhetjük, hogy mely bean-jeinket szeretnénk tranzakcióban használni.

 

A következő pedig maga az interceptor osztály létrehozása, ami a tényleges tranzakció kezelést végzi.

import java.util.logging.Level;
import java.util.logging.Logger;
import javax.inject.Inject;
import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;

@Transactional
@Interceptor
public class TransactionInterceptor {

    private static final Logger LOGGER = Logger.getLogger(TransactionInterceptor.class.getName());

    @Inject
    private EntityManager em;

    @AroundInvoke
    public Object manageTransaction(final InvocationContext ctx) throws Exception {
        if (em.getTransaction().isActive()) {
            return ctx.proceed();
        }

        final EntityTransaction transaction = em.getTransaction();
        Object returnValue = null;
        try {
            transaction.begin();
            returnValue = ctx.proceed();
            transaction.commit();
        } catch (Exception e) {
            try {
                if (transaction.isActive()) {
                    transaction.rollback();
                    LOGGER.log(Level.FINE, "Rolled back transaction");
                }
            } catch (Exception roolbackException) {
                LOGGER.log(Level.WARNING, "Rollback of transaction failed -> {0}", roolbackException);
            }
            throw e;
        }

        return returnValue;
    }
}

A fenti implementáció már meglevő tranzakció esetében nem nyit új tranzakciót, a feltartóztatott metódus, a már nyitott tranzakcióban fog futni.

 

Alapértelmezetten az interceptor-ok ki vannak kapcsolva. Az interceptor-unk engedélyezését a beans.xml-ben tehetjük meg:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://java.sun.com/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd" >
    
    <interceptors>
        <class>myPackage.TransactionInterceptor</class>
    </interceptors>
</beans>

A létrehozott interceptor-unkkal a business kódunk már így festhet:

@Transactional
public class MyClass {

    @Inject
    private EntityManager em;

    public void doIt(/* params */) {
        // do it in transaction
    }
}

Ahhoz, hogy ezt így használni tudjuk, még arra volna szükségünk, hogy az interceptor-ban injektált és a busienss kódban injektált EntityManager ugyanaz a példány legyen. Jelenleg azonban az EntityManagerProducer-ünk ezt nem támogatja, minden injection point-hoz egy új EntityManager példányt kér az EntityManagerFactory-től:

@Produces
public EntityManager createEntityManager() {
    return emf.createEntityManager();
}

Ennek megoldására a következő bejegyzésben térek rá.

Share on FacebookShare on Google+Email this to someoneTweet about this on TwitterShare on RedditShare on LinkedIn

No Comments

Leave a comment