How to delete a relationship of a neo4j node in spring data - spring

I have User nodes and having contact relationships with each other. user1 has two contacts user2 and user3. Now I want to delete user2 from contacts. Following is a code snippet about contact relationships.
#Node
public class User{
...Id and different properties
#Relationship(type = "CONTACT")
public Set<User> contacts = new HashSet<>();
}
Now when I delete a relationship and save back the node it shows the following message:
WARN 38461 --- [nio-8080-exec-2] o.s.d.n.c.m.DefaultNeo4jIsNewStrategy : Instances of class com.talkkia.api.entity.User with an assigned id will always be treated as new without version property!
Code for deleting relationship is here:
#Transactional
#Override
public String deleteContact(String mobile1, String mobile2) {
Optional<User> user1 = userRepository.findOneByMobile(mobile1);
Optional<User> user2 = userRepository.findOneByMobile(mobile2);
if(user1.get().getContacts().contains(user2.get())){
user1.get().getContacts().remove(user2.get());
System.out.println(user1.get().getContacts());
userRepository.save(user1.get());
return user2.get().getName() + " has been deleted from your contact.";
}
return user2.get().getName() + " can't be deleted from contact.";
}

The warning message is exactly what leads into the situation you are faced with.
Because you are using an assigned, manual provided id, Spring Data Neo4j cannot differentiate if the object is new or not.
The solution is to provide a #Version field in the entity (like #Version Long version).
From this SDN can derive the information if the element comes from the database or was newly created.
As a consequence of the wrongly assumed new state of the entity, no relationships will be deleted because it is not necessary from the persistence logic.

#Query("MATCH(user1:User {mobile: $mobile1})-[c:CONTACT]-(user2:User {mobile:$mobile2}) DETACH DELETE c")
public String deleteContact(String mobile1, String mobile2);
Have found the above solution very easy and good in performance I think. #meistermeier point is also valid and considerable.

Related

Neo4J + Spring If relation is empty it errors

I have 2 Nodes:
#Node
class Person
{
String name;
#Relationship(type = "WORKED_AT", direction = INCOMING)
List<Job> jobs;
}
#Node
class Job
{
String name;
}
When I call repository of Person method findAll() it throws error when Person don't have any existing relationship. When I remove Relationship part it works normally.
How do i make it work (it is not mandatory that every Person has a job).
PS: it is not full code - so i did not include here IDs, default repository etc. in those is not any error.
It was a version issue, my neo4j DB version was 4.1.* when I updated to 4.3.* everything was fixed.

JpaRepository merge() method

