如果不进行一些措施,可能会出现下面:
1.例如A用户读取一份数据,跟着B用户也读取这份数据,然后A用户提交修改,最后B用户也提交修改.这种情况就是B用户的修改会覆盖A用户的修改,A用户的修改会显得无效(好像没发生过一样).如果A用户是从银行取钱,也成功的话,取出来的钱当没发生过那就爽歪歪了!如果以最后修改为准,A用户的修改无关紧要,如改一下文档.那这种问题就不是问题了.如果成了问题,一般解决是让B用户操作失败.
2.如果统计一个公司,各个部门下的人数的总工资,在统计的过程中,如果修改了一个用户从部门A到部门B,那么结果可能性有很多种,有可能修改的这个用户的工资只在部门A或B统计了一遍,也有可能在A,B部门都统计了一遍.哈哈,乱七八糟的.如果成了问题,一般解决是让统计失败.


一.版本控制

1.在实体上定义版本锁定字段,类型可以是int,short,long或几种的封装类型,java.sql.TimeStamp.

@Entity
@Table(name = "o_user")
public class User {
...
    @Version
    private int version;
...
}

版本号的作用只是将最初获取的版本号与最后完成事务前获取的版本号进行比较,作为是否提交操作.理论上,实体一经创建,应用就不应直接更改版本字段.


@Test
    public void save() {
        User user=new User();
        user.setId("48d30707-5bdc-4dc7-b3c0-ff85e6becad7");
        user.setEmailAddress("xiejx618@qq.com");
        user.setUsername("xiejx618");
        user.setVersion(1);
        userRepository.save(user);
    }

假设数据库的这条记录的version是1,上面是这个提交修改操作会正常提交,同时会将数据库的version值自增1;再假设数据库的这条记录的version已变成2,而是提交上面的修改,发现最新的2不等于提交的1,那么这个提交将会失败,抛出如下异常:


Caused by: org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction 



二.利用版本控制进行乐观读取锁定.对一个对象进行锁定有以下几种方式:


EntityManager.lock(Object entity, LockModeType lockMode)


EntityManager.refresh(Object entity, LockModeType lockMode)


EntityManager.find(Class<T> entityClass, Object primaryKey,LockModeType lockMode)


下面是第三种应用.当然可以用其余两种


@Transactional(readOnly = true)
    public boolean total(Serializable id) {
        User user = em.find(User.class,id,LockModeType.OPTIMISTIC);
        System.out.println("正在统计..."+user.getVersion());
        try {
            Thread.sleep(30000l);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        user = em.find(User.class,id);
        System.out.println("统计完成..."+user.getVersion());
        return true;
    }

在上面的统计过程中,正好有另一个修改该用户的操作.


public void save() {
        User user=new User();
        user.setId("48d30707-5bdc-4dc7-b3c0-ff85e6becad7");
        user.setEmailAddress("xiejx618@qq.com");
        user.setUsername("xiejx618");
        user.setVersion(1);
        userRepository.save(user);
    }

这个修改如果先提交,并且版本号对应的话,这个操作就会成功提交.而上面的统计还在继续...直到完成统计最后提交,那么会发现最新的版本号和最初读入的版本号不对应,理论上,提交就会失败.抛乐观锁异常.经我测试,在oracle测试是正常的.但在mysql不会抛异常.


org.hibernate.action.internal.EntityVerifyVersionProcess#doBeforeTransactionCompletion,问题主要出在persister.getCurrentVersion获取最新版本号不同,在oracle是可以获取最新的版本号,但mysql获取的版本号与最初读入的版本号相同,所以不抛异常.至于为什么mysql不能获取最新的版本号没有深入去看.


执行统计的sql主要是Hibernate: select version from o_user where id =?


@Override
public void doBeforeTransactionCompletion(SessionImplementor session) {
	final EntityPersister persister = entry.getPersister();

	if ( !entry.isExistsInDatabase() ) {
		// HHH-9419: We cannot check for a version of an entry we ourselves deleted
		return;
	}

	final Object latestVersion = persister.getCurrentVersion( entry.getId(), session );
	if ( !entry.getVersion().equals( latestVersion ) ) {
		throw new OptimisticLockException(
				object,
				"Newer version [" + latestVersion +
						"] of entity [" + MessageHelper.infoString( entry.getEntityName(), entry.getId() ) +
						"] found in database"
		);
	}
}

再将上面的LockModeType.OPTIMISTIC改为LockModeType.OPTIMISTIC_FORCE_INCREMENT,去掉(readOnly = true),如果是只读事务,就不能自增版本号.


执行统计的sql主要是Hibernate: update o_user set version=? where id=? and version=?,因为更新的条数不是1,在mysql和oracle都抛下面的异常.


Caused by: org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction 




还有那些悲观锁太深奥了,对程序性能影响也大...