I tried the same thing using Postgresql and Spring declarative transaction demarcation. It worked fine:
a. POJO User
Code:
public class User {
private int id = -1;
private String name = null;
private int age = 0;
public User() {
}
public int getId() { return (this.id); }
public void setId(int id) { this.id = id; }
public String getName() { return (this.name); }
public void setName(String name) { this.name = name; }
public int getAge() { return (this.age); }
public void setAge(int age) { this.age = age; }
public String toString() {
StringBuffer buffer = new StringBuffer("User[")
.append("id=").append(id)
.append(",name=").append(name)
.append(",age=").append(age)
.append("]");
return buffer.toString();
}
}
b. Hibernate mapping
Code:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>
<class name="User" table="USERS" dynamic-update="false" dynamic-insert="false">
<cache usage="read-write"/>
<id name="id" column="ID" type="long" unsaved-value="-1">
<generator class="native" />
</id>
<property name="name" column="NAME" type="java.lang.String" />
<property name="age" column="AGE" type="integer" />
</class>
</hibernate-mapping>
c. dataContext.xml
Code:
<bean id="transactionManager" class="org.springframework.orm.hibernate.HibernateTransactionManager">
<property name="sessionFactory"><ref local="sessionFactory"/></property>
</bean>
<bean id="txProxyTemplate"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
abstract="true">
<property name="transactionManager">
<ref local="transactionManager"/>
</property>
<property name="transactionAttributes">
<props>
<prop key="incrementAge*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<bean id="userDAO" parent="txProxyTemplate">
<property name="target">
<bean class="UserDAOImpl">
<property name="sessionFactory">
<ref local="sessionFactory"/>
</property>
</bean>
</property>
</bean>
d. UserDAOImpl
Code:
public class UserDAOImpl extends HibernateDaoSupport implements UserDAO {
public void incrementAge(int id) throws DataAccessException {
Session session = SessionFactoryUtils.getSession(getSessionFactory(), false);
Utils.printCollection(TransactionSynchronizationManager.getResourceMap().keySet());
try {
System.out.println(Thread.currentThread().getName() + " inside");
System.out.println(Thread.currentThread().getName() + " Session : " + session);
System.out.println(Thread.currentThread().getName() + " gettting user : " + id);
User user = (User) session.load(User.class, new Integer(id), LockMode.UPGRADE);
System.out.println(Thread.currentThread().getName() + " got user : " + user);
user.setAge(user.getAge()+1);
try {
//sleeping
System.out.println(Thread.currentThread().getName() + " sleeping...");
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " saving...");
session.save(user);
System.out.println(Thread.currentThread().getName() + " saved");
} catch (HibernateException e) {
e.printStackTrace();
throw SessionFactoryUtils.convertHibernateAccessException(e);
}
}
}
e. main class
Code:
public class Testos {
private static final String[] locations = {"dataContext.xml"};
private static ApplicationContext app;
public static void main(String[] args) throws Exception {
app = new FileSystemXmlApplicationContext(locations);
Thread td1 = new Thread("Thread 1") {
public void run() {
UserDAO userDAO = (UserDAO) app.getBean("userDAO");
userDAO.incrementAge(1);
}
};
Thread td2 = new Thread("Thread 2") {
public void run() {
UserDAO userDAO = (UserDAO) app.getBean("userDAO");
userDAO.incrementAge(1);
}
};
td1.start();
td2.start();
}
}
f. output
Code:
13:47:53,802 INFO SQLErrorCodesFactory - Database Product Name is PostgreSQL
13:47:53,802 INFO SQLErrorCodesFactory - Driver Version is PostgreSQL 7.4.4 JDBC3 with SSL (build 215)
13:47:53,992 INFO DataSourceTransactionObject - JDBC 3.0 Savepoint class is available
Thread 1 net.sf.hibernate.impl.SessionFactoryImpl: net.sf.hibernate.impl.SessionFactoryImpl@21e554
Thread 1 org.apache.commons.dbcp.BasicDataSource: org.apache.commons.dbcp.BasicDataSource@1cffeb4
Thread 1 inside
Thread 1 Session : net.sf.hibernate.impl.SessionImpl@c3014
Thread 1 gettting user : 1
Hibernate: select ID, NAME, AGE from DEMOS where ID =? for update
Thread 1 got user : User[id=1,name=Taha Irbouh,age=27]
Thread 1 sleeping...
Thread 2 net.sf.hibernate.impl.SessionFactoryImpl: net.sf.hibernate.impl.SessionFactoryImpl@21e554
Thread 2 org.apache.commons.dbcp.BasicDataSource: org.apache.commons.dbcp.BasicDataSource@1cffeb4
Thread 2 inside
Thread 2 Session : net.sf.hibernate.impl.SessionImpl@8de462
Thread 2 gettting user : 1
Hibernate: select ID, NAME, AGE from DEMOS where ID =? for update
Thread 1 saving...
Thread 1 saved
Hibernate: update DEMOS set AGE=? where ID=?
Thread 2 got user : User[id=1,name=Taha Irbouh,age=28]
Thread 2 sleeping...
Thread 2 saving...
Thread 2 saved
Hibernate: update DEMOS set AGE=? where ID=?
the output shows that
1. we are using the same Hibernate SessionFactory: SessionFactoryImpl@21e554
2. threads 1 and 2 use two Hibernate Sessions: SessionImpl@c3014 and SessionImpl@8de462
3. Hibernate loads the user using for update: pessimistic locking
4. thread 2 waits for thread 1 until it commits
we can make thread 2 fail using Hibernate versionning.