Backend Development 17 min read

Spring Programmatic Transaction Management: A Detailed Tutorial

This article provides a comprehensive, step‑by‑step guide to using programmatic transactions in Spring, covering both PlatformTransactionManager and TransactionTemplate approaches, complete with Maven configuration, SQL setup, test code, and best‑practice usage patterns.

Full-Stack Internet Architecture
Full-Stack Internet Architecture
Full-Stack Internet Architecture
Spring Programmatic Transaction Management: A Detailed Tutorial

Article Overview

This article aims to thoroughly explain how to use transactions in Spring, focusing on programmatic transaction management.

Content

Detailed explanation of Spring's programmatic transaction usage.

Two Ways to Use Transactions in Spring

Spring makes transaction handling easy, mainly offering two approaches:

Programmatic Transaction : Hard‑coded style.

Declarative Transaction : The familiar @Transactional annotation.

Programmatic Transaction

What Is a Programmatic Transaction?

It uses hard‑coded calls to Spring's transaction‑related classes to control transactions.

Two Main Uses of Programmatic Transactions

Method 1: Controlling transactions via PlatformTransactionManager .

Method 2: Controlling transactions via TransactionTemplate .

Method 1: PlatformTransactionManager

This is the most primitive way, with relatively large code volume; other methods are wrappers around it.

SQL Preparation

DROP DATABASE IF EXISTS javacode2018;
CREATE DATABASE IF NOT EXISTS javacode2018;
USE javacode2018;
DROP TABLE IF EXISTS t_user;
CREATE TABLE t_user(
  id int PRIMARY KEY AUTO_INCREMENT,
  name varchar(256) NOT NULL DEFAULT '' COMMENT '姓名'
);

Maven Configuration

org.springframework
spring-jdbc
5.2.3.RELEASE
org.springframework
spring-tx
5.2.3.RELEASE

Test Code (PlatformTransactionManager)

The test demonstrates using JdbcTemplate together with a transaction manager.

@Test
public void test1() throws Exception {
    // Define a data source
    org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
    dataSource.setDriverClassName("com.mysql.jdbc.Driver");
    dataSource.setUrl("jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8");
    dataSource.setUsername("root");
    dataSource.setPassword("root123");
    dataSource.setInitialSize(5);

    // JdbcTemplate for DB operations
    JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);

    // 1. Define transaction manager
    PlatformTransactionManager platformTransactionManager = new DataSourceTransactionManager(dataSource);

    // 2. Define transaction attributes
    TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();

    // 3. Open transaction
    TransactionStatus transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);

    try {
        System.out.println("before:" + jdbcTemplate.queryForList("SELECT * from t_user"));
        jdbcTemplate.update("insert into t_user (name) values (?)", "test1-1");
        jdbcTemplate.update("insert into t_user (name) values (?)", "test1-2");
        // 5. Commit transaction
        platformTransactionManager.commit(transactionStatus);
    } catch (Exception e) {
        // 6. Rollback transaction
        platformTransactionManager.rollback(transactionStatus);
    }
    System.out.println("after:" + jdbcTemplate.queryForList("SELECT * from t_user"));
}

Execution Output

before:[]
after:[{id=1, name=test1-1}, {id=2, name=test1-2}]

Code Analysis

The code follows five steps:

Step 1: Define Transaction Manager – PlatformTransactionManager acts as the administrator that can start, commit, or roll back a transaction.

Spring provides several implementations, e.g., JpaTransactionManager , DataSourceTransactionManager , HibernateTransactionManager , and JtaTransactionManager . In this example, DataSourceTransactionManager is used because JdbcTemplate operates on a plain data source.

PlatformTransactionManager platformTransactionManager = new DataSourceTransactionManager(dataSource);

Step 2: Define Transaction Attributes – Using TransactionDefinition (commonly DefaultTransactionDefinition ) to set isolation level, timeout, propagation behavior, etc.

Step 3: Open Transaction – Call platformTransactionManager.getTransaction(transactionDefinition) to obtain a TransactionStatus object.

TransactionStatus transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);

Step 4: Execute Business Logic – Perform DB operations via JdbcTemplate . Because the same DataSource is used, JdbcTemplate picks up the connection stored in a ThreadLocal, thus participating in the Spring transaction.

