Spring ORM实战教程:JPA与Hibernate框架及事务管理详解

Spring ORM示例 – JPA,Hibernate,事务(第1部分,共4部分)

欢迎来到Spring ORM示例教程。今天我们将学习使用Hibernate JPA事务管理的Spring ORM示例。我将为您展示一个具备以下特性的非常简单的Spring独立应用示例。

  • 依赖注入(@Autowired注解)
  • JPA EntityManager(由Hibernate提供)
  • 注解式事务方法(@Transactional注解)

Spring ORM示例

在Spring ORM示例中,我使用了内存数据库,因此不需要任何数据库设置(但是您可以在spring.xml数据源部分将其更改为其他任何数据库)。这是一个独立的Spring ORM应用程序,以尽量减少所有依赖关系(但是如果您熟悉Spring,可以通过配置轻松将其更改为Web项目)。

注意:对于基于Spring AOP的事务处理(无需使用@Transactional注解的方法解析方法),请查看此教程:Spring ORM AOP事务管理

下图显示了我们最终的Spring ORM示例项目。让我们逐个了解每个Spring ORM示例项目组件。

Spring ORM依赖的Maven依赖项

下面是我们最终的pom.xml文件,包含了Spring ORM的依赖。在我们的Spring ORM示例中,我们使用了Spring 4和Hibernate 4。

<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,这就是为什么我们不必在pom.xml中明确放置hibernate-core的原因。它通过Maven传递依赖被拉入我们的项目中。
  • 我们还需要JDBC驱动作为数据库访问的依赖。我们使用的是HSQLDB,它包含JDBC驱动和一个可用的内存数据库。

Spring框架的ORM模型类

我们可以在我们的模型Bean中使用标准的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 + "]";
	}

}

我们使用@Entity和@Id JPA注解来将我们的POJO标识为实体,并定义它的主键。

Spring ORM DAO类

Spring ORM DAO类

我们创建了一个非常简单的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根据spring.xml配置注入适当的EntityManager实例。

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。
  • 我们想要使用事务管理,因此方法都使用@Transactional Spring注解进行标注。listAll方法只读取数据库,所以我们将@Transactional注解设置为只读以进行优化。

Spring ORM的示例Bean配置XML

这是文章《Spring ORM示例 – JPA,Hibernate,事务》的第3部分(共4部分)。

内容片段: 我们的Spring ORM示例项目的Java类已经准备好了,现在让我们来看看我们的Spring bean配置文件。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
		">
	
	<!-- 扫描类路径中带有注解的组件,这些组件将被自动注册为Spring bean -->
	<context:component-scan base-package="hu.daniel.hari.learn.spring" />
	<!-- 激活在bean类中检测各种注解的功能,例如:@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>

	<!-- 事务配置 -->
	<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
		<property name="entityManagerFactory" ref="entityManagerFactory" />
	</bean>
	<!-- 启用基于注解的事务行为配置 -->
	<tx:annotation-driven transaction-manager="transactionManager" />

</beans>
  1. 首先,我们告诉Spring我们想要使用类路径扫描Spring组件(Services,DAOs),而不是在Spring XML中逐个定义它们。我们还启用了Spring注解检测。
  2. 添加数据源,目前是HSQLDB内存数据库。
  3. 我们建立了一个JPA EntityManagerFactory,应用程序将使用它来获取一个EntityManager。Spring支持3种不同的方式来实现这一点,我们使用了LocalContainerEntityManagerFactoryBean以获得完整的JPA功能。我们设置了LocalContainerEntityManagerFactoryBean的属性,包括:
    • packagesToScan属性指向我们的模型类包。
    • 在Spring配置文件中先前定义的数据源。
    • 将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容器。在Spring上下文初始化后,我们将获取我们的第一个依赖注入的入口点,即服务类实例。在初始化完成后,ProductDao类引用将被注入到ProductService类中。在获得ProductService实例后,我们可以测试它的方法,所有方法调用都将由于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文件,您可以看到底层发生了什么。参考资料:Spring ORM官方文档。您可以从下面的链接下载最终的Spring ORM JPA Hibernate示例项目,并通过它进行实践,以便更多地学习。

下载带有事务的Spring ORM项目

bannerAds