I'm rewriting a big project with SpringBoot 2.2.6 and I'm run into a problem.
In the old project (pure ejb) when a complex entity is updated, the code build entity from DTO's as follows:
public Entity dtoToEntity(DTO dto) {
Entity entity = new Entity();
entity.setId(dto.getID());
// ecc...
// ecc...
entity.setSubEntity(dto.getSubEntity() != null ? new SubEntity(dto.getSubEntity().getId() : null);
// and so on
}
The important section is that related to subentity! After made a mapping like that the old project calls:
EntityManager.merge(entity);
I think that with merge call, if inside the database exists a SubEntity with specified id and other fields valorized, the other fields remain valid and are not set to null because they aren't declared in mapping.
But with SpringBoot I'm using JpaRepository and I don't think the same thing happens if I call:
jpaRepository.save(entity);
I think with this call the other fields of SubEntity with specified id will set to null!
Is it correct?
What can I solve this?
Thanks for your reply first of all!
You are right, I can't do something like that nor with EntityManager.merge() method! Than I try to explain better what I want to do:
Suppose I have a complex Entity, which has many nested Entities (which may have nested entities) as follows:
#Entity
public class Car {
private String name;
....
....
private Engine engine; // nested entity
private Chassis chassis; // nested entity
}
And:
#Entity
public class Engine {
private String company;
private Oil oil; // nested entity
....
}
Now suppose in the database I have a Car, with all relationship filled (Engine, Chassis, Oil ecc..) and suppose I want to update the Car name from Ferrari to Fiat, if I use pure SQL I can simply write:
update Car c set c.name = "Fiat" where c.id = [id];
Now if I use Spring JPA, to ensure that all nested entity (and their field) are not setting to null when I update my entity I have to do:
Car car = carRepository.findById([id]);
car.setName("Fiat"):
carRepository.save(car);
This way I will update Car name and I'm sure that all other entities will remain set because are loaded by findById() method.
My question and my goal is to know if is there a way to do something like that:
Car car = new Car();
car.setId(1); // id of Ferrari car
car.setName("Fiat");
someRepositoryOrEntityManager.saveOrUpdate(car);
And preserve all other field and relation without load all of these by the find method (maybe due to a performance reasons).
Did you give it a try or it is just guesswork?
First of all, you don't need to embrace spring data repositories. You can inject EntityManager if it helps in the migration process.
Secondly, look at the implementation of SimpleJpaRepository.save
#Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
This means that JpaRepository.save calls em.merge if it concludes that the entity is not new.
The check if the entity is new is in AbstractEntityInformation.isNew. It concludes that the entity is new only if its id is null (or 0 for primitive numerical types).
You assign the id from the dto. If it is not null (or non-zero for primitives), there is no reason to believe that the new code will behave in a different way than the old one.
Answer for updated question
If you want to modify an entity without fetching it, I would suggest JPQL or criteria query
Reference:
More about whether an entity is new or not, can be found here.

Spring Boot: H2 Not Actually Retrieving From Database

I'm trying to write a simple Repository test in Spring Boot. The Test code looks like this:
public class UserRepositoryTest {
private final TestEntityManager entityManager;
private final UserRepository userRepository;
#Autowired
public UserRepositoryTest(TestEntityManager entityManager, UserRepository userRepository) {
this.entityManager = entityManager;
this.userRepository = userRepository;
}
#Test
public void test() {
String firstName = "Frank";
String lastName = "Sample";
String email = "frank#example.com";
String username = "frank#example.com";
String password = "floople";
String passwordConfirm = "floople";
RegisterUserRequest registerUserRequest = new RegisterUserRequest(firstName, lastName, email, username, password, passwordConfirm);
User user = new User(registerUserRequest);
user.setSpinsRemaining(0);
userRepository.save(user);
userRepository.setSpinsRemainingToTen();
User found = userRepository.findByUsername(username);
assertThat(found.getSpinsRemaining()).isEqualTo(10);
}
What's I expect to happen is that the new User object is persisted to the database, the row in the database is modified to set spinsRemaining to 10, and then the now-modified row is retrieved from H2 and shoved into a new variable named "found". The "found" variable will point to an instance of a User object with ten spins remaining.
What actually happens is that the "found" variable points to the exact same instance of User that the "user" variable is. In fact, if I modify some property of the "user" variable AFTER persisting it to H2, the resultant "found" object also has the modified property. According to IntelliJ, both "user" and "found" are pointing to the same thing. How is that possible?
Hibernate caches entities inside a transaction in memory ("first level cache"). - Every time it retrieves an entity from database (or when it's asked to do so by the entity id) it will first look for it in cache so you don't have multiple instances of one entity with the same ID.
But in tests it's sometimes useful to have a "fresh" entity as it can uncover bugs in your persistance configuration/code. What you need to do:
Call EntityManager#flush - this will force synchronization of your changes to the database (save method does not guarantee that when called inside a transaction).
Call EntityManager#clear - Hibernate will forget about previous entity instances and will start fetching from DB again.
Alternatively: You can also instruct your Spring repository method to clear entities automatically after a modifying query. - But this will wipe out all entity instances and not only the one you are modifying so it might not be desirable in your application code.

Prevent duplicate child entries while saving parent entity in spring boot spring data jpa involving multiple thread access

We have a spring boot application where we are trying to save information related to notifications received from third-party systems. The information is saved only when there exists a subscription for a specific kind of notification.
In short, we have three different entities present.
1) A subscription entity which is a combination of
a) a subscriber or User
b) a subject on which the subscription is taken
c) type of notification on which a subscriber took the subscription with respect to a subject.
2) A subscriber entity
3) a subject entity
While implementing a save subscription scenario in spring data jpa we are facing an issue related to the duplication of data.
The way the functionality is envisioned we get the information related to both subject, subscriber, and type as part of the notification subscription post message.
{
"notificationTypeCodes" : [""],
"subjectId" : "Person1",
"subscriberId" : "USER1"
}
we then save this information in three different tables 1) subscription table
2) subjectId 3) subscriberId
The subscription table contains the relationship columns containing combination of id wrt ( notificationTypeCodes,subjectId,subscriberId).
The data wrt these ids are placed in their respective tables.
we are populating all three tables in the same transaction using the cascading approach. In order to prevent duplicate values being entered in any of the tables, we tried doing a getsubscritopn before save and set up a condition statement stating that it subscription does not exist them only save the subscription
subscription object = fetchsubscription();
if(subscription is not present)
{
save subscritopm)
}
else{
response stating subscritpion exists
}
But in case of multithreading scenarios, our check is falling as multiple threads are entering the if block before the subscription is saved and duplicate entries are getting crated in all three tables.
we cannot create constraints on a table as we can have scenarios where
different users(subscribers) can subscribe on same subjects and if we have constraints on a subject table this valid scenario might be rolled back.
Is there a way that we can handle the duplicate scenario the DB level. where we can throw a unique constraint exception. if we are storing a duplicate subscription.
Note: we don't have any constraints on the table as of now.
2) we don't want to use synchronized block in the service class
It would be great if someone can provide some insite into the same.
Thanks
in advance
I have now tried to explain the same problem with books publisher and bookspublisher entity
**************** bookspublisher entity************
package com.hellokoding.jpa.model;
import javax.persistence.*;
#Entity
#Table(name = "book_publisher")
public class BookPublisher
{
#Id
private int id;
public BookPublisher() {
}
public Book getBook()
{
return book;
}
public void setBook( Book aBook )
{
book = aBook;
}
public int getId()
{
return id;
}
public void setId( int aId )
{
id = aId;
}
public Publisher getPublisher()
{
return publisher;
}
public void setPublisher( Publisher aPublisher )
{
publisher = aPublisher;
}
#OneToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.EAGER)
#JoinColumn(name = "book_id")
private Book book;
#OneToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.EAGER)
#JoinColumn(name = "publisher_id")
private Publisher publisher;
}
Service to persists bookspublisher data.
The issue is when I am persisting the BookPublisher entity in multithreading environment duplicate data is getting created in both books and publisher tables.
The use case states that only the very first time when a new book or publisher comes it should be persisted in rest of the calls no duplicate entries should be allowed in the tables.
#Service
public class BookPublisherService
{
#Autowired
private BookPublisherRepository bookpublisherRepository;
#Transactional
public void saveSubscription( BookPublisherRequest aRequest )
{
String bookName= aRequest.getBook();
String publisher= aRequest.getPublisher();
BookPublisher subscription = new BookPublisher();
subscription.setBook( new Book( bookName) );
subscription.setPublisher( new Publisher( publisher ) );
bookpublisherRepository.save( subscription );
}
}
Is it possible that if book and publisher values already exists then the same id's are provided to the publisherbook entity and by having unique constrain on that single table(publisherbook ) i can throw publishbook already exists message.
These two statements seem contradictory:
we cannot create constraints on a table as we can have scenarios where
different users(subscribers) can subscribe on same subjects and if we have constraints on a subject table this valid scenario might be rolled back.
and
Is there a way that we can handle the duplicate scenario the DB level. where we can throw a unique constraint exception. if we are storing a duplicate subscription.
It sounds like you need a unique constraint on your Subscription entity to prevent the duplicates. i.e.
#Table(name = "subscriptions", uniqueConstraints = #UniqueConstraint(name = "uq_sub", columnNames = {"notificationTypeCode", "subscriberId", "subjectId"}))

