Spring – 26 – Declarative Transaction JPA & Hibernate

Merhaba Arkadaslar.
Onceki bolumde Programmatic Transaction kavramini inceledik ve DataSourceTransactionManager i kullandik. Bu bolumde bir diger yaklasim olan Declarative Transaction ‘i inceleyecegiz , Native Hibernate ve JPA/EclipseLink entegrasyonu uzerinden orneklerimizi gerceklestirecegiz.

Spring , Declarative Transaction icin destek saglar.

Onceki bolumlerde Spring & JPA entegrasyonunu gerceklestirmistik.

Spring – 21 – JPA – 01
Spring – 22 – JPA – 02

Bununla birlikte JPA konusunda daha detayli bilgi icin ;
EclipseLink/Hibernate JPA

JPA/EclipseLink Example

Domain Layer / Model

Address.java

package _31.jpa.declarative.transaction.model;

import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class Address {

	@Id
	private int addressId;
	private String street;
	private String zipcode;
	private String city;
        //getters and setters
        // constructors
        // toString
.....
}

Customer.java

package _31.jpa.declarative.transaction.model;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToOne;

@Entity
public class Customer {

	@Id
	private int id;
	private String name;
	private String surname;

	@OneToOne
	private Address address;
        //getters and setters
        // constructors
        // toString
.....
}

DAO(Data Access Object) Design & Implementation

Bu kisimda basit olarak persist ve createQuery metodunu kullanacagiz.

AddressDAOImpl.java

@PersistenceContext annotationi EntityManager instance variable’i uzerine ekledik. Boylece EntityManager icin inject islemi gerceklestirilecektir.

persist metodu yardimiyla kayit islemini gerceklestiriyoruz. Dikkat edecek olursak Transaction begin ve commit metotlarini kullanmadik!
Zaten @PersistenceContext ile birlikte kullanmaya calisirsak hata aliriz ;
java.lang.IllegalStateException: Not allowed to create transaction on shared EntityManager – use Spring transactions or EJB CMT instead

package _31.jpa.declarative.transaction.dao;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.springframework.stereotype.Repository;

import _31.jpa.declarative.transaction.model.Address;

@Repository
public class AddressDAOImpl implements AddressDAO {

	@PersistenceContext
	private EntityManager entityManager;

	@Override
	public void insertAddress(Address address) {
		entityManager.persist(address);
	}
}

CustomerDAOImpl.java

package _31.jpa.declarative.transaction.dao;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import org.springframework.stereotype.Repository;

import _31.jpa.declarative.transaction.model.Customer;

@Repository
public class CustomerDAOImpl implements CustomerDAO {

	@PersistenceContext
	private EntityManager entityManager;

	private static final String ALL_CUSTOMERS = "Select c from Customer c";

	@Override
	public void insertCustomer(Customer customer) {
		entityManager.persist(customer);
	}

	@Override
	public List<Customer> findAllCustomers() {
		TypedQuery<Customer> query = entityManager.createQuery(ALL_CUSTOMERS, Customer.class);
		return query.getResultList();
	}

}

Service

DAO Layer i kullanmak icin @Autowired annotation ile inject islemini gerceklestiriyoruz.
Burada dikkat edecegimiz nokta @Transactional annotation’idir.
insertCustomerData metodu icerisinde insertCustomer ve insertAddress metotlarini cagiriyoruz , Customer ve Address tablosuna kayit atiyoruz.
Burada @Transactional annotation’ini kullandigimiz icin bu metot boyunca transaction yonetimi gerceklesmektedir. Bu nedenle DAO Layer da begin commit metotlarini kullanmadik.
Transaction yonetiminin Service Layer’da olmasi daha uygun olacaktir.

CustomerServiceImpl.java

package _31.jpa.declarative.transaction.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import _31.jpa.declarative.transaction.dao.AddressDAO;
import _31.jpa.declarative.transaction.dao.CustomerDAO;
import _31.jpa.declarative.transaction.model.Customer;

