Spring Data Neo4j #GraphId and #Index - java

Can anyone exmplain the difference betwen #GraphId and #Index annotation from org.neo4j.ogm.annotation ?
For now, after reading the docs it seems that #GraphId is used to create identifier for Neo4j internal logic and users should not rely on that, because it can be reused over time. But what about #Index?
As I understand, the main advantage of graph based databases is that once we know the node/relation from which to start things become easy, since all we need to do is just traverse the graph from that starting node. And indexing helps to do so, right? So, we can write something like START n = node:index_name(property_name = value) and immitiately start exloring the graph from the indexed node by 'property_name' property, right?
So, consider this entity :
#ToString
#NodeEntity(label = "Event")
#NoArgsConstructor
public class Event{
public Event(String eventName, LocalDate dateTime){
this.name = eventName;
this.date = dateTime;
}
public Event(Long id, String eventName, LocalDate dateTime){
this(eventName, dateTime);
this.id = id;
}
#Getter
#GraphId
private Long id;
#Getter
#Index(unique = true, primary = true)
#Property(name = "name")
private String name;
#Getter
#Property(name = "date")
#Convert(DateConverter.class)
private LocalDate date;
}
As you can see the String name property is annotated with #Index. How can I write Cypher query to actually start from the node with name = 'something'? What is the index name? Or does Spring Data Neo4j 4.2.0.RELEASE figure it itself when write just MATCH (event:Event {name = 'somehting'} ... ?
#Repository
public interface EventRepository extends Neo4jRepository<Event, String>{
Event findOne(String eventName);
}
Here the repositry class and as you might see I am using String as the type of the id of the entity the repository manages, so I assume Spring uses name property of Event class to generate a query for Event findOne(String eventName);

#Index is similar to #Indexed if your are familiar with spring-data-mongodb or #Index in spring-data-jpa. Basically it indexes the field(among other things) which makes it searching for this field quite fast.
Regarding your repository method, it should be named like this
Event findByName(String eventName);