JPA - Auto-generated field null after save

I have an Account entity and I'm trying to persist it using save function. My code:
#Override
public Account createAccount(String pin) {
Account account = new Account();
account.setBalance(0L);
account.setPin(pin);
return accountRepository.save(account);
}
Now my entity class has an autogenerated field called accountNumber. My entity class:
#Entity
#Table(name = "accounts")
#Data
public class Account {
#Column(name = "account_number", length = 32, insertable = false)
private String accountNumber;
private Long balance;
}
Now after calling save, the entity returned has accountNumber as null but i can see in the intellij database view that it is actually not null. All the other auto-generated fields like id etc are there in the returned entity just the accountNumber is null. Default value for accountNumber is set in the sql file :
ALTER TABLE accounts
ALTER COLUMN account_number SET DEFAULT DefaultValueSerializer(TRUE, TRUE, 12);
Here, DefaultValueSerializer is the function which is generating the account number.
I've tried other solutions available here like using saveAndFlush() etc, nothing worked in my case. What can be an issue?
As mentioned in comment Hibernate is not aware about what happens in database engine level so it does not see the value generated.
It would be wise to move generation of account number to JPA level instead of using db defaults.
I suggest you to study annotations #GeneratedValue and related stuff like #SequenceGenerator. That way the control of generating account number is in JPA level and there is no need for stuff like refreshing entity after save.
One starting point: Java - JPA - Generators - #SequenceGenerator
For non-id fields it is possible to generate value in method annotated with #PrePersist as other answer suggests but you could do the initialization already in the Accounts constructor .
Also see this answer for options.
You can create an annotated #PrePersist method inside the entity in which you set its fields to their default value.
That way jpa is going to be aware of the default.
There are other such annotation avaiable for different entity lifecycle event https://docs.jboss.org/hibernate/entitymanager/3.6/reference/en/html/listeners.html
P.s. if you decide to go this way remember to remove the insertable = false
Use
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
for your IDs. And also leave your saving to saveAndFlush so you can immediately see the changes, If any. I'd also recommend separating IDs and account numbers. They should not be the same. Try debugging your program and see where the value stops passing around.

Resources