Esempio di Spring ORM – JPA, Hibernate, Transaction

Benvenuti al Tutorial di Esempio di Spring ORM. Oggi esamineremo un esempio di Spring ORM utilizzando la gestione delle transazioni Hibernate JPA. Ti mostrerò un esempio molto semplice di un’applicazione stand-alone Spring con le seguenti caratteristiche.

  • Iniezione di dipendenze (annotazione @Autowired)
  • JPA EntityManager (fornito da Hibernate)
  • Metodi transazionali annotati (annotazione @Transactional)

Esempio di Spring ORM

Ho utilizzato un database in memoria per l’esempio di Spring ORM, quindi non c’è bisogno di alcuna configurazione del database (ma è possibile cambiarlo in qualsiasi altro database nella sezione datasource di spring.xml). Questa è un’applicazione standalone di Spring ORM per minimizzare tutte le dipendenze (ma è possibile cambiarla facilmente in un progetto web tramite configurazione se ti familiarizzi con Spring). NOTA: Per un approccio di risoluzione del metodo basato su Spring AOP Transactional (senza annotazione @Transactional), consulta questo tutorial: Gestione transazionale di Spring ORM AOP. L’immagine sottostante mostra il nostro progetto finale di esempio Spring ORM. Esaminiamo uno per uno ciascuno dei componenti del progetto di esempio Spring ORM.

Dependencies Maven di Spring ORM

Di seguito è riportato il nostro file pom.xml finale con le dipendenze di Spring ORM. Abbiamo utilizzato Spring 4 e Hibernate 4 nel nostro esempio di Spring ORM.

<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>hu.daniel.hari.learn.spring</groupId>
	<artifactId>Tutorial-SpringORMwithTX</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<properties>
		<!-- Generic properties -->
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<java.version>1.7</java.version>

		<!-- SPRING & HIBERNATE / JPA -->
		<spring.version>4.0.0.RELEASE</spring.version>
		<hibernate.version>4.1.9.Final</hibernate.version>

	</properties>

	<dependencies>
		<!-- LOG -->
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>1.2.17</version>
		</dependency>

		<!-- Spring -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-orm</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<!-- JPA Vendor -->
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-entitymanager</artifactId>
			<version>${hibernate.version}</version>
		</dependency>

		<!-- IN MEMORY Database and JDBC Driver -->
		<dependency>
			<groupId>hsqldb</groupId>
			<artifactId>hsqldb</artifactId>
			<version>1.8.0.7</version>
		</dependency>

	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.1</version>
				<configuration>
					<source>${java.version}</source>
					<target>${java.version}</target>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>
  • Abbiamo bisogno di spring-context e spring-orm come dipendenze di Spring.
  • Utilizziamo hibernate-entitymanager come implementazione JPA di Hibernate. hibernate-entitymanager dipende da hibernate-core per questo motivo non dobbiamo inserire esplicitamente hibernate-core nel pom.xml. Viene incluso nel nostro progetto tramite le dipendenze transitive di Maven.
  • Abbiamo anche bisogno di un driver JDBC come dipendenza per l’accesso al database. Stiamo utilizzando HSQLDB che contiene il driver JDBC e un database in memoria funzionante.

Classe Modello Spring ORM

Possiamo utilizzare le annotazioni standard JPA per il mapping nei nostri bean del modello perché Hibernate fornisce l’implementazione JPA.

package hu.daniel.hari.learn.spring.orm.model;

import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class Product {

	@Id
	private Integer id;
	private String name;

	public Product() {
	}

	public Product(Integer id, String name) {
		this.id = id;
		this.name = name;
	}
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}

	@Override
	public String toString() {
		return "Product [id=" + id + ", name=" + name + "]";
	}

}

Utilizziamo le annotazioni @Entity e @Id per definire la nostra classe POJO come entità e per definire la sua chiave primaria.

Classe DAO Spring ORM

Creiamo una classe DAO molto semplice che fornisce i metodi persist e findAll.

package hu.daniel.hari.learn.spring.orm.dao;

