Persist a RelationshipEntity that is not a NodeEntity property - java

In SDN4 I wish to persist a #RelationshipEntity which is not a #NodeEntity's property.
Example:
#NodeEntity
public class User{
Long id;
}
#RelationshipEntity(type="FOLLOWS")
public class Follows{
#GraphId private Long relationshipId;
#StartNode private User follower;
#EndNode private User followee;
#Property private Date from;
public Follows(){}
public Follows(User u1, User u2){
this.follower = u1;
this.followee = u2;
}
}
#Repository
interface FollowsRepository extends GraphRepository<Follows>{}
And then persist the Follows #Relationship like this
...
followsRepository.save(new Follows(user1, user2));
...
But when doing so, the Relationship is not persisted!!
Sadly as stated in the accepted answer this cannot (yet) be done (SDN 4.0.0.RELEASE)
Workaround 1
It is possible to persist #RelationshipEntities using #Query in GraphRepositories.
#Query("Match (a:User), (b:User) WHERE id(a) = {0}
AND id(b) = {1} CREATE (a)-[r:FOLLOWS {date:{2}}]->(b) RETURN r ")
Workaround 2
This can also be done by treating Follows as a #NodeEntity, which might not be the most performant thing to do BUT will not affect any of the #NodeEntities of the domain, nor the service layer AND you won't have to mess with the depth factor when loading and persisting entities
#NodeEntity
public class User{
Long id;
}
#NodeEntity
public class Follows{
private Long Id;
#Relationship(type="FOLLOWER")
private User follower;
#Relationship(type="FOLLOWEE")
private User followee;
private Date from;
public Follows(){}
public Follows(User u1, User u2){
this.follower = u1;
this.followee = u2;
}
}
....
//Placed in userService
Follows createFollowsRelationship(User u1, User u2){
return followsRepository.save(new Follows(user1, user2));
}
.....

At the moment, you cannot persist a relationship entity directly when it is not referenced from participating node entities.
You'll have to save the start node and make sure it has a reference to the relationship entity.
There will be some enhancements around how relationship entities are persisted but not in the next release.

Related

Spring get all with a set of custom object with correct ID

I have a User object, and a Ticket object that have a ManyToMAny relationship
class User{
private Long id;
#ManyToMany
private Set<Ticket> tickets;
}
class Ticket{
#ManyToMany
private Set<User> users;
}
Obviously this is a very simplified pseudo-like version of the code, but what would i name the method in my JPA Repository, to get all of the tickets that have a user with the specified ID in it? Is this possible, or should I make a custom query?
You can write 2 different named queries:
public interface TicketRepository extends JpaRepository<Ticket, Long> {
List<Ticket> findAllByUsers(User user);
List<Ticket> findAllByUsersIdIn(List<Long> userIds);
}
Method findAllByUsers(..) takes User object to search and return results, method findAllByUsersIdIn(..) takes user ids to search and return results.

Automatically convert Spring JPA Entity OneToMany to List<ID> in DTO and back (ModelMapper)

Animal.java
#Data
#Entity
public class Animal implements MyEntityInterface {
public enum Sex {MALE, FEMALE}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
private Sex sex;
private boolean castrated;
#OneToMany
private List<Symptom> symptoms;
}
AnimalDTO.java
#Getter
#Setter
public class AnimalDTO implements Serializable {
private long id;
private String name;
private Animal.Sex sex;
private boolean castrated;
private List<Long> symptoms;
}
I wish for a list of Symptoms to be automatically mapped to a list of ID's. This could be achieved in many ways, such as creating a TypeMap, creating a Converter or even just by creating a method in AnimalDTO.java:
public void setSymptoms(List<Symptom> symptoms) {
if (symptoms != null)
this.symptoms = symptoms.stream().map(s -> s.getId()).collect(Collectors.toList());
}
But now imagine it's not only Symptoms, but 50 other fields too. That's a lot of code for the same functionality. And then, it's not only Animal to AnimalDTO, but another 30 different classes with their respective DTOs too.
Also, that still leaves the way back open. From ID to entity. This can (in theory) be achieved easily with the following pseudocode:
List<EntityMemberField.class> list;
for (var entityid : listOfEntityIDsOfDto) {
Object persistedObject = entityManager.find(EntityMemberField.class, entityid);
list.add(persistedObject);
}
...
ModelMapperDestination.setField(list);
This is the same for absolutely every Entity/DTO and should automatically happen for every Entity relationship where the Entity implements MyEntityInterface.
An idea how I could achieve that would be overriding MappingEngineImpl.java from ModelMapper which I register as a Spring Service and inject the EntityManager into, but how could I get ModelMapper to use mine? Or is there maybe an easier way?
The goal is to have a fairly automated conversion from Spring Entities to their corresponding DTO by... just calling modelMapper.map(entity, EntityDTO.class);

Stackoverflow-Exception while loading RelationshipEntity in OGM

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;

How do I retrieve only the ID instead of the Entity of an association?

