为了表述方便,以下表结构是随便取的字段:
一,
数据库有一订单表:trade_order(id,card_no,money,trade_date,pay_date,pay_status,order_status);
数据库有一订单日志表:trade_order_log(id,card_no,log_date,message);
当前表trade_order有一条记录:
id,card_no,money,trade_date,pay_date,pay_status,order_status
1 ,null ,null ,20101010 ,null ,1 ,1
这条记录意思是:一客户在20101010下了一个订单,订单支付状态(pay_status=1)为待支付,订单状态(order_status=1)为有效。二,
现在客户要输入卡号、密码等信息去支付该笔订单;三,
交易程序如下:
......
Connection conn = 从连接池取连接;
conn.setAutoCommit(false);
try{
//修改订单表信息
try {
String strSql = "update trade_order set card_no='2222',money=50 where id=1"
stmt = conn.createStatement();
return stmt.executeUpdate(strSql);
} finally {
stmt.close();
}
//执行存储过程(存储过程功能:扣卡余额和相应日志记录)
CallableStatement cstmt = null;
ResultSet rs = null;
try {
cstmt = conn.prepareCall(" {? = Call Trade(?)} "); //存储过程内容在下面
cstmt.registerOutParameter(1,java.sql.Types.BIGINT);
cstmt.setLong(2, 1);//传入订单ID:1
cstmt.executeUpdate();
} finally {
rs.close();
cstmt.close();
}
//向订单日志表插入数据
try {
String strSql="insert into trade_order_log(id,card_no,log_date,message) values(1,‘2222’,getdate(),'成功扣了50元')";
stmt = conn.createStatement();
stmt.executeUpdate(strSql);
} finally {
stmt.close();
}
//事务提交
conn.commit();
//日前成功扣了钱,下面要做手机充值交易
conn.setAutoCommit(true);
//调用手机充值模块
//......
}catch(SQLException sqlEx){ try
{
conn.rollback();
}
catch(SQLException sqlEx1)
{}
throw sqlEx;
}finally{
conn.setAutoCommit(true);
}//存储过程结构如下
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
go
/*****经典交易存储过程,一般交易都调这里****/
ALTER PROC [dbo].[Trade]
@bintOrderID bigint
as
//通过订单ID查出卡号,和订单金额
select * from trade_order where id=@bintOrderID
update trade_order set pay_status=2,pay_date=getdate() where id=@bintOrderID
//通过卡号查出卡帐户ID,然后更新卡帐户余额
/*更新卡帐户余额*/
update account_info set account_balance=account_balance-订单金额 where account_id=卡帐户ID四、程序就这么多,问题是有时(下面这种情况发生很少)
1,存储过程好像没执行(卡余额没扣除)但订单日志表:trade_order_log里却成功插入了一条日志,下面的手机充值模块执行了
2,存储过程执行了(卡余额扣了),但订单表trade_order里的卡号等信息没update掉,订单日志表:trade_order_log里却成功插入了一条日志,下面的手机充值模块执行了大家帮忙分析下
一,
数据库有一订单表:trade_order(id,card_no,money,trade_date,pay_date,pay_status,order_status);
数据库有一订单日志表:trade_order_log(id,card_no,log_date,message);
当前表trade_order有一条记录:
id,card_no,money,trade_date,pay_date,pay_status,order_status
1 ,null ,null ,20101010 ,null ,1 ,1
这条记录意思是:一客户在20101010下了一个订单,订单支付状态(pay_status=1)为待支付,订单状态(order_status=1)为有效。二,
现在客户要输入卡号、密码等信息去支付该笔订单;三,
交易程序如下:
......
Connection conn = 从连接池取连接;
conn.setAutoCommit(false);
try{
//修改订单表信息
try {
String strSql = "update trade_order set card_no='2222',money=50 where id=1"
stmt = conn.createStatement();
return stmt.executeUpdate(strSql);
} finally {
stmt.close();
}
//执行存储过程(存储过程功能:扣卡余额和相应日志记录)
CallableStatement cstmt = null;
ResultSet rs = null;
try {
cstmt = conn.prepareCall(" {? = Call Trade(?)} "); //存储过程内容在下面
cstmt.registerOutParameter(1,java.sql.Types.BIGINT);
cstmt.setLong(2, 1);//传入订单ID:1
cstmt.executeUpdate();
} finally {
rs.close();
cstmt.close();
}
//向订单日志表插入数据
try {
String strSql="insert into trade_order_log(id,card_no,log_date,message) values(1,‘2222’,getdate(),'成功扣了50元')";
stmt = conn.createStatement();
stmt.executeUpdate(strSql);
} finally {
stmt.close();
}
//事务提交
conn.commit();
//日前成功扣了钱,下面要做手机充值交易
conn.setAutoCommit(true);
//调用手机充值模块
//......
}catch(SQLException sqlEx){ try
{
conn.rollback();
}
catch(SQLException sqlEx1)
{}
throw sqlEx;
}finally{
conn.setAutoCommit(true);
}//存储过程结构如下
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
go
/*****经典交易存储过程,一般交易都调这里****/
ALTER PROC [dbo].[Trade]
@bintOrderID bigint
as
//通过订单ID查出卡号,和订单金额
select * from trade_order where id=@bintOrderID
update trade_order set pay_status=2,pay_date=getdate() where id=@bintOrderID
//通过卡号查出卡帐户ID,然后更新卡帐户余额
/*更新卡帐户余额*/
update account_info set account_balance=account_balance-订单金额 where account_id=卡帐户ID四、程序就这么多,问题是有时(下面这种情况发生很少)
1,存储过程好像没执行(卡余额没扣除)但订单日志表:trade_order_log里却成功插入了一条日志,下面的手机充值模块执行了
2,存储过程执行了(卡余额扣了),但订单表trade_order里的卡号等信息没update掉,订单日志表:trade_order_log里却成功插入了一条日志,下面的手机充值模块执行了大家帮忙分析下
由于JAVA程序和存储过程在同一事务里操作trade_order的同一条记录,不知道是不是有问题
你确定抛出的是SQLException ?而不是别的什么类型的Excepiton
String strSql = "update trade_order set card_no='2222',money=50 where id=1"
stmt = conn.createStatement();
return stmt.executeUpdate(strSql);
} finally {
stmt.close();
}
这里是一个问题。
还有,你的代码这样写很不规范。去看看如何执行多个SQL语句吧。
另外finally不是万能的。如果要重复使用stmt的话,去看下JDK的描述
return stmt.executeUpdate(strSql);
有什么问题?
return stmt.executeUpdate(strSql); 这个地方没方return的,这个笔误。原项目中,这3个数据库操作,都是方法调用的,所以有return
我为了描述方便 ,就把3个方法内容copy到一起,不小心多了个return的
我有一个方案:就是让存储过程执行完返回一个值,java程序判断是否有返回值,来判断是否做rollback,只是不知道为什么会出现问题,没发做测试,测试结果又都是正常的
如果可以的话,最好debug下你的rollback有没有生效
USE [rtcard]
GO
/****** Object: StoredProcedure [dbo].[prcd_DoTrade] Script Date: 10/13/2010 15:28:34 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO/*****经典交易存储过程,一般交易都调这里****/
ALTER PROC [dbo].[prcd_DoTrade]
@bintOrderID bigint,
@bintAdminID bigint,
@varIPAddress varchar(15)
as
--支付帐户ID
DECLARE @bintPayAccountID bigint
--支付金额
DECLARE @bintPayMoney bigint
--接收帐户ID
DECLARE @bintRcvAccountID bigint
--接收金额
DECLARE @bintRcvMoney bigint
--支付帐户余额
DECLARE @bintPayAccBalance bigint
--接收帐户余额
DECLARE @bintRcvAccBalance bigint /*取出订单中的帐户ID和支付接收金额*/
select @bintPayAccountID=pay_account_id,@bintPayMoney=pay_money,@bintRcvAccountID=rcv_account_id,@bintRcvMoney=rcv_money from trade_order where order_id=@bintOrderID /*查询取出帐户变动前的帐户余额*/
select @bintPayAccBalance=account_balance from account_info where account_id=@bintPayAccountID
select @bintRcvAccBalance=account_balance from account_info where account_id=@bintRcvAccountID /*插入交易日志(记录帐户变动前的帐户余额状态)*/
if @bintAdminID is null or @bintAdminID=0
insert into trade_log(order_id,pay_account_id,pay_account_balance,rcv_account_id,rcv_account_balance,ip_address) values(@bintOrderID, @bintPayAccountID, @bintPayAccBalance, @bintRcvAccountID, @bintRcvAccBalance, @varIPAddress)
else
insert into trade_log(order_id,pay_account_id,pay_account_balance,rcv_account_id,rcv_account_balance,employee_id,ip_address) values(@bintOrderID, @bintPayAccountID, @bintPayAccBalance, @bintRcvAccountID, @bintRcvAccBalance, @bintAdminID, @varIPAddress) /*更新支付订单记录支付状态为支付成功*/
update trade_order set pay_date=getdate(),pay_status=3 where order_id=@bintOrderID /*更新帐户余额*/
update account_info set account_balance=account_balance-@bintPayMoney where account_id=@bintPayAccountID
update account_info set account_balance=account_balance+@bintRcvMoney where account_id=@bintRcvAccountID
java程序中的事务,根据存储过程的执行结果判断整个事务事务是否提交。毕竟账户金额操作最重要。
解决办法
当你使用手动事务模式时,必须把SelectMethod 属性的值设置为 Cursor, 或者是确保在你的连接上只有一个STATEMENT操作。
以前遇到过SQL Server有很多问题,否则可能在访问期间由于在一个Connection对象中使用多个不同Statement(preparedStatement或callalbeStatement)对象的现象,造成在java中出现克隆的Connection,引起的事务问题吧,你看看把驱动url上多加上一个 SelectMethod=cursor :即jdbc:microsoft:sqlserver://localhost:port;DatabaseName=xxx;SelectMethod=cursor 试试看