Close

Spring - Declarative Transaction Management with @Transactional Annotation

[Updated: Aug 1, 2018, Created: Sep 6, 2017]

@Transactional provides annotation-based declarative transaction support which is similar to EJB container-managed transaction. With this annotation, we can specify transaction behavior to individual methods without coupling business logic with transaction code.

This example demonstrates the use of @Transactional annotation with it's default settings.

Example

We are going to reuse our previous example. We will create a service layer where we will be using @Transactional annotation.

Service Layer

We can place the @Transactional annotation on an interface definition, a method of an interface, a class definition, or a public method of a class. When placed on the class/interface level, all methods within it will become transactional.

package com.logicbig.example;

import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Transactional
public interface OrderService {

  void persistOrders(List<OrderItem> orderItems);

  List<OrderItem> getAllOrders();
}
@Service
public class OrderServiceImpl implements OrderService {

  @Autowired
  private Dao<OrderItem> dao;

  @Override
  public void persistOrders(List<OrderItem> orderItems) {
      for (OrderItem orderItem : orderItems) {
          long id = dao.save(orderItem);
          System.out.println("id generated: " + id);
      }
  }

  @Override
  public List<OrderItem> getAllOrders() {
      return dao.loadAll();
  }
}

Using the service

@Component
public class OrderItemClientBean {
  @Autowired
  private OrderService orderService;

  public void persistOrderItems() {
      List<OrderItem> orders = Arrays.asList(
              new OrderItem("BWell Ethernet Cable", 5),
              new OrderItem("EDrive SSD", 2000)
      );
      try {
          orderService.persistOrders(orders);
      } catch (Exception e) {
          logException(e);
      }
      List<OrderItem> allOrders = orderService.getAllOrders();
      System.out.println("loaded orders: " + allOrders);

      System.out.println("-- second attempt --");
      List<OrderItem> orders2 = Arrays.asList(
              new OrderItem("BWell Ethernet Cable", 5),
              new OrderItem("EDrive SSD", 20)
      );
      try {
          orderService.persistOrders(orders2);
      } catch (Exception e) {
          logException(e);
      }
      List<OrderItem> allOrders2 = orderService.getAllOrders();
      System.out.println("loaded orders: " + allOrders2);
  }

  private static void logException(Exception e) {
      System.out.println("-- exception --");
      System.err.println("Exception: "+e.getClass().getName());
      System.err.println("Message: "+ e.getMessage());
      System.out.println("---------");
  }
}

In above example, we are performing two transactions. The first transaction will fail and will be rolled back, that's because we are inserting a value for OrderItem#qty which is out of the integer range defined in the corresponding table column. This is to demonstrate that the service method annotated with @Transactional is providing the method level transaction as expected. Without it persisting process will not be atomic for the list of provided orders. The second transaction should be committed successfully.

Java Config

@Configuration
@ComponentScan
@EnableTransactionManagement
public class AppConfig {

  @Bean
  public DataSource h2DataSource() {
      return new EmbeddedDatabaseBuilder()
              .setType(EmbeddedDatabaseType.H2)
              .addScript("createOrderItemTable.sql")
              .build();
  }

  @Bean
  public PlatformTransactionManager transactionManager() {
      DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
      transactionManager.setDataSource(h2DataSource());
      return transactionManager;
  }
}

In above configuration, EnableTransactionManagement annotation enables Spring's annotation-driven transaction management.

Main class

public class ExampleMain {
  public static void main(String[] args) {
      AnnotationConfigApplicationContext context =
              new AnnotationConfigApplicationContext(AppConfig.class);
      OrderItemClientBean orderItemClientBean = context.getBean(OrderItemClientBean.class);
      orderItemClientBean.persistOrderItems();
  }
}

Output

id generated: 1
-- exception --
Exception: org.springframework.dao.DataIntegrityViolationException
Message: PreparedStatementCallback; SQL []; Check constraint violation: "((QTY > 0)
AND (QTY <= 100))"; SQL statement:
insert into ORDER_ITEM (ITEM, QTY) values (?, ?) [23513-196]; nested exception is org.h2.jdbc.JdbcSQLException: Check constraint violation: "((QTY > 0)
AND (QTY <= 100))"; SQL statement:
insert into ORDER_ITEM (ITEM, QTY) values (?, ?) [23513-196]
---------
loaded orders: []
-- second attempt --
id generated: 3
id generated: 4
loaded orders: [OrderItem{id=3, item='BWell Ethernet Cable', qty=5}, OrderItem{id=4, item='EDrive SSD', qty=20}]

Using @javax.transaction.Transactional

Spring provides the exact same functionality for the standard javax.transaction.Transactional annotation without any extra configuration. We just need to add following maven dependency to use it:

 <dependency>
    <groupId>javax.transaction</groupId>
    <artifactId>javax.transaction-api</artifactId>
    <version>1.2</version>
 </dependency>

Example Project

Dependencies and Technologies Used:

  • spring-context 4.3.10.RELEASE: Spring Context.
  • spring-jdbc 4.3.10.RELEASE: Spring JDBC.
  • h2 1.4.196: H2 Database Engine.
  • JDK 1.8
  • Maven 3.3.9

@Transactional Example Select All Download
  • transactional-annotation-example
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • OrderService.java
          • resources

    See Also