问题
I'm trying to implement a TransactionEventHandler
like the one used in neo4j-versioning in order to create a time-machine style, versioned Neo4j database, now using Neo4j 2.x. It fails with the following infinite stack trace:
javax.transaction.SystemException: TM has encountered some problem, please perform necessary action (tx recovery/restart)
at org.neo4j.kernel.impl.transaction.TxManager.assertTmOk(TxManager.java:349)
at org.neo4j.kernel.impl.transaction.TxManager.setRollbackOnly(TxManager.java:758)
at org.neo4j.kernel.TransactionEventHandlers.beforeCompletion(TransactionEventHandlers.java:120)
at org.neo4j.kernel.impl.core.TransactionEventsSyncHook.beforeCompletion(TransactionEventsSyncHook.java:68)
at org.neo4j.kernel.impl.transaction.TransactionImpl.doBeforeCompletion(TransactionImpl.java:368)
at org.neo4j.kernel.impl.transaction.TxManager.commit(TxManager.java:398)
at org.neo4j.kernel.impl.core.IsolatedTransactionTokenCreator.getOrCreate(IsolatedTransactionTokenCreator.java:61)
at org.neo4j.kernel.impl.core.TokenHolder.createToken(TokenHolder.java:114)
at org.neo4j.kernel.impl.core.TokenHolder.getOrCreateId(TokenHolder.java:102)
at org.neo4j.kernel.impl.api.store.DiskLayer.propertyKeyGetOrCreateForName(DiskLayer.java:367)
at org.neo4j.kernel.impl.api.store.CacheLayer.propertyKeyGetOrCreateForName(CacheLayer.java:370)
at org.neo4j.kernel.impl.api.StateHandlingStatementOperations.propertyKeyGetOrCreateForName(StateHandlingStatementOperations.java:939)
at org.neo4j.kernel.impl.api.DataIntegrityValidatingStatementOperations.propertyKeyGetOrCreateForName(DataIntegrityValidatingStatementOperations.java:67)
at org.neo4j.kernel.impl.api.OperationsFacade.propertyKeyGetOrCreateForName(OperationsFacade.java:397)
at org.neo4j.kernel.impl.core.NodeProxy.setProperty(NodeProxy.java:205)
...
This is my test:
@Test
public void test() {
GraphDatabaseService graphDb = new TestGraphDatabaseFactory().newImpermanentDatabase();
Node referenceNode = null;
try (Transaction transaction = graphDb.beginTx()) {
referenceNode = graphDb.createNode();
transaction.success();
}
VersioningTransactionEventHandler versioningTransactionEventHandler = new VersioningTransactionEventHandler(referenceNode);
graphDb.registerTransactionEventHandler(versioningTransactionEventHandler);
try (Transaction tx = graphDb.beginTx()) {
Node node = graphDb.createNode();
tx.success();
}
graphDb.shutdown();
}
This is the VersioningTransactionEventHandler
:
public class VersioningTransactionEventHandler implements TransactionEventHandler<Object> {
private final Node versionDataNode;
public VersioningTransactionEventHandler(Node versionDataNode) {
this.versionDataNode = versionDataNode;
}
@Override
public Object beforeCommit(TransactionData data) throws Exception {
versionDataNode.setProperty("foo", "bar"); // <- this causes the error
return null;
}
@Override
public void afterCommit(TransactionData data, Object state)
{
}
@Override
public void afterRollback(TransactionData data, Object state)
{
}
}
I'm using org.neo4j.neo4j-2.0.1
and org.neo4j.neo4j-kernel-2.0.1
in my application.
Why is the setProperty()
causing this error? How can I fix it? Any clues to what may be wrong here is greatly appreciated.
Update
As Michael Hunger suggested I did a setProperty()
before passing the node in, but now the test silently hangs for infinity and nothing happens. It doesn't matter what property key-value pair is set on the node:
...
referenceNode = graphDb.createNode();
referenceNode.setProperty("foo", "bar"); // <- results in hang
referenceNode.setProperty("herp", "derp"); // <- results in hang also
...
Still any clues? I just want to manipulate a node while inside the transaction event handler like it was done in the 1.9 version, but Neo4j 2.x doesn't have the GraphDatabaseService#getReferenceNode()
method which is passed in the constructor there.
回答1:
Firstly, it seems like you'd want to call incrementTransactionVersion() in your TransactionEventHandler's afterCommit() method. This way, you know that the transaction was successfully committed before you increment the version. I assume that the circularity is caused by the fact the incrementing the version property of the version Node causes another transaction. To work around this, you could check the TransactionData to see if the current transaction was caused by the change to the version Node. This isn't as straightforward as it might be, but here's some pseudo-Java (I haven't even tested that it compiles) that illustrates how this might be done:
Iterable<PropertyEntry<Node>> ii = txData.assignedNodeProperties();
boolean doIncrementVersion = false;
for (PropertyEntry<Node> pEntry : txData.assignedNodeProperties()) {
if (pEntry.key().equals("transactionVersion") {
doIncrementVersion = true;
break;
}
}
if (doIncrementVersion) {
incrementTransactionVersion();
}
Hope this helps!
回答2:
I managed to get rid of the circular error mess by adding a line to the intialization code that creates the version node in the test:
@BeforeClass
public static void setup() {
db = new TestGraphDatabaseFactory().newImpermanentDatabase();
Node versionNode = null;
try (Transaction tx = db.beginTx()) {
versionNode = db.createNode(DynamicLabel.label("__TransactionVersion__"));
versionNode.setProperty("transactionVersion", 0L); // <---
tx.success();
}
transactionEventHandler = new MyTransactionEventHandler(versionNode);
db.registerTransactionEventHandler(transactionEventHandler);
}
Setting the property after creating the node seems to fix it.
So the error seems to be caused by referring to an unknown "token" transactionVersion
inside the transaction event handler in the incrementVersionNumber()
method, which causes the error.
Apparently, this is a bug in Neo4j.
回答3:
This seems to be a bug, caused by the on-the-fly creation of the internal token for the "name" of the property.
Can you try to use that property name before so that the token gets created before?
Just tried it, if you do
@Test
public void test() {
GraphDatabaseService graphDb = new TestGraphDatabaseFactory().newImpermanentDatabase();
Node referenceNode = null;
try (Transaction transaction = graphDb.beginTx()) {
referenceNode = graphDb.createNode();
// use the "foo" property-name once before
referenceNode.setProperty("foo", "bar");
transaction.success();
}
VersioningTransactionEventHandler versioningTransactionEventHandler = new VersioningTransactionEventHandler(referenceNode);
graphDb.registerTransactionEventHandler(versioningTransactionEventHandler);
try (Transaction tx = graphDb.beginTx()) {
Node node = graphDb.createNode();
tx.success();
}
graphDb.shutdown();
}
回答4:
I found a workaround the last hang happening when I called setProperty()
in beforeCommit()
. It seems it only happens on non-write transactions (I thought beforeCommit()
was only called on write operations)?
So then, if I check that before doing the property setting, the hanging disappears:
...
@Override
public Object beforeCommit(TransactionData data) throws Exception {
if (containsWriteChanges(data)) {
versionDataNode.setProperty("foo", "bar");
}
return null;
}
private boolean containsWriteChanges(TransactionData data) {
return data.assignedNodeProperties().iterator().hasNext()
|| data.assignedRelationshipProperties().iterator().hasNext()
|| data.createdNodes().iterator().hasNext()
|| data.createdRelationships().iterator().hasNext()
|| data.deletedNodes().iterator().hasNext()
|| data.deletedRelationships().iterator().hasNext()
|| data.removedNodeProperties().iterator().hasNext()
|| data.removedRelationshipProperties().iterator().hasNext();
}
...
来源:https://stackoverflow.com/questions/23790333/transactioneventhandler-gives-javax-transaction-systemexception-on-node-setprope