Spring Recursive Lazy Loading Children Entities - spring

I am trying to retrieve recursively all offices under a Head Office.. However I'm getting a NullPointerException when trying stream through the second generation.
public class Office extends AbstractPersistableCustom<Long>{
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "parent_id")
#Builder.Default
private List<Office> children = new LinkedList<>();
// For retrieving children recursively
public Stream<Office> flattened() {
return Stream.concat(
Stream.of(this),
children.stream().flatMap(Office::flattened));
}
}
RepositoryWrapper
public Office findOfficeHierarchy(final Long id) {
final Office office = this.repository.findById(id).orElseThrow(() -> new OfficeNotFoundException(id));
office.loadLazyCollections();
return office ;
}
In my controller class
List<Office> recursive= children.stream().flatMap(Office::flattened).collect(Collectors.toList());
I really want to avoid using FetchType.EAGER due to large amounts of data.
Any suggestions?

Related

Bidirectional #OneToOne Spring Data JPA, Hibernate

I am using Bidirectional #OneToOne from Hibernate documentation. I have created an identical model for the test.
I can't get Phone via PhoneDetails. I get an error - Message Request processing failed; nested exception is org.hibernate.LazyInitializationException: could not initialize proxy [com.example.model.Phone#1] - no Session.
I've tried many options and it doesn't work.
Please tell me how to get the Phone correctly? I sit all day trying to do this. I did not find any options on the Internet, so I ask here.
Phone.java
#Entity(name = "Phone")
public class Phone {
#Id
#GeneratedValue
private Long id;
#Column(name = "`number`")
private String number;
#OneToOne(mappedBy = "phone",
cascade = CascadeType.ALL,
orphanRemoval = true,
fetch = FetchType.LAZY)
private PhoneDetails details;
public Phone() {
}
public Phone(String number) {
this.number = number;
}
// Getters and setters are omitted for brevity
public void addDetails(PhoneDetails details) {
details.setPhone( this );
this.details = details;
}
public void removeDetails() {
if ( details != null ) {
details.setPhone( null );
this.details = null;
}
}
}
PhoneDetails.java
#Entity(name = "PhoneDetails")
public class PhoneDetails {
#Id
#GeneratedValue
private Long id;
private String provider;
private String technology;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "phone_id")
private Phone phone;
public PhoneDetails() {
}
public PhoneDetails(String provider, String technology) {
this.provider = provider;
this.technology = technology;
}
// Getters and setters are omitted for brevity
}
LifecycleController.java
#Controller
public class LifecycleController {
#Autowired
ServiceJpa serviceJpa;
#GetMapping(value = "/savePhoneAndPhoneDetails")
public String savePersonAddress () {
Phone phone = new Phone( "123-456-7890" );
PhoneDetails details = new PhoneDetails( "T-Mobile", "GSM" );
phone.addDetails( details );
serviceJpa.savPhone( phone );
return "/savePhoneAndPhoneDetails";
}
#GetMapping(value = "/getPhone")
public String addPersonAddress () {
PhoneDetails address = serviceJpa.findPhoneDetailsById(2L).orElseThrow();
Phone phone = address.getPhone();
/*
An error appears here -
could not initialize proxy
[com.example.model.Phone#1] - no Session
*/
System.out.println(phone.getNumber());
return "/getPhone";
}
}
ServiceJpa.java
#Service
#Transactional
public class ServiceJpa {
#Autowired
PhoneJpa phoneJpa;
#Autowired
PhoneDetailsJpa phoneDetailsJpa;
#Transactional
public void savPhone(Phone phone) {
phoneJpa.save(phone);
}
#Transactional
public Optional<PhoneDetails> findPhoneDetailsById(Long id) {
return phoneDetailsJpa.findById(id);
}
}
interface PhoneJpa.java
#Repository
public interface PhoneJpa extends JpaRepository<Phone, Long> {
}
interface PhoneDetailsJpa.java
#Repository
public interface PhoneDetailsJpa extends JpaRepository<PhoneDetails, Long> {
}
I agree with Andriy's comment with a slight addition of "You should not access [lazily loaded] entity details outside transaction bounds". But, for starters, is there some reason you want the OneToOne to be FetchType.LAZY to begin with? If you changed it to EAGER, your "lazy" problem would be resolved by virtue of it no longer being a lazy reference but being a real hydrated object.
If that is not the exact route you want to take, there are a dozen ways to EAGERLY fetch things in general and frankly too many to present a single solution here as best/ideal. As your code exists, since all the dereferencing (for now) is happening inside your Controller, then Andriy's suggestion to add #Transaction to the Controller may suffice in that it will be lazily fetched when you need it.
But in the future, if you have Lazy elements in a POJO that get returned to the stack higher than the controller, say, just before they are serialized to JSON for example, then even the CONTROLLER's #Transactional wouldn't be "high" enough in the stack and you'll end up with the same Lazy init problem..
Also, by having it be Lazy and then dereferencing it elsewhere, you're guaranteeing two trips to the Database. With proper FETCH/JOIN eager loads, you would limit that to one, which can be another performance benefit.
So either way, you're back to the real problem at hand.. looking for ways to ensure your operations occur ENTIRELY inside a Transaction boundary OR having to completely hydrate the object so no "Lazy" danglers get dereferenced outside of that.. i.e. by making them eager or by force-initializing any potential Lazy proxies/collections.

