Exemplo do Spring ORM – JPA, Hibernate, Transação

Bem-vindo ao Tutorial de Exemplo do Spring ORM. Hoje vamos analisar um exemplo de Spring ORM usando o gerenciamento de transações Hibernate JPA. Vou mostrar um exemplo muito simples de aplicativo autônomo do Spring com as seguintes características.

  • Injeção de Dependência (anotação @Autowired)
  • EntityManager JPA (fornecido pelo Hibernate)
  • Métodos transacionais anotados (@Transactional)

Exemplo de Spring ORM

Eu usei um banco de dados em memória para o exemplo do Spring ORM, então não há necessidade de configurar nenhum banco de dados (mas você pode alterá-lo para qualquer outro banco na seção de datasource do spring.xml). Este é um aplicativo autônomo do Spring ORM para minimizar todas as dependências (mas você pode facilmente transformá-lo em um projeto web por configuração se ficar familiarizado com o Spring). NOTA: Para a abordagem de resolução de métodos baseada em Spring AOP Transacional (sem a anotação @Transactional), por favor, confira este tutorial: Gestão de Transações Spring ORM AOP. A imagem abaixo mostra o projeto final do exemplo do Spring ORM. Vamos passar por cada um dos componentes do projeto de exemplo do Spring ORM um por um.

Dependências do Maven para o Spring ORM

Abaixo está o nosso arquivo pom.xml final com as dependências do Spring ORM. Utilizamos o Spring 4 e o Hibernate 4 no nosso exemplo do 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>
  • Precisamos de spring-context e spring-orm como dependências do Spring.
  • Usamos hibernate-entitymanager para o Hibernate como implementação JPA. hibernate-entitymanager depende do hibernate-core é por isso que não precisamos colocar hibernate-core explicitamente no pom.xml. Ele está sendo incluído em nosso projeto através das dependências transitivas do Maven.
  • Também precisamos do driver JDBC como dependência para acesso ao banco de dados. Estamos usando o HSQLDB que contém o driver JDBC e um banco de dados em memória funcional.

Classe de Modelo do Spring ORM

Podemos usar anotações JPA padrão para mapeamento em nossos beans de modelo porque o Hibernate fornece a implementação 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 as anotações @Entity e @Id JPA para qualificar nosso POJO como uma Entidade e para definir sua chave primária.

Classe DAO do Spring ORM

Criamos uma classe DAO muito simples que fornece os métodos 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 é uma anotação do Spring que informa ao contêiner Spring que podemos usar esta classe através do Spring IoC (Injeção de Dependência).
  • Usamos a anotação JPA `@PersistenceContext` que indica a injeção de dependência em um EntityManager. O Spring injeta uma instância apropriada do EntityManager de acordo com a configuração do spring.xml.

Classe de Serviço do Spring ORM

Nossa classe de serviço simples tem 2 métodos de escrita e 1 método de leitura – adicionar, adicionarTodos e listarTodos.

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();

	}

}
  • Usamos a anotação Spring @Autowired para injetar o ProductDao em nossa classe de serviço.
  • Queremos usar o gerenciamento de transações, então os métodos são anotados com a anotação Spring `@Transactional`. O método listarTodos apenas lê o banco de dados, então definimos a anotação `@Transactional` como somente leitura para otimização.

Exemplo de Configuração de Bean do Spring ORM XML

Nosso projeto de exemplo de ORM Spring está pronto, vamos olhar para o arquivo de configuração de bean do Spring agora. 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. Primeiro, dizemos ao Spring que queremos usar a varredura de classpath para os componentes do Spring (Serviços, DAOs) em vez de defini-los um por um no xml do Spring. Também habilitamos a detecção de anotações do Spring.
  2. Adicionando o datasource, que atualmente é o banco de dados em memória HSQLDB.
  3. Configuramos um EntityManagerFactory JPA que será utilizado pela aplicação para obter um EntityManager. O Spring oferece 3 maneiras diferentes de fazer isso; escolhemos LocalContainerEntityManagerFactoryBean para obter todas as capacidades do JPA. Definimos os atributos do LocalContainerEntityManagerFactoryBean da seguinte forma:
    1. o atributo packagesToScan aponta para o pacote de nossas classes modelo.
    2. o datasource definido anteriormente no arquivo de configuração do Spring.
    3. jpaVendorAdapter como Hibernate e configurando algumas propriedades do Hibernate.
  4. Criamos uma instância do Spring PlatformTransactionManager como JpaTransactionManager. Este gerenciador de transações é apropriado para aplicações que usam uma única EntityManagerFactory JPA para acesso a dados transacionais.
  5. Habilitamos a configuração do comportamento transacional com base em anotações e definimos o transactionManager que criamos.

Programa de Teste de Exemplo Spring ORM Hibernate JPA

Projeto de exemplo Spring ORM JPA Hibernate pronto, então vamos escrever um programa de teste para nossa aplicação.

public class SpringOrmMain {
	
	public static void main(String[] args) {
		
		// Criar contexto de aplicação Spring
		ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/spring.xml");
		
		// Obter serviço do contexto. (a dependência do serviço (ProductDAO) é autowired no ProductService)
		ProductService productService = ctx.getBean(ProductService.class);
		
		// Realizar operações de dados
		
		productService.add(new Product(1, "Bulb"));
		productService.add(new Product(2, "Dijone mustard"));
		
		System.out.println("listAll: " + productService.listAll());
		
		// Testar rollback da transação (chave duplicada)
		
		try {
			productService.addAll(Arrays.asList(new Product(3, "Book"), new Product(4, "Soap"), new Product(1, "Computer")));
		} catch (DataAccessException dataAccessException) {
		}
		
		// Testar lista de elementos após rollback
		System.out.println("listAll: " + productService.listAll());
		
		ctx.close();
		
	}
}

Você pode ver como é fácil iniciar o contêiner Spring a partir de um método principal. Estamos obtendo nosso primeiro ponto de entrada com injeção de dependência, a instância da classe de serviço. A referência da classe ProductDao é injetada na classe ProductService após o contexto do Spring ser inicializado. Depois de obtermos a instância do ProducService, podemos testar seus métodos; todas as chamadas de método serão transacionais devido ao mecanismo de proxy do Spring. Também testamos o rollback neste exemplo. Se você executar o programa de teste de exemplo acima, você verá os logs abaixo.

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]]

Observe que a segunda transação é revertida, é por isso que a lista de produtos não mudou. Se você usar o arquivo log4j.properties do código fonte anexado, poderá ver o que está acontecendo nos bastidores. Referências: https://docs.spring.io/spring/docs/current/spring-framework-reference/html/orm.html Você pode baixar o projeto final de exemplo do Spring ORM JPA Hibernate no link abaixo e brincar com ele para aprender mais.

Download do Projeto Spring ORM com Transação

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