EntityManager: gestione dell'applicazione e gestione del contenitore (2023)

EntityManager: gestione dell'applicazione e gestione del contenitore (1)

Per utilizzare al meglio JPA, dovremmo avere familiarità con i concetti diUnità di persistenza,Contesto di persistenzaEEntityManager.

Cos'è un'unità di persistenza

Una Persistence Unit definisce l'insieme di tutte le classi Java gestite da EntityManager di JPA. Tutte le entità di una Persistence Unit rappresentano i dati all'interno di un database. Quindi, se volessimo mappare due database in JPA, dovremmo definire due unità di persistenza.
Le unità di persistenza sono configurate all'interno del file persistence.xml.

Le classi di unità di persistenza possono essere impacchettate come parte di un archivio JAR, WAR o EJB o come un file JAR che può quindi essere incluso in un WAR o EAR. Nello specifico:

  1. Se si crea un pacchetto in un file JAR EJB, il file persistence.xml deve essere posizionato nella directory META-INF del JAR EJB.
  2. Se si crea un pacchetto in un file WAR, persistence.xml dovrebbe trovarsi nella directoryWEB-INF/classes/META-INF del file WAR.
  3. Se impacchettate un file JAR che sarà incluso in un file WAR o EAR, il file JAR dovrebbe essere lì:
    • nella cartella WEB-INF/lib di WAR
    • nella cartella della libreria EAR

Che cos'è un contesto di persistenza e un EntityManager?

Un contesto di persistenza è aSessioneche incapsula un insieme di entità gestite in un momento specifico dall'EntityManager, che ne controlla il ciclo di vita. Inoltre, l'EntityManager dispone di API che vengono utilizzate per creare e rimuovere entità, cercarle tramite la loro chiave primaria e fare query su di esse. Quando un contesto di persistenza (Sessione) termina, in precedenzagestitoentità diventanodistaccato.

Un'istanza di EntityManager può essere ottenuta tramite l'iniezione di un contenitore (come un contenitore JEE o Spring) o tramite l'interfaccia EntityManagerFactory (di solito nelle applicazioni JSE). La cosa fondamentale da sapere è che il comportamento dell'EntityManager cambia a seconda che venga iniettato dal contenitore o creato dall'applicazione.

(Video) SFML Building an Entity Manager from Scratch Using Factory

EntityManager gestito dal contenitore (transazione dichiarativa)

@PersistenceContextprivate EntityManager entityManager;@Transactional@Overridepublic T save(T entità) { if(getterId(entità) == null) { entityManager.persist(entità); } else { entityManager.merge(entità); } entità di ritorno;}

Il contenitore, prima di eseguire un'operazione sull'Entità, verifica se esiste un Persistence Context connesso alla transazione; se non presente, crea un nuovo contesto di persistenza (sessione) e lo connette. In pratica, viene creato automaticamente un EntityManager per ogni nuova transazione. Operazioni come commit e rollback vengono gestite automaticamente dal contenitore.

NOTA:Il comportamento predefinito di EntityManager è come descritto sopra. In particolare diciamo che il Persistence Context è di tipoAmbito transazione.C'è anche un altro tipo di Persistence Context chiamatoEsteso, per la gestione dei bean statefull, ma è poco utilizzato.

EntityManager gestito dall'applicazione (transazione programmatica)

protected static EntityManagerFactory entityManagerFactory;static { entityManagerFactory = Persistence.createEntityManagerFactory("TEST_UNIT");}@Overridepublic T save(T entità) { EntityManager entityManager = entityManagerFactory.createEntityManager(); try { entityManager.getTransaction().begin(); if(getterId(entità) == null) { entityManager.persist(entità); } else { entityManager.merge(entità); } entityManager.getTransaction().commit(); } catch (Eccezione e) { entityManager.getTransaction().rollback(); } EntityManager.close(); entità di ritorno;}

