Spring ORMの例-JPA、Hibernate、Transaction

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の例プロジェクトの各コンポーネントを1つずつ見ていきましょう。

Spring ORM Maven依存関係

以下は、Spring ORM依存関係を持つ最終的なpom.xmlファイルです。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の依存関係としてspring-contextspring-ormです。
  • 私たちは、HibernateのJPA実装としてhibernate-entitymanagerを使用しています。 hibernate-entitymanagerhibernate-coreに依存しているため、pom.xmlに明示的にhibernate-coreを記述する必要はありません。 Mavenのトランジティブ依存関係によって、プロジェクトに引き込まれます。
  • データベースアクセスのためにJDBCドライバーも依存関係として必要です。私たちはHSQLDBを使用しており、その中にJDBCドライバーと動作中のインメモリデータベースが含まれています。

Spring ORMモデルクラス

私たちは、HibernateがJPAの実装を提供しているため、モデルビーンのマッピングに標準の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 + "]";
	}

}

私たちのPOJOをエンティティとして定義し、その主キーを定義するために、@Entity@IdのJPAアノテーションを使用します。

Spring ORM DAOクラス

私たちは、persistとfindALLメソッドを提供する非常にシンプルなDAOクラスを作成します。

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 IoC(依存性注入)を介してこのクラスを使用できることをSpringコンテナに伝えます。
  • 私たちは、JPAの@PersistenceContextアノテーションを使用して、EntityManagerへの依存関係の注入を示しています。Springは、spring.xmlの設定に従って、適切なEntityManagerのインスタンスを注入します。

Spring ORMサービスクラス

私たちのシンプルなサービスクラスには、2つの書き込みメソッド(add、addAll)と1つの読み取りメソッド(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();

	}

}
  • 私たちは、サービスクラスにProductDaoを注入するために、Springの@Autowiredアノテーションを使用しています。
  • トランザクション管理を使用したいので、メソッドには@TransactionalのSpringアノテーションが付けられています。listAllメソッドはデータベースの読み取りのみを行うため、最適化のために@Transactionalアノテーションを読み取り専用に設定しています。

Spring ORMの例のBean構成XML

私たちの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
		">
	
	<!-- 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コンポーネント(サービス、DAO)を1つずつ定義するのではなく、クラスパススキャンを使用するようSpringに伝えます。また、Springのアノテーション検出も有効にしています。
  2. データソースを追加します。現在、HSQLDBインメモリデータベースを使用しています。
  3. アプリケーションがエンティティマネージャを取得するために使用されるJPA EntityManagerFactoryを設定しました。Springはこれを行うための3つの異なる方法をサポートしていますが、私たちはフルのJPA機能を持つLocalContainerEntityManagerFactoryBeanを使用しました。次のようにLocalContainerEntityManagerFactoryBeanの属性を設定しました:
    1. モデルクラスのパッケージを指すpackagesToScan属性。
    2. 前述のデータソースが定義されているSpring構成ファイル。
    3. HibernateとしてのjpaVendorAdapterといくつかのHibernateプロパティの設定。
  4. Spring PlatformTransactionManagerインスタンスをJpaTransactionManagerとして作成します。このトランザクションマネージャは、トランザクションデータアクセスに単一のJPA EntityManagerFactoryを使用するアプリケーションに適しています。
  5. 注釈に基づいたトランザクションの動作の設定を有効にし、作成したトランザクションマネージャを設定します。

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コンテキストが初期化された後、ProductServiceにProductDaoクラスの参照が注入されます。 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]]

2つ目のトランザクションがロールバックされることに注意してください。これが製品リストが変更されなかった理由です。添付されたソースからlog4j.propertiesファイルを使用すると、裏側で何が起こっているかを確認できます。参考文献:https://docs.spring.io/spring/docs/current/spring-framework-reference/html/orm.html最終的なSpring ORM JPA Hibernateの例プロジェクトを以下のリンクからダウンロードして、さらに学習するためにそれを使って遊んでみてください。

Spring ORM with Transaction Projectをダウンロードする

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