Can anyone exmplain the difference betwen #GraphId and #Index annotation from org.neo4j.ogm.annotation ?
I assume that you've checked the doc of Spring Data Neo4j. But one thing I want to add about #GraphId and #Index is that #GraphId is unique among the whole db while #Index can be the same or unique, depending on your decision.
How can I write Cypher query to actually start from the node with name = 'something'? What is the index name? Or does Spring Data Neo4j 4.2.0.RELEASE figure it itself when write just MATCH (event:Event {name = 'somehting'} ... ?
I believe you write the Cypher query in a correct way. Indices (including graph id) are maintained by the database and kept up-to-date. They are stored in a form of certain data structure. For example, B-Tree or Map, which can reduce the time of searching. You can check out your neo4j db for the indices. They are either stored in files maintained by the db (Where does Neo4j store the data?).
As for how Cypher knows the name of an index, since indices are maintained by the db and Cypher queries are also decoded by the db, it would make sense that the db can access to the index according to the Cypher query. This is just my guessing based on my understanding of DB system.

Related

Reducing model #Entity bloat with Spring Hibernate and MSSQL using DTO + Stored Proc

Edit: I think it would be helpful to explain my goal here first. My goal is to reduce and avoid model/#Entity bloat when using stored procedures with Hibernate. You can get raw data back from the persistent EntityManager when using a stored procedure, but that data will not be mapped. If you send in a stripped down model to Hibernate, Hibernate will only send you back the columns which are annotated as #Column on the #Entity model (almost forcing you to create a new #Entity for every stored procedure!) You can attempt to map this data with a DTO that has more properties, but they won't map to anything because all the fields which were not included on the model will return null.
I've been struggling to find an answer to this in my research. We use an MSSQL database and Spring with JPA/javax/Hibernate persistence, but do not rely on Hibernate for its ORM. All CRUD operations are done using stored procedures. We have several models (Spring #Entity) which work well for retrieving and mapping data. For example, a basic user model.
import javax.persistence;
#Entity
public class User {
#Id
#Column
private int userID;
#Column
private String userName;
#Column
private Date userDOB;
public User(UserDTO userDTO){
userName = userDTO.getUserName();
userID = userDTO.getUserID();
// Extra column userDOB, and no way to map accountDetails from the DTO
}
public int getUserID(){...}
public String getUserName(){...}
public Date getUserDOB(){...}
}
This works well when the stored procedure selects columns in a way that matches up with the model, however in cases where we want to selectively query data using joins, the columns (names and number of columns) often don't match up with the models. In this case, it makes sense to have DTOs to actually map the receiving data from the database using a constructor in the related Model. However, Spring doesn't like injecting the DTO directly (because it complains it isn't an entity), and injecting the model directly into the stored procedure query will fail, since the columns (number of columns!) don't match up.
public class UserDTO {
private String accountDetails;
private int userID;
private String userName;
public int getUserID(){...}
public String getUserName(){...}
}
I've tried using ModelMapper, but the first argument is an Object (the data), which isn't obtainable, since the data can't be mapped. I can of course call StoredProcedureQuery without a Model hint, and receive the raw data, but it will not be mapped.
public class UserRepo {
import javax.persistence;
public List<UserDTO> getUserAccountInfo(){
StoredProcedureQuery query = entityManager.createQuery(storedProcedureSelectUserAccount, User.class);
query.execute(); // Will fail here with SQL Server error: Unknown column userDOB.
List<UserDTO> result = query.getResults();
return result;
}
}
Others have suggested using raw Selects or other String based mapping strategies, but I would really appreciate some advice on retrieving and mapping the returned data using a DTO with the already written Stored Procedures. Thank you!

Override Spring data JPA id annotation.

I'm using Spring-data-Jpa where I've an entity
#Entity(name="person")
public class Person implements Serializable {
#javax.persistence.Id
private long dbId;
#Id
private final String id;
// others attributes removed
}
In above class I've two different ids id (marked with org.springframework.data.annotation.Id) and dbId(marked with javax.persistence.Id) , since my id field is always populated with a unique identifier (for Person class which I'm getting from somewhere else) so while using Spring JpaRepository it always tries to update the record and since it's not in db, nothing happens.
I've debug code and saw that it uses SimpleKeyValueRepository which gets the id field which is id, and thus it always gets a value and tries to update record, can I override this behavior to use dbId instead of id field? Is there any way to achieve same with some configuration or annotation, any help is greatly appreciated.
Each entity must have exactly one #Id. On the other hand, you might want to declare a column as unique. It can be done by:
#Entity(name="person")
public class Person implements Serializable {
#Id
private Long id;
#Column(unique = true)
private final String uuid;
// others attributes removed
}
Also remember, that Spring Data JPA id should be reference Long instead of a primitive as you want to save objects with id = null.
String id should probably be String uuid and be initialized as String uuid = UUID.randomUUID().toString();
Similar situation would be an unique email requirement for user. On one hand it'll be a primary key, but on the other, you won't mark it as #Id.
If you need further clarification or your environment is more complicated, just ask in comments section below.

eclipselink struct fields order

I am working on a EclipseLink application, which uses Oracle Objects as IN and OUT parameters (while invoking stored procedure). As you know we have #Struct annotations available in Eclipselink for representing Oracle Object, I used it and it is working perfectly. But, looks like order of the fields declared in Struct annotated class matters a lot to map to correct field in oracle object. This causes maintenance issues and very difficult to code when object's properties are more. Is there a way in Eclipselink to say map Structure fields based on name and not with order.
Ex: Below is my Struct class. If by chance I declare variables in different order from fields list, wrong/incorrect mappings will happen while fetching records from stored proc. Its always mapping values to fields from top to bottom. #Column name annotation is not able to solve this issue.
#Struct(name = "REC_OBJECT",
fields = {"TRANS_ID", "PROJECT_ID", "LANGUAGE_CODE", "DESCRIPTION"})
#Embeddable
public class Master {
#Column(name = "PROJECT_ID")
private String projectId;
#JsonIgnore
#Column(name = "TRANS_ID")
private String transactionId;
#Column(name = "LANGUAGE_CODE")
private String languageCode;
#Column(name = "DESCRIPTION")
private String description;
}
Please suggest solution for this. Thank you.

