Spring – 25 – Programmatic Transaction

Merhaba Arkadaslar
Onceki bolumde Transaction kavramini inceledik .
Bu bolumde Programmatic Transaction konusunu inceleyecegiz ve ornek bir uygulama yapacagiz.

Spring , hem declarative transaction hem de programmatic transaction icin destek saglar.
Programmactic  transaction yaklasiminda transaction yonetimini kaynak kod icerisinde/metot icerisinde yapariz.
Bu durum genel olarak esneklik saglayabilir bununla birlikte bakimi daha zordur.

PlatformTransactionManager

PlatformTransactionManager arabirimi , TransactionDefinition ve TransactionStatus arabirimlerini kullanarak transaction’lari yonetir. (manages)

Spring , PlatformTransactionManager arabirimini uygulayan bir cok sinif destekler.

  • DataSourceTransactionManager – JDBC
  • JpaTransactionManager – JPA
  • HibernateTransactionManager – Hibernate
  • JdoTransactionManager – JDO
  • JmsTransactionManager – JMS
  • JtaTransactionManager – JTA

spring transaction manager

PlatformTransactionManager.java

package org.springframework.transaction;

public interface PlatformTransactionManager {

    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;

    void commit(TransactionStatus status) throws TransactionException;
    
    void rollback(TransactionStatus status) throws TransactionException;

}

PlatformTransactionManager arabiriminde core method getTransaction metodudur ;
Dikkat edecek olursak bu metot parametre olarak TransactionDefinition almaktadir ve geriye TransactionStatus donmektedir.

TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;

TransactionDefinition

TransactionDefinition arabirimi transaction’a ait ozellikleri yonetir.

TransactionDefinition.java

package org.springframework.transaction;
 
import java.sql.Connection;
 
public interface TransactionDefinition {
 
    // Variable declaration statements omitted
 
    int getPropagationBehavior();
 
    int getIsolationLevel();
 
    int getTimeout();
 
    boolean isReadOnly();
 
    String getName();
 
}

TransactionStatus

TransactionStatus arabirimi , transaction’larin calistirilmasina (execution) dair kontrolleri icerir.
Transaction yeni mi , read-only mi gibi kontrolleri yapabilir.

TransactionStatus.java

package org.springframework.transaction;
 
public interface TransactionStatus extends SavepointManager {
 
    boolean isNewTransaction();
 
    boolean hasSavepoint();
 
    void setRollbackOnly();
 
    boolean isRollbackOnly();
 
    void flush();
 
    boolean isCompleted();
 
}

Domain Object / Model

Address.java

package _30.jdbc.programmatic.transaction.model;

public class Address {

	private int addressId;
	private String street;
	private String zipcode;
	private String city;
        //constructors
        //getters and setters
        //toString

Person sinifini inceleyecek olursak Person HAS-A Address durumu soz konusudur. (Address tipinde bir instan variable tanimli)

Person.java

package _30.jdbc.programmatic.transaction.model;

public class Person {

	private int id;
	private String name;
	private String surname;
	private int birthYear;
	private Address address;

        //constructors
        //getters and setters
        //toString

DAO(Data Access Object) Design & Implementation

PersonDAO.java

package _30.jdbc.programmatic.transaction.dao;

import _30.jdbc.programmatic.transaction.model.Address;
import _30.jdbc.programmatic.transaction.model.Person;

public interface PersonDAO {

	public void insert(Person person);	
	public void insertAddress(Address address);
	public void updatePersonAddress(int id, int addressId);	
}

PersonDAOImpl.java

package _30.jdbc.programmatic.transaction.dao;

import java.util.HashMap;
import java.util.Map;

import javax.sql.DataSource;

import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import _30.jdbc.programmatic.transaction.model.Address;
import _30.jdbc.programmatic.transaction.model.Person;

public class PersonDAOImpl implements PersonDAO {

	private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
	private PlatformTransactionManager transactionManager;

	private final static String INSERT_PERSON = "insert into person (id, name, surname,birthYear) values (:id , :name , :surname , :birthYear)";
	private final static String INSERT_ADDRESS = "insert into address(addressId,street,zipcode,city) values(:addressId, :street , :zipcode , :city)";
	private final static String SET_PERSON_ADDRESS = "update person set addressId=:addressId where id=:id";

	public void setDataSource(DataSource dataSource) {
		namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
	}

	public void setTransactionManager(PlatformTransactionManager transactionManager) {
		this.transactionManager = transactionManager;
	}