I have a class that looks something like this:
#Entity
public class EdgeInnovation {
#Id
public long id;
#ManyToOne
public NodeInnovation destination;
#ManyToOne
public NodeInnovation origin;
}
and another one that looks something like this:
#Entity
public class NodeInnovation {
#Id
public long id;
#OneToOne
public EdgeInnovation replacedEdge;
}
and so each table map to the other, so one entity will refer to other entities that will refer to more entities and so on, so that in the end there will be many entities that will be fetched from the database. Is there any way to only get the value (integer/long) of the key and not the entity it refers to? something like this:
#ManyToOne(referToThisTable="NodeInnovation")
#Entity
public class EdgeInnovation {
#Id
public long id;
#ManyToOne(referToTable="NodeInnovation")
public Long destination;
#ManyToOne(referToTable="NodeInnovation")
public Long origin;
}
and
#Entity
public class NodeInnovation {
#Id
public long id;
#OneToOne(referToTable="EdgeInnovation")
public Long replacedEdge;
}
Here's an example. I want the stuff in green, I get all the stuff in red along with it. This wastes memory and time reading from disk.
You would just map the foreign keys as basic mappings instead of Relationships:
#Entity
public class EdgeInnovation {
#Id
public long id;
#Column(name="DESTINATION_ID")
public Long destination;
#Column(name="ORIGIN_ID")
public Long origin;
}
Or you can have access to both the ID and the referenced entity within EdgeInnovation, but you'll need to decide which you want to use to set the mapping:
#Entity
public class EdgeInnovation {
#Id
public long id;
#Column(name="DESTINATION_ID", updatable=false, insertable=false)
public Long destination_id;
#ManyToOne
public NodeInnovation destination;
#Column(name="ORIGIN_ID", updatable=false, insertable=false)
public Long origin_id;
#ManyToOne
public NodeInnovation origin;
}
In the above example, the origin_id is read-only while the origin reference is used to set the foreign key in the table. Any changes though should be made to both fields to keep the object mappings in synch with each other.
Another alternative is to use the provider's native code to find if the reference is lazy and wasn't triggered, and then get the foreign key value. If it has been triggered, you can just use the reference to get the ID value, since it won't cause a query to fetch anything. This is something you would have to look into EclipseLink's source code for though.
Sorry, I cant comment so I put it here ,
I think it should be like that
#Entity
public class EdgeInnovation {
#Id
public long id;
#ManyToOne
public NodeInnovation destination;
#ManyToOne
public NodeInnovation origin;
}
And the other class is :
#Entity
public class NodeInnovation {
#Id
public long id;
#OneToMany(mappedBy="origin")
public List<EdgeInnovation> replacedEdges;
}
If I'm getting the situation wrong sorry, (Could you draw your classes with the relations so I can get it straight?)
Why not use a new construction in JPA and a custom constructor in NodeInnovation? Basically, create a transient property in NodeInnovation for use when you only want the EdgeInnovation id:
#Entity
public class NodeInnovation {
#Id #GeneratedValue private Long id;
private Integer type;
#OneToOne
private EdgeInnovation replacedEdge;
#Transient
private Long replacedEdgeId;
public NodeInnovation() {}
public NodeInnovation(Long id, Integer type, Long replacedEdgeId ) {
this.id = id;
this.type = type;
this.replacedEdgeId = replacedEdgeId;
}
...
}
Use it like so:
NodeInnovation n = em.createQuery("select new NodeInnovation(n.id, n.type, n.replacedEdge.id) from NodeInnovation n where n.id = 20", NodeInnovation.class).getSingleResult();
You didn't say how you were selecting NodeInnovation, whether directly or through a join, but either way the trick is the new NodeInnovation in the JPQL or CriteriaBuilder query.
I am aware I am quite late but some people might look for an answer to the same question - in your JPA repository you could do something like this:
#Query("SELECT new java.lang.Integer(model.id) FROM #{#entityName} model WHERE model.relationModeFieldlName.id IN :relationModelIds")
List<Integer> findIdByRelationModelIdIn(#Param("relationModelIds") List<Long> relationModelIds);

transient domain instance jpa&spring

My domain objects are roughly like:
#Entity
public class Customer{
#Id
private String id;
private String name;
#OneToMany(cascade=CascadeType.ALL)
private List<Account> accountList;
}
#Entity
public class Account{
#Id
private String id;
private String name;
#OneToMany
private List<Service> serviceList;
#ManyToOne(cascade=CascadeType.ALL)
#JoinColumn(nullable=false)
private Customer customer;
}
#Entity
public class Service{
#Id
private String id;
private String name;
#ManyToOne
#JoinColumn(nullable=false)
private Account account;
}
I have a transactional Spring service. I want to return an Account instance to fronthand but I don't want to send Customer and Service List informations because of bandwith issues.
When I do:
account.setServiceList(null);
It gives no error but after a while it deletes all my service list.
When I do:
account.setCustomer(null);
It says customer_id of account cannot be null.
I just want to return a transient instance without a validation. How can I handle this.
The solution to your problem is to make the entity detached, by calling entityManager.detach(accountInstance) (or entityManager.clear() if you use JPA 1.0 to detach all entities) and only THAN change anything to it. If you use Hibernate's sessions, use the Session.evict() method.
The other solution is to use a DTO, something that has only the fields that you need.
PS: I think you have an bug, in which the bidirectional relationships are missing on one side the mapped property. E.g in Customer you should have something like
#OneToMany(cascade=CascadeType.ALL, mappedBy="customer")
private List<Account> accountList;
The same with the other relationship.

Categories