Index Entities without direct relation in Hibernate Search

I'm trying to use Hibernate Search on two Entities, that do not (and must not) share a relation on object-level, however they're connected by a join table that uses their IDs. (legacy)
These are more or less the two Entities:
#Entity
#Indexed
class Person {
#Id
private long id;
#Field
private String name;
....
}
#Entity
#Indexed
class Address {
#Id
private long id;
#Field
private String street;
#Field
private String zip;
....
}
They are connected by their IDs:
#Entity
class Relation {
#Id
private long id;
private long personId;
private long addressId;
}
The goal I'm trying to achieve is finding similar persons that share a similar address via Hibernate Search. This means I'm searching for attributes from both Person and Address.
I guess the easiest way is to "emulate" an #IndexedEmbedded relation which means denormalizing the data and add "street" and "zip" from Address to a Person document. I stumbled upon Hibernate Search Programmatic API, but I'm not sure if that's the right way to go (and how to go on from to there)..
Would this be the proper way of doing things or am I missing something?
If you cannot add this relationship into the model, you will be pretty much out of luck. You are right that you would have to index the Person and corresponding Address data into the same document (this is what #IndexedEmbedded does really). The normal/best way to customize the Document is via a custom (class) bridge. The problem in your case, however, is that you would need access to the current Hibernate Session within the implementation of the custom bridge.
Unless you are using some approach where this Session for example is bound to a ThreadLocal, there won't be a way for you to load the matching Address data for a given Person within the bridge implementation.

Improving JPA entity classes for existing DB schema

I am attempting to implement a Hibernate/JPA2 solution over an existing schema, which cannot be changed. Here is a minimal example of the existing schema:
CREATE TABLE REASON (
REASON_CODE CHAR(1),
REASON_DESCRIPTION CHAR(50))
CREATE TABLE HEADER (
REASON_CODE CHAR(1),
OTHERFIELD1 CHAR(40),
OTHERFIELD2 CHAR(40) )
Normally this would be the "correct" way from a DB perspective: Link REASON to HEADER by the REASON_CODE. However it's presenting me with an awkward problem in Java and I'm not sure of the best way to solve it. I've modeled these entities as follows:
#Entity
#Table(name="REASON")
public class Reason implements java.io.Serializable {
#Id
#Column(name="REASON_CODE", unique=true, nullable=false, length=1)
private Character reasonCode;
#Column(name="REASON_DESCRIPTION", nullable=false, length=25)
private String reasonDescription;
}
#Entity
#Table(name="HEADER")
public class Header implements java.io.Serializable {
#ManyToOne
#JoinColumn(name = "REASON_CODE", nullable = false)
private Reason reason;
#Column(name="OTHERFIELD1")
private String otherField1;
#Column(name="OTHERFIELD2")
private String otherField2;
}
Once again, as far as I can tell, this is "correct" from a Java perspective - linking Header to Reason with a reference.
The problem is that when I need to use one of these Reason values in my code I wind up with awkward syntax like:
Reason r = reasonService.findOne('X'); // X is the REASON_CODE in the database record
// Do some processing with variable r
Or this:
header.setReason(reasonService.findOne('X'));
Ideally I could implement Reason as an enum like:
public enum Reason {
X_MARKSTHESPOT("X"),
C_MEANSSOMETHINGELSE("C"),
F_MEANSATHIRDTHING("F") ;
private String code;
private Reason(String code) {
this.code = code;
}
}
And then simply have this in my code:
header.setReason(Reason.X_MARKSTHESPOT);
But from what I understand that is not possible with JPA, which offers only EnumType.STRING (basically the name) or EnumType.ORDINAL (even worse, the index in the enum list). A possible way around this would be JPA 2.1's Converter, but I have never used it. I have also read here (in one of the answers) that a Hibernate User Type might be useful. One of our programmers has solved this in another app by writing two complete classes - an enum class for internal use and a "shadow" class which iterates through the enum and syncs the records in the database on every startup. But this seems like a kludgey way to do it. What is the best way to handle this, bearing in mind that the database schema cannot be changed?

Categories