事务基本概念

  • 事务的特性(ACID)

    • 原子性(Atomicity)
      原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
    • 一致性(Consistency)
      事务前后数据的完整性必须保持一致。
    • 隔离性(Isolation)
      事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离。
    • 持久性(Durability)
      持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
  • 如果不考虑事物的隔离性会出现以下问题

    如果不考虑事务的隔离性,会出现什么问题?

    1. 脏读

      一个事务读取到另一个事务的未提交数据。

    2. 不可重复读:两次读取的数据不一致(侧重于update)

      指的是读取数据的客户端开启事务后两次读取的数据不一致。(对方数据提交前后两次不一致)

    3. 虚读(幻读) (侧重于insert)

      两次读取的数据不一致

    4. 丢失更新

      两个事务对同一条记录进行操作,后提交的事务,将先提交的事务的修改覆盖了。

  • 未避免以上问题,所以数据库系统有以下四种隔离级别

    • Serializable:可避免脏读、不可重复读、虚读情况的发生。(串行化)

      最高隔离级别,一般不采用,因为在某个用于开启事务后,其他数据库无法访问该数据,会一直等待。导致效率特别低。

    • Repeatable read:可避免脏读、不可重复读情况的发生。(可重复读)不可以避免虚读

      mysql的默认级别

    • Read committed:可避免脏读情况发生(读已提交)

    • Read uncommitted:最低级别,以上情况均无法保证。(读未提交)

    • 如何解决丢失更新的问题

      解决丢失更新可以采用两种方式:

      1.悲观锁
      悲观锁 (假设丢失更新一定会发生 ) —– 利用数据库内部锁机制,管理事务
      提供的锁机制
      1.共享锁
      select * from table lock in share mode(读锁、共享锁)
      2.排它锁
      select * from table for update (写锁、排它锁)

      /*
      mysql数据库内部提供两种常用 锁机制:共享锁(读锁)和排它锁(写锁)
      允许一张数据表中数据记录,添加多个共享锁,添加共享锁记录,对于其他事务可读不可写的
      一张数据表中数据记录,只能添加一个排它锁,在添加排它锁的数据 不能再添加其他共享锁和排它锁的 ,对于其他事物可读不可写的
      */
      update语句默认添加排它锁

      2.乐观锁
      乐观锁 (假设丢失更新不会发生)——- 采用程序中添加版本字段解决丢失更新问题

      create table product (
      id int,
      name varchar(20),
      updatetime timestamp
      );
      insert into product values(1,'冰箱',null);
      update product set name='洗衣机' where id = 1;
      解决丢失更新:在数据表添加版本字段,每次修改过记录后,版本字段都会更新,如果读取是版本字段,
      与修改时版本字段不一致,说明别人进行修改过数据 (重改)

  • 在数据库中查看以及设置事务的隔离级别

    • 查看事务隔离级别

      1. select @@tx_isolation 查询当前事务隔离级别
      2. /*
      3. mysql中默认的事务隔离级别是 Repeatable read.
      4. oracle 中默认的事务隔离级别是 Read committed
      5. */
    • mysql中怎样设置事务隔离级别

      1. set session transaction isolation level read committed; /*设置当前会话事务隔离级别*/
      2. set global transaction isolation level read committed; /*全局的*/
    • jdbc设置事物隔离

      1. //使用java.sql.Connection接口中提供的方法
      2. void setTransactionIsolation(int level) throws SQLException
      3. /*
      4. 参数level可以取以下值:
      5. level - 以下 Connection 常量之一:
      6. Connection.TRANSACTION_READ_UNCOMMITTED、
      7. Connection.TRANSACTION_READ_COMMITTED、
      8. Connection.TRANSACTION_REPEATABLE_READ
      9. Connection.TRANSACTION_SERIALIZABLE。
      10. (注意,不能使用 Connection.TRANSACTION_NONE,因为它指定了不受支持的事务。)
      11. */
  • 在service中进行事物控制

    在实际开发中,如果service中会进行多个数据库操作,我们应该使service的执行过程为一个完整的事务,所以我们需要在service中加入事务控制。所以在Dao层需要对connection进行操作。使用ThreaddLocal可以使我们在同一线程中不经过传参也能获取到Connectio。(其工具类如下:)

    1. public class JdbcUtil {
    2. private static final String DRIVERCLASS;
    3. private static final String URL;
    4. private static final String USERNAME;
    5. private static final String PASSWORD;
    6. private static final ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
    7. static {
    8. DRIVERCLASS = ResourceBundle.getBundle("jdbc").getString("driverClass");
    9. URL = ResourceBundle.getBundle("jdbc").getString("url");
    10. USERNAME = ResourceBundle.getBundle("jdbc").getString("username");
    11. PASSWORD = ResourceBundle.getBundle("jdbc").getString("password");
    12. }
    13. static {
    14. try {
    15. // 将加载驱动操作,放置在静态代码块中.这样就保证了只加载一次.
    16. Class.forName(DRIVERCLASS);
    17. } catch (ClassNotFoundException e) {
    18. e.printStackTrace();
    19. }
    20. }
    21. public static Connection getConnection() throws SQLException {
    22. Connection con = tl.get();// 从ThreadLocal中获取Connection。第一次获取得到的是null.
    23. if (con == null) {
    24. // 2.获取连接
    25. con = DriverManager.getConnection(URL, USERNAME, PASSWORD);
    26. tl.set(con); // 将con装入到ThreadLocal中。
    27. }
    28. // tl.remove(); //解除
    29. return con;
    30. }
    31. // 关闭操作
    32. public static void closeConnection(Connection con) throws SQLException {
    33. if (con != null) {
    34. con.close();
    35. }
    36. }
    37. public static void closeStatement(Statement st) throws SQLException {
    38. if (st != null) {
    39. st.close();
    40. }
    41. }
    42. public static void closeResultSet(ResultSet rs) throws SQLException {
    43. if (rs != null) {
    44. rs.close();
    45. }
    46. }
    47. }

    在service中(主要是要最开始要开启事务,结束了要结交,失败了需要回滚)

    1. public class AccountService {
    2. public void account(String get, String send, double money) throws AccountException{
    3. AccountDaoImpl dao = new AccountDaoImpl();
    4. Connection con = null;
    5. try {
    6. con = JdbcUtils.getConnection();
    7. // 开启事务
    8. con.setAutoCommit(false);
    9. dao.accountOut( accountOut, money);
    10. dao.accountIn( accountIn, money);
    11. } catch (Exception e) {
    12. e.printStackTrace();
    13. // 出现问题,进行事务回滚
    14. if (con != null) {
    15. try {
    16. con.rollback();
    17. } catch (SQLException e1) {
    18. e1.printStackTrace();
    19. }
    20. }
    21. throw new AccountException(e.getMessage());
    22. } finally {
    23. // 事务提交
    24. // 关闭con
    25. if (con != null) {
    26. try {
    27. con.commit();
    28. con.close();
    29. } catch (SQLException e) {
    30. e.printStackTrace();
    31. }
    32. }
    33. }
    34. }
    35. }
posted @ 2017-02-24 17:23  forever-zs  阅读(277)  评论(0编辑  收藏  举报