	private TransactionStatus getTransactionStatus() {
		TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
		TransactionStatus status = transactionManager.getTransaction(transactionDefinition);
		return status;
	}

	@Override
	public void insert(Person person) {
		
		TransactionStatus status = getTransactionStatus();
		Map<String, Object> params = new HashMap<String, Object>();
		params.put("id", person.getId());
		params.put("name", person.getName());
		params.put("surname", person.getSurname());
		params.put("birthYear", person.getBirthYear());
		try {

			namedParameterJdbcTemplate.update(INSERT_PERSON, params);
			// logging
			System.out.println("Person is inserted... " + person);
			//
			
			insertAddress(person.getAddress());
			updatePersonAddress(person.getId(), person.getAddress().getAddressId());
			
			// commit
			transactionManager.commit(status);

		} catch (Exception e) {
			System.out.println(e.getMessage());
			System.out.println("rollback...");
			transactionManager.rollback(status);
		}
	}

	@Override
	public void insertAddress(Address address) {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put("addressId", address.getAddressId());
		params.put("street", address.getStreet());
		params.put("zipcode", address.getZipcode());
		params.put("city", address.getCity());
		namedParameterJdbcTemplate.update(INSERT_ADDRESS, params);
		// logging
		System.out.println("Address is inserted... " + address);
	}

	@Override
	public void updatePersonAddress(int id, int addressId) {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put("id", id);
		params.put("addressId", addressId);
		namedParameterJdbcTemplate.update(SET_PERSON_ADDRESS, params);
	}

}

Onceki bolumlerde NamedParameterJdbcTemplate sinifini kullanmistik.
PlatformTransactionManager arabirimini kullaniyoruz. XML konfigurasyonumuz ile inject islemini gerceklestirecegiz.

insert metodunu incelecek olursak once Person tablosuna kayit ekliyoruz ;

namedParameterJdbcTemplate.update(INSERT_PERSON, params);

daha sonrasinda insertAddress metodunu cagiriyoruz ve Address tablosuna kayit ekliyoruz.

Sonraki adimda , Person tablosuna ekledigimiz kayit icin addressId degerini guncelliyoruz.(null olarak eklemistik)

Son olarak commit ediyoruz.

transactionManager.commit(status);

Herhangi bir problem oldugunda Rollback islemini gerceklestiriyoruz.

transactionManager.rollback(status);

Create Table

Person ve Address tablosunu olusturalim ;
Oracle icin ;

CREATE TABLE ADDRESS 
(
  ADDRESSID NUMBER NOT NULL 
, STREET VARCHAR2(20) 
, ZIPCODE VARCHAR2(5) 
, CITY VARCHAR2(10) 
, CONSTRAINT ADDRESS_PK PRIMARY KEY 
  (
    ADDRESSID 
  ) 
);


CREATE TABLE PERSON 
(
  ID NUMBER NOT NULL 
, NAME VARCHAR2(20) 
, SURNAME VARCHAR2(20) 
, BIRTHYEAR NUMBER 
, ADDRESSID NUMBER 
, CONSTRAINT PERSON_PK PRIMARY KEY 
  (
    ID 
  )

);

ALTER TABLE PERSON
ADD CONSTRAINT PERSON_FK1 FOREIGN KEY
(
  ADDRESSID 
)
REFERENCES ADDRESS
(
  ADDRESSID 
)


MySQL icin ;

CREATE TABLE `springjdbc`.`address` (
  `ADDRESSID` INT NOT NULL COMMENT '',
  `STREET` VARCHAR(20) NULL COMMENT '',
  `ZIPCODE` VARCHAR(5) NULL COMMENT '',
  `CITY` VARCHAR(10) NULL COMMENT '',
  PRIMARY KEY (`ADDRESSID`)  COMMENT '');


CREATE TABLE `springjdbc`.`person` (
  `ID` INT NOT NULL COMMENT '',
  `NAME` VARCHAR(20) NULL COMMENT '',
  `SURNAME` VARCHAR(20) NULL COMMENT '',
  `BIRTHYEAR` INT NULL COMMENT '',
  `ADDRESSID` INT NULL COMMENT '',
  PRIMARY KEY (`ID`)  COMMENT '');


ALTER TABLE `springjdbc`.`person`
ADD FOREIGN KEY (ADDRESSID)
REFERENCES `springjdbc`.`address`(ADDRESSID)

XML Configuration

Onceki bolumlerde benzer konfigurasyonu gerceklestirmistik. Burada ek olarak DataSourceTransactionManager sinifi icin bean tanimi gerceklestiriyoruz.

30.jdbc.programmatic.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"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