JPA - How to copy and modify it's content of Page object?

I have this Meeting and Favorite models;
public class Meeting implements Serializable {
private long id;
private String meetingTitle;
private Date meetingStartDate;
private User host;
}
public class MeetingFavorite implements Serializable {
private long id;
private boolean active = false;
private Meeting meeting;
private Date updatedDate;
}
And I can successfully fetch MeetingFavorite page object like;
#GetMapping(value = "/favorite-meetings", consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
public ResponseEntity searchFavoriteMeetings(
MeetingFavoriteSpecification search, HttpSession session) {
Page<MeetingFavorite> page = meetingsService.findFavoriteMeetings(search);
return ResponseEntity.ok(page);
}
Is it possible to get Meeting contents only from MeetingFavorite Page w/ it's pagination data?
I tried this and it returns Meeting objects. But pagination data is lost.
Page<MeetingFavorite> page = meetingsService.findFavoriteMeetings(search);
List<Meeting> meetings = new ArrayList<Meeting>();
page.forEach(entity -> meetings.add(entity.getMeeting()));
final Page<Meeting> meetingPage = new PageImpl<>(meetings);
return ResponseEntity.ok(meetingPage);
Oh, I found the way. Thanks.
List<Meeting> meetings = new ArrayList<Meeting>();
page.forEach(entity -> meetings.add(entity.getMeeting()));
Sort sort = new Sort(Sort.Direction.DESC, "updatedDate");
Pageable pageable = new PageRequest(search.getOffset() / search.getLimit(), search.getLimit(), sort);
final Page<Meeting> meetingPage = new PageImpl<Meeting>(meetings, pageable, page.getTotalElements());
return ResponseEntity.ok(meetingPage);

Bidirectional #OneToMany and #ManyToOne - what`s the right way to remove child from collection without removing from database

