Neo4j check if node exists before creating? - java

I have a feeling I'm going about this all wrong. But anyway.
I have an sql database which has essentially a purposefully denormalised table which I've constructed to make this task easier for me, so I can just grab stuff from one table.
What I have is a table of pairs, something like this:
user_lo | user_hi | something_else | other stuff
1000 | 1234 | 1231251654 | 123
1050 | 1100 | 1564654 | 45648
1080 | 1234 | 456444894648 | 1
And so on.
So for my neo4j graph db, I want each user id as a node, the other stuff isn't too important but will be the stuff in the relations basically.
I only want one node for each user, so my feeling is that if I do something like this:
while (rs.next()) {
node_lo = db.createNode();
node_lo.setProperty("user_id", rs.getInt(1));
node_hi = db.createNode();
node_hi.setProperty("user_id", rs.getInt(2));
}
That when we add the node with user_id 1234 for the second time, it will just create a new node, but I what I want is for it to just grab this node instead of creating it so I can add it to the relationship to 1080 in this case.
So what is the way to do this?

Have you looked at CREATE UNIQUE?
If you can't use Cypher, maybe you can use unique nodes?

Use an index to search, and if no result of found, create a new one.
Index<Node> userIndex = graphDatabaseService.index().forNodes('UserNodes');
IndexHits<Node> userNodes = userIndex.get('id', 1234);
if(!userNodes.hasNext()){
//Create new User node
} else {
Node userNode = userNodes.next();
}
Is this the type of operation you are looking for?

You'll probably want to use the UniqueNodeFactory provided by Neo4j.
public Node getOrCreateUserWithUniqueFactory( String username, GraphDatabaseService graphDb )
{
UniqueFactory<Node> factory = new UniqueFactory.UniqueNodeFactory( graphDb, "UserNodes" )
{
#Override
protected void initialize( Node created, Map<String, Object> properties )
{
created.setProperty( "id", properties.get( "id" ) );
}
};
return factory.getOrCreate( "id", id );
}

Normalize your SQL tables to look like nodes and relationships. Then with cypher in your migration you can make the migration rerunnable by something like
start a = node:node_auto_index('id:"<PK_Value>"')
delete a
create a = {id: "<PK_VALUE>", etc}
for nodes and since you should have in your many-to-many middle table:
start LHS = node:node_auto_index('id:"<LHS_PK>"'),
RHS = node:node_auto_index('id:"<RHS_PK>"')
create unique LHS=[:<relType> {<rel props>}]->RHS
now you will end up with no duplicates and can rerun as much as you like.

use this function:
where:
ID is the key which you want to check if already exist
Type: is the type of the node ( the label)
this function will create the node and return it, then you can add more properties.
public static Node getOrCreateUserWithUniqueFactory( long ID, GraphDatabaseService graphDb, String Type )
{
UniqueFactory<Node> factory = new UniqueFactory.UniqueNodeFactory( graphDb, Type )
{
#Override
protected void initialize( Node created, Map<String, Object> properties )
{
created.addLabel( DynamicLabel.label( Type ) );
created.setProperty( "ID", properties.get( "ID" ) );
}
};
return factory.getOrCreate( "ID", ID );
}

using cypher query, you can create a unique node with the following syntax,
CYPHER 2.0 merge (x:node_auto_index{id:1})
when making a REST call, one can make batch insertion like
$lsNodes[] = array(
'method'=> 'POST', 'to'=> '/cypher',
'body' => array(
'query' => 'CYPHER 2.0 merge (x:node_auto_index{id_item:{id}})',
'params' => array('id'=>1)
),
'id'=>0
);
$sData = json_encode($lsNodes);
similarly for creating relationships in a batch request, do the following
$lsNodes[] = array(
'method'=> 'POST', 'to'=> '/cypher',
'body' => array(
'query' => 'start a=node:node_auto_index(id={id1}), b = node:node_auto_index(id={id2}) create unique a-[:have{property:30}}]-b;',
'params' => array(
'id1' => 1, 'id2'=> 2
)
),
'id' => 0
);
$sData = json_encode($lsNodes);

Related

Performance Improvement: Replacing recursive method call by a recursive sql query

