Why is my modified neo4j node property not persisted to the db? - neo4j

I'm using Spring Data Neo4J 3.3.1 and I'm having difficulty updating nodes. I have code equivalent to this, and it has zero impact on the database:
#Transactional
public void editTitle(Long nodeId, String newTitle) {
MyNode existingNode = nodeRepository.findOne(nodeId);
existingNode.getSubObject().setTitle(newTitle);
nodeRepository.save(existingNode);
}
This is the MyNode class:
#NodeEntity
public class MyNode {
#Fetch
#RelatedTo(type="LINKED_TO", direction = Direction.OUTGOING)
private SubObject subObject;
public SubObject getSubObject() {
return subObject;
}
}
This is the SubObject class:
#NodeEntity
public class SubObject {
private String title;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
I am able to save new nodes of type MyNode without a problem. I simply call nodeRepository.save(newNode); and it saves the MyNode AND the SubObject to the database at once.
I have confirmed that the transaction is being committed. I've followed the call stack all the way down to the NeoStoreTransaction class and observed the executeModified method processing a command, but the field I changed is not represented there.

For edits, you will need to save related nodes explicitly:
#Transactional
public void editTitle(Long nodeId, String newTitle) {
MyNode existingNode = nodeRepository.findOne(nodeId);
existingNode.getSubObject().setTitle(newTitle);
subObjectRepository.save(existingNode.getSubObject());
nodeRepository.save(existingNode);
}
That's because the save operation treats related nodes differently depending on whether they have already been persisted to the database.
For cases where the related node has not been persisted yet, the related node will be saved automatically.
For cases where the related node has already been persisted, e.g. when you're coming back later to make an edit, changes to properties on the related node will NOT be picked up.

Related

How to return multiple fields with Spring Data Neo4j RX?

I am using Spring Data Neo4j RX. And I have a query like this:
#Query("MATCH (a:Repo)-[:REPO_DEPEND_ON]->(b:Repo) WHERE a.name= $name RETURN a.name, b.name")
String[] getSingleRepoDependencyTo(String name);
I know the return type is wrong here, as it cannot be a String array. But how can I get the result properly, which contains two fields?
I have searched online for a long time but cannot find an answer. The "#QueryResult" annotation is not supported in this RX version yet.
Thanks for your help.
Assuming that you have a mapped #Node Repo with its relationships like
#Node
public class Repo {
// other things
String name;
#Relationship("REPO_DEPEND_ON") Repo repo;
}
and defining this method in a ...extends Neo4jRepository<Repo,...>,
you could use Projections.
public interface RepoProjection {
String getName();
DependingRepo getRepo();
/**
* nested projection
*/
interface DependingRepo {
String getName();
}
}
Important to keep in mind that the returned values should be the nodes and relationship to make it work this way.
You could also remove the custom query and do something like:
RepoProjection findByName(String name)
if you do not have the need for a findByName in this repository for the entity itself.
Take a look here: https://neo4j.github.io/sdn-rx/current/#projections.interfaces
It seems to list exactly what you want. From those docs:
interface NamesOnly {
String getFirstName();
String getLastName();
}
interface PersonRepository extends Neo4jRepository<Person, Long> {
List<NamesOnly> findByFirstName(String firstName);
}
There are some other variations too.
You can use annotation #QueryResult on your expected model. For instance you can do that in this way.
DTO:
import org.springframework.data.neo4j.annotation.QueryResult;
#QueryResult
public class SomeDto {
private int someInt;
private SomeObject sobj;
private double sdouble;
private AnotherObject anObj;
//getters setters
}
Neo4jRepository:
public interface DomainObjectRepository extends Neo4jRepository<DomainObject, Long> {
#Query("MATCH(n:SomeTable) RETURN someInt, sobj, sdouble, anObj") //Return a few columns
Optional<SomeDto> getSomeDto();
}

Play 2.6 - Java - Transaction managment

I try to migrate a Play1 application in Play2 (version 2.6.15 at the moment).
With Play1, transactions were automatically managed when a request arrived. So I didn’t have to worry about consistency of my database updates. Database updates were done all over the service layer.
I have to keep that logic in Play2 and I’m not sure how to do that. I saw some threads with Scala but the answers don't help me. So I'll take my chances :)
To understand how it works, I worked on a snippet with 2 twos models : Person and Address. I want to save a person and the list of their addresses and rollback in case an error occurs on one of the requests.
To start my migration, I was inspired by play-java-jpa-example : I created some repositories that contain all access to database, based on JPAApi and executed on DatabaseExecutionContext
public class PersonRepository implements IPersonRepository {
private final JPAApi jpaApi;
private final DatabaseExecutionContext executionContext;
#Inject
public PersonRepository(JPAApi jpaApi, DatabaseExecutionContext executionContext) {
this.jpaApi = jpaApi;
this.executionContext = executionContext;
}
...
#Override
public CompletionStage<Person> add(Person person) {
return supplyAsync(() -> jpaApi.withTransaction(em -> {
em.persist(person);
return person;
}), executionContext);
}
}
public class AddressRepository implements IAddressRepository {
...
#Override
public CompletionStage<Address> add(Address address) {
return supplyAsync(() -> jpaApi.withTransaction(em -> {
em.persist(address);
return address;
}), executionContext);
}
}
I have a service to save Address
public class AddressService implements IAddressService {
....
#Override
public CompletionStage<Address> add(Address address) {
//do some stuff here
return addressRepository.add(address);
}
}
And a service to save a Person, with
public class PersonService implements IPersonService {
...
#Override
public CompletionStage<Person> add(Person person, List<Address> address) {
return add(person).thenApplyAsync(p -> {
for (Address a : address) {
a.person=p;
addressServiceAsync.add(a);
}
return p;
}, ec.current());
}
}
With this implementation of add(Person person, List address), if saving the second address fails, the person and the first address will have been persisted in the database which is not good enough for me.
I tried to remove transaction management in repositories and put it in my PersonService.add function. By passing the entity manager to the functions of my services and repositories it works (I only tested with synchronous calls). Something like that :
public class PersonService implements IPersonService {
#Override
public CompletionStage<Person> add(Person person, List<Address> address) {
return supplyAsync(() -> jpaApi.withTransaction(em -> {
Person person1 = personRepository.insert(em, person);
for (Address a : address) {
a.person = person1;
addressService.add(em, a);
}
return person1;
})).exceptionally(throwable -> {
Logger.error("pan", throwable);
return null;
});
}
}
I don’t like the approach (giving em to all functions) and wonder about asynchronous calls.
What is planned in Play to handle this kind of rollback problem with JPAApi and DatabaseExecutionContext ?
I didn’t see any explicit thread evoking this point, maybe I missed something. What would be the best practice to solve this problem?
Thanks for your help.