@Service
public class CustomerServiceImpl implements CustomerService {

	@Autowired
	private CustomerDAO customerDAO;

	@Autowired
	private AddressDAO addressDao;

	@Transactional
	@Override
	public void insertCustomerData(Customer customer) {
		customerDAO.insertCustomer(customer);
		addressDao.insertAddress(customer.getAddress());
	}

	@Transactional(readOnly = true)
	@Override
	public List<Customer> listCustomers() {
		return customerDAO.findAllCustomers();

	}

}

XML Configuration

31.jpa.declarative.transaction.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
     http://www.springframework.org/schema/beans/spring-beans.xsd
     http://www.springframework.org/schema/tx
	 http://www.springframework.org/schema/tx/spring-tx.xsd
     http://www.springframework.org/schema/context
     http://www.springframework.org/schema/context/spring-context.xsd">


	<!--Scan @Service , @Repository ... annotations -->
	<context:component-scan base-package="_31.jpa.declarative.transaction" />


	<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="location">
			<value>jdbc/jdbc.properties</value>
		</property>
	</bean>

	<bean id="dataSourceId"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="${jdbc.driverClassName}" />
		<property name="url" value="${jdbc.url}" />
		<property name="username" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
	</bean>

	<bean id="entityManagerFactory"
		class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">

		<property name="dataSource" ref="dataSourceId" />
		<property name="jpaVendorAdapter">
			<bean
				class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter" />
		</property>
		<property name="packagesToScan" value="_31.jpa.declarative.transaction" />

		<property name="jpaProperties">
			<props>
				<prop key="eclipselink.ddl-generation">drop-and-create-tables</prop>
				<prop key="eclipselink.logging.level">OFF</prop>
			</props>
		</property>

		<property name="jpaPropertyMap">
			<map>
				<entry key="eclipselink.weaving" value="false" />
			</map>
		</property>
	</bean>


	<!-- Transactions -->
	
	<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
		<property name="entityManagerFactory" ref="entityManagerFactory" />
	</bean>
	
	<tx:annotation-driven transaction-manager="transactionManager"
		proxy-target-class="true" />

</beans>

EntityManagerFactory ve JDBC konfigurasyonlarini onceki bolumlerde inceledik.

xmlns:tx=”http://www.springframework.org/schema/tx” ve
xsi:schemaLocation olarak ilgili tanimlamalari eklememiz gereklidir !
http://www.springframework.org/schema/tx 
http://www.springframework.org/schema/tx/spring-tx.xsd

Burada dikkat edecek olursak Transaction Manager olarak JpaTransactionManager i kullaniyoruz.
@Transactional annotation’ini kullanabilmek icin < tx : annotation> tanimlamasini kullaniyoruz.

	<!-- Transactions -->
	<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
		<property name="entityManagerFactory" ref="entityManagerFactory" />
	</bean>
	<tx:annotation-driven transaction-manager="transactionManager"
		proxy-target-class="true" />

Run Application

Ornegimizi inceleyecek olursak ; 3 tane Customer ve Address objesi olusturup kaydetmeye calisiyoruz.
Burada dikkat edecegimiz nokta address3 icin id degerini 2 olarak verdik , id degeri unique olmasi gerektigi icin (primary key)
ORA-00001: unique constraint nedeniyle java.sql.SQLIntegrityConstraintViolationException a neden olacaktir.

Burada insertCustomerData metodu @Transactional olarak tanimlandigi icin Customer icin kaydin basarili olmasi yeterli olmayacaktir. Ilgili Address kaydi exceptiona neden olacaktir dolayisiyla roll-back olacagi icin Customer kaydi da eklenmeyecektir.

SpringJPADeclarativeTransactionTest.java

package _31.jpa.declarative.transaction.test;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import _31.jpa.declarative.transaction.model.Address;
import _31.jpa.declarative.transaction.model.Customer;
import _31.jpa.declarative.transaction.service.CustomerService;
import _31.jpa.declarative.transaction.service.CustomerServiceImpl;

