Spring Transaction Management with Pure Annotations: Step-by-Step Implementation
This tutorial demonstrates how to implement Spring declarative transaction management using only annotations, covering project setup, configuration classes, MyBatis integration, service layer @Transactional usage, and JUnit tests verifying commit and rollback behavior.
Introduction to Spring Declarative Transactions
The article explains how to use Spring's @Transactional annotation to manage transactions at the business layer, ensuring ACID properties (Atomicity, Consistency, Isolation, Durability) for database operations. The example simulates a bank transfer between two accounts, where both debit and credit must succeed or both fail.
Project Setup and Dependencies
A Maven project is created with the following key dependencies (versions as used in the demo):
Spring Context, Spring JDBC, Spring Test: 5.3.17
MySQL Connector/J: 8.0.28
Druid connection pool: 1.2.8
MyBatis: 3.5.9
MyBatis-Spring: 2.0.7
JUnit: 4.13.2 (test scope)
Dependencies are declared in pom.xml and downloaded via Maven refresh.
Database Preparation
A MySQL database testdb is created with a bankuser table (id, name, money) using InnoDB engine. Three initial rows are inserted:
INSERT INTO `bankuser` (`id`, `name`, `money`) VALUES
(1, '任肥肥', 3000),
(2, '侯胖胖', 3000),
(3, '乔豆豆', 3000);Connection properties are stored in jdbc.properties (driver, URL, username, password) and Druid pool settings (initialSize=3, maxActive=20, maxWait=3000).
Configuration Classes
JdbcConfig
Annotated with @PropertySource("classpath:jdbc.properties") to load properties. Uses @Value to inject values into fields. Provides two @Bean methods: getDataSource(): Creates and configures a DruidDataSource with the injected properties. getTransactionManager(DataSource): Returns a DataSourceTransactionManager for JDBC transaction management.
MybatisConfig
Defines two beans: getSqlSessionFactoryBean(DataSource): Configures SqlSessionFactoryBean with the data source and type aliases package com.jobs.domain. (Optional MyBatis logging configuration is commented out.) getMapperScannerConfigurer(): Sets base package com.jobs.dao for automatic mapper interface scanning.
SpringConfig
Main configuration class annotated with:
@Configuration @ComponentScan("com.jobs") @Import({JdbcConfig.class, MybatisConfig.class}) @EnableTransactionManagement— crucial for enabling Spring's annotation-driven transaction support.
Domain Entity
BankUserclass with fields id, name, money, constructors, getters/setters (omitted), and toString().
DAO Layer (MyBatis Annotations)
BankUserDaointerface uses MyBatis annotations:
@Update("update bankuser set money = money + #{money} where name = #{name}")for addMoney.
@Update("update bankuser set money = money - #{money} where name = #{name}")for minusMoney. @Select with @Results mapping columns to properties for getBankUserList() returning List<BankUser>.
Service Layer with @Transactional
Interface BankUserService
Declares three methods:
@Transactional void transferMoney(String from, String to, Integer money)— normal transfer.
@Transactional void transferError(String from, String to, Integer money)— intentionally fails to test rollback. List<BankUser> getBankUserList() — read-only, no transaction needed.
The article recommends placing @Transactional on the interface (or interface methods) rather than the implementation class, so that any future implementation automatically inherits the transaction behavior.
Implementation BankUserServiceImpl
Injects BankUserDao via @Resource (to avoid IDE false warnings). Implements: transferMoney: calls minusMoney then addMoney. transferError: calls minusMoney, then triggers int result = 1 / 0; (ArithmeticException), then addMoney (never reached). The comment emphasizes not catching exceptions inside the service method; otherwise Spring would not see the exception and would commit the transaction. Exception handling should be done by the caller. getBankUserList: delegates to DAO.
JUnit Test Verification
Test class BankUserServiceTest uses @RunWith(SpringJUnit4ClassRunner.class) and @ContextConfiguration(classes = SpringConfig.class) to load the Spring context. Autowires BankUserService.
Test 1: Successful Transfer ( transferMoney )
Prints account balances before and after transferring 1000 from "任肥肥" to "乔豆豆". Output shows balances updated correctly (2000, 3000, 4000).
Test 2: Failed Transfer with Rollback ( transferError )
Attempts to transfer 1000 from "侯胖胖" to "乔豆豆". The test method wraps the call in try-catch to capture the exception. After the exception, balances are printed again and remain unchanged (3000, 3000, 3000), proving the transaction rolled back.
Key takeaway: The caller (test method) handles the exception; the service method lets it propagate.
Conclusion
Spring's annotation-driven transaction management simplifies complex business logic by allowing developers to write fine-grained DAO operations and compose them in service methods annotated with @Transactional. The framework ensures atomicity automatically, reducing boilerplate code and potential errors.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Captain
Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
