5

我遇到了以下错误:

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(). 一个这样的调用,在我们的框架数据库抽象层的深处,从一个单独的数据源(比如说邮政编码数据源)获取(SELECTs)经度和纬度信息——这个数据源是严格只读的。

<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.

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

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

提前致谢。

4

4 回答 4

3

如我所见,您基本上有两种选择。

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

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

如果由于某种原因这不切实际,那么继续...

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

通过快速搜索,我找到了一个很好的例子: 一次查询多个数据库

比我有更好的 Google-fu 的人可能很快就能想出更好的例子。

不过,基础是您在查询中使用 FROM database.tablename 而不是 FROM tablename。

但是,我相信这将要求数据库位于同一 MySQL 服务器上。

于 2013-02-26T14:57:44.597 回答
2

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

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

<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,这将不起作用。谢天谢地,这对我来说不是这样。

于 2013-02-27T16:50:24.517 回答
0

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

于 2014-09-15T19:26:12.177 回答
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 this,因为我的用途是用于调试。

于 2021-01-14T06:18:56.653 回答