I'm new to graph db and i'm having problems to get the api work within a transaction.
I have a simple code that uses the neo4j graph db api to create nodes and relationship. My code runs in JUnit and tries to create 2 nodes and a relationship between them using begin and end transaction given below.
The code works fine in a happy scenario. However, if something fails within the code, the nodes are still committed into the graph database. Not sure if i'm doing something wrong out here. I would have expected the 2 nodes created to be rolled back.
Here is the code snippet:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:/applicationContext.xml" })
public class RestBatchLoaderTest {
#Autowired
SpringRestGraphDatabase graphDatabaseService;
#Test
public void createNode() {
Transaction tx =graphDatabaseService.beginTx();
try {
Map<String,Object> nodeprops1 = new HashMap<String, Object>();
nodeprops1.put("name", "James Parker");
nodeprops1.put("age", Integer.valueOf(11));
Node james = graphDatabaseService.createNode(nodeprops1);
Assert.assertNotNull(james);
Map<String,Object> nodeprops2 = new HashMap<String, Object>();
nodeprops2.put("name", "Bing P");
nodeprops2.put("age", Integer.valueOf(34));
Node bing= graphDatabaseService.createNode(nodeprops2);
Node aa = null;
// Failure point: should rollback the previous node in the finally.
graphDatabaseService.remove(aa);
Map<String,Object> relprops = new HashMap<String, Object>();
RelationshipType type = new RelationshipType() {
#Override
public String name() {
return "MARRIED_TO";
}
};
graphDatabaseService.createRelationship(joe, jane, type, relprops);
tx.success();
} finally {
tx.finish();
}
}
The graphDatabaseService object is autowired using spring configuration. Here is the configuration:
<neo4j:config graphDatabaseService="graphDatabaseService"/>
<bean id="graphDatabaseService" class="org.springframework.data.neo4j.rest.SpringRestGraphDatabase">
<constructor-arg value="http://localhost:7474/db/data/"/>
</bean>
Also, I notice tx object is an instance of NullTransaction when graphDatabaseService.beginTx() is called in the code above.
Any ideas, what is going wrong?
Thanks.
I think figured out what the problem was. The configuration needs to have batch enabled - true. Also i used the RestAPI wrapper to the graph database object to run it as one atomic code. See code below:
#Autowired
SpringRestGraphDatabase graphDatabaseService;
private RestAPI restAPI;
#Before
public void init(){
this.restAPI = ((RestGraphDatabase)graphDatabaseService).getRestAPI();
}
#Test
public void testEnableBatchTransactions() throws Exception {
System.setProperty(Config.CONFIG_BATCH_TRANSACTION,"true");
Transaction tx = restAPI.beginTx();
try {
Node n1 = restAPI.createNode(map("name", "node1"));
Node n2 = restAPI.createNode(map("name", "node2"));
Node n3 = restAPI.createNode(map("name", "node3"));
//String s = null;
//s.toString();
Node n4 = restAPI.createNode(map("name", "node4"));
tx.success();
} finally {
tx.finish();
}
assertTrue(tx instanceof BatchTransaction);
}
Also System.setProperty(Config.CONFIG_BATCH_TRANSACTION,"true"); enables the batch mode.
To test this, try un-commenting the code snippet and run the test. Nodes n1, n2 and n3 will not be committed in the db.
You specified graphDatabaseService.remove(aa); as your failure point as aa is NULL. Looking into the documentation of org.springframework.data.neo4j.rest.SpringRestGraphDatabase there is no Exception documented that is being thrown if the node is NULL. Have you verified that an exception is actually thrown? Otherwise your code will run through to tx.success();. If an exception is thrown, please specify further what version of neo4j and spring you are using.
Edit:
After reading a little more, I see in the source of org.springframework.data.neo4j.rest.SpringRestGraphDatabase that it should give you a NullTransaction that basically does nothing (see here).
Furthermore, the Spring Data neo4j documentationstates that each operation is in its own transaction as the neo4j REST adapter does not allow transaction that span over multiple operations (see here).
Related
Hoping someone else is having the same issue as me, or has other ideas.
I'm currently running Play 1.4.x (not by choice), but also working on upgrading to play 1.5.x, though I verified the same issue happens on both versions.
I created a simple Functional Test that loads data via fixtures
My fixture for loading test data is like so
data.yml
User(testUser):
name: blah
AccessToken(accessToken):
user: testUser
token: foo
Data(testData):
user: testUser
...
I've created a controller to do something with the data like this, that has middleware for authentication check. The routes file will map something like /foo to BasicController.test
public class BasicController extends Controller{
#Before
public void doAuth(){
String token = "foo"; // Get token somehow from header
AccessToken token = AccessToken.find("token = ?", token).first(); // returns null;
// do something with the token
if(token == null){
//return 401
}
//continue to test()
}
public void test(){
User user = //assured to be logged-in user
... // other stuff not important
}
}
Finally I have my functional test like so:
public class BasicControllerTest extends FunctionalTest{
#org.junit.Before
public void loadFixtures(){
Fixtures.loadModels("data.yml");
}
#Test
public void doTest(){
Http.Request request = newRequest()
request.headers.put(...); // Add auth token to header
Http.Response response = GET(request, "/foo");
assertIsOk(response);
}
}
Now, the problem I'm running into, is that I can verify the token is still visible in the headers, but running AccessToken token = AccessToken.find("token = ?", token).first(); returns null
I verified in the functional test, before calling the GET method that the accessToken and user were created successfully from loading the fixtures. I can see the data in my, H2 in-memory database, through plays new DBBrowser Plugin in 1.5.x. But for some reason the data is not returned in the controller method.
Things I've tried
Ensuring that the fixtures are loaded only once so there is no race condition where data is cleared while reading it.
Using multiple ways of querying the database via nativeQuery jpql/hql query language and through plays native way of querying data.
Testing on different versions of play
Any help would be very much appreciated!
This issue happens on functional tests, because JPA transactions must be encapsulated in a job to ensure that the result of the transaction is visible in your method. Otherwise, since the whole functional test is run inside a transaction, the result will only visible at the end of the test (see how to setup database/fixture for functional tests in playframework for a similar case).
So you may try this:
#Test
public void doTest() {
...
AccessToken token = new Job<AccessToken>() {
#Override
public User doJobWithResult() throws Exception {
return AccessToken.find("token = ?", tokenId).first();
}
}.now().get();
....
}
Hoping it works !
I think I had a similar issue, maybe this helps someone.
There is one transaction for the functional test and a different transaction for the controller. Changes made in the test will only become visible by any further transaction if those changes were committed.
One can achieve this by closing and re-opening the transaction in the functional test like so.
// Load / Persist date here
JPA.em().getTransaction().commit(); // commit and close the transaction
JPA.em().getTransaction().begin(); // reopen (if you need it)
Now the data should be returned in the controller method.
So your test would look like this:
public class BasicControllerTest extends FunctionalTest{
#org.junit.Before
public void loadFixtures(){
Fixtures.loadModels("data.yml");
JPA.em().getTransaction().commit();
// JPA.em().getTransaction().begin(); reopen (if you need it)
}
#Test
public void doTest(){
Http.Request request = newRequest()
request.headers.put(...); // Add auth token to header
Http.Response response = GET(request, "/foo");
assertIsOk(response);
}
}
I did never try this with fixtures. But i would assume they run in the same transaction.
I am unable to wrap my head around this peculiar issue.
I am using arangodb 3.0.10 and arangodb-java-driver 3.0.4.
I am executing a very simple AQL fetch query. (See code below) All my unit tests pass every time and problem never arises when debugging. The problem does not occur all the time (around half the time). It gets even stranger, the most frequent manifestation is NullPointerException at
return cursor.getUniqueResult();
but also got once a ConcurrentModificationException
Questions:
Do I have to manage the database connection? Like closing the driver
connection after each use.
Am i doing something completely wrong
with the ArangoDB query?
Any hint in the right direction is appreciated.
Error 1:
java.lang.NullPointerException
at org.xworx.sincapp.dao.UserDAO.get(UserDAO.java:41)
Error 2:
java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextNode(HashMap.java:1437)
at java.util.HashMap$EntryIterator.next(HashMap.java:1471)
at java.util.HashMap$EntryIterator.next(HashMap.java:1469)
at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.write(MapTypeAdapterFactory.java:206)
at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.write(MapTypeAdapterFactory.java:145)
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68)
at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.write(MapTypeAdapterFactory.java:208)
at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.write(MapTypeAdapterFactory.java:145)
at com.google.gson.Gson.toJson(Gson.java:593)
at com.google.gson.Gson.toJson(Gson.java:572)
at com.google.gson.Gson.toJson(Gson.java:527)
at com.google.gson.Gson.toJson(Gson.java:507)
at com.arangodb.entity.EntityFactory.toJsonString(EntityFactory.java:201)
at com.arangodb.entity.EntityFactory.toJsonString(EntityFactory.java:165)
at com.arangodb.impl.InternalCursorDriverImpl.getCursor(InternalCursorDriverImpl.java:94)
at com.arangodb.impl.InternalCursorDriverImpl.executeCursorEntityQuery(InternalCursorDriverImpl.java:79)
at com.arangodb.impl.InternalCursorDriverImpl.executeAqlQuery(InternalCursorDriverImpl.java:148)
at com.arangodb.ArangoDriver.executeAqlQuery(ArangoDriver.java:2158)
at org.xworx.sincapp.dao.UserDAO.get(UserDAO.java:41)
ArangoDBConnector
public abstract class ArangoDBConnector {
protected static ArangoDriver driver;
protected static ArangoConfigure configure;
public ArangoDBConnector() {
final ArangoConfigure configure = new ArangoConfigure();
configure.loadProperties(ARANGODB_PROPERTIES);
configure.init();
final ArangoDriver driver = new ArangoDriver(configure);
ArangoDBConnector.configure = configure;
ArangoDBConnector.driver = driver;
}
UserDAO
#Named
public class UserDAO extends ArangoDBConnector{
private Map<String, Object> bindVar = new HashMap();
public UserDAO() {}
public User get(#NotNull String objectId) {
bindVar.clear();
bindVar.put("uuid", objectId);
String fetchUserByObjectId = "FOR user IN User FILTER user.uuid == #uuid RETURN user";
CursorResult<User> cursor = null;
try {
cursor = driver.executeAqlQuery(fetchUserByObjectId, bindVar, driver.getDefaultAqlQueryOptions(), User.class);
} catch (ArangoException e) {
new ArangoDaoException(e.getErrorMessage());
}
return cursor.getUniqueResult();
}
As AntJavaDev said, you access bindVar more than once the same time. When one thread modify bindVar and another tried to build the AQL call at the same time by reading bindVar. This leads to the ConcurrentModificationException.
The NullPointerException results from an AQL call with no result. e.g. when you clear bindVar and directly after that, execute the AQL in another thread with no content in bindVar.
To your questions:
1. No, you do not have to close the driver connection after each call.
2. Beside the shared bindVar, everything looks correct.
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.
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!
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.
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();
}
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();
}
...
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.
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!
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.
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();
}
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();
}
...
public class Test
{
private static RestAPI rest = new RestAPIFacade("myIp","username","password");
public static void main(String[] args)
{
Map<String, Object> foo = new HashMap<String, Object>();
foo.put("Test key", "testing");
rest.createNode(foo);
}
}
No output it just hangs on connection indefinitely.
Environment:
Eclipse
JDK 7
neo4j-rest-binding 1.9: https://github.com/neo4j/java-rest-binding
Heroku
Any ideas as to why this just hangs?
The following code works:
public class Test
{
private static RestAPI rest = new RestAPIFacade("myIp","username","password");
public static void main(String[] args)
{
Node node = rest.getNodeById(1);
}
}
So it stands that I can correctly retrieve remote values.
I guess this is caused by lacking usage of transactions. By default neo4j-rest-binding aggregates multiple operations into one request (aka one transaction). There are 2 ways to deal with this:
change transactional behaviour to "1 operation = 1 transaction" by setting
-Dorg.neo4j.rest.batch_transaction=false for your JVM. Be aware this could impact performance since every atomic operation is a seperate REST request.
use transactions in your code:
.
RestGraphDatabse db = new RestGraphDatabase("http://localhost:7474/db/data",username,password);
Transaction tx = db.beginTx();
try {
Node node = db.createNode();
node.setPropery("key", "value");
tx.success();
} finally {
tx.finish();
}