Backend Development 9 min read

Master Programmatic Transactions in Spring Boot 2.3.9 with Templates & Managers

This guide explains how to use Spring Boot's programmatic transaction APIs—including TransactionTemplate, TransactionManager, and transactional event listeners—through detailed code examples, configuration tips, rollback handling, and best‑practice recommendations for backend Java development.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Master Programmatic Transactions in Spring Boot 2.3.9 with Templates & Managers

Environment

Spring Boot 2.3.9.RELEASE

Programmatic Transaction Management Options

Use

TransactionTemplate

or

TransactionalOperator

Directly create a

TransactionManager

implementation

Spring officially recommends the

TransactionTemplate

approach.

Preparation

<code>@Entity
@Table(name = "BC_USERS")
@Data
public class Users {
    private String username;
    private String password;
    private Integer status = 0;
}

public interface UsersRepository extends JpaRepository<Users, String> {
    @Modifying
    @Query("update Users u set u.status=?1,u.password='123123' where u.id=?2")
    int updateUsers(Integer status, String id);
}

@Mapper
public interface UsersMapper {
    int insertUser(Users user);
}

<!-- Mapper.xml -->
<insert id="insertUser" parameterType="com.pack.domain.Users">
    insert into bc_users (id, username, password) values (#{id}, #{username}, #{password})
</insert>
</code>

1. TransactionTemplate

1.1 With Return Value

<code>@Service
public class UserService {
    @Resource
    private TransactionTemplate transactionTemplate;
    @Resource
    private UsersRepository usersRepository;

    public Integer saveUsers(Users users) {
        transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        Integer result = transactionTemplate.execute(new TransactionCallback<Integer>() {
            @Override
            public Integer doInTransaction(TransactionStatus status) {
                return usersMapper.insertUser(users);
            }
        });
        return result;
    }
}
</code>

1.2 Without Return Value

<code>public void saveUsers(Users users) {
    transactionTemplate.execute(new TransactionCallbackWithoutResult() {
        @Override
        protected void doInTransactionWithoutResult(TransactionStatus status) {
            usersMapper.insertUser(users);
        }
    });
}
</code>

1.3 Transaction Rollback

<code>public Users saveUser(Users users) {
    return transactionTemplate.execute(new TransactionCallback<Users>() {
        @Override
        public Users doInTransaction(TransactionStatus status) {
            try {
                return usersMapper.insertUser(users);
            } catch (Exception e) {
                status.setRollbackOnly();
            }
            return null;
        }
    });
}
</code>

1.4 Configuring Transaction Attributes

<code>private TransactionTemplate transactionTemplate;

public UserService(PlatformTransactionManager transactionManager) {
    this.transactionTemplate = new TransactionTemplate(transactionManager);
    this.transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);
    this.transactionTemplate.setTimeout(30); // seconds
}
</code>

Test Code (Demonstrating NOT_SUPPORTED error)

<code>public Integer updateUsers(Integer statusValue, String id) {
    return transactionTemplate.execute(new TransactionCallback<Integer>() {
        @Override
        public Integer doInTransaction(TransactionStatus status) {
            return usersRepository.updateUsers(statusValue, id);
        }
    });
}

@Modifying
@Query("update Users u set u.status=?1 where u.id=?2")
int updateUsers(Integer status, String id);
</code>
<code>org.springframework.dao.InvalidDataAccessApiUsageException: Executing an update/delete query; nested exception is javax.persistence.TransactionRequiredException: Executing an update/delete query
    at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:403)
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:257)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:531)
</code>

2. TransactionalOperator

Applicable to reactive programming; not covered here.

3. TransactionManager

Two main types:

PlatformTransactionManager

and

ReactiveTransactionManager

(reactive not covered).

3.1 PlatformTransactionManager Example

<code>private PlatformTransactionManager transactionManager;
private DefaultTransactionDefinition definition;
private TransactionStatus status;
@Resource
private UsersRepository usersRepository;

