首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

Spring 事務與 JdbcTemplate 整合實戰:從 XML 配置到註解驅動的完整遷移指南

     編輯:Java 程式設計入門

Spring 的聲明式事務管理與 JdbcTemplate 是 Java 數據訪問層的經典組合。本文將從傳統 XML 配置入手,逐步演進至現代化註解驅動配置,涵蓋數據源、事務管理器、JdbcTemplate、DAO 實現、事務傳播行為、異常回滾規則、測試驗證全鏈路,並給出生產環境優化建議。

一、核心組件關係圖

DataSource (連接池)
    ↓ 注入
DataSourceTransactionManager (事務管理器)
    ↓ 注入
JdbcTemplate (JDBC 操作模板)
    ↓ 注入
UserDao / OrderDao ... (業務 DAO)
    ↓ @Transactional
Service Layer (事務邊界)

二、傳統 XML 配置(兼容舊項目)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://www.springframework.org/schema/beans"
       xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="https://www.springframework.org/schema/tx"
       xsi:schemaLocation="
           https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
           https://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- 1. 連接池 (DBCP2 示例) -->
    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://192.168.1.10:3306/test?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai"/>
        <property name="username" value="root"/>
        <property name="password" value="xx"/>
        <property name="maxTotal" value="20"/>
        <property name="maxIdle" value="10"/>
        <property name="maxWaitMillis" value="5000"/>
    </bean>

    <!-- 2. 事務管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 3. JdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 4. DAO Bean -->
    <bean id="userDao" class="com.example.dao.UserDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>

    <!-- 5. 聲明式事務 (AOP 切面) -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="save*" propagation="REQUIRED" rollback-for="Exception"/>
            <tx:method name="update*" propagation="REQUIRED" rollback-for="Exception"/>
            <tx:method name="delete*" propagation="REQUIRED" rollback-for="Exception"/>
            <tx:method name="*" propagation="SUPPORTS" read-only="true"/>
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <aop:pointcut id="serviceMethods" expression="execution(* com.example.service.*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethods"/>
    </aop:config>
</beans>

三、現代化註解驅動配置(Spring 4+ / Spring Boot 推薦)

3.1 Java Config 類

@Configuration
@EnableTransactionManagement // 開啟註解事務支持
@ComponentScan(basePackages = "com.example")
@PropertySource("classpath:jdbc.properties")
public class DataConfig {

    @Autowired Environment env;

    @Bean
    public DataSource dataSource() {
        HikariDataSource ds = new HikariDataSource(); // 生產級推薦 HikariCP
        ds.setDriverClassName(env.getProperty("jdbc.driver"));
        ds.setJdbcUrl(env.getProperty("jdbc.url"));
        ds.setUsername(env.getProperty("jdbc.username"));
        ds.setPassword(env.getProperty("jdbc.password"));
        ds.setMaximumPoolSize(20);
        ds.setMinimumIdle(5);
        return ds;
    }

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

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

3.2 DAO 實現(構造器注入,無 Setter)

@Repository
public class UserDaoImpl implements UserDao {

    private final JdbcTemplate jdbcTemplate;

    @Autowired
    public UserDaoImpl(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public int insert(User user) {
        String sql = "INSERT INTO users(username, email, created_at) VALUES (?, ?, ?)";
        return jdbcTemplate.update(sql, user.getUsername(), user.getEmail(), new Timestamp(System.currentTimeMillis()));
    }

    @Override
    public User findById(Long id) {
        String sql = "SELECT id, username, email, created_at FROM users WHERE id = ?";
        return jdbcTemplate.queryForObject(sql, new UserRowMapper(), id);
    }

    @Override
    public List findAll() {
        String sql = "SELECT id, username, email, created_at FROM users";
        return jdbcTemplate.query(sql, new UserRowMapper());
    }

    private static class UserRowMapper implements RowMapper {
        @Override
        public User mapRow(ResultSet rs, int rowNum) throws SQLException {
            User u = new User();
            u.setId(rs.getLong("id"));
            u.setUsername(rs.getString("username"));
            u.setEmail(rs.getString("email"));
            u.setCreatedAt(rs.getTimestamp("created_at"));
            return u;
        }
    }
}

3.3 Service 層事務邊界

@Service
@Transactional // 類級默認 REQUIRED, rollbackFor=Exception
public class UserServiceImpl implements UserService {

    private final UserDao userDao;

