使用CFTRANSACTION的多个数据源

时间:2013-02-25 15:32:08

标签: mysql coldfusion transactions coldfusion-9

我遇到了以下错误:

Datasource names for all the database tags within the cftransaction tag must be the same.

这来自以下代码:

transaction action="begin" {
  try {
    var data = {};

    data.time = getTickCount();

    addToLog("Persist", "Started persist operations");

    doClientPersist();
    cleanUp(arguments.importId);

    addToLog("Persist", "Completed the persist operations successfully", ((getTickCount()-data.time)/1000));

    return true;
  } catch (any e) {
    transactionRollback();
    data.error = e;
  }
}

该事务有效地在doClientPersist()内包含了一些较低级别的方法。一个这样的调用,深入我们的框架数据库抽象层,从一个单独的数据源(比如邮政编码数据源)获取(SELECT)经度和纬度信息 - 这个数据源是严格只读的。

<cffunction name="getLatitudeAndLongitude" access="package" returntype="query" output="false">
  <cfargument name="postcode" type="string" required="true" />
  <cfset var qPostcode = ''/>
  <cfquery name="qPostcode" datasource="postcodesDatasource">
    SELECT 
      a.latitude, 
      a.longitude
    FROM 
      postcodes AS a
    WHERE 
      a.postcode = <cfqueryparam cfsqltype="CF_SQL_VARCHAR" value="#postcode#"/>
  </cfquery>
  <cfreturn qPostcode/>
</cffunction>

<cffunction name="getPostcodeCoordinates" access="public" returntype="struct" output="false">
  <cfargument name="postcode" type="string" required="true"/>
  <cfscript>
    var data = {};

    data.postcode = getFormattedPostcode(arguments.postcode);
    data.valid    = isValidPostcode(data.postcode);
    data.coords   = {};

    if (data.valid) {
      data.query = getLatitudeAndLongitude(data.postcode);
      if (isQuery(data.query) && data.query.recordCount) {
        data.coords["latitude"]  = data.query["latitude"][1];
        data.coords["longitude"] = data.query["longitude"][1];
      } else if (data.valid == 2) {
        /** No match, try short postcode (RECURSIVE) **/
        data.coords = getPostcodeCoordinates(trim(left(data.postcode, len(data.postcode)-3)));
      }
    }
    return data.coords;
  </cfscript>
</cffunction>

在阅读问题时,文档会说:

In a transaction block, you can write queries to more than one database, but you must commit or roll back a transaction to one database before writing a query to another.

不幸的是,如上所述,获取此邮政编码数据的代码与实际的持久操作完全无关,因为它执行了一个无法更改的低级方法Web我无法在进行“顶级”事务之前提交对远程数据源的调用。

无论如何,我可以在事务中包装“顶级”方法并且仍然可以调用“postcode”数据源 - 我们必须复制每个客户端的邮政编码信息是愚蠢的,但是如果出现问题,可以回滚必须

提前致谢。

4 个答案:

答案 0 :(得分:3)

我可以看到,你基本上有两种选择。

1)在交易之外查询您的数据。根据应用程序的具体情况,这可能是在事务块之前移动该方法,在事务块之前拆分方法并移动部分方法,将数据预取到RAM中(将数据保存为一个查询)变量),然后让你的方法使用这个预先获取的数据,而不是直接查询数据库。

然而,所有这些解决方案的结果都是相同的。也就是说,SELECT查询本身是在事务之外执行的。

如果由于某种原因这是不切实际的,那么就...... [/ p>

2)使用相同的数据源。请注意,您不必使用相同的数据库,只需使用相同的数据源。所以,你可以在MySQL中使用database.tablename语法。

通过快速搜索,我发现了一个很好的例子: Querying multiple databases at once

拥有更好Google-fu的人可能会很快找到更好的例子。

但基本原则是您在查询中使用FROM database.tablename而不是FROM tablename。

我认为这需要数据库位于同一个MySQL服务器上。

答案 1 :(得分:1)

所以我对如何解决这个问题感到困惑。我接受了史蒂夫的回答,因为他给了我这个想法(谢谢),但是添加了下面的代码来展示解决方案的简单示例。

对我来说,数据源数据无法复制,上面的代码需要包装事务。 所以它只给我留下了一个解决方案,在我看来只有一半解决方案......

<cffunction name="methodA" access="public" returntype="query" output="false">
  <cfset var q = ""/>
  <cfquery name="q" datasource="test_1">
    SELECT id, name FROM table_a
  </cfquery>
  <cfset methodB = methodB()/>
  <cfreturn q/>
