Transactional TestNG 测试导致 Large Objects 可能无法在自动提交模式下使用

Transactional TestNG test causes Large Objects may not be used in auto-commit mode

我知道 Postgres 在处理大对象时需要一个事务。对我来说很奇怪的是,即使我在交易中,我也会收到这个错误——或者至少我认为我是。我是 运行 我在 TestNG 测试用例中的代码,如下所示:

public class MasterpriceFileRepositoryJdbcTest extends AbstractTransactionalTestNGSpringContextTests

这是我的测试方法:

public void insertAndRetrieve() throws Exception {

    MasterpriceFile expectedMasterpriceFile = repository.save("A test file.txt", "text/plain", new ByteArrayInputStream("This is a test file".getBytes()));
    MasterpriceFile actualMasterpriceFile = repository.getById(expectedMasterpriceFile.getId());
    assertThat(actualMasterpriceFile).isEqualToComparingFieldByField(expectedMasterpriceFile);

}

这是我存储库中的保存方法(我想测试的那个):

public MasterpriceFile save(final String filename, final String contentType, final InputStream inputStream) throws IOException {
    try {
        // NOTE: Below code is postgres specific. Needs to be changed if DB vendor changes.
        ConnectionHandle connectionHandle = (ConnectionHandle) getTemplate().getDataSource().getConnection();
        PGConnection connection = (PGConnection) connectionHandle.getInternalConnection();

        LargeObjectManager largeObjectManager = connection.getLargeObjectAPI();
        long contentOid = largeObjectManager.createLO(LargeObjectManager.READWRITE);

        LargeObject largeObject = largeObjectManager.open(contentOid);
        IOUtils.copy(inputStream, largeObject.getOutputStream());
        largeObject.close();

        MasterpriceFile masterpriceFile = new MasterpriceFile();
        masterpriceFile.setContentType(contentType);
        masterpriceFile.setCreationDate(DateTime.now());
        masterpriceFile.setContentOid(contentOid);
        masterpriceFile.setFilename(filename);

        masterpriceFile.setId(updateAndReturnSimpleKeyAsLong(INSERT, "id",
                convert(masterpriceFile.getCreationDate()),
                masterpriceFile.getContentOid(),
                masterpriceFile.getFilename(),
                masterpriceFile.getContentType()));

        return masterpriceFile;         
    }
    catch (SQLException e) {
        throw getTemplate().getExceptionTranslator().translate("store masterprice file", null, e);
    }

}   

运行 测试给出了以下结果:

org.springframework.jdbc.UncategorizedSQLException: store masterprice file; uncategorized SQLException for SQL []; SQL state [25P01]; error code [0]; Large Objects may not be used in auto-commit mode.; nested exception is org.postgresql.util.PSQLException: Large Objects may not be used in auto-commit mode.
    at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:84)
    at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81)
    at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81)
    at solve.scm.repository.jdbc.MasterpriceFileRepositoryJdbc.save_aroundBody4(MasterpriceFileRepositoryJdbc.java:73)
    at solve.scm.repository.jdbc.MasterpriceFileRepositoryJdbc$AjcClosure5.run(MasterpriceFileRepositoryJdbc.java:1)
    at org.springframework.transaction.aspectj.AbstractTransactionAspect.ajc$around$org_springframework_transaction_aspectj_AbstractTransactionAspecta73e96cproceed(AbstractTransactionAspect.aj:66)
    at org.springframework.transaction.aspectj.AbstractTransactionAspect$AbstractTransactionAspect.proceedWithInvocation(AbstractTransactionAspect.aj:72)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:281)
    at org.springframework.transaction.aspectj.AbstractTransactionAspect.ajc$around$org_springframework_transaction_aspectj_AbstractTransactionAspecta73e96c(AbstractTransactionAspect.aj:70)
    at solve.scm.repository.jdbc.MasterpriceFileRepositoryJdbc.save(MasterpriceFileRepositoryJdbc.java:44)
    at solve.scm.repository.jdbc.MasterpriceFileRepositoryJdbcTest.insertAndRetrieve_aroundBody0(MasterpriceFileRepositoryJdbcTest.java:28)
    at solve.scm.repository.jdbc.MasterpriceFileRepositoryJdbcTest$AjcClosure1.run(MasterpriceFileRepositoryJdbcTest.java:1)
    at org.springframework.transaction.aspectj.AbstractTransactionAspect.ajc$around$org_springframework_transaction_aspectj_AbstractTransactionAspecta73e96cproceed(AbstractTransactionAspect.aj:66)
    at org.springframework.transaction.aspectj.AbstractTransactionAspect$AbstractTransactionAspect.proceedWithInvocation(AbstractTransactionAspect.aj:72)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:281)
    at org.springframework.transaction.aspectj.AbstractTransactionAspect.ajc$around$org_springframework_transaction_aspectj_AbstractTransactionAspecta73e96c(AbstractTransactionAspect.aj:70)
    at solve.scm.repository.jdbc.MasterpriceFileRepositoryJdbcTest.insertAndRetrieve(MasterpriceFileRepositoryJdbcTest.java:26)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:84)
    at org.testng.internal.MethodInvocationHelper.runTestMethod(MethodInvocationHelper.java:200)
    at org.springframework.test.context.testng.AbstractTestNGSpringContextTests.run(AbstractTestNGSpringContextTests.java:171)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.testng.internal.MethodInvocationHelper.invokeHookable(MethodInvocationHelper.java:212)
    at org.testng.internal.Invoker.invokeMethod(Invoker.java:707)
    at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:901)
    at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1231)
    at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:127)
    at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:111)
    at org.testng.TestRunner.privateRun(TestRunner.java:767)
    at org.testng.TestRunner.run(TestRunner.java:617)
    at org.testng.SuiteRunner.runTest(SuiteRunner.java:334)
    at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:329)
    at org.testng.SuiteRunner.privateRun(SuiteRunner.java:291)
    at org.testng.SuiteRunner.run(SuiteRunner.java:240)
    at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
    at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
    at org.testng.TestNG.runSuitesSequentially(TestNG.java:1224)
    at org.testng.TestNG.runSuitesLocally(TestNG.java:1149)
    at org.testng.TestNG.run(TestNG.java:1057)
    at org.testng.remote.RemoteTestNG.run(RemoteTestNG.java:111)
    at org.testng.remote.RemoteTestNG.initAndRun(RemoteTestNG.java:204)
    at org.testng.remote.RemoteTestNG.main(RemoteTestNG.java:175)
Caused by: org.postgresql.util.PSQLException: Large Objects may not be used in auto-commit mode.
    at org.postgresql.largeobject.LargeObjectManager.createLO(LargeObjectManager.java:301)
    at solve.scm.repository.jdbc.MasterpriceFileRepositoryJdbc.save_aroundBody4(MasterpriceFileRepositoryJdbc.java:52)
    ... 44 more

任何人都可以告诉我这个案例有什么问题吗?我好像在交易中

org.springframework.transaction.aspectj.AbstractTransactionAspect.ajc$around$org_springframework_transaction_aspectj_AbstractTransactionAspecta73e96cproceed(AbstractTransactionAspect.aj:66

那么为什么 Postgres 认为我不是?

这是导致堆栈跟踪的 postgres 中的方法:

public long createLO(int mode) throws SQLException
{
    if (conn.getAutoCommit())
        throw new PSQLException(GT.tr("Large Objects may not be used in auto-commit mode."),
                                PSQLState.NO_ACTIVE_SQL_TRANSACTION);
    FastpathArg args[] = new FastpathArg[1];
    args[0] = new FastpathArg(mode);
    return fp.getOID("lo_creat", args);
}

我通过更改以下代码行使其工作:

ConnectionHandle connectionHandle = (ConnectionHandle) getTemplate().getDataSource().getConnection();

进入这个:

ConnectionHandle connectionHandle = (ConnectionHandle) DataSourceUtils.getConnection(getTemplate().getDataSource());

看起来 DataSourceUtils.getConnection 检索参与事务的 current 连接,而 getTemplate().getDataSource().getConnection() returns一个新的连接。