Добро пожаловать в Руководство по примерам 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>
- Сначала мы сообщаем Spring, что хотим использовать сканирование classpath для компонентов Spring (сервисов, DAO), а не определять их один за другим в spring xml. Мы также включили обнаружение аннотаций Spring.
- Добавление источника данных, который в настоящее время является базой данных в памяти HSQLDB.
- Мы настроили
EntityManagerFactory
JPA, который будет использоваться приложением для получения EntityManager. Spring поддерживает 3 различных способа сделать это, мы использовалиLocalContainerEntityManagerFactoryBean
для полной поддержки JPA. Мы установили атрибутыLocalContainerEntityManagerFactoryBean
следующим образом:- атрибут packagesToScan, который указывает на пакет наших классов моделей.
- источник данных, определенный ранее в конфигурационном файле Spring.
- jpaVendorAdapter как Hibernate и установка некоторых свойств Hibernate.
- Мы создаем экземпляр Spring PlatformTransactionManager как JpaTransactionManager. Этот менеджер транзакций подходит для приложений, которые используют единственный JPA EntityManagerFactory для доступа к транзакционным данным.
- Мы включаем настройку транзакционного поведения на основе аннотаций и устанавливаем созданный нами 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 по следующей ссылке и поиграться с ним, чтобы узнать больше.
Source:
https://www.digitalocean.com/community/tutorials/spring-orm-example-jpa-hibernate-transaction