דוגמת Spring ORM – JPA, Hibernate, Transaction

ברוכים הבאים להדרכת דוגמה של Spring ORM. היום נתחיל להבין דוגמה של Spring ORM באמצעות ניהול עסקאות Hibernate JPA. אנסה להראות לך דוגמה פשוטה מאוד של אפליקציה עצמאית של Spring עם התכונות הבאות.

  • הזרקת תלות (@Autowired אנוטציה)
  • JPA EntityManager (מסופק על ידי 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 כמימוש JPA עבור Hibernate. hibernate-entitymanager תלוי ב־hibernate-core, ולכן אנו אינו צריכים לכלול את hibernate-core בקובץ ה־pom.xml באופן בירורי. הוא מתווסף לפרויקט שלנו דרך תלות מייבן.
  • נדרש גם מנהג 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 + "]";
	}

}

אנו משתמשים ב־@Entity ו־@Id אנוטציות של JPA כדי לסמן את POJO שלנו כיישות ולהגדיר את המפתח הראשי שלו.

דגם 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 שאנו יכולים להשתמש בכיתה זו דרך IoC של Spring (הטמעת תלות).
  • אנו משתמשים ב- JPA באמצעות האנוטציה @PersistenceContext המציינת הזרמת תלות ל- EntityManager. Spring מביא לתוך התוכנית מופע מתאים של EntityManager על פי התצורה של spring.xml.

מחלקת שירות ORM של Spring

המחלקה הפשוטה שלנו מכילה 2 שיטות כתיבה ושיטה קריאה אחת – 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();

	}

}
  • אנו משתמשים באנוטציה @Autowired של Spring כדי להזרים את ProductDao במחלקת השירות שלנו.
  • אנו רוצים להשתמש בניהול עסקאות, לכן השיטות מודגשות באנוטציה @Transactional של Spring. שיטת ה- listAll קוראת בלבד מהמסד נתונים ולכן אנו מגדירים את האנוטציה @Transactional כקריאה בלבד למטרת אופטימיזציה.

דוגמת תצורת חרוזת Spring ORM XML

קבצי ה-Java של דוגמת ORM שלנו עבור 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 שרוצים להשתמש בסריקה של נתיב ה-Classpath עבור רכיבי Spring (שירותים, DAOs) במקום להגדיר אותם אחד-אחד ב- spring xml. כמו כן הפעלנו גילוי של האנוטציות של Spring.
  2. הוספת מקור נתונים, שכרגע הוא מסד נתונים בזיכרון HSQLDB.
  3. הגדרנו JPA EntityManagerFactory שישמש על ידי האפליקציה לקבלת EntityManager. Spring תומך ב-3 דרכים שונות לעשות זאת, אנו בחרנו ב-LocalContainerEntityManagerFactoryBean עבור יכולות JPA מלאות. הגדרנו את תכונות ה-LocalContainerEntityManagerFactoryBean כך:
    1. תכונת packagesToScan שמצביעה על תיקיית המודל שלנו.
    2. מקור נתונים שהוגדר מראש בקובץ התצורה של Spring.
    3. jpaVendorAdapter כהגדרת Hibernate והגדרת כמה מאפייני 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 ממתודת main. אנו מקבלים את נקודת הכניסה הראשונה להתנקת תלות, מופע של מחלקת השירות. רישום מחלקת ProductDao מופעל לתוך מחלקת ProductService לאחר שההקשר של Spring מאותחל. לאחר שקיבלנו את מופע של ProducService, נוכל לבדוק את השיטות שלו, כל קריאת שיטה תהיה transactional עקב מנגנון ה-proxy של Spring. אנו גם בודקים ביטול עסקה בדוגמה זו. אם תריץ את תכנית המבחן של הדוגמה של ORM של Spring שמוצגת למעלה, תקבל את הלוגים הבאים.

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