import hu.daniel.hari.learn.spring.orm.model.Product;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.springframework.stereotype.Component;

@Component
public class ProductDao {

	@PersistenceContext
	private EntityManager em;

	public void persist(Product product) {
		em.persist(product);
	}

	public List<Product> findAll() {
		return em.createQuery("SELECT p FROM Product p").getResultList();
	}

}
  • @Component è un’annotazione di Spring che indica al container di Spring che possiamo utilizzare questa classe tramite l’Inversione di Controllo (Dependency Injection) di Spring.
  • Utilizziamo l’annotazione JPA @PersistenceContext che indica l’iniezione di dipendenza verso un EntityManager. Spring inietta un’istanza appropriata di EntityManager in base alla configurazione di spring.xml.

Classe di servizio Spring ORM

La nostra semplice classe di servizio ha 2 metodi di scrittura e 1 metodo di lettura – add, addAll e listAll.

package hu.daniel.hari.learn.spring.orm.service;

import hu.daniel.hari.learn.spring.orm.dao.ProductDao;
import hu.daniel.hari.learn.spring.orm.model.Product;

import java.util.Collection;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
public class ProductService {

	@Autowired
	private ProductDao productDao;

	@Transactional
	public void add(Product product) {
		productDao.persist(product);
	}
	
	@Transactional
	public void addAll(Collection<Product> products) {
		for (Product product : products) {
			productDao.persist(product);
		}
	}

	@Transactional(readOnly = true)
	public List<Product> listAll() {
		return productDao.findAll();

	}

}
  • Utilizziamo l’annotazione Spring @Autowired per iniettare ProductDao nella nostra classe di servizio.
  • Vogliamo utilizzare la gestione delle transazioni, quindi i metodi sono annotati con l’annotazione Spring @Transactional. Il metodo listAll legge solo dal database, quindi impostiamo l’annotazione @Transactional come readonly per ottimizzazione.

Esempio di configurazione del bean Spring ORM XML

I nostri file di classi Java di esempio di Spring ORM sono pronti, ora diamo un’occhiata al nostro file di configurazione del bean Spring. spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://www.springframework.org/schema/beans" 
	xmlns:p="https://www.springframework.org/schema/p"
	xmlns:context="https://www.springframework.org/schema/context" 
	xmlns:tx="https://www.springframework.org/schema/tx" 
	xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="
		https://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans-3.0.xsd
		https://www.springframework.org/schema/context
		https://www.springframework.org/schema/context/spring-context-3.0.xsd
		https://www.springframework.org/schema/tx
		https://www.springframework.org/schema/tx/spring-tx.xsd
		">
	
	<!-- Scans the classpath for annotated components that will be auto-registered as Spring beans -->
	<context:component-scan base-package="hu.daniel.hari.learn.spring" />
	<!-- Activates various annotations to be detected in bean classes e.g: @Autowired -->
	<context:annotation-config />

	<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="org.hsqldb.jdbcDriver" />
		<property name="url" value="jdbc:hsqldb:mem://productDb" />
		<property name="username" value="sa" />
		<property name="password" value="" />
	</bean>
	
	<bean id="entityManagerFactory" 
			class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
			p:packagesToScan="hu.daniel.hari.learn.spring.orm.model"
            p:dataSource-ref="dataSource"
			>
		<property name="jpaVendorAdapter">
			<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
				<property name="generateDdl" value="true" />
				<property name="showSql" value="true" />
			</bean>
		</property>
	</bean>

	<!-- Transactions -->
	<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
		<property name="entityManagerFactory" ref="entityManagerFactory" />
	</bean>
	<!-- enable the configuration of transactional behavior based on annotations -->
	<tx:annotation-driven transaction-manager="transactionManager" />

