我遇到了以下错误:
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”数据源 - 我们必须复制每个客户端的邮政编码信息是愚蠢的,但是如果出现问题,可以回滚必须。
提前致谢。
答案 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 年的 Adobe。这是我设计的解决方案。
我想要一种将调试消息发送到我编写的电话应用程序的方法。他们共享对数据库的访问。
这是我的解决方案的改编版。我的实际代码做了一些其他的事情,所以没有直接测试
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 ,因为我的用途是用于调试。