public class SpringJPADeclarativeTransactionTest {

	public static void main(String[] args) {

		ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
				"31.jpa.declarative.transaction.xml");

		// get CustomerService bean
		CustomerService customerService = context.getBean(CustomerServiceImpl.class);

		// prepare Customer and Address data.
		Customer customer = new Customer(1, "Levent", "Erguder");
		Address address = new Address(1, "Java Street", "34000", "Istanbul");
		customer.setAddress(address);

		//
		Customer customer2 = new Customer(2, "Orcun", "Erpis");
		Address address2 = new Address(2, "Bakanliklar Street", "06000", "Ankara");
		customer2.setAddress(address2);

		Customer customer3 = new Customer(3, "Hakan", "Gencel");
		// duplicate Address id , throw exception
		// customers3 record will be rollbacked too.
		Address address3 = new Address(2, "Alemdag Road", "34000", "Istanbul");
		customer3.setAddress(address3);

		customerService.insertCustomerData(customer);
		customerService.insertCustomerData(customer2);

		try {
			customerService.insertCustomerData(customer3);
		} catch (Exception e) {
			System.out.println("Rollback...");
			System.out.println(e.getMessage());
		}

		//
		System.out.println("Customer List : ");
		for (Customer customerRecord : customerService.listCustomers()) {
			System.out.println(customerRecord);
		}

		context.close();

	}
}

Ornegimizi calistirdigimizda ;

Rollback...
Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.6.1.v20150916-55dc7c3): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLIntegrityConstraintViolationException: ORA-00001: unique constraint (LEVENT.SYS_C0022062) violated

Error Code: 1
Call: INSERT INTO ADDRESS (ADDRESSID, CITY, STREET, ZIPCODE) VALUES (?, ?, ?, ?)
	bind => [4 parameters bound]
Query: InsertObjectQuery(Address [addressId=2, street=Alemdag Road, zipcode=34000, city=Istanbul])
Customer List : 
Customer [id=1, name=Levent, surname=Erguder, address=Address [addressId=1, street=Java Street, zipcode=34000, city=Istanbul]]
Customer [id=2, name=Orcun, surname=Erpis, address=Address [addressId=2, street=Bakanliklar Street, zipcode=06000, city=Ankara]]

Output u inceleyecek olursak ;

customerService.insertCustomerData(customer3);

java.sql.SQLIntegrityConstraintViolationException a neden olacaktir. catch blogunda yakaladik ve e.getMessage() metodu calisti.
Sonrasinda Customer icin query calismaktadir ve ilk 2 kaydin basariyla eklendigini gorebiliriz.

Hibernate Example

Spring Hibernate entegrasyonunu onceki bolumlerde gerceklestirdik. Ilgili dependency tanimlini eklemistik.

Spring – 23 – Hibernate

Ilgili ornegi _32 paketinde bulabilirsiniz. Yukaridaki ornegimizden farkli olarak su kisimlarda degisiklikler olacak ;

CustomerDAOImpl.java

package _32.hibernate.declarative.transaction.dao;

import java.util.List;

import javax.persistence.TypedQuery;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import _32.hibernate.declarative.transaction.model.Customer;

@Repository
public class CustomerDAOImpl implements CustomerDAO {

	private static final String ALL_CUSTOMERS = "Select c from Customer c";

	@Autowired
	private SessionFactory sessionFactory;
	
	private Session getCurrentSession() {
		return sessionFactory.getCurrentSession();
	}

	@Override
	public void insertCustomer(Customer customer) {
		Session session = getCurrentSession();
		session.persist(customer);
	}

	@Override
	public List<Customer> findAllCustomers() {
		Session session = getCurrentSession();
		TypedQuery<Customer> query = session.createQuery(ALL_CUSTOMERS, Customer.class);
		return query.getResultList();
	}

}