</beans>
  1. Prima diciamo a Spring che vogliamo utilizzare la scansione del classpath per i componenti Spring (Servizi, DAO) anziché definirli uno per uno in spring xml. Abbiamo anche abilitato il rilevamento delle annotazioni Spring.
  2. Aggiungendo la fonte dati, che attualmente è il database in memoria HSQLDB.
  3. Abbiamo configurato un EntityManagerFactory JPA che verrà utilizzato dall’applicazione per ottenere un EntityManager. Spring supporta 3 diverse modalità per farlo, abbiamo utilizzato LocalContainerEntityManagerFactoryBean per le funzionalità complete di JPA. Abbiamo impostato gli attributi di LocalContainerEntityManagerFactoryBean come segue:
    1. attributo packagesToScan che punta al pacchetto delle nostre classi modello.
    2. datasource definita in precedenza nel file di configurazione di Spring.
    3. jpaVendorAdapter come Hibernate e impostando alcune proprietà di Hibernate.
  4. Creiamo un’istanza di Spring PlatformTransactionManager come JpaTransactionManager. Questo gestore delle transazioni è adatto per le applicazioni che utilizzano un singolo EntityManagerFactory JPA per l’accesso ai dati transazionali.
  5. Abilitiamo la configurazione del comportamento transazionale basato su annotazioni e impostiamo il transactionManager che abbiamo creato.

Esempio di programma di test Spring ORM Hibernate JPA

Il nostro progetto di esempio di Spring ORM JPA Hibernate è pronto, quindi scriviamo un programma di test per la nostra applicazione.

public class SpringOrmMain {
	
	public static void main(String[] args) {
		
		//Creare contesto dell'applicazione Spring
		ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/spring.xml");
		
		//Ottenere servizio dal contesto. (la dipendenza del servizio (ProductDAO) è autowired in ProductService)
		ProductService productService = ctx.getBean(ProductService.class);
		
		//Effettuare alcune operazioni sui dati
		
		productService.add(new Product(1, "Bulb"));
		productService.add(new Product(2, "Dijone mustard"));
		
		System.out.println("listAll: " + productService.listAll());
		
		//Test rollback della transazione (chiave duplicata)
		
		try {
			productService.addAll(Arrays.asList(new Product(3, "Book"), new Product(4, "Soap"), new Product(1, "Computer")));
		} catch (DataAccessException dataAccessException) {
		}
		
		//Test lista degli elementi dopo il rollback
		System.out.println("listAll: " + productService.listAll());
		
		ctx.close();
		
	}
}

Puoi vedere quanto sia facile avviare il contenitore Spring da un metodo principale. Stiamo ottenendo il nostro primo punto di ingresso per l’iniezione delle dipendenze, l’istanza della classe di servizio. Il riferimento della classe ProductDao è iniettato nella classe ProductService dopo che il contesto di Spring è stato inizializzato. Dopo aver ottenuto l’istanza di ProductService, possiamo testare i suoi metodi, tutte le chiamate ai metodi saranno transazionali grazie al meccanismo di proxy di Spring. Testiamo anche il rollback in questo esempio. Se esegui il programma di test dell’esempio di ORM di primavera sopra, otterrai i log seguenti.

Hibernate: insert into Product (name, id) values (?, ?)
Hibernate: insert into Product (name, id) values (?, ?)
Hibernate: select product0_.id as id0_, product0_.name as name0_ from Product product0_
listAll: [Product [id=1, name=Bulb], Product [id=2, name=Dijone mustard]]
Hibernate: insert into Product (name, id) values (?, ?)
Hibernate: insert into Product (name, id) values (?, ?)
Hibernate: insert into Product (name, id) values (?, ?)
Hibernate: select product0_.id as id0_, product0_.name as name0_ from Product product0_
listAll: [Product [id=1, name=Bulb], Product [id=2, name=Dijone mustard]]

Nota che la seconda transazione viene annullata, ecco perché la lista dei prodotti non è cambiata. Se utilizzi il file log4j.properties dall’origine allegata, puoi vedere cosa succede sotto il cofano. Riferimenti: https://docs.spring.io/spring/docs/current/spring-framework-reference/html/orm.html Puoi scaricare il progetto di esempio finale di Spring ORM JPA Hibernate dal seguente link e giocarci per imparare di più.

Scarica il progetto Spring ORM con transazione

Source:
https://www.digitalocean.com/community/tutorials/spring-orm-example-jpa-hibernate-transaction