Ejemplo de Spring ORM – JPA, Hibernate, Transacción

Bienvenido al Tutorial de Ejemplo de Spring ORM. Hoy vamos a ver un ejemplo de Spring ORM utilizando el manejo de transacciones Hibernate JPA. Te mostraré un ejemplo muy simple de una aplicación independiente de Spring con las siguientes características.

  • Inyección de Dependencias (anotación @Autowired)
  • EntityManager JPA (proporcionado por Hibernate)
  • Métodos transaccionales anotados (@Transactional)

Ejemplo de Spring ORM

He utilizado una base de datos en memoria para el ejemplo de Spring ORM, por lo que no es necesario configurar ninguna base de datos (pero puedes cambiarlo a cualquier otra base de datos en la sección de origen de datos de spring.xml). Esta es una aplicación independiente de Spring ORM para minimizar todas las dependencias (pero puedes cambiar fácilmente a un proyecto web mediante configuración si te familiarizas con Spring). NOTA: Para el enfoque de resolución de métodos basado en Spring AOP Transactional (sin la anotación @Transactional), consulta este tutorial: Spring ORM AOP Transaction Management. La siguiente imagen muestra nuestro proyecto final de ejemplo de Spring ORM. Vamos a revisar cada uno de los componentes del proyecto de ejemplo de Spring ORM uno por uno.

Dependencias de Maven para Spring ORM

A continuación se muestra nuestro archivo pom.xml final con las dependencias de Spring ORM. Hemos utilizado Spring 4 e Hibernate 4 en nuestro ejemplo de 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>
  • Necesitamos spring-context y spring-orm como dependencias de Spring.
  • Usamos hibernate-entitymanager para Hibernate como implementación JPA. hibernate-entitymanager depende de hibernate-core, por eso no necesitamos agregar hibernate-core explícitamente en el archivo pom.xml. Se incorpora a nuestro proyecto a través de las dependencias transitivas de Maven.
  • También necesitamos el controlador JDBC como dependencia para el acceso a la base de datos. Estamos utilizando HSQLDB, que contiene el controlador JDBC y una base de datos en memoria funcional.

Clase de Modelo Spring ORM

Podemos utilizar anotaciones JPA estándar para el mapeo en nuestras clases de modelo porque Hibernate proporciona la implementación 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 + "]";
	}

}

Usamos las anotaciones @Entity y @Id de JPA para calificar nuestro POJO como una entidad y para definir su clave primaria.

Clase DAO Spring ORM

Creamos una clase DAO muy simple que proporciona los métodos persist y 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 es una anotación de Spring que le indica al contenedor de Spring que podemos usar esta clase a través de Spring IoC (Inyección de Dependencias).
  • Utilizamos la anotación JPA @PersistenceContext que indica la inyección de dependencias a un EntityManager. Spring inyecta una instancia adecuada de EntityManager según la configuración de spring.xml.

Clase de Servicio Spring ORM

Nuestra clase de servicio simple tiene 2 métodos de escritura y 1 de lectura: add, addAll y 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();

	}

}
  • Utilizamos la anotación Spring @Autowired para inyectar ProductDao en nuestra clase de servicio.
  • Queremos utilizar la gestión de transacciones, por lo que los métodos están anotados con @Transactional de Spring. El método listAll solo lee la base de datos, por lo que configuramos la anotación @Transactional como de solo lectura para optimización.

Ejemplo de Configuración de Bean XML de Spring ORM

Nuestros archivos de proyecto de ejemplo de spring ORM están listos, veamos ahora nuestro archivo de configuración de beans de 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. Primero le indicamos a Spring que queremos usar el escaneo de classpath para los componentes de Spring (Servicios, DAOs) en lugar de definirlos uno por uno en spring xml. También hemos habilitado la detección de anotaciones de Spring.
  2. Añadiendo el origen de datos, que actualmente es la base de datos HSQLDB en memoria.
  3. Configuramos una EntityManagerFactory JPA que será utilizada por la aplicación para obtener un EntityManager. Spring admite 3 formas diferentes de hacer esto; hemos utilizado LocalContainerEntityManagerFactoryBean para todas las capacidades de JPA. Configuramos los atributos de LocalContainerEntityManagerFactoryBean de la siguiente manera:
    1. El atributo packagesToScan que apunta al paquete de nuestras clases de modelo.
    2. El origen de datos definido anteriormente en el archivo de configuración de Spring.
    3. jpaVendorAdapter como Hibernate y configurando algunas propiedades de Hibernate.
  4. Creamos una instancia de Spring PlatformTransactionManager como JpaTransactionManager. Este administrador de transacciones es apropiado para aplicaciones que utilizan una única EntityManagerFactory JPA para el acceso a datos transaccionales.
  5. Habilitamos la configuración del comportamiento transaccional basado en anotaciones y configuramos el transactionManager que creamos.

Ejemplo de programa de prueba de Spring ORM Hibernate JPA

Nuestro proyecto de ejemplo de Spring ORM JPA Hibernate está listo, así que escribamos un programa de prueba para nuestra aplicación.

public class SpringOrmMain {
	
	public static void main(String[] args) {
		
		//Crear el contexto de la aplicación Spring
		ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/spring.xml");
		
		//Obtener el servicio desde el contexto. (La dependencia del servicio (ProductDAO) se inyecta automáticamente en ProductService)
		ProductService productService = ctx.getBean(ProductService.class);
		
		//Realizar algunas operaciones de datos
		
		productService.add(new Product(1, "Bulb"));
		productService.add(new Product(2, "Dijone mustard"));
		
		System.out.println("listAll: " + productService.listAll());
		
		//Probar la reversión de la transacción (clave duplicada)
		
		try {
			productService.addAll(Arrays.asList(new Product(3, "Book"), new Product(4, "Soap"), new Product(1, "Computer")));
		} catch (DataAccessException dataAccessException) {
		}
		
		//Probar la lista de elementos después de la reversión
		System.out.println("listAll: " + productService.listAll());
		
		ctx.close();
		
	}
}

Puedes ver lo fácil que es iniciar el contenedor Spring desde un método principal. Obtenemos nuestro primer punto de entrada con inyección de dependencias, la instancia de la clase de servicio. La referencia de la clase ProductDao se inyecta en la clase ProductService después de que se inicializa el contexto de Spring. Después de obtener la instancia de ProducService, podemos probar sus métodos; todas las llamadas a métodos serán transaccionales debido al mecanismo de proxy de Spring. También probamos la reversión en este ejemplo. Si ejecutas el programa de prueba de ejemplo de Spring ORM anterior, obtendrás los siguientes registros.

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 que la segunda transacción se revierte, por eso la lista de productos no cambió. Si utilizas el archivo log4j.properties del origen adjunto, puedes ver qué sucede bajo el capó. Referencias: https://docs.spring.io/spring/docs/current/spring-framework-reference/html/orm.html Puedes descargar el proyecto final de ejemplo de Spring ORM JPA Hibernate desde el siguiente enlace y jugar con él para aprender más.

Descargar Proyecto Spring ORM con Transacciones

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