本人在编写一个软件,主要用于货物销售,采用struts架构,后台使用MS SQLSERVER。
我碰到的问题是:
一次销售可能包含多个产品,那么数据库设计采用主表-子表的形式,一对多关系。在jsp页面输入每个产品的销售数量时,可能会出现大于库存数量,或者无该产品库存(存储库存的表没有对应的记录),一旦碰上这种情况,我需要回滚本次销售发生的所有操作,包括库存数量足够的记录。我不知道该怎么处理,设置autocommit为false,在javabean里逐条写sql语句好像不可取,但数据库存储过程又无法传递数组参数(一次销售中产品种类的数量不确定,只能用数据等数据结构),该怎么处理好呢?我能想到的办法是:先把数据存到一张临时表,然后调用存储过程把数据存入正式表,在存储过程中启用事务处理,并使用
cursor逐条验证库存数量,一旦发现一条数量不足,回滚所有操作。但我觉得我这个方法挺笨的,请教高手,给一个好的方法。

解决方案 »

  1.   

    如果是一个数据库的话,直接用数据库的事务处理就可以了。
    如果是多个数据库,可以考虑JTA。
    UserTransaction userTran;
    try
    {
    userTran = UserTransactionManager.getUserTransaction();
    }
    catch (NamingException exp)
    {
    logger.error("UserTransaction init exception");
    throw new BPServiceException("事务初始化异常",exp);
    }try
    {
    userTran.begin(); // 启动事务// 这里是正常的业务逻辑 userTran.commit(); // 提交事务
    }
    catch (Exception exp)
    {
    try
    {
    userTran.rollback(); // 发生异常,回滚事务
    }
    catch (Exception exp1)
    {
    logger.error("UserTransaction rollback exception.", exp1);
    }
    }
    如果用Spring的话,可以直接使用Spring的事务管理
      

  2.   

    感谢samboy2002
    我还想问问,你的代码里面的业务逻辑怎么写?是一条sql语句定义一个statement吗?那岂不是很复杂
      

  3.   

    没必要见临时表啊。sqlserver 是不能传递数组参数,可是你可以在程序里面所有产品的id拼接成字符串穿进去。
    然后再存储过程里面进行split ,
    这个函数到处都是,在一sp里面处理的话,肯定可以事务同步了吧。
      

  4.   

    但我要传递的内容不仅仅是产品ID,目前至少有仓库代码,货物代码,销售数量,销售金额,这么串起来又要分拆开很麻烦,而且sql做这个应该效率也不高吧?
      

  5.   

    通常情况下数据库操作要独立出来,封装个DAO层,这样在业务层需要操作数据库的时候直接调用DAO就可以了。
    但是一个业务可能涉及多个DAO的操作,所以事务是要放在业务层的。上面那段代码是直接操作JTA的,如果你使用类似SSH这样的开发框架的话,不需要这么复杂,直接在Spring的配置文件配置,或是用Annotation就可以了。至于SQL的Statement,一个对象可以执行多个SQL。更主要的是简化同一段代码中的业务逻辑,而不是减少几个SQL语句。
      

  6.   

    UserTransaction是属于EJB吗?我用的容器是tomcat,好像不支持EJB
      

  7.   

    必须要在一个事务里面,一般是在service层。
      

  8.   

    如果你的SQL中有复杂的判断条件的话,使用存储过程会好一些。如果只是简单的Insert/Update之类的,在程序中处理会更方便一些,虽然程序上的性能会略低一些,但是一般不会有太大的差别。
      

  9.   


    UserTransaction是JTA中定义的接口,引用jta-1.0.jar就可以了。但是还需要一个JTA的实现,比如JOTM之类的。
    如果不涉及到多个数据库操作的话,也可以直接用JDBC的事务,方法这上面的类似,只是方法名有点差异。
      

  10.   


    非常感谢老兄如此热心及时的回复。
    我只用了struts,也没用过spring和hibernate。
    你的回答我还是不太明白,能给例子吗?
    以下是我写的存储过程,但只对单条数据操作
    begin transaction
    update depository set kcsl=kcsl-@ghsl where dm_ck=@dmck and dm_hw=@dmhw
    if @@rowcount=0
    begin
    print 'no rows were updated'
    rollback transaction
    end
    else
    begin
    select @result=kcsl from depository where dm_ck=@dmck and dm_hw=@dmhw
    if @result<0
    begin
    print 'ghsl is more than kcsl'
    rollback transaction
    end
    else
    begin
    insert into sellitem(dmck,dmhw,ghsl,ghje,mainid)values(@dmck,@dmhw,@ghsl,0,0)
    print 'success'
    commit transaction
    end
    end
      

  11.   


    我的业务逻辑虽然不复杂,主要就是去库存表里看看所要销售的货物是否存在,数量是否足够,一旦发现一条数量不足,回滚所有操作。但写成sql语句还是不少的,我估计只能在存储过程中实现。
    但是,这样问题又回到最初,我如何把我的多条货物数据(每条记录至少有仓库代码,货物代码,销售数量,销售金额4个字段)传递给存储过程。如果数据能够到达存储过程,做事务处理肯定没问题的。所以我才采用临时表的解决办法。难道没有在Java中操作的更好的办法吗?顺便问一句,帖子回复时如何表示代码?
      

  12.   

    回复内容输入框上面有一排按钮,其中有一个是代码。把你的存储过程改成JAVA程序差不多是这样:public void execute(String p1, int p2)
    {
    Connection conn = getConnection();

    conn.setAutoCommit(false);
    try
    {
    Statement stmt = conn.createStatement();
    int count = stmt.executeUpdate("update....");
    if(count == 0) throw new Exception();

    ResultSet rs = stmt.executeQuery("select....");
    if(rs.getInt("result") == 0)
    throw new Exception();

    stmt.execute("insert....");
    conn.commit();
    }catch(Exception exp){
    conn.rollback();
    }
    }
    如果需要多个产品处理的话,可以修改参数,改成List之类的。但是这段程序有个需要注意的地方是getConnection()的地方,使用之后要释放,然后如果传入List参数的话,不要每次都getConnection().
      

  13.   

    samboy2002,谢谢你,你的回答让我有些谱了,因为开发机器不在身边,我明天试验一下,非常感谢!!!回复输入框在页面的最下面吧?我的电脑上没有一排按钮啊?
      

  14.   

    “提交回复”按钮上面有个禁用UBB的选项,把前面的勾去掉就有了。
      

  15.   

    MS 的sqlserver数据库,对于高度并发的事务处理,支持相当薄弱,他是一种通过读写锁的方式来控制并发时数据脏读问题的,因此如果你的业务量和数据量很大的,我个人觉得,有必要考虑是否使用sqlserver,我想你可能是2k版本。如果你非要使用sqlserver数据库,那就必须极大的牺牲性能来支持大事务,因为这种事务操作如果不是数据库支持,自己去实现是相当困难的,我建议你用oracle。