I have started on a new job and my first task is to improve performance of code for which user action takes 40 mins currently sometimes. On analysis I found that there is a Parent - Child - Grandchilren - ....and so on tree kind of relationship and a recursive method is implemented in current code which is taking time, because network calls to database are being made recursively.
As an improvement, I want to hit database once and fetch all data recursively at once.
Service layer code (this method calls itself recursively):
private void **processRecommendedEvaluationMetaForReadyToExport**(EvaluationMeta parentEvaluationMeta,
Set<EvaluationMeta> childEvaluationMetas,
Map<String, MCGEvaluationMetadata> mcgHsimContentVersionEvaluationMetadataMap) {
// get the list of child evaluation recommendations for the given parent evaluation
Map<String, List<MCGEvaluationMetaMaster>> hsimAndchildEvaluationDefinitionsMap = mcgEvaluationMetaDao.findRecommendedChildEvaluationMeta(parentEvaluationMeta.getId());
Iterator childEvaluationDefinitionsMapIterator = hsimAndchildEvaluationDefinitionsMap.entrySet().iterator();
while (childEvaluationDefinitionsMapIterator.hasNext()) {
Map.Entry childEvaluationDefinition = (Map.Entry) childEvaluationDefinitionsMapIterator.next();
if (childEvaluationDefinition.getValue() != null && !((List<MCGEvaluationMetaMaster>) childEvaluationDefinition.getValue()).isEmpty()) {
for (MCGEvaluationMetaMaster mcgEvaluationMetaMaster : (List<MCGEvaluationMetaMaster>) childEvaluationDefinition.getValue()) {
MCGEvaluationMetadata mcgEvaluationMetadata = mcgHsimContentVersionEvaluationMetadataMap.get(mcgEvaluationMetaMaster.getHsim() + mcgEvaluationMetaMaster.getMcgContentVersion().getContentVersion());
// consider only evaluation definitions which are either published/disabled status to be marked as ready to export and also skip adding the
// parent evaluation meta as it will be marked as ready to export later
if (canMcgEvaluationBeMarkedAsReadyToExport(mcgEvaluationMetadata) && !childEvaluationMetas.contains(mcgEvaluationMetadata.getEvaluationMeta()) &&
!parentEvaluationMeta.getResource().getName().equals(mcgEvaluationMetadata.getEvaluationMeta().getResource().getName())) {
childEvaluationMetas.add(mcgEvaluationMetadata.getEvaluationMeta());
**processRecommendedEvaluationMetaForReadyToExport**(mcgEvaluationMetadata.getEvaluationMeta(), childEvaluationMetas, mcgHsimContentVersionEvaluationMetadataMap);
}
}
}
}
}
DAO Layer:
private static final String GET_RECOMMENDED_CHILD_EVALUATIONS =
"MCGEvaluationMetaRecommendation.getRecommendedChildEvaluations";
public Map<String, List<MCGEvaluationMetaMaster>> findRecommendedChildEvaluationMeta(final String evaluationMetaId) {
Map<String, List<MCGEvaluationMetaMaster>> recommendedChildGuidelineInfo = new HashMap<>();
if (evaluationMetaId != null) {
final Query query = getEntityManager().createNamedQuery(GET_RECOMMENDED_CHILD_ASSESSMENTS);
query.setParameter(ASSESSMENT_META_ID, evaluationMetaId);
List<MCGEvaluationMetaRecommendation> resultList = query.getResultList(); // get the MCGEvaluationMetaRecommendation
// for the given parent evaluation meta id
if (resultList != null && !resultList.isEmpty()) {
for (MCGEvaluationMetaRecommendation mcgEvaluationMetaRecommendation : resultList) {
populateRecommendedChildGuidelineInfo(mcgEvaluationMetaRecommendation, recommendedChildGuidelineInfo);
}
}
}
return recommendedChildGuidelineInfo;
}
private void populateRecommendedChildGuidelineInfo(MCGEvaluationMetaRecommendation mcgEvaluationMetaRecommendation,
Map<String, List<MCGEvaluationMetaMaster>> recommendedChildGuidelineInfo){
if (mcgEvaluationMetaRecommendation.getParentEvaluationResponseDefinition() != null) {
List<MCGEvaluationMetaMaster> mcgEvaluationMetaMasterList;
String evaluationResponseDefinitionId = mcgEvaluationMetaRecommendation.getParentEvaluationResponseDefinition().getId();
MCGEvaluationMetaMaster mcgEvaluationMetaMaster = mcgEvaluationMetaRecommendation.getChildMCGEvaluationMetaMaster();
if (recommendedChildGuidelineInfo.get(evaluationResponseDefinitionId) != null) {
mcgEvaluationMetaMasterList = recommendedChildGuidelineInfo.get(evaluationResponseDefinitionId);
//check if there exists a list of recommended evaluation definitions for the evaluationResponseDefinitionId
// if so, check if the current recommended evaluation definition is already there in the list if not add it
// or create a new list of recommended evaluation definitions and add to it
if (mcgEvaluationMetaMasterList != null && !mcgEvaluationMetaMasterList.contains(mcgEvaluationMetaMaster)) {
mcgEvaluationMetaMasterList.add(mcgEvaluationMetaMaster);
}
} else {
mcgEvaluationMetaMasterList = new ArrayList<>();
mcgEvaluationMetaMasterList.add(mcgEvaluationMetaMaster);
recommendedChildGuidelineInfo.put(evaluationResponseDefinitionId, mcgEvaluationMetaMasterList);
}
}
}
Hibernate Query:
<query name="MCGEvaluationMetaRecommendation.getRecommendedChildEvaluations">
<![CDATA[
SELECT mcgEvaluationMetaRecommendation
FROM com.casenet.domain.evaluation.mcg.MCGEvaluationMetaRecommendation mcgEvaluationMetaRecommendation
INNER JOIN mcgEvaluationMetaRecommendation.parentMCGEvaluationMetadata parentMCGEvaluationMeta
WHERE parentMCGEvaluationMeta.evaluationMeta.id = :evaluationMetaId
AND mcgEvaluationMetaRecommendation.obsolete = 0
AND parentMCGEvaluationMeta.obsolete = 0
]]>
</query>
Simplified TABLE structure below:
Table: *MCGEvaluationMetaRecommendation*
mcg_evaluation_meta_recommendation_id
obsolete
parent_evaluation_response_definition_id
child_mcg_evaluation_meta_master_id
parent_mcg_evaluation_metadata_id
Table: *MCGEvaluationMetadata*
mcg_evaluation_metadata_id
evaluation_meta_id
mcg_evaluation_meta_master_id
created_date
obsolete
Below is the query I have written trying to substitute the recursive method, but something is wrong as the query keeps excecuting and doesn't complete even after 6-7 mins
WITH parent_child AS (
SELECT
meta.mcg_evaluation_metadata_id METADATA_ID,
meta.mcg_evaluation_meta_master_id META_MASTER_ID,
meta.evaluation_meta_id META_ID,
meta.obsolete META_OBSOLETE,
rec.mcg_evaluation_meta_recommendation_id REC_META_RECOMM_ID,
rec.parent_evaluation_response_definition_id REC_PARENT_EVALUATION_RESPONSE_DEF_ID,
rec.child_mcg_evaluation_meta_master_id REC_CHILD_EVALUATION_META_MASTER_ID,
rec.parent_mcg_evaluation_metadata_id REC_PARENT_EVALUATION_METADATA_ID,
rec.obsolete REC_OBSOLETE
FROM
MCGevaluationMetaRecommendation rec,
MCGevaluationMetadata meta
WHERE
rec.parent_mcg_evaluation_metadata_id = meta.mcg_evaluation_metadata_id
),
generation AS (
SELECT
METADATA_ID,
META_MASTER_ID,
META_ID,
META_OBSOLETE,
REC_META_RECOMM_ID,
REC_PARENT_EVALUATION_RESPONSE_DEF_ID,
REC_CHILD_EVALUATION_META_MASTER_ID,
REC_PARENT_EVALUATION_METADATA_ID,
REC_OBSOLETE,
0 AS level
FROM
parent_child child
WHERE child.META_ID = 'root-id-passed-as-query-param'
AND child.META_OBSOLETE = 0
AND child. REC_OBSOLETE = 0
UNION ALL
SELECT
child.METADATA_ID,
child.META_MASTER_ID,
child.META_ID,
child.META_OBSOLETE,
child.REC_META_RECOMM_ID,
child.REC_PARENT_EVALUATION_RESPONSE_DEF_ID,
child.REC_CHILD_EVALUATION_META_MASTER_ID,
child.REC_PARENT_EVALUATION_METADATA_ID,
child.REC_OBSOLETE,
level+1 AS level
FROM
parent_child child
JOIN generation g
ON g.REC_CHILD_EVALUATION_META_MASTER_ID = child.META_MASTER_ID
)
SELECT *
FROM generation g
JOIN parent_child parent
ON g.REC_PARENT_EVALUATION_METADATA_ID = parent.METADATA_ID
ORDER BY level DESC
OPTION (MAXRECURSION 0);
Please can someone help me in identifying what is wrong about my query, or if there is some other way of improving performance in this scenario. If this query works than I will handle other logic on java side.
I think it would be easier for you to simply use a #temp table and a loop.
This way you get an idea on where the time is spent, how deep the rabbit hole goes etc.. It's also less overwhelming for the query engine and in my experience it is hardly ever slower than a recursive CTE.
Pseudo code:
DECLARE #level int,
#rowcount int
SET #level = 0
PRINT Convert(varchar, CURRENT_TIMESTAMP) + ' - Starting up...'
SELECT level = #level,
<fields you need>
INTO #temp
FROM <tables>
WHERE <filters>
SELECT #rowcount = ##ROWCOUNT
PRINT Convert(varchar, CURRENT_TIMESTAMP) + ' - Loaded ' + Convert(varchar, #rowcount) + ' rows for level ' + Convert(vachar, #rowcount)
CREATE CLUSTERED INDEX idx0 ON #temp (level)
-- keep adding 'new generation' of children as long as there are new 'parents'
WHILE #rowcount > 0
BEGIN
SET #level = #level + 1
INSERT #temp (level, <fields>)
SELECT level = #level
FROM <tables> tbls
JOIN #temp parent
ON parent.level = #level - 1
AND parent.<x> = tbls.<x>
AND parent.<y> = tbls.<y>
etc..
WHERE <filters>
SELECT #rowcount = ##ROWCOUNT
PRINT Convert(varchar, CURRENT_TIMESTAMP) + ' - Loaded ' + Convert(varchar, #rowcount) + ' rows for level ' + Convert(vachar, #rowcount)
UPDATE STATISTICS #temp
END
-- return results
SELECT * FROM #temp
The UPDATE STATISTICS might be overkill (read: premature optimization), depends on how 'different' each level is when it comes to number of children.

JPA double relation with the same Entity

I have these Entities:
#Entity
public class Content extends AbstractEntity
{
#NotNull
#OneToOne(optional = false)
#JoinColumn(name = "CURRENT_CONTENT_REVISION_ID")
private ContentRevision current;
#OneToMany(mappedBy = "content", cascade = CascadeType.ALL, orphanRemoval = true)
private List<ContentRevision> revisionList = new ArrayList<>();
}
#Entity
public class ContentRevision extends AbstractEntity
{
#NotNull
#ManyToOne(optional = false)
#JoinColumn(name = "CONTENT_ID")
private Content content;
#Column(name = "TEXT_DATA")
private String textData;
#Temporal(TIMESTAMP)
#Column(name = "REG_DATE")
private Date registrationDate;
}
and this is the db mapping:
CONTENT
+-----------------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------------------+--------------+------+-----+---------+----------------+
| ID | bigint(20) | NO | PRI | NULL | auto_increment |
| CURRENT_CONTENT_REVISION_ID | bigint(20) | NO | MUL | NULL | |
+-----------------------------+--------------+------+-----+---------+----------------+
CONTENT_REVISION
+-----------------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------------------+--------------+------+-----+---------+----------------+
| ID | bigint(20) | NO | PRI | NULL | auto_increment |
| REG_DATE | datetime | YES | | NULL | |
| TEXT_DATA | longtext | YES | | NULL | |
| CONTENT_ID | bigint(20) | NO | MUL | NULL | |
+-----------------------------+--------------+------+-----+---------+----------------+
I have also these requirements:
Content.current is always a member of Content.revisionList (think about Content.current as a "pointer").
Users can add a new ContentRevision to an existing Content
Users can add a new Content with an initial ContentRevision (cascade persist)
Users can change Content.current (move the "pointer")
Users can modify Content.current.textData, but saves Content (cascade merge)
Users can delete ContentRevision
Users can delete Content (cascade remove to ContentRevision)
Now, my questions are:
Is this the best approach? Any best practice?
Is it safe to cascade merge when the same entity is referenced twice? (Content.current is also Content.revisionList[i])
Are Content.current and Content.revisionList[i] the same instance? (Content.current == Content.revisionList[i] ?)
Thanks
#jabu.10245 I'm very grateful for your effort. Thank you, really.
However, there's a problematic (missing) case from your tests: when you run it inside a container using CMT:
#RunWith(Arquillian.class)
public class ArquillianTest
{
#PersistenceContext
private EntityManager em;
#Resource
private UserTransaction utx;
#Deployment
public static WebArchive createDeployment()
{
// Create deploy file
WebArchive war = ShrinkWrap.create(WebArchive.class, "test.war");
war.addPackages(...);
war.addAsResource("persistence-arquillian.xml", "META-INF/persistence.xml");
war.addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml");
// Show the deploy structure
System.out.println(war.toString(true));
return war;
}
#Test
public void testDetached()
{
// find a document
Document doc = em.find(Document.class, 1L);
System.out.println("doc: " + doc); // Document#1342067286
// get first content
Content content = doc.getContentList().stream().findFirst().get();
System.out.println("content: " + content); // Content#511063871
// get current revision
ContentRevision currentRevision = content.getCurrentRevision();
System.out.println("currentRevision: " + currentRevision); // ContentRevision#1777954561
// get last revision
ContentRevision lastRevision = content.getRevisionList().stream().reduce((prev, curr) -> curr).get();
System.out.println("lastRevision: " + lastRevision); // ContentRevision#430639650
// test equality
boolean equals = Objects.equals(currentRevision, lastRevision);
System.out.println("1. equals? " + equals); // true
// test identity
boolean same = currentRevision == lastRevision;
System.out.println("1. same? " + same); // false!!!!!!!!!!
// since they are not the same, the rest makes little sense...
// make it dirty
currentRevision.setTextData("CHANGED " + System.currentTimeMillis());
// perform merge in CMT transaction
utx.begin();
doc = em.merge(doc);
utx.commit(); // --> ERROR!!!
// get first content
content = doc.getContentList().stream().findFirst().get();
// get current revision
currentRevision = content.getCurrentRevision();
System.out.println("currentRevision: " + currentRevision);
// get last revision
lastRevision = content.getRevisionList().stream().reduce((prev, curr) -> curr).get();
System.out.println("lastRevision: " + lastRevision);
// test equality
equals = Objects.equals(currentRevision, lastRevision);
System.out.println("2. equals? " + equals);
// test identity
same = currentRevision == lastRevision;
System.out.println("2. same? " + same);
}
}
since they are not the same:
if I enable cascading on both properties, an Exception is thrown
java.lang.IllegalStateException:
Multiple representations of the same entity [it.shape.edea2.jpa.ContentRevision#1] are being merged.
Detached: [ContentRevision#430639650];
Detached: [ContentRevision#1777954561]
if I disable cascade on current, the change get lost.
the strange thing is that running this test outside the container results in successful execution.
Maybe it's lazy loading (hibernate.enable_lazy_load_no_trans=true), maybe something else, but it's definitely NOT SAFE.
I wonder if there's a way to get the same instance.
Is it safe to cascade merge when the same entity is referenced twice?
Yes. If you manage an instance of Content, then it's Content.revisionList and Content.current are managed as well. Changes in any of those will be persisted when flushing the entity manager. You don't have to call EntityManager.merge(...) manually, unless you're dealing with transient objects that need to be merged.
If you create a new ContentRevision, then call persist(...) instead of merge(...) with that new instance and make sure it has a managed reference to the parent Content, also add it to the content's list.
Are Content.current and Content.revisionList[i] the same instance?
Yes, should be. Test it to be sure.
Content.current is always a member of Content.revisionList (think about Content.current as a "pointer").
You could do that check in in SQL with a check constraint; or in Java, although you'd have to be sure the revisionList is fetched. By default it's lazy fetched, meaning Hibernate will run another query for this list if you access the getRevisionList() method. And for that you need a running transaction, otherwise you'll be getting a LazyInitializationException.
You could instead load the list eagerly, if that's what you want. Or you could define a entity graph to be able to support both strategies in different queries.
Users can modify Content.current.textData, but saves Content (cascade merge)
See my first paragraph above, Hibernate should save changes on any managed entity automatically.
Users can delete ContentRevision
if (content.getRevisionList().remove(revision))
entityManager.remove(revision);
if (revision.equals(content.getCurrentRevision())
content.setCurrentRevision(/* to something else */);
Users can delete Content (cascade remove to ContentRevision)
Here I'd prefer to ensure that in the database schema, for instance
FOREIGN KEY (content_id) REFERENCES content (id) ON DELETE CASCADE;
UPDATE
As requested, I wrote a test. See this gist for the implementations of Content and ContentRevision I used.
I had to make one important change though: Content.current cannot really be #NotNull, especially not the DB field, because if it were, then we couldn't persist a content and revision at the same time, since both have no ID yet. Hence the field must be allowed to be NULL initially.
As a workaround I added the following method to Content:
#Transient // ignored in JPA
#AssertTrue // javax.validation
public boolean isCurrentRevisionInList() {
return current != null && getRevisionList().contains(current);
}
Here the validator ensures that the there is always a non-null current revision and that it is contained in the revision list.
Now here are my tests.
This one proves that the references are the same (Question 3) and that it is enough to persist content where current and revisionList[0] is referencing the same instance (question 2):
#Test #InSequence(0)
public void shouldCreateContentAndRevision() throws Exception {
// create java objects, unmanaged:
Content content = Content.create("My first test");
assertNotNull("content should have current revision", content.getCurrent());
assertSame("content should be same as revision's parent", content, content.getCurrent().getContent());
assertEquals("content should have 1 revision", 1, content.getRevisionList().size());
assertSame("the list should contain same reference", content.getCurrent(), content.getRevisionList().get(0));
// persist the content, along with the revision:
transaction.begin();
entityManager.joinTransaction();
entityManager.persist(content);
transaction.commit();
// verify:
assertEquals("content should have ID 1", Long.valueOf(1), content.getId());
assertEquals("content should have one revision", 1, content.getRevisionList().size());
assertNotNull("content should have current revision", content.getCurrent());
assertEquals("revision should have ID 1", Long.valueOf(1), content.getCurrent().getId());
assertSame("current revision should be same reference", content.getCurrent(), content.getRevisionList().get(0));
}
The next ensures that it's still true after loading the entity:
#Test #InSequence(1)
public void shouldLoadContentAndRevision() throws Exception {
Content content = entityManager.find(Content.class, Long.valueOf(1));
assertNotNull("should have found content #1", content);
// same checks as before:
assertNotNull("content should have current revision", content.getCurrent());
assertSame("content should be same as revision's parent", content, content.getCurrent().getContent());
assertEquals("content should have 1 revision", 1, content.getRevisionList().size());
assertSame("the list should contain same reference", content.getCurrent(), content.getRevisionList().get(0));
}
And even when updating it:
#Test #InSequence(2)
public void shouldAddAnotherRevision() throws Exception {
transaction.begin();
entityManager.joinTransaction();
Content content = entityManager.find(Content.class, Long.valueOf(1));
ContentRevision revision = content.addRevision("My second revision");
entityManager.persist(revision);
content.setCurrent(revision);
transaction.commit();
// re-load and validate:
content = entityManager.find(Content.class, Long.valueOf(1));
// same checks as before:
assertNotNull("content should have current revision", content.getCurrent());
assertSame("content should be same as revision's parent", content, content.getCurrent().getContent());
assertEquals("content should have 2 revisions", 2, content.getRevisionList().size());
assertSame("the list should contain same reference", content.getCurrent(), content.getRevisionList().get(1));
}
SELECT * FROM content;
id | version | current_content_revision_id
----+---------+-----------------------------
1 | 2 | 2
UPDATE 2
It was hard to reproduce that situation on my machine, but I got it to work. Here is what I've done so far:
I changed all #OneToMany relations to use lazy fetching (the default) and rerun the following test case:
#Test #InSequence(3)
public void shouldChangeCurrentRevision() throws Exception {
transaction.begin();
entityManager.joinTransaction();
Document document = entityManager.find(Document.class, Long.valueOf(1));
assertNotNull(document);
assertEquals(1, document.getContentList().size());
Content content = document.getContentList().get(0);
assertNotNull(content);
ContentRevision revision = content.getCurrent();
assertNotNull(revision);
assertEquals(2, content.getRevisionList().size());
assertSame(revision, content.getRevisionList().get(1));
revision.setTextData("CHANGED");
document = entityManager.merge(document);
content = document.getContentList().get(0);
revision = content.getCurrent();
assertSame(revision, content.getRevisionList().get(1));
assertEquals("CHANGED", revision.getTextData());
transaction.commit();
}
The test passed with lazy fetching. Note that lazy fetching requires it to be executed within a transaction.
For some reason the content revision instance you're editing is not the same as the one in the one-to-many list. To reproduce that I've modified my test as follows:
#Test #InSequence(4)
public void shouldChangeCurrentRevision2() throws Exception {
transaction.begin();
Document document = entityManager.find(Document.class, Long.valueOf(1));
assertNotNull(document);
assertEquals(1, document.getContentList().size());
Content content = document.getContentList().get(0);
assertNotNull(content);
ContentRevision revision = content.getCurrent();
assertNotNull(revision);
assertEquals(2, content.getRevisionList().size());
assertSame(revision, content.getRevisionList().get(1));
transaction.commit();
// load another instance, different from the one in the list:
revision = entityManager.find(ContentRevision.class, revision.getId());
revision.setTextData("CHANGED2");
// start another TX, replace the "current revision" but not the one
// in the list:
transaction.begin();
document.getContentList().get(0).setCurrent(revision);
document = entityManager.merge(document); // here's your error!!!
transaction.commit();
content = document.getContentList().get(0);
revision = content.getCurrent();
assertSame(revision, content.getRevisionList().get(1));
assertEquals("CHANGED2", revision.getTextData());
}
And there, I got exactly your error. Then I modified the cascading setting on the #OneToMany mapping:
#OneToMany(mappedBy = "content", cascade = { PERSIST, REFRESH, REMOVE }, orphanRemoval = true)
private List<ContentRevision> revisionList;
And the error disappeared :-) ... because I removed CascadeType.MERGE.

How to join on foreign key in querydsl

I am new to querydsl and I'm trying to use querydsl in pure java (no hibernate, JPA or anything).
We have a database where the tables are linked through minimum 3 columns
I followed the doc here and ended up with my schema duly created.
Here are my pseudo tables :
Item
Corporation (pk) mmcono
Item number (pk) mmitno
Environnement (pk) mmenv
Item description mmitds
Item_warehouse
Corporation (fk for Item) mbcono
Item number (fk for Item) mbitno
Environnement (fk for Item) mbenv
Warehouse number mbwhlo
Other properties (not important)
Inside the Item_wharehouse class, I manually added the foreignKey (because it's not defined in the actual db schema)
public final com.querydsl.sql.ForeignKey<QItemWharehouse > _ItemWharehouseFk = createInvForeignKey(Arrays.asList(mbitno, mbcono, mbenv), Arrays.asList("mmitno", "mmcono", "mbenv"));
I'm working on the following code in my main class:
SQLTemplates templates = SQLServer2012Templates.builder().printSchema().build();
Configuration configuration = new Configuration(templates);
QItem mm = new QItem ("mm");
QItemWarehouse mb = new QItemWarehouse("mb");
JtdsDataSource ds = getDataSource();
SQLQueryFactory queryFactory = new SQLQueryFactory(configuration, ds);
String toto = queryFactory.select(mm.mmitno, mm.mmitds)
.from(mm)
.join( ???????????? )
.where(mb.mbwhlo.eq("122"))
.fetch()
As per doc here I should be able to do something like this : AbstractSQLQuery.innerJoin(ForeignKey<E> key, RelationalPath<E> entity)
What I want in the end is to allow joining table without having to specify manually all the columns required for the join condition.
As stated before, my model starts with minimum 3 columns in the pk, and it's not uncommon to have 6 or 7 cols in the on clause! It's a lot of typing and very error prone, because you can easily miss one and get duplicate results.
I would like something like .join(mb._ItemWharehouseFk, ???) and let querydsl handle little details like generating the on clause for me.
My trouble is that I can't find the second parameter of type RelationalPath<E> entity for the join method.
I am doing something wrong ? What do I miss ? Is it even possible to accomplish what I want ?
Oups I found the problem : I had it all in the wrong order.
The foreign key was located in the itemWarehouse class.
it should have been named this way :
public final com.querydsl.sql.ForeignKey<QItem> _ItemFk = createInvForeignKey(Arrays.asList(mbitno, mbcono, mbenv), Arrays.asList("mmitno", "mmcono", "mbenv"));
that means that you just have to reverse the order in the statement this way :
SQLTemplates templates = SQLServer2012Templates.builder().printSchema().build();
Configuration configuration = new Configuration(templates);
QItem mm = new QItem ("mm");
QItemWarehouse mb = new QItemWarehouse("mb");
JtdsDataSource ds = getDataSource();
SQLQueryFactory queryFactory = new SQLQueryFactory(configuration, ds);
List<Tuple> toto = queryFactory.select(mm.mmitno, mm.mmitds)
.from(mb)
.join(mb._ItemFk, mm )
.where(mb.mbwhlo.eq("122"))
.fetch()
And you get your nice on clause generated. It's just a question of how you construct your relation.
#Enigma, I sincerely hope it will help you for your Friday afternoon. I wouldn't want your boss to be disappointed with you :-)

DataNucleus: "IllegalArgumentException: No enum constant" -- how to fix

I'm working with an inherited a MySQL dB schema that I can't change. I am trying to use DataNucleus (JDO) with it.
I have the following in the schema:
CREATE TABLE `vendors` (
`vendor_id` INT(11) NOT NULL AUTO_INCREMENT,
...
`registration_method` ENUM('Company','Basic Upgrade','Website','Secure Admin','Unknown') NULL DEFAULT NULL,
...
PRIMARY KEY (`vendor_id`),
...
And I have defined a POJO to work with it:
#PersistenceCapable( table="vendors" )
public class VendorRec {
...
public static enum registration_method_enum {
Company( "Company" ), Basic_Upgrade( "Basic Upgrade" ), Website( "Website" ), Secure_Admin( "Secure Admin" ), Unknown( "Unknown" );
private String registration_method;
registration_method_enum( String registration_method ) {
this.registration_method = registration_method;
}
public String getRegistration_method() {
return this.registration_method;
}
};
...
#PrimaryKey
private Integer vendor_id;
...
private registration_method_enum registration_method;
...
I am using DataNucleus's JDO interface to retrieve this class:
Set<Integer> vendorIds = new HashSet();
...
Query q = persistenceManager.newQuery( VendorRec.class );
q.declareParameters( "java.util.Set vendorIds" );
q.setFilter( "vendorIds.contains( vendor_id )" );
q.setOrdering("vendor_fname ascending, vendor_lname ascending");
final Iterator<VendorRec> vendorIt = ( ( List<VendorRec> )q.execute( vendorIds ) ).iterator();
I get the following exception:
java.lang.IllegalArgumentException: No enum constant ca.vendorlink.app.schema.VendorRec.registration_method_enum.Secure Admin
...
Well duh! Of course there isn't an enum constant "registration_method_enum.Secure Admin"! Spaces are not allowed! But that's the dB definition I've inherited... :-(
Suggestions for fixing this problem? Is there a different way I should be defining the enum?
Many thanks in advance.

Neo4j : Retrieving All Nodes and Relationship connected to a Node in Neo4j Rest OR through Cypher

I want to Retrieve all nodes and relationship connected to a node.
I Tried to do this in two ways:
1st Through Neo4j REST API i Tried this
URI traverserUri = new URI( startNode.toString() + "/traverse/node" );
WebResource resource = Client.create()
.resource( traverserUri );
String jsonTraverserPayload = t.toJson();
ClientResponse response = resource.accept( MediaType.APPLICATION_JSON )
.type( MediaType.APPLICATION_JSON )
.entity( jsonTraverserPayload )
.post( ClientResponse.class );
System.out.println( String.format(
"POST [%s] to [%s], status code [%d], returned data: "
+ System.getProperty( "line.separator" ) + "%s",
jsonTraverserPayload, traverserUri, response.getStatus(),
response.getEntity( String.class ) ) );
response.close();
And get Following Response :
[ {
"outgoing_relationships" : "http://localhost:7474/db/data/node/82/relationships/out",
"data" : {
"band" : "The Clash",
"name" : "Joe Strummer"
},
"traverse" : "http://localhost:7474/db/data/node/82/traverse/{returnType}",
"all_typed_relationships" : "http://localhost:7474/db/data/node/82/relationships/all/{-list|&|types}",
"property" : "http://localhost:7474/db/data/node/82/properties/{key}",
"all_relationships" : "http://localhost:7474/db/data/node/82/relationships/all",
"self" : "http://localhost:7474/db/data/node/82",
"properties" : "http://localhost:7474/db/data/node/82/properties",
"outgoing_typed_relationships" : "http://localhost:7474/db/data/node/82/relationships/out/{-list|&|types}",
"incoming_relationships" : "http://localhost:7474/db/data/node/82/relationships/in",
"incoming_typed_relationships" : "http://localhost:7474/db/data/node/82/relationships/in/{-list|&|types}",
"create_relationship" : "http://localhost:7474/db/data/node/82/relationships"
}, {
"outgoing_relationships" : "http://localhost:7474/db/data/node/83/relationships/out",
"data" : {
}]
But the problem is if i want to see the relationship of this node again i will have to hit the link "http://localhost:7474/db/data/node/82/relationships/all"
Cant we get Data in which Node and its relationship are shown directly instead of link to relationship without hitting the link again????
2nd thing I have tried to do is to get this from cypher query :
START a=node(3)
MATCH (a)-[:KNOWS]->(b)-[:KNOWS]->(c)-[:KNOWS]->(d)
RETURN a,b,c,d
But this also didn't work because at (b) and (c) there will be multiple values as a result for which i will have to iterate and write another query
Cant we get this done in single query because i have so many connected relationship that it is getting hard to iterate again and again. Any Help would be Appreaciated.
It's easy to get all nodes connected to a given node with Cypher
START a=node(3)
MATCH (a)-[:KNOWS*]->(d)
RETURN distinct d
But if you have large number of connected nodes and deep connections, you might not get a good performance.
If you know the bounds of the connections, specify it explicitly in the query would be helpful for performance,
START a=node(3)
MATCH (a)-[:KNOWS*1..3]->(d)
RETURN Distinct d
Regarding the question about multiple nodes, or duplicate nodes. I understand what you mean. Here is something I did with such a query to weed out duplicates. More about if a KNOWS b which KNOWS c, but c is really a. Kind of like that. We can use something like WHERE NOT
start player=node({0})
match player-[:player.active*2..2]-friendsOfFriends,
where not(player-[:player.active]-friendsOfFriends) and player <> friendsOfFriends
return distinct friendsOfFriends
order by friendsOfFriends.username asc
If you make your query
MATCH (a)-[r1:KNOWS]->(b)-[r2:KNOWS]->(c)-[r3:KNOWS]->(d) RETURN a,r1,b,r2,c,r3,d;
The r(x) will return the respective details regarding the relationship. There will be a "row" for each path that matches the query.
If you define your deserializer so it recognizes the r(x) and constructs a relationship rather than an entity then you should be able to do it all in one query.

Categories

Resources