如何避免 Jackrabbit DataStore "table or view does not exist" 错误?

How to avoid the Jackrabbit DataStore "table or view does not exist" error?

有时,我们的 Jackrabbit (v2.6.1) 系统在设置新环境时无法在 Oracle 数据库中自动创建 DATASTORE_DATASTORE table。在这种情况下,我们会在应用程序日志文件中看到以下错误:

DbDataStore                              - Can not insert new record
java.sql.SQLSyntaxErrorException: ORA-00942: table or view does not exist
    at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:447)
    at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:396)
    at oracle.jdbc.driver.T4C8Oall.processError(T4C8Oall.java:951)
    at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:513)
    at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:227)
    at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:531)
    at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:208)
    at oracle.jdbc.driver.T4CPreparedStatement.executeForDescribe(T4CPreparedStatement.java:886)
    at oracle.jdbc.driver.OracleStatement.executeMaybeDescribe(OracleStatement.java:1175)
    at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1296)
    at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:3613)
    at oracle.jdbc.driver.OraclePreparedStatement.execute(OraclePreparedStatement.java:3714)
    at oracle.jdbc.driver.OraclePreparedStatementWrapper.execute(OraclePreparedStatementWrapper.java:1378)
    at org.apache.commons.dbcp.DelegatingPreparedStatement.execute(DelegatingPreparedStatement.java:172)
    at org.apache.commons.dbcp.DelegatingPreparedStatement.execute(DelegatingPreparedStatement.java:172)
    at org.apache.commons.dbcp.DelegatingPreparedStatement.execute(DelegatingPreparedStatement.java:172)
    at org.apache.jackrabbit.core.util.db.ConnectionHelper.execute(ConnectionHelper.java:516)
    at org.apache.jackrabbit.core.util.db.ConnectionHelper.reallyExec(ConnectionHelper.java:404)
    at org.apache.jackrabbit.core.util.db.ConnectionHelper.call(ConnectionHelper.java:379)
    at org.apache.jackrabbit.core.util.db.ConnectionHelper.call(ConnectionHelper.java:375)
    at org.apache.jackrabbit.core.util.db.ConnectionHelper$RetryManager.doTry(ConnectionHelper.java:557)
    at org.apache.jackrabbit.core.util.db.ConnectionHelper.exec(ConnectionHelper.java:375)
    at org.apache.jackrabbit.core.util.db.ConnectionHelper.query(ConnectionHelper.java:359)
    at org.apache.jackrabbit.core.data.db.DbDataStore.addRecord(DbDataStore.java:321)
    at org.apache.jackrabbit.core.value.BLOBInDataStore.getInstance(BLOBInDataStore.java:121)
    at org.apache.jackrabbit.core.value.InternalValue.getBLOBFileValue(InternalValue.java:626)
    at org.apache.jackrabbit.core.value.InternalValue.create(InternalValue.java:381)
    at org.apache.jackrabbit.core.value.InternalValueFactory.create(InternalValueFactory.java:108)
    at org.apache.jackrabbit.core.value.ValueFactoryImpl.createBinary(ValueFactoryImpl.java:77)

我们的 DataStore Jackrabbit 配置如下所示:

<DataStore class="org.apache.jackrabbit.core.data.db.DbDataStore">
        <param name="url" value="jdbc:oracle:thin:@//db:1521/SID" />
        <param name="user" value="foo" />
        <param name="password" value="foo" />
        <param name="databaseType" value="oracle" />
        <param name="driver" value="oracle.jdbc.OracleDriver" />
        <param name="minRecordLength" value="1024" />
        <param name="maxConnections" value="3" />
        <param name="copyWhenReading" value="true" />
        <param name="tablePrefix" value="" />
        <param name="schemaObjectPrefix" value="datastore_"/>
</DataStore>

这似乎只发生在我们在同一个数据库实例上有多个 Jackrabbit 实例时,每个 Jackrabbit 实例都在其自己的数据库中 schema/user。该问题仅出现在 DATASTORE_DATASTORE table - 所有其他 Jackrabbit table 在这种情况下都可以正常工作。

可能是什么导致了这个问题?

可以在 Jackrabbit DataStore FAQ:

中找到回答此问题的提示

Q: When I use the database data store I get the message: 'Table or view does not exists'. A: Maybe the data store table already exists in another schema. When starting the repository, the database data store checks if the table already exists (using a database meta data call), and will create the table if not. If the table exists, but is in another schema, the table is not created, but accessing it may fail (if the other schema is not in the schema search path for this user).

事实证明,对于 DataStore(所有其他 tables 使用不同的方式检查 tables 是否存在),Jackrabbit 使用数据库的元数据来检查是否存在DATASTORE_DATASTORE table,默认情况下,这是以不考虑用户数据库模式的方式完成的。这意味着如果在同一个 Oracle 实例上有另一个 Jackrabbit 模式已经有一个名为 DATASTORE_DATASTORE 的 table,那么 Jackrabbit 会认为 table 已经存在并且不会尝试创建它在用户的架构中。

DataStore 配置中设置推荐的 tablePrefix 属性 不起作用:

<DataStore class="org.apache.jackrabbit.core.data.db.DbDataStore">
        <param name="url" value="jdbc:oracle:thin:@//db:1521/SID" />
        <param name="user" value="foo" />
        <param name="password" value="foo" />
        <param name="databaseType" value="oracle" />
        <param name="driver" value="oracle.jdbc.OracleDriver" />
        <param name="minRecordLength" value="1024" />
        <param name="maxConnections" value="3" />
        <param name="copyWhenReading" value="true" />
        <param name="tablePrefix" value="foo." />
        <param name="schemaObjectPrefix" value="datastore_"/>
</DataStore>

这样做(句点在 tablePrefix 末尾)将导致 Jackrabbit 永远找不到 table,并且会导致更多错误,因为 Jackrabbit 不会尝试每次启动应用程序时创建 table。

有两种方法可以永久修复此问题:

唯一Table名称

此修复需要操作 tablePrefixschemaObjectPrefix 值,以便 table 的名称在整个数据库中是唯一的。例如对于第一个 Jackrabbit 实例使用这个:

<DataStore class="org.apache.jackrabbit.core.data.db.DbDataStore">
        <param name="url" value="jdbc:oracle:thin:@//db:1521/SID" />
        <param name="user" value="foo1" />
        <param name="password" value="foo1" />
        <param name="databaseType" value="oracle" />
        <param name="driver" value="oracle.jdbc.OracleDriver" />
        <param name="minRecordLength" value="1024" />
        <param name="maxConnections" value="3" />
        <param name="copyWhenReading" value="true" />
        <param name="tablePrefix" value="" />
        <param name="schemaObjectPrefix" value="FOO1_"/>
</DataStore>

这导致此用户的 table 调用 FOO1_DATASTORE。然后,同一数据库中的第二个 Jackrabbit 实例可以使用这样的配置来确保唯一的 table 名称:

<DataStore class="org.apache.jackrabbit.core.data.db.DbDataStore">
        <param name="url" value="jdbc:oracle:thin:@//db:1521/SID" />
        <param name="user" value="foo2" />
        <param name="password" value="foo2" />
        <param name="databaseType" value="oracle" />
        <param name="driver" value="oracle.jdbc.OracleDriver" />
        <param name="minRecordLength" value="1024" />
        <param name="maxConnections" value="3" />
        <param name="copyWhenReading" value="true" />
        <param name="tablePrefix" value="" />
        <param name="schemaObjectPrefix" value="foo2_"/>
</DataStore>

这导致此架构的 table 被称为 FOO2_DATASTORE

虽然这行得通,但有点丑 error-prone,因为它需要对配置进行更改,而这些更改很容易被忽略 - 并且与所有其他 Jackrabbit tables 不同,后者行得通没有这个 hack 就好了。

自定义 DbDataStore Class

另一个解决方案是解决 Jackrabbit 代码中的问题。最初的问题源于 Jackrabbit 通过其 ConnectionHelper.tableExists 方法 (http://grepcode.com/file/repo1.maven.org/maven2/org.apache.jackrabbit/jackrabbit-core/2.6.1/org/apache/jackrabbit/core/util/db/ConnectionHelper.java?av=f#201) 检查数据库中是否存在 table 的方式,该方法检查名为 checkTablesWithUserName 的标志,默认设置为 false,导致对整个数据库进行全局检查(错误!):

要将该标志设置为 true,需要调用一个单独的构造函数以允许将标志设置为 true。为了让它工作,我们需要创建我们自己的 UserCheckDbDataStore class - 它需要与 ConnectionHelper class 在同一个包中以使用替代构造函数:

package org.apache.jackrabbit.core.util.db;

import javax.sql.DataSource;

import org.apache.jackrabbit.core.data.db.DbDataStore;
import org.apache.jackrabbit.core.util.db.ConnectionHelper;

public class UserCheckDbDataStore extends DbDataStore {
    @Override
    protected ConnectionHelper createConnectionHelper(DataSource dataSrc) throws Exception {
        // Provide "true" as the second parameter to check in the user's schema
        return new ConnectionHelper(dataSrc, true, false);
    }
}

在 class 路径中使用此 class,以下 Jackrabbit 配置在我在 Oracle 和 MySQL 上测试的场景中工作正常:

<DataStore class="org.apache.jackrabbit.core.util.db.UserCheckDbDataStore">
        <param name="url" value="jdbc:oracle:thin:@//db:1521/SID" />
        <param name="user" value="foo" />
        <param name="password" value="foo" />
        <param name="databaseType" value="oracle" />
        <param name="driver" value="oracle.jdbc.OracleDriver" />
        <param name="minRecordLength" value="1024" />
        <param name="maxConnections" value="3" />
        <param name="copyWhenReading" value="true" />
        <param name="tablePrefix" value="" />
        <param name="schemaObjectPrefix" value="datastore_"/>
</DataStore>

理想情况下,这个 bug/feature 应该在 Jackrabbit bug 跟踪器中报告,但我不确定它是否会 fixed/merged 因为过去有类似的请求但进展很小.