    @Autowired
    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = BusinessException.class)
    public void registerWithBonus(User user, Coupon coupon) {
        userDao.insert(user);           // 寫入用戶
        couponService.bind(user.getId(), coupon.getCode()); // 綁定優惠券(可能拋出異常)
        // 任一異常導致整個事務回滾
    }

    @Override
    @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
    public User getUser(Long id) {
        return userDao.findById(id);
    }
}

四、關鍵事務屬性深度解析

屬性可選值默認值說明
propagationREQUIRED, SUPPORTS, MANDATORY, REQUIRES_NEW, NOT_SUPPORTED, NEVER, NESTEDREQUIREDREQUIRED: 有事務加入,無則新建。REQUIRES_NEW: 掛起當前事務,強制新建獨立事務。
isolationDEFAULT, READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLEDEFAULT (數據庫默認)MySQL 默認 REPEATABLE_READ,Oracle/PostgreSQL 默認 READ_COMMITTED。
timeoutint (秒)-1 (無超時)超時自動回滾,需數據庫支持。
readOnlybooleanfalse提示數據庫優化(如不加鎖),僅作為優化建議,不強制禁止寫操作。
rollbackForClass[]RuntimeException, Error指定觸發回滾的受檢異常,常用 Exception.class 覆蓋所有。
noRollbackForClass[]空指定不觸發回滾的異常(極少用)。

五、JdbcTemplate 進階用法

5.1 批量操作

List batchArgs = users.stream()
    .map(u -> new Object[]{u.getUsername(), u.getEmail(), new Timestamp(System.currentTimeMillis())})
    .collect(Collectors.toList());
int[] results = jdbcTemplate.batchUpdate(
    "INSERT INTO users(username, email, created_at) VALUES (?, ?, ?)", batchArgs);

5.2 查詢映射(Lambda RowMapper,Java 8+)

List list = jdbcTemplate.query(
    "SELECT id, username, email FROM users WHERE status = ?",
    (rs, rowNum) -> new User(rs.getLong("id"), rs.getString("username"), rs.getString("email")),
    UserStatus.ACTIVE);

5.3 回調執行複雜 JDBC 操作

jdbcTemplate.execute((ConnectionCallback) con -> {
    try (CallableStatement cs = con.prepareCall("{call complex_proc(?, ?)}")) {
        cs.setLong(1, userId);
        cs.registerOutParameter(2, Types.INTEGER);
        cs.execute();
        return cs.getInt(2);
    }
});

六、測試驗證:事務回滾測試模板

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = DataConfig.class)
@Transactional // 測試方法默認回滾,保持數據庫乾淨
public class UserServiceTxTest {

    @Autowired UserService userService;
    @Autowired UserDao userDao;

    @Test
    public void testRegisterWithBonus_RollbackOnException() {
        User user = new User("tx_user", "tx@test.com");
        Coupon coupon = new Coupon("INVALID_CODE"); // 會導致綁定失敗拋出異常

        assertThrows(BusinessException.class, () -> userService.registerWithBonus(user, coupon));

        // 驗證事務回滾:用戶不應存在
        assertNull(userDao.findByUsername("tx_user"));
    }
}

七、生產環境優化清單

  1. 連接池選型:強制使用 HikariCP(Spring Boot 2.x 默認),禁用 DBCP/C3P0。
  2. 預編譯語句緩存:HikariCP 設置 dataSource.setDataSourceProperties(Map.of("prepStmtCacheSize", "250", "prepStmtCacheSqlLimit", "2048"))(MySQL)。
  3. 事務邊界最小化:只在 Service 層標註 @Transactional,DAO/Repository 層不標註;查詢方法加 readOnly=true。
  4. 異常轉換策略:全局 @ControllerAdvice 捕獲 DataAccessException 統一轉為業務異常,避免 SQLException 泄露。
  5. 慢 SQL 監控:啟用 spring.jdbc.template.query-timeout 或接入 Druid/P6Spy 監控執行時間超閾值 SQL。
  6. 數據源健康檢查:Spring Boot Actuator /actuator/health 自動檢測 DataSource 連通性。

八、總結

Spring 事務與 JdbcTemplate 的整合核心在於:DataSource → TransactionManager → JdbcTemplate → DAO → @Transactional Service 五層依賴鏈。從 XML 遷移到註解驅動不僅簡化配置,更利於 IDE 重構、編譯期檢查、單元測試隔離。掌握事務傳播、隔離、回滾規則、批量操作、回調執行等進階用法,配合 HikariCP、讀寫分離、分庫分表中間件,即可構建高性能、強一致性的數據訪問層。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved