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

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

优点:

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

缺点:

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。

示例:

接上面示例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("领取失败!");
             }
         }
         
         
     }

 }