Spring 事务

数据库的事务

概念和特性

事务是数据库操作的一个基本单元,它包含一组操作,这些操作要么全部成功执行,要么全部不执行,保持数据的一致性

  • 原子性 (Atomicity):一个事务中的多个操作要么全部发生,要不都不发生
  • 一致性 (Consistency):事务执行前后,数据从一个合法状态转为另一个合法状态(不能数据虽然改了,但是是不合法的数据)
    比如修改前后都要满足索引的约束,主键、外检、唯一、余额不能为负(业务自定义)等
  • 隔离性 (Isolation):多个事务并发执行时,一个事务的执行不影响其他事务(并发时要保证数据是安全的
  • 持久性 (Durability):事务完成后,对数据的影响是永久性的(不能隔一段时间数据变化了或又恢复了)

隔离级别

隔离级别 底层实现 脏读 不可重复读 幻读
READ UNCOMMITTED(读未提交) 不加锁
READ COMMITTED(读已提交) 写加锁,读不加锁(行锁)
REPEATABLE READ(可重复度) 对行读写都加锁(行锁)
SERIALIZABLE(串行化) 对表加锁(表锁)

脏读:一个事务读取了另一个事务修改过(还未提交)的数据。读到了无效的数据

不可重复度:同一个事务内多次读取同一行数据,读取到的结果不一致,因为期间其他事务修改数据并提交。同一事务多次单行读取结果不一致

幻读:和不可重复读有点类似,只不是过幻读指的是范围查询是多行的。同一事务多次范围读取结果不一致

  • Mysql 默认是 RR,Oracle 和 PG 默认是 RC,Oracle 不支持 RU
  • 虽然都不是 SERIALIZABLE,但是也是数据安全的,比如使用 MVCC,使用了扩展的隔离级别等

JDBC 事务

配置数据源

DriverManager 是 JDBC1.0 提供的(不支持连接池),DataSource 是 JDBC2.0 提供的(支持连接池,意味着连接可复用)

DriverManager 获取连接示例

String url = "jdbc:mysql://localhost:3306/mydb";
String user = "username";
String password = "password";
Connection conn = DriverManager.getConnection(url, user, password);

DataSource 获取连接示例(实现有多个,C3P0、DPCB、Hikari、Druid)

// 不带连接池
MysqlDataSource dataSource = new MysqlDataSource();
dataSource.setURL("jdbc:mysql://localhost:3306/mydb");
dataSource.setUser("username");
dataSource.setPassword("password");

// Hikari
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
dataSource.setUsername("username");
dataSource.setPassword("password");
dataSource.setMaximumPoolSize(10); // 连接池大小

// DBCP
BasicDataSource dataSource = new BasicDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/mydb");
dataSource.setUsername("username");
dataSource.setPassword("password");
dataSource.setInitialSize(5);  // 初始连接数
dataSource.setMaxTotal(10);    // 最大连接数

使用示例

手动控制事务,也叫编程式事务

Connection conn = null;
try {
    // 获取数据库连接
    conn = dataSource.getConnection();
    
    // 关闭自动提交,开启事务
    conn.setAutoCommit(false);
    
    // 执行SQL操作1
    PreparedStatement stmt1 = conn.prepareStatement("UPDATE account SET balance = balance - ? WHERE id = ?");
    stmt1.setInt(1, 100);
    stmt1.setInt(2, 1);
    stmt1.executeUpdate();
    
    // 执行SQL操作2
    PreparedStatement stmt2 = conn.prepareStatement("UPDATE account SET balance = balance + ? WHERE id = ?");
    stmt2.setInt(1, 100);
    stmt2.setInt(2, 2);
    stmt2.executeUpdate();
    
    // 提交事务
    conn.commit();
} catch (SQLException e) {
    // 回滚事务
    if (conn != null) {
        try {
            conn.rollback();
        } catch (SQLException ex) {
            ex.printStackTrace();
        }
    }
    e.printStackTrace();
} finally {
    // 恢复自动提交并关闭连接
    if (conn != null) {
        try {
            conn.setAutoCommit(true);
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

Spring 事务

编程式事务:手动控制事务的回滚和提交,比如 JDBC 的事务就叫编程式事务

声明式事务:不需要手动控制事务的提交和回滚,使用注解自动完成,Spring 的 @Transactional 就是声明式事务

事务管理器

PlatformTransactionManager 是 Spring 事务的核心接口,定义了事务的基本操作

public interface PlatformTransactionManager {
    TransactionStatus getTransaction(TransactionDefinition definition)
        throws TransactionException;
    
    void commit(TransactionStatus status) throws TransactionException;
    
    void rollback(TransactionStatus status) throws TransactionException;
}
  • DataSourceTransactionManager:用于 JDBC 和 MyBatis
  • HibernateTransactionManager:用于 Hibernate
  • JpaTransactionManager:用于 JPA
  • JtaTransactionManager:用于分布式事务

TransactionTemplate 是对 PlatformTransactionManager 进一步的封装,用的确实不多,了解一下就行了

Spring 中配置 PlatformTransactionManager

@Configuration
@EnableTransactionManagement  // 启用注解事务
public class AppConfig {

    // 1. 配置数据源
    @Bean
    public DataSource dataSource() {
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
        dataSource.setUsername("root");
        dataSource.setPassword("password");
        dataSource.setMaximumPoolSize(10);
        return dataSource;
    }

    // 2. 配置事务管理器(事务管理需要数据源)
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    // 3. 配置 JdbcTemplate(jdbc 也需要数据源)
    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}

Spring 中使用编程式事务

@Service
public class AccountService {
    
    @Autowired
    private PlatformTransactionManager transactionManager;
    
    public void transfer(int fromId, int toId, int amount) {
        DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
        TransactionStatus status = transactionManager.getTransaction(definition);
        
        try {
            // 业务代码
            jdbcTemplate.update("UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, fromId);
            jdbcTemplate.update("UPDATE accounts SET balance = balance + ? WHERE id = ?", amount, toId);
            
            transactionManager.commit(status);
        } catch (Exception e) {
            transactionManager.rollback(status);
            throw e;
        }
    }
}

Spring 中使用声明式事务

@Configuration
@EnableTransactionManagement
public class AppConfig {
  
    @Bean
    public DataSource dataSource() {
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
        dataSource.setUsername("root");
        dataSource.setPassword("password");
        dataSource.setMaximumPoolSize(10);
        return dataSource;
    }
    
    @Bean // Spring 中需要手动配置事务管理器(SpringBoot中会自动配置,SpringBoot 中只要配置好了数据数据源,DataSource 也会自动配置)
    public TransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource());
    }
}

@Service
public class AccountService {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    @Transactional // 加上这个注解就好了,外部调用这个方法时,如果方法出现异常会自动回滚
    public void transfer(int fromId, int toId, int amount) {
        // 扣钱
        jdbcTemplate.update("UPDATE account SET balance = balance - ? WHERE id = ?", amount, fromId);
        // 加钱
        jdbcTemplate.update("UPDATE account SET balance = balance + ? WHERE id = ?", amount, toId);
    }
    
}

@Transactional

Spring 提供 @Transactional 注解实现声明式事务注解

可以用在类上,可以用在方法上

可以配置隔离级别和传播行为

还有一些使用限制,比如不能标注在 private 方法上,不能同类中调用等

详细看这里asdfasdf

posted @ 2024-11-02 20:39  CyrusHuang  阅读(54)  评论(0)    收藏  举报