Spring和Hibernate 下如何保持事务的一致性和并发控制。

声明:小弟也是一个新手,如果有表述不清楚或者不正确的地方请多指正。


方法1:这个是Hibernate的一个乐观锁的实现。是从数据库角度保证数据库的一致性的。

原理是:在数据库中创建一个字段,为version,类型为int,并置默认值为0. hibernate从数据库中找出该条记录的时候,会保存该条记录version。 在操作完该条记录后,往数据库中更新该条记录的时候,会再从数据库中查找一次该条记录的version。并且把第二次的version跟第一次保存的version进行对比。如果两个version相同,则进行更新操作。把version的数值加1,如果两个version不同,则进行事务的回滚操作。

优点:

  1. 它提供了一个方便,以自动化的方式来保持一致性,像上述的案例。这意味着,每一个动作只能执行一次,它保证用户或服务器过程中看到的是最新状态。
  2. 它需要很少的工作来设置。
  3. 由于其乐观的天性,速度非常快。没有锁定的任何地方,只是多了一个字段添加到查询中
缺点:


    1.要注意的是,由于乐观锁定是使用系统中的程式来控制,而不是使用资料库中的锁定机制,因而如果有人特意自行更新版本讯息来越过检查,则锁定机制就会无效,例如在上例中自行更改stu的version属性,使之与资料库中的版本号相同的话就不会有错误,像这样版本号被更改,或是由于资料是由外部系统而来,因而版本资讯不受控制时,锁定机制将会有问题,设计时必须注意。

    2.如果手工设置stu.setVersion()自行更新版本以跳过检查,则这种乐观锁就会失效,应对方法可以将Student.Java的setVersion设置成private

示例:

数据表:

DROP TABLE IF EXISTS `ac_mall_awards`;
CREATE TABLE `ac_mall_awards` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `award_id` bigint(20) DEFAULT NULL,
  `total` int(11) DEFAULT NULL,
  `remainder` int(11) DEFAULT NULL,
  `score` float DEFAULT NULL,
  `saleing` int(11) DEFAULT NULL,
  `ext` text,
  `status` int(11) DEFAULT NULL,
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  `version` bigint(20) DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

实体;
@Entity
@Table(name = "ac_mall_awards")
public class MallAwardPO extends EntityModel {
    @ManyToOne
    @JoinColumn(name = "award_id")
    private AwardPO award;
    
    @Column(name = "total")
    private int total;
    
    @Column(name = "remainder")
    private int remainder;
    
    @Column(name = "score")
    private float score;
   
    @Column(name = "saleing")
    private int saleing;
    
    @Column(name = "status")
    private int status;
    
    @Column(name = "ext")
    private String ext;
    
    
    @Version
    private long version;

    public AwardPO getAward() {
        return award;
    }

    public void setAward(AwardPO award) {
        this.award = award;
    }

    public int getTotal() {
        return total;
    }

    public void setTotal(int total) {
        this.total = total;
    }

    public int getRemainder() {
        return remainder;
    }

    public void setRemainder(int remainder) {
        this.remainder = remainder;
    }

    public float getScore() {
        return score;
    }

    public void setScore(float score) {
        this.score = score;
    }

    public int getSaleing() {
        return saleing;
    }

    public String getSaleingDesc() {
        return EnumStatusUtils.getStatus(EnumMallAwardSaleingStatus.class, saleing).getDesc();
    }
    
    public void setSaleing(int saleing) {
        this.saleing = saleing;
    }

    public int getStatus() {
        return status;
    }
    
    public String getStatusDesc() {
        return EnumStatusUtils.getStatus(EnumMallAwardStatus.class, status).getDesc();
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public String getExt() {
        return ext;
    }

    public void setExt(String ext) {
        this.ext = ext;
    }

    public long getVersion() {
        return version;
    }

    public void setVersion(long version) {
        this.version = version;
    }    
}

2.使用JAVA 中的 synchronized关键字,synchronized是一种同步锁。


它修饰的对象有以下几种:
1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。

在以下示例中,使用了synchronized关键字修饰的代码块 synchronized(lock) {}后面 大括号扩住的部分。

当线程访问该代码块时候,会判断该代码块是否有人在使用。

如果有人在使用则等待,如果没人使用,则进入该代码块。

如果该代码块中任意一行报错或者执行错误,则进行数据的回滚操作。因为对该Service声明了Spring的事务注解@ Transactional。

如果要详细了解synchronized和Spring的Transactional机制请看小弟的前三篇博客:

java synchronized详解:http://blog.csdn.net/yueaini10000/article/details/51801972

Java中Synchronized的用法:http://blog.csdn.net/yueaini10000/article/details/51802064

Spring @Transactional工作原理:http://blog.csdn.net/yueaini10000/article/details/51802341




示例:

接上面示例1的实体

/**
 *
 * @author Bruce
 *  领取商品业务逻辑
 */
@Service
@Transactional
public class MallAwardReceiveService {
    private static Logger logger = Logger.getLogger(MallAwardReceiveService.class);
    
    @Autowired
    private MallAwardManager mallAwardManager;
    
    @Autowired
    private ScoreService scoreService;
    
    @Autowired
    private MallAwardLogManager mallAwardLogManager;
        
    @Autowired
    private UserAwardManager userAwardManager;
        
    @Autowired
    private MyScoreService myScoreService;
    
    @Autowired
    private UserTakeOverAddressService userTakeOverAddressService;
    