Step 5: Commit or Rollback

// Commit
platformTransactionManager.commit(transactionStatus);
// Rollback
platformTransactionManager.rollback(transactionStatus);

Method 2: TransactionTemplate

TransactionTemplate wraps the boilerplate of opening, committing, and rolling back a transaction, providing two convenient methods.

Test Code (TransactionTemplate)

@Test
public void test1() throws Exception {
    // Data source configuration (same as above)
    org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
    dataSource.setDriverClassName("com.mysql.jdbc.Driver");
    dataSource.setUrl("jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8");
    dataSource.setUsername("root");
    dataSource.setPassword("root123");
    dataSource.setInitialSize(5);

    JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
    PlatformTransactionManager platformTransactionManager = new DataSourceTransactionManager(dataSource);
    DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
    transactionDefinition.setTimeout(10); // 10 seconds timeout
    TransactionTemplate transactionTemplate = new TransactionTemplate(platformTransactionManager, transactionDefinition);

    transactionTemplate.executeWithoutResult(status -> {
        jdbcTemplate.update("insert into t_user (name) values (?)", "transactionTemplate-1");
        jdbcTemplate.update("insert into t_user (name) values (?)", "transactionTemplate-2");
    });
    System.out.println("after:" + jdbcTemplate.queryForList("SELECT * from t_user"));
}

Execution Output

after:[{id=1, name=transactionTemplate-1}, {id=2, name=transactionTemplate-2}]

TransactionTemplate Methods

executeWithoutResult : No return value; receives a Consumer<TransactionStatus> where business logic is placed.

execute : Returns a value; receives a TransactionCallback<T> where doInTransaction contains the business logic.

Both methods automatically commit the transaction if no exception occurs and setRollbackOnly() is not called; otherwise they roll back.

Correct Usage Pattern for Programmatic Transactions

Register a PlatformTransactionManager and a TransactionTemplate as Spring beans and reuse them in services.

package com.javacode2018.tx.demo3;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import javax.sql.DataSource;

@Configuration
@ComponentScan
public class MainConfig3 {
    @Bean
    public DataSource dataSource() {
        org.apache.tomcat.jdbc.pool.DataSource ds = new org.apache.tomcat.jdbc.pool.DataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8");
        ds.setUsername("root");
        ds.setPassword("root123");
        ds.setInitialSize(5);
        return ds;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean
    public TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) {
        return new TransactionTemplate(transactionManager);
    }
}

Service example that combines two business methods into a single transaction:

package com.javacode2018.tx.demo3;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionTemplate;
import java.util.List;

@Component
public class UserService {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Autowired
    private TransactionTemplate transactionTemplate;

    // Business method 1
    public void bus1() {
        transactionTemplate.executeWithoutResult(status -> {
            jdbcTemplate.update("delete from t_user");
            bus2();
        });
    }

    // Business method 2
    public void bus2() {
        transactionTemplate.executeWithoutResult(status -> {
            jdbcTemplate.update("insert into t_user (name) VALUE (?)", "java");
            jdbcTemplate.update("insert into t_user (name) VALUE (?)", "spring");
            jdbcTemplate.update("insert into t_user (name) VALUE (?)", "mybatis");
        });
    }

    // Query all rows
    public List userList() {
        return jdbcTemplate.queryForList("select * from t_user");
    }
}

Test case showing the combined transaction:

package com.javacode2018.tx.demo3;

import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Demo3Test {
    @Test
    public void test1() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig3.class);
        UserService userService = context.getBean(UserService.class);
        userService.bus1();
        System.out.println(userService.userList());
    }
}

Running test1() prints:

[{id=18, name=java}, {id=19, name=spring}, {id=20, name=mybatis}]

If any exception occurs or transactionStatus.setRollbackOnly() is invoked inside bus1 or bus2 , the whole transaction will be rolled back.

Conclusion

Although programmatic transaction code looks complex, it reveals how Spring controls transactions under the hood. Most developers prefer declarative transactions, which are built on the same mechanisms but provide a simpler annotation‑based API.

backendJavatransactionDatabaseSpringProgrammatic
Full-Stack Internet Architecture
Written by

Full-Stack Internet Architecture

Introducing full-stack Internet architecture technologies centered on Java

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.