MST

星途 面试题库

面试题:Java数据库事务管理及隔离级别在实际编程中的应用

在Java数据库编程中,事务管理至关重要。请详细描述事务的ACID特性,并举例说明不同事务隔离级别(读未提交、读已提交、可重复读、串行化)在并发操作数据库时会产生哪些问题及如何避免。同时,阐述如何在Java代码中通过JDBC或Spring框架来管理事务。
16.0万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

事务的ACID特性

  1. 原子性(Atomicity)
    • 事务中的所有操作要么全部成功提交,要么全部失败回滚,就像一个不可分割的原子一样。例如,在银行转账操作中,从账户A向账户B转账100元,这涉及从账户A扣除100元以及向账户B增加100元两个操作。这两个操作必须作为一个整体执行,如果其中一个操作失败(比如账户A余额不足),整个转账事务应回滚,保证数据的一致性,不会出现账户A钱扣了但账户B没收到的情况。
  2. 一致性(Consistency)
    • 事务执行前后,数据库的完整性约束没有被破坏。例如,在上述银行转账例子中,转账前A + B的总金额等于转账后A + B的总金额,数据库的一致性得到维护。数据库中的各种约束(如主键约束、外键约束等)必须在事务结束后仍然成立。
  3. 隔离性(Isolation)
    • 多个并发事务之间相互隔离,一个事务的执行不能被其他事务干扰。不同的事务隔离级别决定了这种隔离的程度。例如,在高并发场景下,多个用户同时进行转账操作,隔离性确保每个用户的操作相互不影响,不会出现数据混乱的情况。
  4. 持久性(Durability)
    • 一旦事务提交,对数据库的修改就是永久性的。即使系统崩溃或出现其他故障,已提交的事务对数据的修改也不会丢失。例如,银行转账事务提交后,无论后续系统发生什么情况,账户A和账户B的余额变化都是确定且持久的。

不同事务隔离级别在并发操作数据库时的问题及避免方法

  1. 读未提交(Read Uncommitted)
    • 问题:会出现脏读(Dirty Read),即一个事务可以读取到另一个未提交事务的数据。例如,事务T1更新了一条记录,但未提交,事务T2此时读取了这条更新后的数据,如果T1随后回滚,T2读取到的数据就是无效的“脏数据”。
    • 避免方法:提高事务隔离级别到读已提交或更高。
  2. 读已提交(Read Committed)
    • 问题:会出现不可重复读(Non - Repeatable Read),在一个事务内多次读取同一数据,由于其他事务的修改并提交,导致每次读取结果不一致。例如,事务T1读取了一条记录,事务T2对该记录进行了修改并提交,T1再次读取时得到不同的结果。
    • 避免方法:将事务隔离级别提高到可重复读。
  3. 可重复读(Repeatable Read)
    • 问题:会出现幻读(Phantom Read),在一个事务内执行相同的查询,由于其他事务插入新数据并提交,导致查询结果集的行数发生变化。例如,事务T1查询符合某条件的记录集,事务T2插入了符合该条件的新记录并提交,T1再次执行相同查询时,结果集中多了T2插入的记录。
    • 避免方法:将事务隔离级别提高到串行化。
  4. 串行化(Serializable)
    • 问题:由于事务是串行执行的,并发性能极低,会严重影响系统的吞吐量。
    • 避免方法:在实际应用中,只有在对数据一致性要求极高且并发量较小的场景下使用。一般可根据业务需求,选择较低的隔离级别以提高并发性能。

在Java代码中通过JDBC或Spring框架来管理事务

  1. 通过JDBC管理事务
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.PreparedStatement;
    import java.sql.SQLException;
    
    public class JdbcTransactionExample {
        public static void main(String[] args) {
            Connection connection = null;
            PreparedStatement pstmt1 = null;
            PreparedStatement pstmt2 = null;
            try {
                // 加载数据库驱动
                Class.forName("com.mysql.cj.jdbc.Driver");
                // 获取数据库连接
                connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "root", "password");
                // 关闭自动提交,开启事务
                connection.setAutoCommit(false);
                // 执行第一个SQL语句
                String sql1 = "UPDATE accounts SET balance = balance - 100 WHERE account_id = 1";
                pstmt1 = connection.prepareStatement(sql1);
                pstmt1.executeUpdate();
                // 执行第二个SQL语句
                String sql2 = "UPDATE accounts SET balance = balance + 100 WHERE account_id = 2";
                pstmt2 = connection.prepareStatement(sql2);
                pstmt2.executeUpdate();
                // 提交事务
                connection.commit();
            } catch (ClassNotFoundException | SQLException e) {
                if (connection != null) {
                    try {
                        // 回滚事务
                        connection.rollback();
                    } catch (SQLException ex) {
                        ex.printStackTrace();
                    }
                }
                e.printStackTrace();
            } finally {
                if (pstmt1 != null) {
                    try {
                        pstmt1.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                if (pstmt2 != null) {
                    try {
                        pstmt2.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                if (connection != null) {
                    try {
                        connection.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
  2. 通过Spring框架管理事务
    • 基于XML配置
      • 配置数据源和事务管理器:
      <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
          <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
          <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
          <property name="username" value="root"/>
          <property name="password" value="password"/>
      </bean>
      <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
          <property name="dataSource" ref="dataSource"/>
      </bean>
      <tx:annotation - driven transaction - manager="transactionManager"/>
      
      • 在Service层使用事务注解:
      import org.springframework.stereotype.Service;
      import org.springframework.transaction.annotation.Transactional;
      
      @Service
      public class AccountService {
          @Transactional
          public void transfer(int fromAccountId, int toAccountId, double amount) {
              // 转账逻辑,调用DAO层方法更新数据库
          }
      }
      
    • 基于Java配置
      import org.apache.commons.dbcp2.BasicDataSource;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.ComponentScan;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.jdbc.datasource.DataSourceTransactionManager;
      import org.springframework.transaction.PlatformTransactionManager;
      import org.springframework.transaction.annotation.EnableTransactionManagement;
      
      import javax.sql.DataSource;
      
      @Configuration
      @ComponentScan(basePackages = "com.example")
      @EnableTransactionManagement
      public class AppConfig {
          @Bean
          public DataSource dataSource() {
              BasicDataSource dataSource = new BasicDataSource();
              dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
              dataSource.setUrl("jdbc:mysql://localhost:3306/mydb");
              dataSource.setUsername("root");
              dataSource.setPassword("password");
              return dataSource;
          }
          @Bean
          public PlatformTransactionManager transactionManager() {
              return new DataSourceTransactionManager(dataSource());
          }
      }
      
      • Service层同样使用@Transactional注解进行事务管理:
      import org.springframework.stereotype.Service;
      import org.springframework.transaction.annotation.Transactional;
      
      @Service
      public class AccountService {
          @Transactional
          public void transfer(int fromAccountId, int toAccountId, double amount) {
              // 转账逻辑,调用DAO层方法更新数据库
          }
      }