AddressDAOImpl.java

package _32.hibernate.declarative.transaction.dao;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import _32.hibernate.declarative.transaction.model.Address;

@Repository
public class AddressDAOImpl implements AddressDAO{

	@Autowired
	private SessionFactory sessionFactory;
	
	private Session getCurrentSession() {
		return sessionFactory.getCurrentSession();
	}

	@Override
	public void insertAddress(Address address) {
		Session session = getCurrentSession();
		session.persist(address);
	}
}

CustomerServiceImpl.java

package _32.hibernate.declarative.transaction.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import _32.hibernate.declarative.transaction.dao.AddressDAO;
import _32.hibernate.declarative.transaction.dao.CustomerDAO;
import _32.hibernate.declarative.transaction.model.Customer;



@Service
public class CustomerServiceImpl implements CustomerService{

	@Autowired
	private CustomerDAO customerDAO;

	@Autowired
	private AddressDAO addressDao;

	@Transactional
	@Override
	public void insertCustomerData(Customer customer) {
		customerDAO.insertCustomer(customer);
		addressDao.insertAddress(customer.getAddress());
	}

	@Transactional
	@Override	
	public List<Customer> listCustomers() {
		return customerDAO.findAllCustomers();
	}

}

32.hibernate.declarative.transaction.xml

LocalSessionFactoryBean ve HibernateTransactionManager bean’lerini kullanalim.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
     http://www.springframework.org/schema/beans/spring-beans.xsd
     http://www.springframework.org/schema/tx
	 http://www.springframework.org/schema/tx/spring-tx.xsd
     http://www.springframework.org/schema/context
     http://www.springframework.org/schema/context/spring-context.xsd ">


	<!--Scan @Service , @Repository ... annotations -->
	<context:component-scan base-package="_32.hibernate.declarative.transaction" />


	<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="location">
			<value>jdbc/jdbc.properties</value>
		</property>
	</bean>

	<bean id="dataSourceId"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="${jdbc.driverClassName}" />
		<property name="url" value="${jdbc.url}" />
		<property name="username" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
	</bean>


	<bean id="sessionFactory"
		class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">

		<property name="dataSource" ref="dataSourceId" />
		<property name="packagesToScan" value="_32.hibernate.declarative.transaction.model" />

		<property name="hibernateProperties">
			<props>
				<prop key="hibernate.show_sql">false</prop>
				<prop key="hibernate.hbm2ddl.auto">create</prop>			
			</props>
		</property>

	</bean>

	<bean id="transactionManager"
		class="org.springframework.orm.hibernate5.HibernateTransactionManager">
		<property name="sessionFactory" ref="sessionFactory" />
	</bean>

	<tx:annotation-driven transaction-manager="transactionManager"
		proxy-target-class="true" />

</beans>

Ornegimizi calistirdigimizda ;

ERROR: HHH000346: Error during managed flush [org.hibernate.exception.ConstraintViolationException: could not execute statement]
Rollback...
could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
Customer List : 
Nov 18, 2016 11:58:00 PM org.hibernate.hql.internal.QueryTranslatorFactoryInitiator initiateService
INFO: HHH000397: Using ASTQueryTranslatorFactory
Customer [id=1, name=Levent, surname=Erguder, address=Address [addressId=1, street=Java Street, zipcode=34000, city=Istanbul]]
Customer [id=2, name=Orcun, surname=Erpis, address=Address [addressId=2, street=Bakanliklar Street, zipcode=06000, city=Ankara]]

customer address

Github kaynak dosyalar/ source folder
leventerguder/injavawetrust-spring-tutorial

Yazimi burada sonlandiriyorum.
Herkese Bol Javali Gunler dilerim.
Be an oracle man , import java.*;
Levent Erguder
OCP, Java SE 6 Programmer
OCE, Java EE 6 Web Component Developer

Print Friendly, PDF & Email

Leave a Reply

Your email address will not be published. Required fields are marked *