问题描述
spring+hibernate 或 spring+mybatis结构以前写简单的crud,还没有感觉到问题。最近写游戏,逻辑复杂多了,比如有个功能,要求,用户登陆后,检测用户持有的道具纪录,如果发现有纪录没有的(因为游戏更新,会加新道具),要插入。我这么干的@servicepublic class UserInfoServiceImpl implements UserInfoService{private GoodsInfoService goodsInfoService;public GoodsInfoService getGoodsInfoService() {return goodsInfoService;}@Resourcepublic void setGoodsInfoService(GoodsInfoService goodsInfoService) {this.goodsInfoService = goodsInfoService;} @Override public boolean selectUserLogin(UserInfo userInfo) { List<GoodsInfo> userGoodsInfos = goodsInfoService.selectUserAllGoods(userId); // 获取玩家所有的道具 //伪代码, //遍历检测是否有缺少的然后插入新的userGoodsInfos = goodsInfoService.selectUserAllGoods(userId); //再次获得新的玩家道具列表。。。。。。。 }}坑爹的是,select用的事务传播级别为support,一般select不需要事务,但是hibernate和mybatis都存在session缓存,在selectUserLogin方法中selectUserAllGoods执行了两次,但是实际上只得到了同样的结果,第二次执行未查询数据库而是直接就从缓存中取得。而实际上结果集在数据库里已经编了,我对这个地方很纠结,不知道应该改造成什么样子,似乎service上面还需要再有一层来处理逻辑,但是service不处理逻辑,那就是纯粹的crud,把dao的工作抢去了。那像我这样检测旧纪录,插入新纪录,插入完后再次查询,并获得新结果集的service层到底该怎么写,如何设置事务传播级别?
解决方案
调整selectUserAllGoods方法的事务隔离级别,事务隔离级别分为如下几种:1. 读未提交,最低的级别,此级别可以读到其他事务未处交的数据,此级别性能最高,但是会有脏读的数据。2. 读已提交,此级别保证读到的数据都是其他事务已提交的数据,不会读到脏数据,但是不可重复读,也就是同样的条件下,第一次读到的数据和第二次读到的数据,可能是会变化的。3. 可重复读,此级别保证的是同样的条件下,第一次读到的数据和第二次读到的数据是不会变的,对应数据库的实现方式应该是就是锁行,锁定读到的那些数据行,但是此级别会有一个问题,那就是幻读,也就是说,同样的条件,第一次可能读到了10条数据,第二次可能读到了11条数据,因为多的那个数据是新插入的。4.序列化,最高隔离级别,对应的基本上就是锁表,安全性最高,但是性能最低,此级别可以保证不会出现幻读。根据你的需求,你的隔离级别应该设置为2.读已提交。mysql的默认级别是3.可重复读,所以你可能需要显式地设置你的事务隔离级别。如果你用的是spring,那么加如下注解就可以了:@Transactional(isolation = Isolation.READ_COMMITTED)如果你用的是JDBC的话,直接在Connection对象上设置隔离级别,如下:conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
解决方案二:
@Override public boolean selectUserLogin(UserInfo userInfo) { List<GoodsInfo> userGoodsInfos = goodsInfoService .selectUserAllGoods(userId); // 获取玩家所有的道具 //伪代码, //遍历检测是否有缺少的然后插入新的 userGoodsInfos = goodsInfoService .selectUserAllGoods(userId); //再次获得新的玩家道具列表 。。。。。。。 } } 其实这里的逻辑还是数据库的访问呗,所以你把dao层扩展就可以了,service还是直接用就行了