1. 示例代码
int CSEMQ::Save(CSEMQItem &msg) {
USE_CURSOR_DBC(pdbor,this->dbc_name_.c_str(),CLIENT_CURSOR);
ITableHandler *th = CBasePluginModule::db_helper_->NewTableHandler(pdbor,this->st_name_.c_str());
AUTO_POINTER_NODECLARE(ITableHandler,th);
vector<CQQ_OBJECT_ID> &v_rec = semq_tss_->v_rec;
pdbor->SetTXHook(TxHook,this); ///< 设置事务挂钩函数
pdbor->BeginTrans();
th->BindField("src_type",(char**)&src_type,sizeof(src_type));
th->BindField("src_id",(char**)&src_id,sizeof(src_id));
......
th->BindField("f007b_0031",buffer,buffer_len);
if (th->Insert2()) {
return -1;
}
unsigned __int64 object_id2;
if (pdbor->GetDBExt()->GetLastID(object_id2,2)) {
return -2;
}
v_rec.push_back(object_id2); ///< 把新增记录的 object_id 写入 TSS 中 , 待事务结束后处理
pdbor->CommitTrans();
return 0;
}
2. 事务挂钩
利用 IDbAccessor2 的 SetTXHook 方法为连接对象设置事务挂钩函数 .
该函数在事务结束后 (Commit 或 Rollback) 时调用 .
出于嵌套事务和程序模块性考虑 ,IDbAccessor2 支持嵌套事务 , 内部有事务嵌套计数器 .
即 CommitTrans 调用并不真正意味数据库事务的提交 .
这样就可能有外层事务的情况下 ,Insert2 的记录并没有真正提交,其它会话无法访问 .
事务钩子的作用就是在产生数据库提交事务动作时再执行 .
实际的事务钩子函数如下 :
struct SEMQ_TSS {
vector<CQQ_OBJECT_ID> v_rec;
};
ACE_TSS<SEMQ_TSS> semq_tss_;
int TxHook(short status,void *mq) {
if (status==0) {
semq_tss_->v_rec.clear();
return 0;
}
CSEMQ *semq = (CSEMQ*)mq;
vector<CQQ_OBJECT_ID> &v = semq_tss_->v_rec;
vector<CQQ_OBJECT_ID>::iterator iter = v.begin();
while(iter!=v.end()) {
semq->SendRecord(*iter); ///< 发送新增的记录
iter++;
}
semq_tss_->v_rec.clear();
return 0;
}
3.Insert2 后 GetLastID 失败的问题
ITableHandler 的 Insert2 采用 ADO 的 AddNew/PutCollect 方法实现 , 目的是支持大字段的操作
IDBExt 的 GetLastID 目的是获取新 Insert 记录的自增字段的值 .(原始语义并不明确)
SQL Server 扩展实现 GetLastID 采用的是 SCOPE_IDENTITY().
Insert2 后获取 object_id 时失败 ,select SCOPE_IDENTITY() 返回 VT_NULL;
建立测试环境诊断此问题 :
2.1 表
CREATE TABLE [dbo].[test1]( [f1] [int] IDENTITY(1,1) NOT NULL, [f2] [nchar](10) COLLATE Chinese_PRC_CI_AS NULL ) ON [PRIMARY] |
2.2 测试代码
USE_CURSOR_DBC(pdbor,"testdb",SERVER_CURSOR); string sql = "select * from test1 where 1<>1"; AUTO_QUERY_RECORDSET(CRecordset,prs,pdbor); prs = pdbor->Query(sql.c_str(),adOpenStatic,adLockOptimistic); prs->AddNew(); prs->PutCollect("f2","dddddd"); prs->Update(); unsigned __int64 object_id2; if (pdbor->GetDBExt()->GetLastID(object_id2)) { return -1; } |
从 SQL Server Profiler 看出 ,Update 后执行的语句为 :
exec sp_executesql N'SET NOCOUNT OFF; INSERT INTO "test".."test1" ("f2") VALUES (@P1); SELECT SCOPE_IDENTITY() AS SCOPE_ID_COLUMN',N'@P1 nvarchar(10)',N'dddddd ' |
可见作用域不同 (sp_executesql 中 insert).
这符合 SCOPE_IDENTITY() 的说明 .
解决 :
修改 GetLastID, 以支持不同语义的 identity. 通过 scope 指明范围 (1- 当前会话当前作用域 2- 当前会话 )
virtual int GetLastID(unsigned __int64 &object_id,short scope=1) = 0; /// 获取最后插入记录的自增序列值
调用GetLastID(object_id2,2);