`
sskhnje
  • 浏览: 16990 次
  • 性别: Icon_minigender_1
  • 来自: 昆明
文章分类
社区版块
存档分类
最新评论

针对一个账号进入存钱、取钱操作的并发控制,hibernate version乐观锁

 
阅读更多

这几天在练习针对一个账号进行存钱、取钱操作的并发控制。

 

entiy:Account

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Version;

import java.io.Serializable;

@Entity(name = "Account")
public class Account implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @Column(name = "AccountNumber")
    private String accountNumber;
    
   /* @Version
    @Column(name = "Version")
    private long version;*/
    
    @Column(name = "Status")
    private String status;
    
    @Column(name = "Score")
    private Double score;
    
    @Column(name = "Cas")
    private Long cas;

    
    public String getAccountNumber() {
        return accountNumber;
    }
    public void setAccountNumber(String accountNumber) {
        this.accountNumber = accountNumber;
    }
    public String getStatus() {
        return status;
    }

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

    public Double getScore() {
        return score;
    }

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

    public Long getCas() {
        return cas;
    }

    public void setCas(Long cas) {
        this.cas = cas;
    }    
}
    

 

 

dao:AccountDao

import org.springframework.stereotype.Repository;
import javax.inject.Inject;
import javax.persistence.FlushModeType;

@Repository
public class AccountDao {

    private ProductionJPAAccess productionJpaAccess;

    public Account get(String accountNumber) {
        return productionJpaAccess.get(Account.class, accountNumber);
    }

    public void save(Account account) {
        productionJpaAccess.save(account);
    }

    public void update(Account account) {
        //System.out.println(productionJpaAccess.getEntityManager().getFlushMode());
        //productionJpaAccess.getEntityManager().setFlushMode(FlushModeType.COMMIT);
        productionJpaAccess.update(account);
        productionJpaAccess.getEntityManager().flush();
    }
    
    @Inject
    public void setProductionJpaAccess(ProductionJPAAccess productionJpaAccess) {
        this.productionJpaAccess = productionJpaAccess;
    }
}

 

 

service: TradeService

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.inject.Inject;

@Service
public class TradeService {
    private final Logger logger = LoggerFactory.getLogger(TradeService.class);

    private AccountDao accountDao;

    private DataConverter dataConverter;

    @Inject
    private GenIdentityDao genIdentityDao;

    public Account get(String accountNumber) {
        return accountDao.get(accountNumber);
    }    
    
    @Transactional(value = "transactionManager",  propagation=Propagation.REQUIRED)    
    public synchronized void handleSimple(String accountNumber, double amount, boolean deposit) throws ResourceNotFoundException, NotEnoughException {
       // logger.error("{}, enter, {}", Thread.currentThread().getName(), this.hashCode());
        Account account = get(accountNumber);
        if (account == null) {
           throw new ResourceNotFoundException("account(" + accountNumber + ") not found");
        }

        double score = account.getScore();
        if (deposit) {
            account.setScore(convert(account.getScore() + amount));
        } else {
            if (account.getScore() < amount) {
                logger.error("--------------------" + Thread.currentThread().getName() + " " + accountNumber + ":" + score + (deposit ? "+" : "-") + amount + " failed");
               throw new NotEnoughException("account(" + accountNumber + ") not enough");
            }
            account.setScore(convert(account.getScore() - amount));

        }

        // account.setCas(account.getCas() + 1);
        accountDao.update(account);
        logger.error("-----" + Thread.currentThread().getName() + " " + accountNumber + ":" + score + (deposit ? "+" : "-") + amount + "=" + account.getScore());
    }

    
    public double convert(double value) {
        long lg = Math.round(value * 100);
        double d = lg / 100.0;
        return d;
    }

    @Transactional(value = "transactionManager")
    public void saveAccount(Account account) {
        accountDao.save(account);
    }

    @Inject
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    @Inject
    public void setDataConverter(DataConverter dataConverter) {
        this.dataConverter = dataConverter;
    }

}

 

 

用了多线程模拟多个请求,

task:MultipleTask

 

import java.util.Random;

public class MultipleTask implements Runnable {

    private TradeService tradeService;

    public MultipleTask(TradeService tradeService) {
        this.tradeService = tradeService;
    }

    @Override
    public void run() {
        String accountNumber = "SYS5";

        Random r = new Random();
        double amount = r.nextDouble() * 10;
        amount = convert(amount);
        boolean deposit = r.nextBoolean();

        try {
            Thread.sleep(r.nextInt(100));
        } catch (InterruptedException e) {
        }

        try {
            tradeService.handleSimple(accountNumber, amount, deposit);
        } catch (ResourceNotFoundException e) {
            e.printStackTrace();
        } catch (NotEnoughException e) {
            e.printStackTrace();
        }
    }

    public double convert(double value) {
        long lg = Math.round(value * 100);
        double d = lg / 100.0;
        return d;
    }

}

 

 

测试类:

 

@Test
    public void testMultiple() throws Exception {
       

        for (int i = 0; i < 30; i++) {
            MultipleTask m = new MultipleTask(tradeService);
            new Thread(m, i + "").start();

        }

        Thread.sleep(1000000000);
    }

 

特别要注意:

1.一定要在accountDao.update()方法的最后加上立即flush的语句,防止因没及时flush,其他线程访问到提交前的数据。

productionJpaAccess.getEntityManager().flush();

 

2.一定要在handleSimple方法里重新从数据库load对象,判断是否存在(已被别的线程删除?)、是否足够(已被别的线程取掉了一些钱?),然后更新。即让重新load,各种判断在synchronized管制之内

如果这几个判断在synchronized管制之外,有可能A线程在判断通过后,等待B线程执行synchronized代码,然后A进入synchronized代码时,数据已经被改变了,即原来的判断就不正确了。

 

3.在test方法中加上 Thread.sleep(1000000000);是为了防止主线程停止后spring容器被关闭。

 

 

 测试结果:

2014-04-24 13:35:22,399 [2] ERROR TradeService - -----2 SYS5:2.5+1.11=3.61
2014-04-24 13:35:22,413 [29] ERROR TradeService - -----29 SYS5:3.61-1.12=2.49
2014-04-24 13:35:22,414 [15] ERROR TradeService - --------------------15 SYS5:2.49-9.33 failed
2014-04-24 13:35:22,417 [27] ERROR TradeService - -----27 SYS5:2.49+4.06=6.55
2014-04-24 13:35:22,420 [23] ERROR TradeService - -----23 SYS5:6.55+1.61=8.16
2014-04-24 13:35:22,424 [17] ERROR TradeService - -----17 SYS5:8.16+3.82=11.98
2014-04-24 13:35:22,426 [7] ERROR TradeService - -----7 SYS5:11.98+9.48=21.46
2014-04-24 13:35:22,431 [21] ERROR TradeService - -----21 SYS5:21.46+2.91=24.37
2014-04-24 13:35:22,434 [5] ERROR TradeService - -----5 SYS5:24.37-4.36=20.01
2014-04-24 13:35:22,436 [19] ERROR TradeService - -----19 SYS5:20.01+2.95=22.96
2014-04-24 13:35:22,439 [3] ERROR TradeService - -----3 SYS5:22.96-2.72=20.24
2014-04-24 13:35:22,442 [11] ERROR TradeService - -----11 SYS5:20.24-2.25=17.99
2014-04-24 13:35:22,444 [25] ERROR TradeService - -----25 SYS5:17.99+3.97=21.96
2014-04-24 13:35:22,447 [13] ERROR TradeService - -----13 SYS5:21.96+6.31=28.27
2014-04-24 13:35:22,450 [9] ERROR TradeService - -----9 SYS5:28.27+3.7=31.97
2014-04-24 13:35:22,454 [28] ERROR TradeService - -----28 SYS5:31.97-4.84=27.13
2014-04-24 13:35:22,460 [26] ERROR TradeService - -----26 SYS5:27.13+2.29=29.42
2014-04-24 13:35:22,464 [8] ERROR TradeService - -----8 SYS5:29.42-1.73=27.69
2014-04-24 13:35:22,466 [0] ERROR TradeService - -----0 SYS5:27.69-9.4=18.29
2014-04-24 13:35:22,469 [16] ERROR TradeService - -----16 SYS5:18.29+2.03=20.32
2014-04-24 13:35:22,472 [22] ERROR TradeService - -----22 SYS5:20.32+2.58=22.9
2014-04-24 13:35:22,475 [24] ERROR TradeService - -----24 SYS5:22.9-3.39=19.51
2014-04-24 13:35:22,477 [1] ERROR TradeService - -----1 SYS5:19.51+2.27=21.78
2014-04-24 13:35:22,480 [14] ERROR TradeService - -----14 SYS5:21.78-8.68=13.1
2014-04-24 13:35:22,485 [10] ERROR TradeService - -----10 SYS5:13.1-2.81=10.29
2014-04-24 13:35:22,488 [18] ERROR TradeService - -----18 SYS5:10.29-1.59=8.7
2014-04-24 13:35:22,491 [4] ERROR TradeService - -----4 SYS5:8.7-2.44=6.26
2014-04-24 13:35:22,523 [12] ERROR TradeService - --------------------12 SYS5:6.26-6.56 failed
2014-04-24 13:35:22,526 [20] ERROR TradeService - -----20 SYS5:6.26+6.03=12.29
2014-04-24 13:35:22,532 [6] ERROR TradeService - -----6 SYS5:12.29+7.46=19.75

 

 

以上是自己实现的同步控制,适于只部署一台服务器。

如果是分布式环境,多个tomcat,用synchronized就没法控制了。

这时候就需要使用hibernate version,乐观锁。

 

hiberate里使用乐观锁很简单,在entity上加一个long类型的属性version,加上@Version,get,set。。

@Version
@Column(name = "Version")
 private long version;

 

然后就可以正常的save,update了。

在更新时会自动加上 , version = ?, update XX set XXXX where id=?, version = ?

如果记录已经被更新了,version就会加1,就找不到这条记录更新不了了,会抛出OptimisticLockException异常。

 

 

 

 

@Test
    public void testVersion() {
        Account a = new Account();
        a.setAccountNumber("SYS7");
        a.setScore(10d);
        tradeService.saveAccount(a);

       // a = tradeService.get("SYS7");
        System.out.println(a.getVersion());

        for (int i = 0; i < 5; i++) {
            a.setScore(a.getScore() + 1);
            tradeService.update(a);
            
            //a = tradeService.get("SYS7");
            System.out.println(a.getVersion());
        }    
    }

 以上代码会抛出javax.persistence.OptimisticLockException异常,因为update后,数据库的version已经更新了,但a还没更新。所以得重新get出来。

 

@Test
    public void testVersion() {
        Account a = new Account();
        a.setAccountNumber("SYS7");
        a.setScore(10d);
        tradeService.saveAccount(a);

        a = tradeService.get("SYS7");
        System.out.println(a.getVersion());

        for (int i = 0; i < 2; i++) {
            a.setScore(11d);
            tradeService.update(a);
            
            a = tradeService.get("SYS7");
            System.out.println(a.getVersion());
        }    
    }

 以上代码打印结果是

0

1

1

因为2次都是把score set成11d,其实值没变,update就没真正执行,也就不会使version增加。

 

 

@Test
    public void testVersion() {
        Account a = new Account();
        a.setAccountNumber("SYS7");
        a.setScore(10d);
        tradeService.saveAccount(a);

        a = tradeService.get("SYS7");
        System.out.println(a.getVersion());

        for (int i = 0; i < 2; i++) {
            a.setScore(a.getScore() + 1);
            tradeService.update(a);
            
            a = tradeService.get("SYS7");
            System.out.println(a.getVersion());
        }    
    }

以上代码打印

0

1

2

是正常的。

分享到:
评论

相关推荐

    pb并发控制.doc、pb并发控制

    在 PowerBuilder 中,数据窗口(DataWindow)是用于与数据库交互的重要组件,它支持多种数据库操作,包括并发控制。并发控制是确保多用户在同时访问相同数据时,不会导致数据不一致性的关键机制。关系型数据库系统...

    银行系统(密码登录,存钱、取钱、转账、销户)

    用户密码登录,存钱、取钱、转账、销户 动态内存vector

    Hibernate+Swing写的银行存钱系统

    总的来说,"Hibernate+Swing写的银行存钱系统"是一个涵盖多方面技术的学习项目,涵盖了Java GUI编程、数据库操作、ORM技术、事务管理等多个核心知识点,对于初学者而言,是深入理解Java编程和数据库应用的绝佳实践。

    c语言下实现模拟ATM机的过程,包括存钱取钱、查询、积分等,可以同时管理5个账户

    c语言下实现模拟ATM机的过程,包括存钱取钱、查询、积分等,可以同时管理5个账户

    这是一个可以进行卡类业务取钱转账,还包括解卡锁卡等操作于一体的简单服务系统

    取钱:卡是否存在,是否冻结,取钱金额是否正确 2.转账:把一个卡里的钱转到其他卡内 (卡是否存在,是否冻结,对方账户是否存在,转账的 金 额是否正确) 3.改密:(1)原密码改密 (2)身份证改密 4.解卡:判断卡号和身份证...

    操作系统实验二并发与调度(初学者).rar

    操作系统是计算机系统的核心组成部分,它负责管理系统的硬件资源和软件资源,为应用程序提供...实验二并发与调度是一个很好的实践平台,它将理论知识与实际编程相结合,帮助学习者巩固并深化对操作系统基本原理的理解。

    银行存取款案例(C#编程) c#经典案例.doc

    putmoney 方法用于存钱,getmoney 方法用于取钱。这些方法会修改账户的余额。 showbalance 方法 showbalance 方法用于显示当前账户的余额。 Main 方法 Main 方法是程序的入口点,用于启动程序。在 ...

    使用Java多线程的同步机制编写应用程序.docx

    实验目的是理解和掌握并行/并发、同步/异步的概念,并通过实现一个模拟银行账户的程序来具体应用这些概念。 首先,理解并行/并发是关键。并行是指多个任务在同一时刻实际地同时执行,而并发则是指多个任务在一段...

    bam网上银行系统存钱,取钱,注册等等一系列操作

    Java网上银行系统是一个利用Java编程语言开发的,为用户提供在线存款、取款、注册等一系列金融服务的应用程序。这个系统的核心目标是模拟真实银行的业务流程,为用户带来便捷、安全的虚拟金融体验。在本文中,我们将...

    struts2整合hibernate的网上银行模拟项目

    Struts2 主要负责MVC(模型-视图-控制器)架构的实现,提供了一种组织应用程序逻辑的方式,而Hibernate 是一个对象关系映射(ORM)工具,用于简化数据库操作。这个“struts2整合hibernate的网上银行模拟项目”结合了...

    JAVA_银行存款取款.doc

    根据给定的文件信息,我们可以总结出以下关于Java编程语言中的...通过上述分析,我们可以看出这是一个简单的银行账户管理系统示例,通过面向对象的编程方式实现了基本的存款和取款功能,并通过控制台与用户进行交互。

    第7节 事务及并发控制技术.docx

    事务是一组数据库操作,它代表了一个业务逻辑的完整执行单元。在数据库中,事务由一系列读写操作组成,这些操作要么全部执行,要么全都不执行,以确保数据的准确性和一致性。事务的概念与程序有所不同,程序通常包含...

    Lec15-并发控制理论1

    这通常通过并发控制算法实现,如两阶段锁、多版本并发控制(MVCC)等。这些算法确保事务在执行时互不见面,直到一个事务提交后,其他事务才能看到其更改,这称为隔离性。 ACID(原子性、一致性、隔离性、持久性)是...

    HibernateSpring数据库的事务HibernateSpring数据库的事务

    假设有一个银行转账的例子,事务A为取款事务,事务B为存款事务。如果这两个事务没有正确地被管理,就可能引发问题。例如: - 在T1时刻,事务A开始,事务B也在T2时刻开始。 - T3时刻,事务A查询账户余额为800;T4...

    C# 多线程银行账户存取简单模拟(未涉及并发互斥)

    每次只有一个线程能够进入`lock`代码块,从而避免了数据竞争。 此外,作者提到了另一个资源,其中涉及了同步和互斥问题。这可能是对上述问题的一种解决方案,使用了更高级的同步原语或者线程间通信技术,比如`...

    网上银行系统(Struts+Hibernate)

    Struts是Java Web开发中的一个MVC(Model-View-Controller)框架,而Hibernate则是一个对象关系映射(ORM)工具,用于处理数据库操作。 首先,让我们深入了解一下Struts框架。Struts通过提供一个结构化的模型,使得...

    手撸一个儿童银行给儿子存钱

    在用户登录成功后,系统会显示一个简洁的首页,首页上会有清晰的菜单选项,包括“存钱”、“取钱”和“查看余额”等按钮。 存钱功能是该应用的核心之一。用户点击“存钱”按钮后,系统会跳转到存钱界面。在这个界面...

    基于struts+hibernate的模拟银行转账

    在本项目中,“基于struts+hibernate的模拟银行转账”是一个利用经典的Java Web开发框架Struts1和持久层框架Hibernate构建的简单银行系统。这个系统旨在模拟网上银行的基本功能,如账户管理、转账操作等,从而帮助...

    关于处理informix并发问题的几点心得

    其中,滚积数交易是一个关键点,因为它涉及到每日余额的更新,而这可能会与用户的解约操作冲突。 在处理这种并发问题时,我们可以采用以下几种方法: 1. **更新游标**:使用更新游标可以锁定记录,防止其他进程...

    简单银行存款取款,以及实现线程java源代码

    当一个线程在执行过程中需要等待其他线程完成特定操作(比如,账户余额不足需要等待存款)时,它会调用`wait()`释放锁并进入等待状态;而其他线程完成相应操作后,通过调用`notify()`唤醒等待的线程。 除了基本的...

Global site tag (gtag.js) - Google Analytics