</cffunction>

<cffunction name="methodB" access="public" returntype="query" output="false">
  <cfset var q = ""/>
  <cfquery name="q" datasource="test_1">
    SELECT id, name FROM table_b
  </cfquery>
  <cfset methodC = methodC()/>
  <cfreturn q/>
</cffunction>

<cffunction name="methodC" access="public" returntype="void" output="false">
  <cfset var q = ""/>
  <!--- 
  This will error with the following: 
    Datasource test_2 verification failed. 
    The root cause was that: java.sql.SQLException: 
    Datasource names for all the database tags within the cftransaction tag must be the same.
  <cfquery name="q" datasource="test_1">
    INSERT INTO test_2.table_z (`id`, `name`) VALUES ('1','test'); 
  </cfquery>
  --->

  <!--- This is the same query, however I have reused the datasource test_1
  and specified the DATABASE test_2 --->
  <cfquery name="q" datasource="test_1">
    INSERT INTO test_2.table_z (`id`, `name`) VALUES ('1','test'); 
  </cfquery>

</cffunction>

<cftransaction action="begin">
  <cfset data = methodA()/>
  <cftransaction action="commit"/>
</cftransaction>

因此,如果不清楚,我的解决方案是删除对第二个数据源的引用 test_2并使用test_1 数据源相反。这意味着硬编码 查询中的第二个test_2 数据库名称。显然,这可以动态完成,但是它确实会导致现有查询出现问题,因为它们需要更改。除此之外,如果第二个数据源是一个不同的数据库平台,如MSSQL,这将不起作用。值得庆幸的是,对我来说情况并非如此。

答案 2 :(得分:0)

有同样的问题,只是将我的cftransaction标签从第二个(或第一个)数据源cfquery中移出。如果您在整个代码中使用它们,这包括CFC。

答案 3 :(得分:0)

我发现这个问题很陈旧,但它仍然是 Lucee 的“问题”,也可能是 2021 年的 Adob​​e。这是我设计的解决方案。

我想要一种将调试消息发送到我编写的电话应用程序的方法。他们共享对数据库的访问。

这是我的解决方案的改编版。我的实际代码做了一些其他的事情,所以没有直接测试

public numeric function QueryQueue(required string sql, struct options = {}, struct params = {}, queryset = "default") {

  param name="request.queryQueue" default="#{}#";
  if (!StructKeyExists(request.queryQueue, arguments.queryset)) {
    request.queryQueue[arguments.queryset] = []
  }

  request.queryQueue[arguments.querySet].append({sql: "#arguments.sql#",
    options: arguments.options,
    params: arguments.params,
    removed: false});

  return request.queryQueue[arguments.queryset].len();
  // returning the length, and thus the position of the query,
  // so it can be specifically called if desired.
  // This is query QueryQueueExecute doesn't actually
  // delete elements, but marks them.
}

public any function QueryQueueExecute(required numeric pos, boolean remove = true, string queryset = "default") {
  if (!request.queryQueue[arguments.queryset][arguments.pos].removed) {
    var theQuery = QueryExecute(sql = request.queryQueue[arguments.queryset][arguments.pos].sql,
      options = request.queryQueue[arguments.queryset][arguments.pos].options,
      params = request.queryQueue[arguments.queryset][arguments.pos].params);
    if (arguments.remove) {
      request.queryQueue[arguments.queryset][arguments.pos].removed = true;
    }
    return theQuery;
  } else {
    return {recordcount: -1}; // a flag to show that the query wasn't executed, because it's already been "removed"
  }
}

public array function QueryQueueExecuteAll(boolean remove = true, string queryset = "default") {
  var queryArray = [];
  for (i = 1; i <= request.queryQueue[arguments.queryset].len(); i++) {
    queryArray.append(QueryQueueExecute(i, false));
    // false is deliberately set here, rather than passing the remove argument
    // since the array will be cleared after the loop.
  }
  if (arguments.remove) {
    request.queryQueue[arguments.queryset].clear();
  }
  return queryArray;
}

这些函数让我可以对查询进行排队,然后执行特定的查询,或者全部执行。如果需要或不需要,还有一个标志可以删除,但我无法想象为什么它不会。

就我而言,我可以在 OnRequestEnd 和我的 ErrorHandler 中执行 run ,因为我的用途是用于调试。