Suppose I have two mapped entities, Field and Cluster. I would like to remove from Field one of mapped Clusters without deleting this Cluster from database. What`s the best way to do it?
Field.java
#Entity
#Table(name = "field")
public class Field extends Base {
...
#OneToMany(mappedBy = "field", fetch = FetchType.LAZY)
private List<Cluster> clusters = new ArrayList<>();
Cluster.java
#Entity
#Table(name = "cluster")
public class Cluster extends Base {
....
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "field_id")
private Field field;
Now I have to do something like that:
public FieldOutDto save(FieldInDto createRequest) {
Field newField = new Field();
modelMapper.map(createRequest, newField);
setClusters(newField, createRequest);
return modelMapper.map(fieldRepository.save(newField), FieldOutDto.class);
}
public FieldOutDto update(String id, FieldInDto updateRequest) {
Field fromDb = fieldRepository.findById(UUID.fromString(id)).orElseThrow(EntityNotFoundException::new);
modelMapper.map(updateRequest, fromDb);
clusterRepository.findAllByField_Id(UUID.fromString(id)).forEach(cluster -> cluster.setField(null));
setClusters(fromDb, updateRequest);
return modelMapper.map(fieldRepository.save(fromDb), FieldOutDto.class);
}
public void delete(String id) {
findById(id).getClusters()
.forEach(cluster -> cluster.setField(null));
fieldRepository.deleteById(UUID.fromString(id));
}
private void setClusters(Field field, FieldInDto request) {
if (request.getClustersUuid() != null && !request.getClustersUuid().isEmpty()) {
field.setClusters(request.getClustersUuid().stream()
.map(id -> {
Cluster cluster = clusterRepository.findById(UUID.fromString(id)).orElseThrow(EntityNotFoundException::new);
cluster.setField(field);
return cluster;
})
.collect(Collectors.toList()));
} else {
field.setClusters(Collections.EMPTY_LIST);
}
}
I try to find a way to get the same result without it in UPDATE
clusterRepository.findAllByField_Id(UUID.fromString(id)).forEach(cluster -> cluster.setField(null));
and in DELETE methods
findById(id).getClusters()
.forEach(cluster -> cluster.setField(null));
One of the ways to remove a child from collection without removing from the database is by creating utility methods to add or remove children(Clusters) in your Parent(Field) class as below.
#Entity
#Table(name = "field")
public class Field extends Base {
...
#OneToMany(mappedBy = "field", fetch = FetchType.LAZY)
private List<Cluster> clusters = new ArrayList<>();
public void addCluster(Cluster cluster) {
cluster.add(cluster);
cluster.setField(this);
}
public void removeCluster(Cluster cluster) {
cluster.remove(cluster);
cluster.setField(null);
}
}
Then you call these methods to add or remove a child(cluster) from the parent(Field) without deleting it from DB in advance.

Relationship Exists in neo4j but not in Spring #NodeEntity

I have a class in my domain called Activity that looks like the following
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
#NodeEntity
public class Activity {
#GraphId
private Long id;
private String title;
private String description;
#Relationship(type = "RELATED_TO", direction = Relationship.UNDIRECTED)
private List<Activity> relatedActivities = new ArrayList<>();
public Activity() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public String getDescription() {
return description;
}
public Collection<Activity> getRelatedActivities() {
System.out.println("getting relatedActivities");
System.out.println(relatedActivities);
return relatedActivities;
}
public void addRelatedActivity(Activity activity) {
this.relatedActivities.add(activity);
}
}
I create relationships using the following repository class:
#RepositoryRestResource(collectionResourceRel = "relationships", path = "relationships")
public interface RelationshipRepository extends GraphRepository<Relationship> {
#Query("MATCH (a1:Activity), (a2:Activity) " +
"WHERE a1.title = {0} AND a2.title = {1}" +
"CREATE (a1)-[:RELATED_TO]->(a2)")
void addRelationship(String a1Title, String a2Title);
}
I have verified that this code works using the neo4j browser, which lets me see existing nodes and relationships between them. However, when I access getRelatedActivities() on an Activity object, it's always an empty array, even if that Activity has other Activity nodes related to it, clearly visible in neo4j.
How can I get the relatedActivites on an Activity to automatically populate based on its relationships correctly?
The problem in your code is that you define the "target" as an Activity here
#Relationship(type = "RELATED_TO", direction = Relationship.UNDIRECTED)
private List<Activity> relatedActivities = new ArrayList<>();
but you also have a RelationshipEntity class in your code base: Relationship with the same type RELATED_TO.
When OGM gets the result it tries to match every field but since it converts the relationship type RELATED_TO to the RelationshipEntity and not an Activity object, it does not fill the list in the Activity class.

Hibernate One To Many join column not Saving

I am using Hibernate and Spring/ Spring MVC.
I have two entities, AccommodationRequest and Restriction.
The relationship is AccommodationRequest can have many Restriction.
When I save the AccommodationRequest with a Restriction, both the results are saved to the database
but the Restriction is missing the foreign key / join column (accommodationRequestId).
I am sure there is something I must be missing. Any help is appreciated.
AccommodationRequest is defined:
#Entity
public class AccommodationRequest {
....
#OneToMany(mappedBy="accommodationRequest",
targetEntity=Restriction.class,
fetch=FetchType.EAGER,
cascade=CascadeType.ALL)
private List<Restriction> restrictions;
public List<Restriction> getRestrictions(){
return restrictions;
}
public void setRestrictions(List<Restriction> restrictions){
this.restrictions = restrictions;
}
....
Restriction is defined:
#Entity
public class Restriction {
#Id
#GeneratedValue
private long id;
private String restriction;
#ManyToOne
#JoinColumn(name="accommodationRequestId")
private AccommodationRequest accommodationRequest;
public AccommodationRequest getAccommodationRequest() {
return accommodationRequest;
}
public void setAccommodationRequest(AccommodationRequest accommodationRequest) {
this.accommodationRequest = accommodationRequest;
}
And below is the code that does the saving:
Restriction restriction = new Restriction();
restriction.setRestriction("test restrcition 6");
List<Restriction> restrictions = new ArrayList<Restriction>();
restrictions.add(restriction);
long id = accommodationService.saveOrUpdate(accommodationRequest);
accommodationRequest.setRestrictions(restrictions);
accommodationService.saveOrUpdate(accommodationRequest);
You haven't set AccommodationRequest to your Restriction.
Restriction restriction = new Restriction();
restriction.setRestriction("test restrcition 6");
restriction.setAccommodationRequest(accommodationRequest); // You have to do this
List<Restriction> restrictions = new ArrayList<Restriction>();
restrictions.add(restriction);
long id = accommodationService.saveOrUpdate(accommodationRequest);
accommodationRequest.setRestrictions(restrictions);
accommodationService.saveOrUpdate(accommodationRequest);

Resources