Qui lo sviluppatore crea ogni volta EntityManager da EntityManagerFactory. Ha più controllo sul flusso, ma anche più responsabilità (ad esempio deve ricordarsi di chiudere l'EntityManager, deve chiamare esplicitamente le operazioni di commit e rollback).

Ora che abbiamo compreso questi concetti di base, creiamo un'applicazione con le classi DAO.Useremo le entità del postRelazioni dell'APPcon un database in memoria H2.

Esamineremo l'utilizzo dell'EntityManager gestito dall'applicazione, quindi lo confronteremo con quello gestito dal Container.

Passaggio 1: creare l'interfaccia JpaDao

Questa interfaccia conterrà i metodi generali di findById, save, ecc...

(Video) #10 JPA & POSTGRESQL | JAVA PERSISTENCE API JPA | Tutorial | Java | JakartaEE | Docker | PostgreSQL

interfaccia pubblica JpaDao { T findById(ID id); Collezione findAll(); T save(T entità); void delete(entità T); vuoto cancella();}

Creiamo l'interfaccia UserDao che estende semplicemente JpaDao:

interfaccia pubblica UserDao estende JpaDao {}

Passaggio 2: creare una classe astratta JpaDaoImpl

public abstract class JpaDaoImpl implementa JpaDao { protected static EntityManagerFactory entityManagerFactory; classe privata persistenteClass; private final String FIND_ALL; static { entityManagerFactory = Persistenza.createEntityManagerFactory("TEST_UNIT"); } public JpaDaoImpl() { this.persistentClass = (Class) ((ParameterizedType) getClass() .getGenericSuperclass()).getActualTypeArguments()[0]; FIND_ALL = "seleziona e da " + persistentClass.getSimpleName() + " e"; } @Override public T findById(ID id) { EntityManager entityManager = entityManagerFactory.createEntityManager(); T entità = entityManager.find(persistentClass, id); entitàManager.close(); entità di ritorno; } @Override public Collection findAll() { EntityManager entityManager = entityManagerFactory.createEntityManager(); Collection entità = entityManager.createQuery(FIND_ALL, persistentClass).getResultList(); entitàManager.close(); entità di ritorno; } @Override public T save(T entità) { EntityManager entityManager = entityManagerFactory.createEntityManager(); try { entityManager.getTransaction().begin(); if(getterId(entità) == null) { entityManager.persist(entità); } else { entityManager.merge(entità); } entityManager.getTransaction().commit(); } catch (Eccezione e) { entityManager.getTransaction().rollback(); } EntityManager.close(); entità di ritorno; } @Override public void delete(T entity) { EntityManager entityManager = entityManagerFactory.createEntityManager(); try { entityManager.getTransaction().begin(); entità = entityManager.merge(entità); entitàManager.remove(entità); entityManager.getTransaction().commit(); } catch (Eccezione e) { entityManager.getTransaction().rollback(); } EntityManager.close(); } //utilizza un ciclo forEach e non una query di eliminazione perché JPA non utilizza la cascata nella query @Override public void clear() { EntityManager entityManager = entityManagerFactory.createEntityManager(); try { entityManager.getTransaction().begin(); Raccolta all = findAll(); all.stream().map(entityManager::merge) .forEach(entityManager::remove); entityManager.getTransaction().commit(); } catch (Eccezione e) { entityManager.getTransaction().rollback(); } EntityManager.close(); } //ottenere il valore del campo annotato con @Id private ID getterId(T entity) { try { Field id = Arrays.stream(persistentClass.getDeclaredFields()) .filter(field -> field.isAnnotationPresent(Id.class) ) .findAny() .orElse(null); id.setAccessibile(true); return (ID) id.get(entità); } catch (Exception e) { throw new RuntimeException(e); } }}

Analizziamo il codice:

  1. ILEntityManagerFactoryil campo viene inizializzato all'avvio dell'app. Legge l'unità di persistenza all'interno del file persistence.xml.
  2. ILpersistenteClassIl campo è valutato con la classe concreta dell'entità.
  3. Ogni volta che viene invocato un metodo, creiamo EntityManager da EntityManagerFactory.
  4. Per le operazioni di scrittura nel database, creiamo e gestiamo manualmente le transazioni.
  5. ILeliminareIl metodo, prima di chiamare EntityManager remove, chiama l'unione che trasforma un'entità scollegata in un'entità gestita (le operazioni possono essere eseguite solo su entità gestite).
  6. ILgetterIdmetodo, tramite riflessione, prende il valore del campo annotato con @Id da JPA.

Il metodo clear cancella tutte le righe della tabella. Usiamo EntityManager remove per ogni entità piuttosto che una singola query JPQL poiché le query non supportano le operazioni a cascata. Ad esempio, se provassimo a eliminare tutte le righe di USERS con una query DELETE, potremmo ottenere errori sulla violazione del vincolo FK su CONTACTS se non eliminiamo prima le righe su CONTACTS. Utilizzando invece l'EntityManager remove, quando eliminiamo una riga di USERS cancelleremo anche qualsiasi riga di CONTACTS grazie al cascadeset nella relazione OneToOne.

Abbiamo creato una classe DAO generale che può essere utilizzata da tutte le entità! Creiamo la classe concreta UserDaoImpl:

classe pubblica UserDaoImpl estende JpaDaoImpl implementa UserDao { private static UserDaoImpl userDao; private UserDaoImpl() {} public static UserDao getInstance() { if(userDao == null) { userDao = new UserDaoImpl(); } return userDao; }}

Si noti che UserDaoImpl è un semplice singleton che non contiene metodi. Contiene già tutto findAll, findById, save, ecc, poiché itextends JpaDaoImpl.

Passaggio 3: creare il file persistence.xml

All'interno di resources/META-INF, creiamo persistence.xml:

(Video) Transaction Control – A functional approach to modular transaction management

  com.vincenzoracca.jpaproject.entities.UserEntity com.vincenzoracca.jpaproject.entities.CarEntity  com.vincenzoracca.jpaproject.entities.ContactEntity com.vincenzoracca.jpaproject.entities.ArticleEntity           

Analizziamo il file:

  1. Nelunità di persistenzasezione, dichiariamo il nome e il tipo dell'unità, che può essereRISORSA_LOCALEOJTA..JTA è un tipo di transazione gestita solo da contenitori JEE, forse scriverò un articolo a riguardo: basti sapere che se dobbiamo distribuire l'applicazione all'interno di un application server, può essere utile utilizzare questo tipo di transazione .
  2. ILclassesezioni, indicano le varie classi Java mappate come entità.
  3. ILproprietàindica le varie proprietà JPA e, in questo caso, Hibernate che possiamo impostare.

NOTA:JTA supporta sia l'EntityManager gestito dal contenitore che quello gestito dall'applicazione. Nel secondo caso, puoi iniettare ajavax.transaction.UserTransactionclasse dal contenitore.

Passaggio 4: testare i DAO

Creiamo jUnit per testare le DAO:

public class JpaRelationsTest { private UserDao userDao; privato CarDao carDao; privato ArticleDao articleDao; @Before public void init() { userDao = UserDaoImpl.getInstance(); carDao = CarDaoImpl.getInstance(); articoloDao = ArticoloDaoImpl.getInstance(); System.out.println("\n***************************************** ********"); } @Dopo public void destroy(){ System.out.println("*********************************** ***************\N"); System.out.println("BEGIN distruggere"); articoloDao.clear(); carDao.clear(); utenteDao.clear(); System.out.println("FINE distruzione\n"); } @Test public void oneToOneTest() { System.out.println("BEGIN oneToOneTest"); assertEquals(0, userDao.findAll().size()); UserEntity userEntity = createUser(); ContactEntity contactEntity = createContact(userEntity); userDao.save(userEntity); UserEntity recuperato = userDao.findById(userEntity.getId()); System.out.println(recuperato); assertEquals(userEntity, recuperato); assertEquals(contactEntity, userEntity.getContactEntity()); System.out.println("END oneToOneTest"); } private UserEntity createUser(){ UserEntity userEntity = new UserEntity(); userEntity.setCode("1"); userEntity.setName("Vincenzo"); userEntity.setCognome("Racca"); ritorno userEntity; } private ContactEntity createContact(UserEntity userEntity) { ContactEntity contactEntity = new ContactEntity(); contactEntity.setCity("Napoli"); contactEntity.setTelephoneNumber("333333333"); contactEntity.setUserEntity(userEntity); userEntity.setContactEntity(contactEntity); return contactEntity; }}

Analizziamo il metodo oneToOneTest:

  1. Ci assicuriamo conassertEquals(0, userDao.findAll().size())che la tabella USERS è vuota.
  2. Creiamo una UserEntity, la sua ContactEntity, quindi salviamo la UserEntity. Ricorda che UserEntity, avendo l'estensionecascatasu ContactEntity,quando verrà persistita, verrà salvata in cascata anche l'eventuale ContactEntity.
  3. Recuperiamo quindi la userEntity dal db e verifichiamo che lui e la sua contactEntity siano uguali a quelli inseriti un attimo prima.

Se abilitiamo i log sulla Transazione, vediamo che effettivamente gli inserimenti sono attiviUTENTIECONTATTIavvenuto all'interno della stessa transazione. Quando infatti eseguiamo esplicitamente il commit della transazione, JPA inserisce anche contactEntity:

22:12:10.602 [principale] DEBUG o.h.e.t.internal.TransactionImpl - beginHibernate: inserire in USERS (user_id, codice, nome, cognome) i valori (null, ?, ?, ?)22:12:17.066 [principale] DEBUG o.h.e.t.internal. TransactionImpl - committingHibernate: inserire in CONTACTS (city, telephone_number, user_id) i valori (?, ?, ?)

Gestione del contenitore vs Gestione dell'applicazione

Ma cosa succederebbe se un metodo di logica aziendale avesse più metodi DAO e volessimo chiamarli all'interno della stessa transazione?
Attualmente, con i metodi che abbiamo scritto, creeresti comunque una transazione per ogni DAO, quindi non avremmo una singola transazione.
Una possibile soluzione, sempre parlando di EntityManager gestito dall'applicazione, è quella di utilizzare JavaCDI(Context and Dependecy Injection) e iniettare l'EntityManager.

(Video) SAP BTP Training | Spring Boot App with Postgre Backing Service in Cloud Foundry | PostgreSQL in CF

Per quanto riguarda l'EntityManager gestito da Containers, non avremo questo problema, poiché nei metodi annotati con@Transazionale, verifica se esiste già una transazione in esecuzione. Se sì, usa quello, altrimenti ne crea uno nuovo (questo è il comportamento predefinito, ma puoi cambiarlo).

Diamo un'occhiata alla classe JpaDaoImpl con EntityManager gestito dal Container:

public abstract class JpaDaoImpl implementa JpaDao { private Class persistentClass; private final String FIND_ALL; @PersistenceContext private EntityManager entityManager; public JpaDaoImpl() { this.persistentClass = (Class) ((ParameterizedType) getClass() .getGenericSuperclass()).getActualTypeArguments()[0]; FIND_ALL = "seleziona e da " + persistentClass.getSimpleName() + " e"; } @Override public T findById(ID id) { T entità = entityManager.find(persistentClass, id); entità di ritorno; } @Override public Collection findAll() { Collection entity = entityManager.createQuery(FIND_ALL, persistentClass).getResultList(); entità di ritorno; } @Transactional @Override public T save(T entità) { if(getterId(entità) == null) { entityManager.persist(entità); } else { entityManager.merge(entità); } entità di ritorno; } @Transactional @Override public void delete(T entità) { entità = entityManager.merge(entità); entitàManager.remove(entità); } //utilizza un ciclo forEach e non una query di eliminazione perché JPA non utilizza la cascata nella query @Transactional @Override public void clear() { Collection all = findAll(); all.forEach(this::delete); } //ottenere il valore del campo annotato con @Id private ID getterId(T entity) { try { Field id = Arrays.stream(persistentClass.getDeclaredFields()) .filter(field -> field.isAnnotationPresent(Id.class) ) .findAny() .orElse(null); id.setAccessibile(true); return (ID) id.get(entità); } catch (Exception e) { throw new RuntimeException(e); } }}

Come possiamo vedere, le transazioni vengono gestite automaticamente e l'EntityManager non viene creato dallo sviluppatore ma dal Container.

Notiamo anche che ilchiarometodo chiama ileliminaredella stessa classe. Questo è possibile perché il Container creerà una sola transazione, nel metodo clear. Quando passerà aleliminaremetodo, poiché esiste già una transazione, utilizzerà quella, quindi il commit verrà eseguito solo alla fine delchiarometodo.

Conclusioni

In questo articolo JPA, abbiamo visto cos'è un EntityManager, come usarlo, sia con applicazioni gestite che con container gestiti.

Puoi trovare il progetto completo sul mio github a questo link:Progetto APP.
Troverai un modulo dedicato all'application managed e uno al container managed, utilizzando Spring 5.

(Video) Corso Spring Data JPA in 2 ore - 07 - Relazione One To Many

Articoli sull'APP:APP

...

Videos

1. SPRING BOOT WEB APP NOTE: PROGRAMMAZIONE SPRING BOOT LIVE IN ITALIANO #3
(Prof. Andrea Pollini)
2. Mini-corso (1° Parte) JPA e Hibernate: realizzare il mapping tra classi java e tabelle di database.
(Reteinformaticalavoro)
3. Hibernate ORM PanacheEntity with PostgreSQL | Quarkus Tutorial | QUARKUS | JPA | Hibernate | Java
(Giuseppe Scaramuzzino)
4. The newest JAX-RS and JPA Features (Part 1/2)
(Parleys)
5. Mini corso (parte 2) JPA e Hibernate realizzare il mapping tra classi java e tabelle di database.
(Reteinformaticalavoro)
6. Spring Boot Hibernate MySQL CRUD REST API Tutorial | Controller, Service and DAO Layer | Full Course
(Java Guides)
Top Articles
Latest Posts
Article information

Author: Allyn Kozey

Last Updated: 06/09/2023

Views: 5271

Rating: 4.2 / 5 (63 voted)

Reviews: 86% of readers found this page helpful

Author information

Name: Allyn Kozey

Birthday: 1993-12-21

Address: Suite 454 40343 Larson Union, Port Melia, TX 16164

Phone: +2456904400762

Job: Investor Administrator

Hobby: Sketching, Puzzles, Pet, Mountaineering, Skydiving, Dowsing, Sports

Introduction: My name is Allyn Kozey, I am a outstanding, colorful, adventurous, encouraging, zealous, tender, helpful person who loves writing and wants to share my knowledge and understanding with you.