I'm struggling to write this, so I may have to give an example to help explain the problem I'm experiencing.
Say we have nodes of three types (these nodes may have more relationships of their own, e.g. Product Family, has product manager):
Product
Product Family
Battery
With these relationships
A product can be be in 0 or more families
A product can have 0 or more batteries.
When using spring-data-neo4j and saving a new Product, I wish to include these relatiopnships, such as the batteries they require and the product family they belong to. However if I only supply say an ID rather then a fully populated object, it overwrites this object along with properties and relations accordingly.
This isn't great as it means that I have to end up sending a fully populated object, with all it's relations everytime I wish to save something, and some of these relations may go quite deep.
My domain is as follows:
#Node
public class Product {
#Id
#GeneratedValue(generatorClass = SnowflakeGenerator.class)
private Long productId;
private String name;
#Relationship(type = "REQUIRES_BATTERY", direction = OUTGOING)
private List<Battery> batteryList;
#Relationship(type = "IN_FAMILY", direction = OUTGOING)
private List<ProductFamily> productFamilyList;
}
#Node
public class Battery {
#Id
#GeneratedValue(generatorClass = SnowflakeGenerator.class)
private Long batteryId;
private String name;
}
#Node
public class ProductFamily {
#Id
#GeneratedValue(generatorClass = SnowflakeGenerator.class)
private Long familyId;
private String name;
}
This could very well by from coming from a Relational Database mindset and is a 'limitation' of using Neo4J.
TLDR When persisting somethign in Neo4J using spring-data how can I save just a relationship, rather than a whole related Node.
You can make use of projections in Spring Data Neo4j. (https://docs.spring.io/spring-data/neo4j/docs/current/reference/html/#projections)
This gives you the option to put a "mask" on the object tree, you want to persist (and what should stay untouched).
For example in your case:
interface ProductProjection {
// without defining e.g. String getName() here, SDN would not ever touch this property.
List<BatteryProjection> getBatteryList();
List<ProductFamilyProjection> getProductFamilyList();
}
interface BatteryProjection {
String getName();
}
interface ProductFamilyProjection {
String getName();
}
Related
I´m having a relative complex, hierarchical data model in my Spring application and I need to query it as well as update all entities by letting the client pass the changed entity to the server.
Using GraphQL for querying solves exactly my needs, but to enable update and create capabilities however, I´d need to duplicate all my entities as *.graphqls files won´t support using "type" elements as "input" elements.
For the newly created Input element, I´d need to define a Java Object matching it too.
For queries I´d end up with
Portfolio.java
#Entity
public class Portfolio {
#Id
private int id;
private String name;
....
}
and portfolio.graphqls
type Portfolio {
id: ID!
name: String
....
}
But for updates I´d end up with
Portfolio.java
#Entity
public class Portfolio {
#Id
private int id;
private String name;
....
}
PortfolioInput.java
#Entity
public class PortfolioInput {
#Id
private int id;
private String name;
....
}
portfolio.graphqls
type Portfolio {
id: ID!
name: String
....
}
input PortfolioInput {
id: ID!
name: String
....
}
type PortfolioMutation{
updatePortfolio(input: PortfolioInput):Portfolio
}
My problem with that is, I need to keep 4 entities in sync with their fields now, just because GraphQL does not allow using the type as input and I´d need to do that for many other entities as well.
Is there any other solution, or do people go the extra mile and accept the additional complexity, or do you simply switch to REST for POST/PUT operations and only use GraphQL for GET?
I have two entities Merchant and Customer:
public class Merchant{
private UUID id;
private String name;
//... other fields and getters/setters
}
public class Customer{
private UUID id;
private String name;
//... other fields and getters/setters
}
These two entities are sightly different from each-other.
What I'am trying to to do is when I search with the term "John" I want to get both a merchant named "John Market" and a customer called "John Smith".
To achieve this I indexed these entities to a single index.
#Document(indexName = "merchant_customer_index", type = "merchantorcustomer")
public class MerchantOrCustomer {
#Id
private UUID id;
private String name;
private int type;
//...
My query can return both Merchant and Customer:
List<MerchantOrCustomer> result = elasticsearchTemplate.queryForList(nativeSearchQuery, MerchantOrCustomer.class);
I distinguish them programmatic(if(result.get(i).getType() == 0 we received Merchant else Customer)
Then use their id to extract actual object from relational db.
I searched a lot, but couldn't find anything that can help to estimate if it is a good practice. Is it a good practice?
Please, give me a hint if there is a better way.
There doesn't seem to be anything wrong with what you did unless there is some collusion as mentioned by #Ivan in comments.
Here is another possible way to do if you were using elasticTemplate- Spring Data Elasticsearch: Multiple Index with same Document or if you are using queryBuilder - https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/java-search.html
I have the following classes:
#NodeEntity
public class Item{
//...
}
#RelationshipEntity(type = "HAS")
public class HasRelation{
//...
#StartNode
private User user;
#EndNode
private Item item;
}
#NodeEntity
public class User{
//...
#Relationship(type="HAS")
private Set<HasRelation> has;
}
So now I have a User Sven with ID 1 having an Item Hammer in the Database and want to load it.
When I call the OGM session.load(User.class, 1) I always get an Stackoverflow-Exception, because the User hold a Relationship, holding the User, holding a relationship, and so on.
This feels like the wrong way to use OGM for me and I don't want to restrict the Depth by which I load to 0.
However the OGM specification tells me, that there is no other way, since the RelationshipEntity needs a Start- and EndNode and has to be referenced in one of those.
So I don't see a way to prevent this Exception other than resticting the Loading-Depth to 0.
Is there a better way?
You are exposing the data as JSON. The converter also needs to traverse the
'object tree' and this causes the stackoverflow.
The solution is simple: You are defining an outgoing relationship in the User class so this object does not need to be visited again when the jackson lib hits the relationship.
#RelationshipEntity(type = "LIKES")
public class LikedBook {
#Id
#GeneratedValue
private Long id;
private String how;
#StartNode
#JsonIgnore // <- "do not go back"
private User user;
#EndNode
private Book book;
I have the below unidirectional Many To One mapping
#Entity
public class Item implements Serializable {
private Integer id;
private Double amount;
private Country origin;
#ManyToOne(optional=true)
#JoinColumn
public Country getOrigin() {
return this.origin;
}
}
#Entity
public class Country implements Serializable{
private String code;
private String desc;
}
Let say the relationship is optional so I am trying to remove the relation by updating it to null using code below
Country country = null;
//item is detached
item.setOrigin(country);
em.merge(item);
But the result turns out to be relationship is not removed.
However, this code works fine if country is not null and the system can update the relationship in DB.
It just simply ignore the field if it's null.
Can someone points out what setting can be changed in order to achieve my desired result?
P.S. Please be reminded that I am not wanting to delete the entity Country, but just remove the relationship between them.
Thanks all it's a mistaken question. It actually works.
There's just some client side issue submitting wrong data to it.
I am trying to model such situation - there is a cash transfer (I mean a car that carries money), that has required amounts of each currency, and also an actual amount for each currency. And it seems to me pointless to create two separate classes, one for required amount and another for actual amount. So the implementation would look like this:
#Entity
public class CashTransferCurrencyAmount {
// id, version and so on
#Column(length = 3)
private String currencyCode;
#Basic
private BigDecimal amount;
#ManyToOne
private CashTransfer cashTransfer;
}
#Entity
public class CashTransfer {
// id, version and so on
#OneToMany(mappedBy="cashTransfer")
private Set<CashTransferCurrencyAmount> requiredCurrencyAmountSet = new HashSet<CashTransferAmountCurrency>();
#OneToMany(mappedBy="cashTransfer")
private Set<CashTransferCurrencyAmount> actualCurrencyAmountSet = new HashSet<CashTransferAmountCurrency>();
}
But how is a CashTransferCurrencyAmount instance to know to which collection it belongs? I have two ideas:
1 - add a discriminator field to CashTransferCurrencyAmount:
public enum RequestType {
ACTUAL,
REQUIRED
}
#Basic
#Enumerated(EnumType.STRING)
private RequestType requestType;
and add #WHERE annotations to collections in CashTransfer. This is preferable for me.
2 - create two join tables. one for mapping requested amounts and one for mapping actual amounts. I dislike this one as I don't want too many tables in my DB.
Are there any other ways to achieve this? I this approach correct?
And please don't tell me to put both requested and actual amounts in one entity. The real case is more complicated, each CashTransferCurrencyAmount has it's own collections so it can't be solved that way.
EDIT
As for requests for complete story - there used to be two values in CashTransferCurrencyAmount - required (I think it should be 'requested') and actual, but now each amount has it's own collection - how this amount is split into denominations. So I need a collection of amounts, each one having a collection of denominations. The type of CurrencyAmount and CurencyDenomination seems to be the same for requested ones and for actual ones.
Since you want CashTransferCurrencyAmount instance to know which collection it belongs to, I assume you want to have some logic based on that. The way I would model your situation would be using inheritance.
You're saying "it seems to me pointless to create two separate classes", I would however try to convince you that you should. You could use a "Single Table" inheritance type, so that you don't introduce additional tables in your DB, which is what you're trying to accomplish.
My shot would look something like:
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "request_type", discriminatorType = DiscriminatorType.STRING)
public abstract class CashTransferCurrencyAmount {
// id, version and so on
#Column(length = 3)
private String currencyCode;
#Basic
private BigDecimal amount;
#ManyToOne
private CashTransfer cashTransfer;
}
#Entity
#DiscriminatorValue("REQUIRED")
public class CashTransferCurrencyAmountRequired extends CashTransferCurrencyAmount {
// required anount specific stuff here
}
#Entity
#DiscriminatorValue("ACTUAL")
public class CashTransferCurrencyAmountActual extends CashTransferCurrencyAmount {
// actual anount specific stuff here
}
#Entity
public class CashTransfer {
// id, version and so on
#OneToMany(mappedBy="cashTransfer")
private Set requiredCurrencyAmountSet = new HashSet();
//Stackoverflow deleting my generic sets! But it's exactly the same as in your code...
#OneToMany(mappedBy="cashTransfer")
private Set actualCurrencyAmountSet = new HashSet();
}