Create a relationship by ID using Neo4J and Spring Data - spring

I have defined a simple node object like this:
#Node("product")
public class Product {
#Id
private String sku;
...
#Relationship(type = "SOLD_BY")
private Set<Shop> shops = new HashSet<>();
...
}
However, my concerns are:
To create (and save) a new Product I must not only create the Product, but also the list of Shop objects. And maybe a Shop object holds an Address object or a list of Employees itself. This means I'm forced to construct a huge list of objects.
Also, maybe the Shop objects are already existing in the database and I don't want to overwrite them by saving the new Product.
By loading a Product, all related Shop objects will be loaded and so on. But I'm just interested in the Product, not in everything connected to it.
So instead I want to do this:
#Relationship(type = "SOLD_BY")
private Set<String> shops = new HashSet<>(); // Just the IDs of the Shops
Is this possible?
Is this a valid approach to solve my problem?

Related

How to retrieve an Entity Object just with #Id (Proxy object), check if it exists and assign it to a #ManyToOne association

I have an entity Product which have many fields and associations (around 60).
And a table ProductView which has a #ManyToOne(fetch = FetchType.LAZY) association with Product.
Is there a optimal way to retrieve Product object and assign it to ProductView ?
If its used JPA findById(productId) or JPQL/EntityManager selects-> It will retrieve all products fields and associations
Product product = productRepository.findById(productId);
ProductView productView = new ProductView(product);
save(productView);
If its used JPA getOne -> It solves the problem but the Proxy can throw error if Product does not exists. And this error can not be handled because it happens at runtime.
Product product = productRepository.getOne(productId);
ProductView productView = new ProductView(product);
save(productView);
If a DTO is used or Interface which refers to the same Product Table -> We will get just an object with Id field, but a lot more processes will need to be added (Which I am not familiar with)
Delete foreign keys from ProductView table (#ManyToOne -> #Column) and simple assign productIds. But in this way, there will be no logic connection between tables.
ProductView DB
How usually developers avoid this problem ?
I don't understand what the problem is. Just use getOne approach and at the end of your method, use flush which will throw the constraint violation exception that you can handle. This is the way to go.

save and read non-aggregate Roots

i'm a beginner in Spring data jpa. I learned from the docs that we create repositories for aggregate roots only. for example an object Order encapsulates operations on multiple LineItem objects. The Order contains the LineItem objects. so Order is the aggregate root, for which a repository will be created.
the we can save, read Order objects easily using, repository methods:
OrderRepository.save(order)
OrderRepository.findAll
My question is, if I want to create Lineitem objects before Order objects to store them in a combobox , how can I save them if I can't create repository for them?
Or if I want simply to just show the list of Lineitems how can I read the objects?
If you have Items that exists before Orders, this means only one thing: Orders and Items are two different aggregate roots, hence they could be handled in different ways. So this solves your problem: you need two Repositories, one for Orders and one for Items. In every other context where you handle Items inside Orders the formrrs cannot exist before latters.
This, anyway, needs a bit of work on the save, because you've to check for every Order if an Item in added/removed.
Suppose that we have a model:
#Entity
public class Order {
#Id
private Long id;
#OneToMany(cascade = ALL, orphanRemoval = true)
private List<Item> items = new ArrayList();
public Order(Item... items) {
items.addAll(Arrays.asList(items) ;
}
//...
}
#Entity
public class Item {
#Id
private Long id;
//...
}
To save items that belong to the Order we do the following:
Item item1 = new Item();
Item item2 = new Item();
Order order = new Order(item1, item2);
orderRepo.save(order);
Because we used cascade = ALL for items they will be saved together with Order.
The same will work for other operations: update, delete...
UPDATE
If the model assumes an independent work with entities (for example: list of item categories) then we should create a repository for them that allow us to store entities separately from other AR.
public interface ItemRepo extends JpaRepository<Item, Long> {
}
Item item1 = new Item();
Item item2 = new Item();
itemRepo.save(asList(item1, item2);
Order order = new Order(item1, item2);
orderRepo.save(order);
But I think that it's not the best choice for order items cause they cannot live without Order.

Getting multiple entries from extra lazy loaded collection

Is it possible to somehow get multiple objects from a one-to-many-collection by index/key, which is marked with extra lazy load?
I have a big collection where I can't fetch all entries but still want to get multiple objects from it.
For example:
class System
{
...
#OneToMany(mappedBy = "system")
#MapKey(name = "username")
#LazyCollection(LazyCollectionOption.EXTRA)
private Map<String, User> users = new HashMap<>();
public List<User> getUsers(List<String> usernames)
{
//what to do
}
}
It's just a simple example but it portraits my problem.
I know I could just use the Criteria API or (named) queries but I try to keep the logic where it belongs to.
Unfortunately it seems that Hibernate does not support loading multiple entries from a collection inside a entity.
Only ways I found:
use eager/lazy loading and get all objects (which won't work if there are many)
use extra lazy loading and get multiple objects by retrieving one by one (can hurt performance)
use Session.createFilter which can not be called inside an entity

JPA MERGE failed to update entity field value when this field is a collection(using ElementCollection)

Here we have a Manifest class that includes list of students and teachers, both could be null.
class Manifest{
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "MANIFEST_STUDENT")
List<String> students = new ArrayList<String>();
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "MANIFEST_TEACHER")
List<String> teachers = new ArrayList<String>();;
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "MANIFEST_OTHERS")
List<String> others = new ArrayList<String>();;
}
on the UI, there are two multiple select, one for student and one for teacher that let user choose for current manifest.
here is the problem:
When user deselect all students or teachers from the list(meaning remove all students or teachers from current manifest) and click save, unfortunately nothing can be saved, from UI and database it shows that the multiselect chosen looks the SAME as before.
from service layer, the code is simply like this.
manifest.merge();
It seems we must keep at least one student or teacher for the collection field to make the change valid. So what's going on here and what is the solution? BTW, we are on Openjpa.
Kind of resolve the issue, more like a work around:
Before calling merge(), place several condition checkers to make sure the collection fields are not null
public void save(Manifest entity) {
if(entity.getStudents()==null){
entity.setStudents(new ArrayList<String>());
}
if(entity.getTeachers()==null){
entity.setTeachers(new ArrayList<String>());
}
if(entity.getOthers()==null){
entity.setOthers(new ArrayList<String>());
}
entity.merge();
}
Simple as it, it seems the UI returns those collection fields as null even we initiate them as with empty String lists.
cheers.
Initializing a value in a JPA managed class, such as class Manifest, has no bearing on what, or how, JPA will create the class as JPA maps extracted rows to the class. In particular, the result of:
List<String> students = new ArrayList<String>();
is likely to be:
On creation (by JPA) of a new instance, assign an ArrayList<String>() to students.
JPA overwrites students with the data it extracts - the empty ArrayList is dereferenced/lost.
If your code is clearing a list, such as students, use obj.getStudents().clear(). More likely to run into problems if you call obj.setStudents(someEmptyList).
The issue here is how the JPA manager handles empty datasets: as null or as an empty list. The JPA spec (old, not sure about the just released update) doesn't take a position on this point. A relevant article here.
From your comments, it's apparent that OpenJPA may not be respecting a null value for a Collection/List, while it happily manages the necessary changes for when the value is set to an empty list instead. Someone knowing more about OpenJPA than I may be able to help at this stage - meanwhile you've got a workaround.

What is the work-around for deleting orphan entities using JPA 2.0 and #OneToMany?

I'm using JPA 2.0, Hibernate 4.1.0.Final, Spring 3.1.1.RELEASE, and Java 1.6. I have this entity with a one-to-many relationship to another entity …
import javax.persistence.CascadeType;
...
#Entity
#Table(name = "classroom")
public class Classroom implements Serializable
{
...
#OneToMany(mappedBy = "classroom", cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE})
private Set<ClassroomUser> roster;
However, when I update my entity with a different set of ClassroomUser objects
classroom.setRoster(newRoster);
and save the entity, all the previous ClassroomUser objects remain. What is the proper/shortest way to update my entity while removing all the orphan records from the database?
Thanks, - Dave
Use orphanRemoval:
#OneToMany(mappedBy="classroom", cascade={CascadeType.ALL}, orphanRemoval=true)
Whenever an entry is removed from the persistent set, it will get deleted. And this means you need to work with the persistent set. I.e. you are not allowed to replace the set, instead you should do:
classroom.getRoster().clear();
classroom.getRoster().addAll(newRoster);
EXAMPLE how to synchronize persistent set with a user required set:
/**
* Assemble ClassroomUser relations.
* #param classroom Classroom entity. Must be attached persistent or transient. Never null.
* #param userIds Collection of user identifiers. Can be empty. Never null.
*/
private void assembleClassroomUsers(Classroom classroom, Collection<Integer> userIds) {
// Make sure relation set exists (might be null for transient instance)
if (classroom.getUsers() == null) {
classroom.setUsers(new HashSet<ClassroomUser>());
}
// Create working copy of the collection
Collection<Integer> ids = new HashSet<Integer>(userIds);
// Check existing relations and retain or remove them as required
Iterator<ClassroomUser> it = classroom.getUsers().iterator();
while (it.hasNext()) {
Integer userId = it.next().getUser().getId();
if (!ids.remove(userId)) {
it.remove(); // This will be picked by the deleteOrphans=true
}
}
// Create new relations from the remaining set of identifiers
for (Integer userId : ids) {
ClassroomUser classroomUser = new ClassroomUser();
classroomUser.setClassroom(classroom);
// User must not have ClassroomUser relations initialized, otherwise Hibernate
// will get conflicting instructions what to persist and what to drop => error.
// It might be safer to use dummy transient instance...
User dummyUser = new User();
dummyUser.setId(userId);
classroomUser.setUser(dummyUser);
classroom.getUsers().add(classroomUser);
}
}
This approach might seem a little bit complex. You might be able to create something simpler (but probably not too much) with custom equals/hashCode and some Set<E> manipulation methods (e.g. from Guava).

Resources