Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot persist entities to Stardog with Empire and Spring declaritive transaction management due to StardogException: A transaction is already open exception #113

Open
jblaufuss opened this issue Jan 28, 2016 · 3 comments

Comments

@jblaufuss
Copy link

We cannot use Empire to persist data to a Stardog instance when using Spring-managed declarative transaction management. What appears to be happening is the Empire query-execution code at com.clarkparsia.empire.impl.EntityManagerImpl.execute() method is attempting to create a transaction in addition to the one started previously by the Spring TransactionInterceptor proxy. Since Stardog does not currently support nested transactions (accourding to its documentation) this fails with an exception (attached below).

The expected behavior is that no transactions besides the one started by the TransactionInterceptor are created, and all the statements are executed within it. Our particular test case works if we remove the transaction management calls from EntityManagerImpl.execute(). A version of the same test also works if we replace the JPA impl with Hibernate and a relational database.

The src/test/java/com.thomsonreuters.empireeval.EmpireEval.testInsert() unit test in the attached demo application replicates this problem.

EmpireStardogTransactionBugTest.zip

Thanks for looking into this.


Versions Used:

Empire 1.0 (code pulled from github ~1/25/2016, built from develop branch)
Stardog Community 4.0.3
Spring 4.2.2.RELEASE

Potentially problematic code:

com.clarkparsia.empire.impl.EntityManagerImpl

    /**
     * Execute this operation.  Removes & adds all the specified data in as few database calls as possible.  Then
     * verifies the results of the operations
     * @throws DataSourceException if there is an error while performing the add/remove operations
     * @throws PersistenceException if any objects were failed to be added or removed from the database
     */
    public void execute() throws DataSourceException {
        // TODO: should this be in its own transaction?  or join the current one?

        if (getDataSource() instanceof SupportsTransactions) {
            ((SupportsTransactions)getDataSource()).begin();
        }

        try {
            for (URI aGraphURI : mRemove.keySet()) {
                if (doesSupportNamedGraphs() && aGraphURI != null) {
                    asSupportsNamedGraphs().remove(aGraphURI, mRemove.get(aGraphURI));
                }
                else {
                    getDataSource().remove(mRemove.get(aGraphURI));
                }
            }

            for (URI aGraphURI : mAdd.keySet()) {
                if (doesSupportNamedGraphs() && aGraphURI != null) {
                    asSupportsNamedGraphs().add(aGraphURI, mAdd.get(aGraphURI));
                }
                else {
                    getDataSource().add(mAdd.get(aGraphURI));
                }
            }

            if (getDataSource() instanceof SupportsTransactions) {
                ((SupportsTransactions)getDataSource()).commit();
            }

            verify();
        }
        catch (DataSourceException e) {
            if (getDataSource() instanceof SupportsTransactions) {
                ((SupportsTransactions)getDataSource()).rollback();
            }
            throw e;
        }
    }

Exception:

javax.persistence.PersistenceException: com.clarkparsia.empire.ds.DataSourceException: com.complexible.stardog.StardogException: A transaction is already open
    at com.clarkparsia.empire.impl.EntityManagerImpl.persist(EntityManagerImpl.java:385)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:293)
    at com.sun.proxy.$Proxy20.persist(Unknown Source)
    at com.thomsonreuters.empireeval.dao.PersonDao.persist(PersonDao.java:31)
    at com.thomsonreuters.empireeval.service.PeopleService.insertPeople(PeopleService.java:21)
    at com.thomsonreuters.empireeval.service.PeopleService$$FastClassBySpringCGLIB$$dd86d87c.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:717)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:281)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:653)
    at com.thomsonreuters.empireeval.service.PeopleService$$EnhancerBySpringCGLIB$$cb2429ca.insertPeople(<generated>)
    at com.thomsonreuters.empireeval.EmpireEval.testInsert(EmpireEval.java:36)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:254)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:89)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:193)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Caused by: com.clarkparsia.empire.ds.DataSourceException: com.complexible.stardog.StardogException: A transaction is already open
    at com.complexible.stardog.empire.StardogEmpireDataSource.begin(StardogEmpireDataSource.java:226)
    at com.clarkparsia.empire.impl.EntityManagerImpl$DataSourceOperation.execute(EntityManagerImpl.java:1032)
    at com.clarkparsia.empire.impl.EntityManagerImpl.finishCurrentDataSourceOperation(EntityManagerImpl.java:402)
    at com.clarkparsia.empire.impl.EntityManagerImpl.persist(EntityManagerImpl.java:377)
    ... 48 more
Caused by: com.complexible.stardog.StardogException: A transaction is already open
    at com.complexible.stardog.api.impl.AbstractConnection.assertNotInTransaction(AbstractConnection.java:271)
    at com.complexible.stardog.api.impl.AbstractConnection.begin(AbstractConnection.java:158)
    at com.complexible.stardog.empire.StardogEmpireDataSource.begin(StardogEmpireDataSource.java:223)
    ... 51 more
@mhgrove
Copy link
Owner

mhgrove commented Jan 28, 2016

At a glance, I suspect this is related to the todo in that code, specifically, needing to be able to join a current transaction.

That's pretty straightforward to add, probably only needs to be added to SupportsTransactions. But I've never tested Empire with Spring, so there might be other issues you'll hit given that Empire isn't fully JPA-compliant.

@jblaufuss
Copy link
Author

Thanks for taking a look. We're going ahead with Empire provisionally to see how it works for us. I'll let you know if I run into any other issues using it with Spring.

@mhgrove
Copy link
Owner

mhgrove commented Jan 29, 2016

Sure.

If you're using Empire mostly for the Java Bean to/from RDF capabilities, or you run into other Spring JPA incompatibilities, the rendering engine of Empire was cleaned up and released separately as Pinto, and could be an option for you.

The upside would be it doesn't do anything for transactions, so you can control them yourself and get exactly the behavior you want. The downside is that it doesnt do anything for transactions so you have to control them yourself.

I'm going to move Empire to using Pinto internally (#99) which will simplify moving between just bean serialization and the more heavyweight JPA support.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants