In the tutorial below I will focus on transactions management with Spring 3. For the sake of simplicity, we will create a standalone Java application – but the same logic can be used in J2EE application. We will create:

  • Entity objects (simple POJO) – the basic entities mapped to the relational database (via annotations).
  • DAO objects handling database (Hibernate) queries.
  • Business Object that will expose services around our data.
  • Main application to simply run our code. This is what will probably need to be replaced with your web application logic, if you are creating one.

Our application will be very simple – two main tables/objects: User and Email, with the many-to-many relationship between them. Let’s start with the SQL to create and populate database. I’m using MySQL but it should work on any other DB with the minimal changes:

CREATE TABLE IF NOT EXISTS `email` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `address` VARCHAR(250) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
 
INSERT INTO `email` (`id`, `address`) VALUES
(1, 'a1@opensesam.net'),
(2, 'a2@opensesam.net');
 
CREATE TABLE IF NOT EXISTS `user` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `age` INT(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
 
INSERT INTO `user` (`id`, `age`) VALUES (1, 34), (2, 19);
 
CREATE TABLE IF NOT EXISTS `user_email` (
  `user_id` INT(11) NOT NULL,
  `email_id` INT(11) NOT NULL,
  KEY `user_id` (`user_id`),
  KEY `email_id` (`email_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
 
INSERT INTO `user_email` (`user_id`, `email_id`) VALUES (1, 1), (1, 2);
 
ALTER TABLE `user_email`
  ADD CONSTRAINT `user_email_ibfk_2` FOREIGN KEY (`email_id`) REFERENCES `email` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
  ADD CONSTRAINT `user_email_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION;

Next, our entity objects. You can download a full source code, so I’m displaying here only the relevant parts of the code. Entity objects are simple POJOs but they contain annotations for Hibernate:

package net.opensesam.entity;
 
import javax.persistence.*;
 
@Entity
@Table(name = "user")
public class User {
 
    @Id
    private int id;
 
    @Column(name = "age")
    private int age;
 
    @ManyToMany
    @JoinTable(name = "user_email", 
            joinColumns = { @JoinColumn(name = "user_id") },
            inverseJoinColumns = {@JoinColumn(name = "email_id")} )
    private Set<Email> emails = new HashSet<Email>();
//the rest are just setters and getters
@Entity
@Table(name = "email")
public class Email {
 
    @Id
    private int id;
 
    @Column(name = "address")
    private String address;
 
    @ManyToMany(mappedBy = "emails")
    private Set<User> users = new HashSet<User>();
//...

Our DAO objects will need a reference to SessionFactory – we will use a constructor to pass that object and let Spring do the job. Let’s define a getFirstEmail method in UserDao – suppose this is for finding only one, primary email for a user.

public class UserDao extends AbstractDao<User> {
    public UserDao(SessionFactory sessionFactory) {
        super(User.class, sessionFactory);
    }
 
    public Email getFirstEmail(int userId) {
        Email email = (Email) query(
                "select email from Email email join email.users u where u.id = ?")
                .setInteger(0, userId).setMaxResults(1).uniqueResult();
 
        return email;
    }
}

EmailDao is not even worth showing here – it simply extends AbstractDao. Now, our business logic finally. We will be coding to an interface, so we’ll create interface net.opensesam.bo.UserBo:

package net.opensesam.bo;
 
import net.opensesam.entity.*;
 
public interface UserBo {
 
  User findById(int id);
 
  void insert(User user);
 
  void complexTransaction(User user);
 
  Email getPrimaryEmail(User user);
 
}

The methods are implemented by UserBoImpl. All of them are used from the Main class, so you can refer to the source code – let’s have a look at only one of them – public void complexTransaction(User user). This is to emulate a few-steps transaction:

public void complexTransaction(User user) {
        User u = findById(user.getId());
        // update all user's email addresses
        for (Email e : u.getEmails()) {
            e.setAddress(e.getAddress() + ".new");
        }
        /* If exception is thrown, no changes should be persisted in DB
       if (true) {
           throw new RuntimeException("Ouch, an exception!");
       }
       */
        // update user's age
        u.setAge(u.getAge() + 100);
    }

If an exception is run in the middle of the method, Spring should rollback the transaction. Otherwise, the whole complexTransaction method should execute as one transaction.
All the magic happens in Spring configuration – beans.xml in my case:

<bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
 
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="*" />
        </tx:attributes>
    </tx:advice>
 
    <aop:config>
        <aop:advisor advice-ref="txAdvice"
            pointcut="execution(* net.opensesam.bo.*.*(..))" />
    </aop:config>

We need to define transactionManager that will understand the persistence technology we are using – hence HibernateTransactionManager in this case. Transaction manager obviously needs a reference to SessionFactory. Every time our transactional method is run, Transaction Manager has to run as well and do all the work for us, like: opening connection if needed, starting transaction, rolling back / committing. This means we need some kind of proxy object, or a wrapper around UserBoImpl class. AOP comes in very handy here, as we can have that done declaratively, without any changes to the source code. First we define when our transaction management code should run by setting pointcut=”execution(* net.opensesam.bo.*.*(..))”. You may want to refer to Spring AOP tutorial for the very basics of AOP. Next, we define our advice (what should run) txAdvice that refers to the transaction manager we use. tx:attributes element contains additional information like: which methods should be transactional, which of them can be declared as read-only for possible optimizations and under what conditions should rollback be triggered. By default any unchecked exception will trigger rollback. The example above is very simple, basically any method from any class from net.opensesam.bo package will be treated as transactional.

The above works as expected – here is what I can see in MySQL log, when the exception is not thrown:

       807 Query  SET autocommit=0
          807 Query  /* load net.opensesam.entity.User */ select user0_.id as id0_0_, user0_.age as age0_0_ from user user0_ where user0_.id=1
          807 Query  /* load collection net.opensesam.entity.User.emails */ select emails0_.user_id as user1_0_1_, emails0_.email_id as email2_1_, email1_.id as id1_0_, email1_.address as address1_0_ from user_email emails0_ inner join email email1_ on emails0_.email_id=email1_.id where emails0_.user_id=1
          807 Query  /* update net.opensesam.entity.User */ update user set age=234 where id=1
          807 Query  /* update net.opensesam.entity.Email */ update email set address='a1@opensesam.net.new.new' where id=1
          807 Query  /* update net.opensesam.entity.Email */ update email set address='a2@opensesam.net.new.new' where id=2
          807 Query  commit

Compare it with what happens if when the exception is thrown:

       812 Query  SET autocommit=0
          812 Query  /* load net.opensesam.entity.User */ select user0_.id as id0_0_, user0_.age as age0_0_ from user user0_ where user0_.id=1
          812 Query  /* load collection net.opensesam.entity.User.emails */ select emails0_.user_id as user1_0_1_, emails0_.email_id as email2_1_, email1_.id as id1_0_, email1_.address as address1_0_ from user_email emails0_ inner join email email1_ on emails0_.email_id=email1_.id where emails0_.user_id=1
          812 Query  rollback

The list of jar files used while working on the code:

  • org.springframework.context-3.0.5.RELEASE.jar
  • org.springframework.beans-3.0.5.RELEASE.jar
  • org.springframework.orm-3.0.5.RELEASE.jar
  • org.springframework.transaction-3.0.5.RELEASE.jar
  • hibernate3.jar
  • hibernate-jpa-2.0-api-1.0.0.Final.jar
  • org.springframework.core-3.0.5.RELEASE.jar
  • commons-logging-1.1.1.jar
  • org.springframework.asm-3.0.5.RELEASE.jar
  • aopalliance-1.0.jar
  • org.springframework.aop-3.0.5.RELEASE.jar
  • org.springframework.expression-3.0.5.RELEASE.jar
  • org.springframework.jdbc-3.0.5.RELEASE.jar
  • aspectjweaver-1.6.6.jar
  • mysql-connector-java-5.1.13.jar
  • jta-1.1.jar
  • dom4j-1.6.1.jar
  • slf4j-log4j12-1.6.1.jar
  • slf4j-api-1.6.1.jar
  • log4j-1.2.16.jar
  • commons-collections-3.2.1.jar
  • javassist-3.8.0.GA.jar
  • antlr-2.7.7.jar

Spring Transactions AOP – source code