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
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.
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;
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
Leave a Reply