	<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="personDAOImplId" class="_30.jdbc.programmatic.transaction.dao.PersonDAOImpl">
		<property name="dataSource" ref="dataSourceId" />
		<property name="transactionManager" ref="transactionManagerId" />
	</bean>

	<bean id="transactionManagerId"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSourceId" />
	</bean>
</beans>

Run Application

JdbcProgrammaticTransactionTest.java

package _30.jdbc.programmatic.transaction.test;

import java.sql.SQLException;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import _30.jdbc.programmatic.transaction.model.Address;
import _30.jdbc.programmatic.transaction.model.Person;
import _30.jdbc.programmatic.transaction.service.PersonDAOService;

public class JdbcProgrammaticTransactionTest {
	public static void main(String[] args) throws SQLException {
		
		ApplicationContext ctx = new ClassPathXmlApplicationContext(
				"30.jdbc.programmatic.transaction.xml");

		PersonDAOService pService = ctx.getBean(PersonDAOService.class);

		// create Person object
		Person person = pService.createPerson(1, "Levent", "Erguder", 1989);

		// create Address object
		Address address = pService.createAddress(100, "Java Street", "34000", "Istanbul");

		// setAddress
		person.setAddress(address);

		// insert
		pService.insert(person);

		Person person2 = pService.createPerson(2, "Name", "Surname", 1989);
		Address address2 = pService.createAddress(101, "new street", "throw exception!", "my city");
		person2.setAddress(address2);

		pService.insert(person2);

		((ClassPathXmlApplicationContext) ctx).close();

	}
}

Kodu inceleyecek olursak , createPerson ve createAddress metotlariyla ilgili Person ve Address objelerini olusturuyoruz. insert metodunu cagirip kayit ekliyoruz.

Burada dikkat etmemiz gereken nokta 2.kayit sirasinda hata alacagiz.
Address objesini olusturken zipcode icin 5 karakterden uzun bir deger girdik.
Address tablosunda ise ZIPCODE VARCHAR2(5) olacak sekilde olusturmustuk.

Person objesi icin herhangi bir problemli durum bulunmamaktadir. Person kaydi basariyla eklenecektir , fakat sonrasinda Address kaydinda problem olacagi icin rollback islemi yapilacaktir.Bunun sonucunda eklenen Person kaydi da geriye alinacaktir.

Ornegimizi calistirdigimizda;

INFO: Loaded JDBC driver: oracle.jdbc.driver.OracleDriver
Person is inserted... Person [id=1, name=Levent, surname=Erguder, birthYear=1989, address=Address [addressId=100, street=Java Street, zipcode=34000, city=Istanbul]]
Address is inserted... Address [addressId=100, street=Java Street, zipcode=34000, city=Istanbul]
Person is inserted... Person [id=2, name=Name, surname=Surname, birthYear=1989, address=Address [addressId=101, street=new street, zipcode=throw exception!, city=my city]]
...
...
PreparedStatementCallback; uncategorized SQLException for SQL [insert into address(addressId,street,zipcode,city) values(?, ? , ? , ?)]; SQL state [72000]; error code [12899]; ORA-12899: value too large for column "LEVENT"."ADDRESS"."ZIPCODE" (actual: 16, maximum: 5)
; nested exception is java.sql.SQLException: ORA-12899: value too large for column "LEVENT"."ADDRESS"."ZIPCODE" (actual: 16, maximum: 5)

rollback...

Output’u inceleyecek olursak bekledigimiz gibi Person kaydi ve Address kaydi eklendi. Daha sonrasinda ZIPCODE 4 karakterden fazla oldugu icin ORA-12899: hatasi vermektedir.

Tablolari inceleyecek olursak sadece basarili transaction olan ilk Person ve Addres kaydinin eklendigini , 2.olarak basarili eklense de Person kaydinin eklenmedigini gorebiliriz.

person address table

Address tablosu icin truncate yapabilmek icin oncelikle Foreign key’i disable yapabilirsiniz.

ALTER TABLE PERSON
DISABLE CONSTRAINT PERSON_FK1;

TRUNCATE TABLE PERSON;
TRUNCATE TABLE ADDRESS;

ALTER TABLE PERSON
ENABLE CONSTRAINT PERSON_FK1;

person table

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 *