Tag Archives: spring

数据库事务

学数据库的时候就会经常提到事务这个词,事务的ACID,事务的rollback,为什么需要事务。但以前写的项目中,很少重视这个问题(好像因为我很少写后台的缘故)。最近写高并发的东西需要事务体现的特别明显。 最简单的例子:

public void updateName(Long id, String name){
    User user = userDao.findById(id);
    user.setName(name);
    userDao.save(user);
}

public void updateGender(Long id, boolean male){
    User user = userDao.findById(id);
    user.setGender(male);
    userDao.save(user);
}

如果直接在方法中这么写,会出现并发问题。由于多个线程最终还是串行的(CPU是串行的),所以,这段代码的执行顺序可能如下:

thread1: User user = userDao.findById(id);  //   name: "Alis", gender: false
thread2: User user = userDao.findById(id);  //   name: "Alis", gender: false

thread1: user.setName("Bob"); // name: "Bob", gender: false
thread2: user.setGender(true); // name: "Alis", gender: true

thread1: userDao.save(user);   // name: "Bob", gender: false
thread2: userDao.save(user);   // name: "Alis", gender: true

//expected result in database: name: "Bob", gender: true;
//final result in database: name: "Alis", gender: true

可以看出,我们实际上想最终得到一个男Bob,结果得到了一个女Alis。当然,由于并发执行,并不能保证执行顺序,也有可能是一个女Bob,男Bob,完全凭运气。因为两个线程不曾互相通气,说哪个属性已经被改变了。

这个场景似乎在Web上很不容易想象出来,但仔细一想又会存在。例如,小明正在修改自己的个人信息,同时,由于连续使用3天,系统给小明的信息中更新了一下积分,如果积分和个人信息都放在profile表里,就很有可能发生并发,导致数据覆盖。虽然可能性很小,但一旦发生,结果往往是不可挽回的。

解决的办法很简单,事务。

JDBC就提供了事务

conn = DriverManager.getConnection();
conn.setAutoCommit(false);

//do queries...

conn.commit();

使用Hibernate,也提供了相应的接口

Transaction tx = session.beginTransaction();

// do queries...

tx.commit()

使用Spring的话,也提供了@Transactional注解,在需要事务的方法上加上注解就好了

@Transactional
public void updateName(){ 
     //...
}

想来Spring还着实做了不少简化开发的工作啊。