    private Object lock = new Object();
    /**
     * 领取商品业务逻辑
     * @return
     */
    public void receiveMallAward(long mallAwardId) {
        UserContext userContext = UserContextUtils.getUserContextInThreadLocal();
        if(userContext.isGuest()) {
            throw new ServiceException("需要登录后才能进行商品兑换!");
        }
        
        /*1.获取用户积分并检查,可兑换积分是否足够  如果不足返回不足信息
         *2.获取兑换商品总数量,如果为-1,则没有限制,如果不为-1,则获取剩余数量 ,剩余数量,如果不足,返回不足信息
         *3.检查商品是否过期,
         *4.根据mallawardid  减少mallaward商品数量,生成mallawardlogs日志
         *5.根据用户ID查询ac_user_addresses信息,如果为空则提示需要添加地址信息
         *6.生成 ac_user_awards 表信息
         */
        synchronized(lock) {
            //持久获奖品信息到数据库
            try {    
                
                //1.获取用户积分并检查,可兑换积分是否足够  如果不足返回不足信息
                ScorePO myScore = myScoreService.getMyScore();//用户积分信息表
                MallAwardPO mallAwardPo = mallAwardManager.getById(mallAwardId);//积分商城表
                
                
                if(myScore == null ||myScore.getExchangeableScore() < mallAwardPo.getScore()){
                    throw new ServiceException("剩余可兑换积分不足!");
                }
                
                //2.获取兑换商品总数量,如果为-1,则没有限制,如果不为-1,则获取剩余数量 ,剩余数量,如果不足,返回不足信息
                if(mallAwardPo.getTotal() !=-1 && (mallAwardPo.getRemainder()<=0)){
                    throw new ServiceException("商品剩余数量不足!");
                }
                
                //3.检查商品是否过期
                //两个Date类型的变量可以通过compareTo方法来比较。
                //此方法的描述是这样的:如果参数 Date 等于此 Date,则返回值 0;
                //如果此 Date 在 Date 参数之前,则返回小于 0 的值;
                //如果此 Date 在 Date 参数之后,则返回大于 0 的值。
//                if((new Date().compareTo(mallAwardPo.getAward().getEnabledAt())) > 0){
//                    throw new ServiceException("商品已过兑换期限!");
//                }
                
                //5.根据用户userid 查询ac_user_addresses,是否存在,如果存在则获取ID,如果不存在,则提示去完善信息
                //用户地址信息
                UserTakeOverAddressPO userTakeAddressPo = userTakeOverAddressService.getDefaultUserTakeOverAddress(userContext.getUserId());
                if(userTakeAddressPo == null ){
                    throw new ServiceException("请完善个人配送地址信息!");
                }
                
                /*
                if(!userAddressService.isUserAddressByUserId(userContext.getUserId())){
                    throw new ServiceException("请完善个人配送地址信息!");
                }
                */
                //6.生成 ac_user_awards 表信息(
                //UserAddressPO userAddressPo = userAddressRepository.findByUserId(userContext.getUserId());
                
                UserAwardPO userAwardPo=new UserAwardPO();
                userAwardPo.setUserId(userContext.getUserId());
                userAwardPo.setAward(mallAwardPo.getAward());
                userAwardPo.setType(mallAwardPo.getAward().getType());
                //userAwardPo.setUserAddress(userAddressPo);
                userAwardPo.setStatus(EnumUserAwardStatus.NOTSHIP.getValue());
                userAwardPo.setSource(EnumUserAwardSource.SOURCE_AWARD.getValue());
                userAwardPo.setEnabledAt(DateUtils.getBeforDay(new Date(), 60));
                
                
                //4.根据mallawardid  减少mallaward商品数量,减少用户可兑换积分,生成mallawardlogs日志
                //01.减少mallaward商品数量
                if(mallAwardPo.getTotal() != -1 && (mallAwardPo.getRemainder()>0)){
                    mallAwardPo.setRemainder(mallAwardPo.getRemainder()-1);
                    
                }
                mallAwardPo.setUpdatedAt(new Date());
                //03.生成mallawardlogs日志
                MallAwardLogPO mallAwardLogPO =new MallAwardLogPO();
                mallAwardLogPO.setPrevScore(myScore.getExchangeableScore());
                //02.减少用户可兑换积分
                scoreService.changeUserScore(userContext.getUserId(), EnumExchangeableStatus.EXCHANGEABLE, -1*mallAwardPo.getScore(), "商城积分兑换", null);
                
                mallAwardLogPO.setUserId(myScore.getUserId());
                mallAwardLogPO.setMallAward(mallAwardPo);
                mallAwardLogPO.setAward(mallAwardPo.getAward());
                mallAwardLogPO.setNumber(1);
                mallAwardLogPO.setUsedScore(mallAwardPo.getScore());
                mallAwardLogPO.setIp(userContext.getIp());
                mallAwardLogPO.setCreatedAt(new Date());
                mallAwardLogPO.setCreatedAt(new Date());
                mallAwardManager.save(mallAwardPo);
                logger.info("更新sc_score数据成功:" + myScore.toString());
                mallAwardLogManager.save(mallAwardLogPO);
                logger.info("插入ac_mall_award_logs数据成功:" + mallAwardLogPO.toString());
                userAwardManager.save(userAwardPo);
                logger.info("插入ac_user_awards数据成功:" + userAwardPo.toString());
                
        
            } catch (DataAccessException exp) {
                logger.error(exp.getMessage(), exp);
                throw new ServiceException("领取失败!");
            }
        }
        
        
    }

}



  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值