Neo4j SDN 4 Cypher query based on GraphId and entity type

I have implemented a base class for all entities in my application that can have a subscribers:
#NodeEntity
public abstract class Subscribable extends BaseEntity {
private final static String SUBSCRIBED_TO = "SUBSCRIBED_TO";
#Relationship(type = SUBSCRIBED_TO, direction = Relationship.INCOMING)
private Set<User> subscribers = new HashSet<>();
public Set<User> getSubscribers() {
return subscribers;
}
public void setSubscribers(Set<User> subscribers) {
this.subscribers = subscribers;
}
public boolean addSubscriber(User subscriber) {
return subscribers.add(subscriber);
}
}
Also I have a few concrete types that extend Subscribable class, for example Product, Tag, User etc
Right now in order to get all User subscribed to a particular Subscribable I use a following method with a Cypher query:
#Query("MATCH (sub:Subscribable)<-[:SUBSCRIBED_TO]-(u:User) WHERE id(sub) = {subscribableId} RETURN u")
Set<User> findAllUsersBySubscribableId(#Param("subscribableId") Long subscribableId);
The issue is that AFAIK different objects of different types can potentially have the same Id so looks like the approach to get all User based only on subscribableId(GraphId) can not be consistent and potentially can lead to a different results.
The idea is to supply additional parameter to findAllUsersBySubscribableId method, for example String type in order to specify what exact type I expect under provided subscribableId.. for example "Product"
Please help to extend the following Cypher query in order to support entity type:
MATCH (sub:Subscribable)<-[:SUBSCRIBED_TO]-(u:User) WHERE id(sub) = {subscribableId} RETURN u

Cannot use enum in repository query (neo4j/Spring Data)

I'm having a problem querying based on an Enum property of my NodeEntity.
The NodeEntity in question is defined:
#NodeEntity(label = "Entity")
public class MyEntity {
#GraphId
private Long internalId;
....
private State state;
#Transient
public enum State {
STATEONE, STATETWO, STATETHREE
}
....
It saves without a problem, the state Enum represented perfectly, and I can query using other properties (Strings) with no problem at all. However the problem is the following query in a repository:
#Query("MATCH (entity:Entity {state:{0}})" +
"RETURN entity")
List<MyEntity> findByState(MyEntity.State state)
i.e. find all entities with the given state.
There's no exception, however using this simply returns a List of 0 Entities.
I've tried all kinds of variations on this, using a WHERE clause for example, with no luck.
The Entities are persisted properly, using findAll() in the same test returns the expected List of Entities with their states exactly as I would expect.
Any thoughts?
Not quite sure what the value #Transient adds to the enum. It is anyway not persistable as a node or relationship in Neo4j. It is sufficient to define the field as one that should persist with
private State state;
and leave off the #Transient annotation from the enum.
With it, SDN ignores the field sent to the derived query.
However, if you have a good reason to mark the enum #Transient, please do share it and we'll re-visit this case.
There is a general problems using spring data rest interface to search on enum fields. Just using the enum-to-string converter cannot work for search where you want to find if the value is IN a collection of values:
public interface AppointmentRepository extends Neo4jRepository<Appointment, Long> {
Page<Appointment> findByDayOfWeekIn(#Param("days") List<DayOfWeek> days, Pageable pageable);
}
The above does not work out of the box because neo4j will try to convert a List to your property type: DayOfWeek
In order to work around this I needed a custom converter that handles both requests providing collection of values (the search) and single values (the normal read and write entity):
#SuppressWarnings({ "unchecked", "rawtypes" })
public abstract class SearchQueryEnumConverter<T extends Enum> {
private Class<T> enumType;
public SearchQueryEnumConverter() {
enumType = (Class<T>) ((ParameterizedType) this.getClass()).getActualTypeArguments();
}
public Object toGraphProperty(Object value) {
if (Collection.class.isAssignableFrom(value.getClass())) {
List<T> values = (List<T>) value;
return values.stream().map(Enum::name).collect(Collectors.toList());
}
return ((Enum) value).name();
}
public Object toEntityAttribute(Object value) {
if (Collection.class.isAssignableFrom(value.getClass())) {
List<String> values = (List<String>) value;
return values.stream().map(v -> (T) T.valueOf(enumType, v)).collect(Collectors.toList());
}
return (T) T.valueOf(enumType, value.toString());
}
}
The abstract converter can be reified by all enums, and used as parameter of the #Convert annotation:
public enum EnumType {
VALUE_A, VALUE_B;
public static class Converter extends SearchQueryEnumConverter<EnumType> implements AttributeConverter {
}
}
#NodeEntity
public Entity {
#Property
#Convert(EnumType.Converter.class)
EnumType type;
}

Neo4J SDN- 3.0.0 ExecutingRestAPI.getNodeById is invoced repeatedly

I'm using Neo4J-2.0.1 and SDN - 3.0.0.RELEASE.
I have a NodeEntity as follow:
#NodeEntity
public class Group {
#GraphId
Long id;
#Indexed(unique = true, indexType = IndexType.SIMPLE)
public String name;
public String property1;
public String property2;
public Group() {
}
public Group(String str) {
name = str;
}
}
I have a groups repository :
public interface GroupsRepository extends GraphRepository<Group> {
Group getGroupByName(String name);
}
After the getGroupByName(...) method is invoked, the
ExecutingRestAPI.getNodeById(...)
method is invoked as the number of the properties that the Group has.
How can I avoid this kind of behaviour?
Are there any additional queries being executed under the hood?
You cannot avoid this behavior at the moment, it loads the dependent entities individually. The REST integration is currently not at all optimized.

Resources