欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

hibernate进阶改造 Hibernate

程序员文章站 2022-03-07 15:25:54
...

 

 

原帖内容已删除,对描述部分做新的修改,将不针对hibernate这一个orm框架:

 

我将会致力于从根本上解决脏数据读取问题。

 

对于我们现在的大多数框架来说,任何操作都是基于数据库的。相当于把sql语言转换形式进行实现:

 

"update User set level = level +1;" 等价于 "User user= get(User); user.setlevel(user.getLevel+1);" ?

 

我们的应用都是基于数据库的数据操作,前者是告诉数据库自行修改数据操作,而后者着是直接替换数据库中的值!

 

有严重的并发修改问题。所谓并发修改不仅仅是同一时间修改同一条记录的时候的问题,这种情况数据库有锁等方式解决。

但是对于后者这种方式,数据库将无能为力,因为orm框架每次update到数据库中的值全部都是全新的!全部都是替换操作。

 

在并发修改的情况下(从 User user= get(User);把user信息取出来到update(user);这个过程中,任何一个其他线程的 get(User) update(user) ;操作都会照成潜在的并发问题。第一个取出的user的对象是可靠的,其他的非指向user引用的对象全部都为不可靠数据!)user的信息会被任意的覆盖,因为所有的user信息都是取得保存在内存中,数据库中uid为1的user记录在数据库中永远只有一个,并且有锁机制保证修改顺序。

 

但是在orm框架中会存在多个user对象,只不过这些user对象的uid为1,而且可怕的是这些user互不相关!在你update(user);的时候实质上是在不停的插入全新的user信息,内存中的user值和数据库中的值没有任何关系。但是,从业务逻辑上来说,你对user对象属性操作仅仅是代替数据库操作。

 

本质上user的属性在整个内存中应当只有1个,因为你只是代替数据库去修改某个属性。你不应该具有多个存储能力,每个实体实质上都存储了一条记录的信息。

 

举例:

 

	@Transactional
	public void a(){
		User user1=baseDAO.get(User.class,1);		
		user1.setLevel(user1.getLevel()+1);
		
		User user2=baseDAO.get(User.class,1);		
		user2.setLevel(user2.getLevel()+1);
		
		baseDAO.update(user1);
		baseDAO.update(user2);
	}

 在这个方法里面,任意对user的操作都是可靠的,因为hibernate的一级缓存将后面取出来的user2指向的是user1的引用,user1、user2的修改实质上仅仅修改内存中的唯一user对象。在update之后没有任何问题,因为从始至终只修改的是一个记录。

 

典型的并发修改例子:

 

@Transactional
	public void b(){
		User user1=baseDAO.get(User.class,1);		
		user1.setLevel(user1.getLevel()+1);
		
		User user2=baseDAO.get(User.class,1);		
		user2.setLevel(user2.getLevel()+1);

		baseDAO.update(user1);
		baseDAO.update(user2);
	}

	@Transactional
	public void c(){
		User user3=baseDAO.get(User.class,1);		
		user3.setLevel(user3.getLevel()+1);	
		
		try {
			Thread.sleep(5000);//模拟业务操作需要5秒时间
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		baseDAO.update(user3);
	}
 

 

b方法比c方法晚一秒钟调用,假设user的leve原本的值为1;在method b,method c执行完毕之后 数据库中实际值为2,而不是3。

因为b方法修改的数据库记录被c方法的修改结果覆盖了。

 

这类问题根本原因不是用加锁或者修改业务逻辑去解决的。而是为什么在内存中会有多个不一样的user。数据应该都是唯一的,数据库中找不到id为1的2条记录,内存中也不应该出现id为1的2个对象。

 

既然我们是orm框架用面向对象的方式去修改数据,操作数据的本质应该不能改变:既永远只操作一条记录。可以参照method a是如何实现的,无论你update(user);多少次,最终修改的只有一条记录。只不过hibernate这种方式仅仅在一个事物中是这样做的,没有做到整个内存中只存在一个user对象。

 

 

针对,hibernate这个orm框架来说。他可以用一级缓存保证同一个事物内数据的一致性,但是处理不了不同事物的一致。那么还有一个二级缓存可能让内存中的user指向同一个对象(应该是有的),具体有没有这么做,还需要测试、研究下才能最终确认。

 

如果我的推断是正确的,那么在hibernate开启二级缓存之后,才能算是一个完整的orm框架。否则将是一个错误设计,简单的说:数据中只有一条记录,那么内存中只能有一个实体,update操作只能由这个实体操作,其他的任意实体都不可取。你以为你按照产品说明书上一步步操作没有问题,实际上问题一直存在!只不过你没有碰。

 

------

ps:

1、我是在接触游戏开发才有这个想法,至于一些设计可能你觉得有问题,这个不想多争论,除非你指出我设计上的错误。

2、并发问题、并发压力之类的,一般都说的是数据库底层操作的问题,但是实际在业务方法上你可能悄无声息的制造了并发问题。

3、这里请不要说xx不适合开发xx之类的,这里说的和开发什么东西没太大关系,任何一个使用hibernate的应用都会有这样的问题,除非你从业务上就避免了多个地方修改同一条记录的逻辑。一旦有,你就应该有这个思考。

4、如果错,请狂喷。在下洗耳恭听、虚心学习。hibernate进阶改造
            
    
    
        Hibernate

相关标签: Hibernate