Spring - Programmatic Transaction

[Updated: Aug 29, 2017, Created: Aug 28, 2017]

Understanding Spring Framework transaction abstraction

Spring provides PlatformTransactionManager interface to abstract the underlying transaction mechanism. Following is its definition:

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;
}

This abstraction can even work in JTA environment.

In following example, we will use programmatic transactions by using DataSourceTransactionManager, which is an implementation of PlatformTransactionManager. This implementation internally uses transaction features provided by java.sql.Connection. It can be used for a single JDBC DataSource.

Example

Creating Spring Jdbc application

@Component
public class OrderItemJdbcTemplateDao implements Dao<OrderItem> {
  @Autowired
  private DataSource dataSource;
  private JdbcTemplate jdbcTemplate;

  @PostConstruct
  private void postConstruct() {
      jdbcTemplate = new JdbcTemplate(dataSource);
  }

  @Override
  public long save(OrderItem orderItem) {
      String sql = "insert into ORDER_ITEM (ITEM, QTY) values (?, ?)";
      KeyHolder holder = new GeneratedKeyHolder();
      jdbcTemplate.update(new PreparedStatementCreator() {
          @Override
          public PreparedStatement createPreparedStatement(Connection connection)
                  throws SQLException {
              PreparedStatement ps = connection.prepareStatement(sql.toString(),
                      Statement.RETURN_GENERATED_KEYS);
              ps.setString(1, orderItem.getItem());
              ps.setInt(2, orderItem.getQty());
              return ps;
          }
      }, holder);
      Number key = holder.getKey();
      if (key != null) {
          return key.longValue();
      }
      throw new RuntimeException("No generated primary key returned.");
  }

  @Override
  public OrderItem load(long id) {
      List<OrderItem> persons = jdbcTemplate.query("select * from Person where id =?",
              new Object[]{id}, (resultSet, i) -> {
                  return toOrderItem(resultSet);
              });

      if (persons.size() == 1) {
          return persons.get(0);
      }
      throw new RuntimeException("No item found for id: " + id);
  }

  @Override
  public int delete(long id) {
      return 0; //todo
  }

  @Override
  public int update(OrderItem customer) {
      return 0;//todo
  }

  @Override
  public List<OrderItem> loadAll() {
      return jdbcTemplate.query("select * from ORDER_ITEM", (resultSet, i) -> {
          return toOrderItem(resultSet);
      });
  }

  private OrderItem toOrderItem(ResultSet resultSet) throws SQLException {
      OrderItem orderItem = new OrderItem();
      orderItem.setId(resultSet.getLong("ID"));
      orderItem.setItem(resultSet.getString("ITEM"));
      orderItem.setQty(resultSet.getInt("QTY"));
      return orderItem;
  }
}
public class OrderItem {
  private long id;
  private String item;
  private int qty;
    .............
}

Configuring PlatformTransactionManager

@Configuration
@ComponentScan
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;
  }
}

src/main/resources/createOrderItemTable.sql

CREATE TABLE ORDER_ITEM(
ID BIGINT PRIMARY KEY AUTO_INCREMENT,
ITEM VARCHAR(255) NOT NULL,
QTY INT(255) NOT NULL check (QTY > 0 and QTY <= 100),
);

Performing programmatic transactions

@Component
public class OrderItemClientBean {

  @Autowired
  private PlatformTransactionManager transactionManager;
  @Autowired
  private Dao<OrderItem> dao;

  public void persistOrderItems() {
      TransactionStatus ts =
              transactionManager.getTransaction(new DefaultTransactionDefinition());
      try {
          long id = dao.save(new OrderItem("BWell Ethernet Cable", 5));
          System.out.println("id generated: "+id);
          id = dao.save(new OrderItem("EDrive SSD", 2000));
          System.out.println("id generated: "+id);
          transactionManager.commit(ts);
      } catch (Exception e) {
              transactionManager.rollback(ts);
              System.out.println("-- exception message --");
              System.err.println(e.getMessage());
              System.out.println("---------");
      }
      System.out.println("loaded items: " + dao.loadAll());

      System.out.println("-- second attempt --");
      //new transaction boundary
      ts = transactionManager.getTransaction(new DefaultTransactionDefinition());
      try {
          long id = dao.save(new OrderItem("BWell Ethernet Cable", 5));
          System.out.println("id generated: "+id);
          id = dao.save(new OrderItem("EDrive SSD", 20));
          System.out.println("id generated: "+id);
          transactionManager.commit(ts);
      } catch (Exception e) {
          transactionManager.rollback(ts);
          System.out.println("-- exception message --");
          System.err.println(e.getMessage());
          System.out.println("---------");
      }
      System.out.println("loaded items: " + dao.loadAll());
  }
}

In above example, we are performing two transactions. The first transaction is inserting a value for OrderItem#qty which is out of the integer range defined in the corresponding table column, so that will end up in an exception and the transaction will be rolled back. The second transaction should be committed successfully.

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 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 items: []
-- second attempt --
id generated: 3
id generated: 4
loaded items: [OrderItem{id=3, item='BWell Ethernet Cable', qty=5}, OrderItem{id=4, item='EDrive SSD', qty=20}]

Example Project

Dependencies and Technologies Used :

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

Programmatic Transaction Example Select All Download
  • programmatic-transaction-example
    • src
      • main
        • java
          • com
            • logicbig
              • example
        • resources

See Also