public UserService3(PlatformTransactionManager transactionManager) {
    this.transactionManager = transactionManager;
    definition = new DefaultTransactionDefinition();
    definition.setName("pgName");
    definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
}

public Integer saveUsers(Users users) {
    TransactionStatus status = this.transactionManager.getTransaction(definition);
    Integer result = null;
    try {
        result = usersMapper.insertUser(users);
    } catch (Exception e) {
        transactionManager.rollback(status);
        throw e;
    }
    transactionManager.commit(status);
    publisher.publishEvent(new UsersEvent(users));
    return result;
}
</code>

4. Transaction Event Listening

Use

@TransactionalEventListener

to listen to transaction phases (AFTER_COMMIT, AFTER_COMPLETION, AFTER_ROLLBACK, BEFORE_COMMIT). It works only with declarative transactions managed by

PlatformTransactionManager

.

<code>public @interface TransactionalEventListener {
    TransactionPhase phase() default TransactionPhase.AFTER_COMMIT;
    boolean fallbackExecution() default false;
    @AliasFor(annotation = EventListener.class, attribute = "classes")
    Class<?>[] value() default {};
    @AliasFor(annotation = EventListener.class, attribute = "classes")
    Class<?>[] classes() default {};
    String condition() default "";
}
</code>
<code>public enum TransactionPhase {
    BEFORE_COMMIT, // before commit
    AFTER_COMMIT,  // after commit
    AFTER_ROLLBACK, // after rollback
    AFTER_COMPLETION // after completion
}
</code>
<code>// Listener component
@Component
public class TxListenerComponent {
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void handleUsersAfterCommit(UsersEvent usersEvent) {
        Users user = (Users) usersEvent.getSource();
        System.out.println("AfterCommit received event: " + user.getPassword());
    }
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
    public void handleUsersAfterCompletion(UsersEvent usersEvent) {
        Users user = (Users) usersEvent.getSource();
        System.out.println("AfterCompletion received event: " + user.getPassword());
    }
    @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
    public void handleUsersAfterRollback(UsersEvent usersEvent) {
        Users user = (Users) usersEvent.getSource();
        System.out.println("AfterRollback received event: " + user.getPassword());
    }
    @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
    public void handleUsersBeforeCommit(UsersEvent usersEvent) {
        Users user = (Users) usersEvent.getSource();
        System.out.println("BeforeCommit received event: " + user.getPassword());
    }
}

// Publishing event
@Resource
private ApplicationEventPublisher publisher;
@Resource
private UsersMapper usersMapper;

public Integer saveUsers(Users users) {
    Integer result = transactionTemplate.execute(new TransactionCallback<Integer>() {
        @Override
        public Integer doInTransaction(TransactionStatus status) {
            return usersMapper.insertUser(users);
        }
    });
    publisher.publishEvent(new UsersEvent(users));
    return result;
}
</code>

Run Result

<code>2021-06-17 14:02:56.830 DEBUG 10000 --- [nio-8081-exec-1] com.pack.mapper.UsersMapper.insertUser   : ==>  Preparing: insert into bc_users (id, username, password) values (?, ?, ?)
2021-06-17 14:02:56.840 DEBUG 10000 --- [nio-8081-exec-1] com.pack.mapper.UsersMapper.insertUser   : ==>  Parameters: mmmmm(String), mmmmm(String), mmmmm(String)
2021-06-17 14:02:56.842 DEBUG 10000 --- [nio-8081-exec-1] com.pack.mapper.UsersMapper.insertUser   : <==    Updates: 1
BeforeCommit收到事件通知:mmmmm
AfterCommit收到事件通知:mmmmm
AfterCompletion收到事件通知:mmmmm
</code>

Summary : Programmatic transaction management is suitable for a small number of transactional operations, such as when most of the service logic is non‑transactional computation followed by a single commit. For many transactional steps, declarative (annotation‑based) transactions are generally more appropriate.

JavaBackend DevelopmentSpring Boottransaction managementProgrammatic Transactions
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

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.