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

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

  • Injeção de Dependência (anotação @Autowired)
  • EntityManager JPA (fornecido pelo Hibernate)
  • Métodos transacionais anotados (anotação @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 qualquer banco de dados (mas você pode alterá-lo para qualquer outro banco de dados na seção de origem de dados do spring.xml). Este é um aplicativo autônomo do Spring ORM para minimizar todas as dependências (mas você pode facilmente alterá-lo para um projeto da web por meio de configuração se ficar familiarizado com o Spring). NOTA: Para uma abordagem de resolução de métodos baseada em Transações com AOP do Spring (sem a anotação @Transactional), por favor, verifique este tutorial: Gerenciamento de Transações Spring ORM AOP. A imagem abaixo mostra nosso projeto final de 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 do Spring ORM

Abaixo está nosso arquivo pom.xml final com as dependências do Spring ORM. Nós usamos o Spring 4 e o Hibernate 4 em 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 das dependências spring-context e spring-orm como dependências do Spring.
  • Usamos o hibernate-entitymanager para o Hibernate como implementação JPA. O hibernate-entitymanager depende do hibernate-core, por isso não precisamos colocar o hibernate-core explicitamente no pom.xml. Ele é incorporado 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 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 Spring ORM

Criamos uma classe DAO muito simples que fornece 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 do Spring que podemos usar essa classe por meio da IoC (Injeção de Dependência) do Spring.
  • Nós utilizamos a anotação JPA @PersistenceContext, que indica a injeção de dependência para um EntityManager. O Spring injeta uma instância apropriada de EntityManager de acordo com a configuração spring.xml.

Classe de Serviço Spring ORM

Nossa classe de serviço simples possui 2 métodos de escrita e 1 método de leitura – 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();

	}

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

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

Nossas classes Java do projeto de exemplo Spring ORM estão prontas, vamos agora olhar para o arquivo de configuração de bean do 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. Primeiro, informamos ao Spring que desejamos usar a varredura de classpath para os componentes Spring (Serviços, DAOs) em vez de defini-los um a um no XML do Spring. Também habilitamos a detecção de anotações do Spring.
  2. Adicionando a fonte de dados, que atualmente é o banco de dados em memória HSQLDB.
  3. Configuramos uma EntityManagerFactory JPA que será usada pela aplicação para obter um EntityManager. O Spring suporta 3 maneiras diferentes de fazer isso, e nós usamos LocalContainerEntityManagerFactoryBean para obter todas as capacidades JPA. Configuramos os atributos do LocalContainerEntityManagerFactoryBean da seguinte forma:
    1. O atributo packagesToScan aponta para o pacote de nossas classes de modelo.
    2. O datasource definido anteriormente no arquivo de configuração do Spring.
    3. O jpaVendorAdapter como Hibernate e a configuração de 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 configuramos o transactionManager que criamos.

Programa de Teste de Exemplo do Spring ORM Hibernate JPA

Nosso projeto de exemplo usando Spring ORM JPA Hibernate está 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 algumas operações de dados
		
		productService.add(new Product(1, "Bulb"));
		productService.add(new Product(2, "Dijone mustard"));
		
		System.out.println("listAll: " + productService.listAll());
		
		//Testar reversão de 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 reversão
		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 de 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 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 a reversão nesta exemplo. Se você executar o programa de teste de exemplo de ORM Spring 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 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.

Baixar Projeto Spring ORM com Transação

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