Пример Spring ORM – JPA, Hibernate, Transaction

Добро пожаловать в Руководство по примерам Spring ORM. Сегодня мы рассмотрим пример использования Spring ORM с управлением транзакциями Hibernate JPA. Я покажу вам очень простой пример самостоятельного приложения Spring со следующими особенностями.

  • Внедрение зависимостей (аннотация @Autowired)
  • EntityManager JPA (предоставлен Hibernate)
  • Аннотированные транзакционные методы (аннотация @Transactional)

Пример Spring ORM

Я использовал базу данных в памяти для примера Spring ORM, так что нет необходимости в настройке базы данных (но вы можете изменить ее на любую другую базу данных в разделе datasource файла spring.xml). Это автономное приложение Spring ORM для минимизации всех зависимостей (но вы легко можете изменить его на веб-проект, настроив, если ознакомитесь с Spring). ПРИМЕЧАНИЕ: Для подхода к разрешению метода на основе Spring AOP (без аннотации @Transactional) пожалуйста, обратитесь к этому учебнику: Управление транзакциями Spring ORM AOP. Ниже показана окончательная структура проекта на примере Spring ORM. Давайте рассмотрим каждый из компонентов проекта на примере Spring ORM по очереди.

Зависимости Maven Spring ORM

Ниже представлен наш окончательный файл pom.xml с зависимостями Spring ORM. Мы использовали Spring 4 и Hibernate 4 в нашем примере 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>
  • Нам нужны spring-context и spring-orm в качестве зависимостей Spring.
  • Мы используем hibernate-entitymanager для Hibernate в качестве реализации JPA. hibernate-entitymanager зависит от hibernate-core, поэтому нам не нужно явно указывать hibernate-core в pom.xml. Он подтягивается в наш проект через транзитивные зависимости Maven.
  • Нам также нужен JDBC-драйвер в качестве зависимости для доступа к базе данных. Мы используем HSQLDB, который содержит JDBC-драйвер и работающую в памяти базу данных.

Класс модели Spring ORM

Мы можем использовать стандартные аннотации JPA для маппинга в наших модельных бинах, потому что Hibernate предоставляет реализацию 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 + "]";
	}

}

Мы используем аннотации JPA @Entity и @Id, чтобы класс POJO был определен как сущность и чтобы определить его первичный ключ.

Класс DAO Spring ORM

Мы создаем очень простой класс DAO, который предоставляет методы persist и 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 – это аннотация Spring, которая сообщает контейнеру Spring, что мы можем использовать этот класс через внедрение зависимостей Spring IoC.
  • Мы используем аннотацию JPA @PersistenceContext, которая указывает на внедрение зависимостей в EntityManager. Spring внедряет правильный экземпляр EntityManager в соответствии с конфигурацией spring.xml.

Класс службы Spring ORM

Наш простой служебный класс имеет 2 метода записи и 1 метод чтения – add, addAll и 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();

	}

}
  • Мы используем аннотацию Spring @Autowired для внедрения ProductDao в наш класс службы.
  • Мы хотим использовать управление транзакциями, поэтому методы аннотированы аннотацией Spring @Transactional. Метод listAll только читает базу данных, поэтому мы устанавливаем аннотацию @Transactional в режим “только чтение” для оптимизации.

Пример конфигурации бина Spring ORM XML

Наши классы java примера Spring ORM уже готовы, давайте посмотрим на наш файл конфигурации бина 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. Сначала мы сообщаем Spring, что хотим использовать сканирование classpath для компонентов Spring (сервисов, DAO), а не определять их один за другим в spring xml. Мы также включили обнаружение аннотаций Spring.
  2. Добавление источника данных, который в настоящее время является базой данных в памяти HSQLDB.
  3. Мы настроили EntityManagerFactory JPA, который будет использоваться приложением для получения EntityManager. Spring поддерживает 3 различных способа сделать это, мы использовали LocalContainerEntityManagerFactoryBean для полной поддержки JPA. Мы установили атрибуты LocalContainerEntityManagerFactoryBean следующим образом:
    1. атрибут packagesToScan, который указывает на пакет наших классов моделей.
    2. источник данных, определенный ранее в конфигурационном файле Spring.
    3. jpaVendorAdapter как Hibernate и установка некоторых свойств Hibernate.
  4. Мы создаем экземпляр Spring PlatformTransactionManager как JpaTransactionManager. Этот менеджер транзакций подходит для приложений, которые используют единственный JPA EntityManagerFactory для доступа к транзакционным данным.
  5. Мы включаем настройку транзакционного поведения на основе аннотаций и устанавливаем созданный нами transactionManager.

Пример тестовой программы Spring ORM Hibernate JPA

Наш пример проекта Spring ORM JPA Hibernate готов, поэтому давайте напишем тестовую программу для нашего приложения.

public class SpringOrmMain {
	
	public static void main(String[] args) {
		
		//Создайте контекст приложения Spring
		ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/spring.xml");
		
		//Получите сервис из контекста (зависимость сервиса (ProductDAO) автоматически внедряется в ProductService)
		ProductService productService = ctx.getBean(ProductService.class);
		
		//Выполните операции с данными
		
		productService.add(new Product(1, "Bulb"));
		productService.add(new Product(2, "Dijone mustard"));
		
		System.out.println("listAll: " + productService.listAll());
		
		//Проверьте откат транзакции (дублированный ключ)
		
		try {
			productService.addAll(Arrays.asList(new Product(3, "Book"), new Product(4, "Soap"), new Product(1, "Computer")));
		} catch (DataAccessException dataAccessException) {
		}
		
		//Проверьте список элементов после отката
		System.out.println("listAll: " + productService.listAll());
		
		ctx.close();
		
	}
}

Вы можете увидеть, насколько легко мы можем запустить контейнер Spring из главного метода. Мы получаем нашу первую точку входа с внедрением зависимостей, экземпляр служебного класса. Ссылка на класс ProductDao внедряется в класс ProductService после инициализации контекста Spring. После получения экземпляра ProducService мы можем тестировать его методы; все вызовы методов будут транзакционными из-за механизма прокси Spring. Мы также тестируем откат в этом примере. Если вы запустите тестовую программу выше с примером spring ORM, вы получите следующие журналы.

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

Обратите внимание, что вторая транзакция откатывается, вот почему список продуктов не изменился. Если вы используете файл log4j.properties из прикрепленного источника, вы можете увидеть, что происходит под капотом. Ссылки: https://docs.spring.io/spring/docs/current/spring-framework-reference/html/orm.html Вы можете загрузить проект примера Spring ORM JPA Hibernate по следующей ссылке и поиграться с ним, чтобы узнать больше.

Загрузите проект Spring ORM с транзакциями

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