Stop Spring MVC Fetch LazyLoad data - java

Created a simple spring boot project using SpringBoot 1.5.15.BUILD-SNAPSHOT with data-jpa and spring-mvc(web) with 2 entities.
Parent entity
#Entity
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Integer id;
#Column(name = "name")
private String name;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "parent_id")
private Collection<Child> children;
//Getter & Setter remove for brevity
}
Child entity
#Entity
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Integer id;
#Column(name = "name")
private String name;
}
Whenever an endpoint to fetch all Parent data is called, children data is also returned, yet, from my understanding by default fetchType is LazyLoading.
The following Spring Mvc rest code used to fetch data
#RestController
#RequestMapping("test")
public class Controller {
#Autowired
ParentRepository parentRepository;
#RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET)
public ResponseEntity<Collection<Parent>> findAll(HttpServletRequest request) {
List<Parent> parents = parentRepository.findAll();
return new ResponseEntity<>(parents, HttpStatus.OK);
}
}
What is expected and that is what should happen is only Parent data and not with Children data collection, should be fetched as they are lazyloaded.
How can i stop this undesired behavior of always eager loading children.
N.B: I tried setting fetchType to LazyLoad, though still when i call '/test', children data is also fetched

Finally i managed to stop Jackson from kindly pulling lazy-loaded data for me.
To do that just
Add the following maven dependency
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hibernate5</artifactId>
<version>2.9.6</version>
</dependency>
Then subclass ObjectMapper like so and register hibernate module depending on the hibernate version you are using.I'm using Hibernate 5, so i did this
public class HibernateAwareObjectMapper extends ObjectMapper {
public HibernateAwareObjectMapper() {
registerModule(new Hibernate5Module());
}
}
Finally add a Bean
#Bean
public ObjectMapper objectMapper(){
return new HibernateAwareObjectMapper();
}
With above code in-place, lazy-loaded attribute are not auto-initialized.

Related

Multi-level sub-menu JSON in Spring boot

I have a some records in the table having parent child relations, screenshot below:
How do I write a JPA Entity to retrieve those records with respect to Parent-Child relation. Your help is appreciated.
The code that did not help me well is as below:
#Entity
#Table(name = PlatformConstant.TABLE_MENU)
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Menu implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
#Column(name = "url", nullable = false)
private String url;
#Column(name = "description")
private String description;
#Column(name = "qr_code")
private Blob qrCode;
#JsonManagedReference
#OneToMany(mappedBy = "parent")
private Set<Menu> children;
#ManyToOne
#JsonBackReference
private Menu parent;
}
My code above has the following wrong output:
Using the JpaRepository find all, and applying the answer from #lucid, the new output is as below:
the code:
#Autowired
private MenuService menuService;
#CrossOrigin
#GetMapping("/all")
#ResponseBody
public List<Menu> getMenus() {
return (List<Menu>) menuService.findAll().stream()
.filter (menu-> Objects.isNull(menu.getParent()).collect(Collectors.toList()));
}
the output:
Thank you.
Jackson provides these annotations to control the parent-child relationships.
#JsonBackReference: skips property during the serialization process
#JsonManagedReference: forward reference and serialized annotated property
In your case, you don't want parent object to be serialized inside your child reference, you can annotate it with #JsonBackReference
#JsonManagedReference
#OneToMany(mappedBy = "parent")
private Set<Menu> children;
#ManyToOne
#JsonBackReference
private Menu parent;
Now, to remove child objects from response, we can filter that
Like this
menuService.findAll().stream()
.filter(menu-> Objects.isNull(menu.getParent()))
.collect(Collectors.toList());

Spring Data/Hibernate - Propagating Generated Keys

Usually I'm able to Google my way out of asking questions here (thank you SO community), but I'm a bit stuck here. This problem has to do with propagating generated keys to joined objects when calling JpaRepository.save()
We have entities that are defined like so:
Parent object
#Entity
#Table(name = "appointment")
public class Appointment implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "APPT_ID", columnDefinition = "integer")
private Long apptId;
...
#OneToMany(targetEntity = ApptReminder.class, mappedBy = "appointment", cascade = {
CascadeType.MERGE, CascadeType.PERSIST}, fetch = FetchType.EAGER)
#NotFound(action = NotFoundAction.IGNORE)
private List<ApptReminder> apptReminders = new ArrayList<>();
}
Child Object:
#Entity
#Table(name = "appt_reminder")
public class ApptReminder implements Serializable {
#EmbeddedId
private ReminderKey reminderKey = new ReminderKey();
...
#ManyToOne
#NotFound(action = NotFoundAction.IGNORE)
private Appointment appointment;
}
Embedded Id Class
#Embeddable
public class ReminderKey implements Serializable {
#Column(name = "APPT_ID", columnDefinition = "integer")
private Long apptId;
#Column(name = "CALL_NUM", columnDefinition = "integer")
private Short callNum;
....
}
Repository:
public interface AppointmentRepository extends JpaRepository<Appointment, Long> {
}
And we have a bunch of sets of objects hanging off of the child object all sharing the embedded key attributes. When we call save on the parent object appointmentRepository.save(appointment) the child objects get saved, but the appt_id of the first appointment inserted gets an auto generated key of 1, and the first apptReminder record gets an appt_id of 0.
This affects all joined objects that share the embedded ID of ReminderKey with similar and predictable effects.
When we call appoitnmentRepository.save(appointment) on the top level entity, how do we get the autogenerated keys to propagate through to child entities? I feel like this should be very easy. Perhaps there's an element of the way I laid out the mappings or the usage of an embedded id that's preventing this from working.
One last thing of note is that this is running against an H2 database while in development, but will be used against MySQL afterwards. This could be attributable to H2's MySQL compatibility
I think you need to use JoinColumns annotation to marry Appointment apptId to ReminderKey apptId.
Solved this way:
Detach appointment from apptReminder on persist operations:
public class Appointment implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "APPT_ID", columnDefinition = "integer")
private Long apptId;
...
#OneToMany(targetEntity = ApptReminder.class, mappedBy = "appointment", cascade = CascadeType.DETACH, fetch = FetchType.EAGER)
#NotFound(action = NotFoundAction.IGNORE)
private List<ApptReminder> apptReminders = new ArrayList<>();
}
Create a DAO to handle persistence operations:
#Repository
public class AppointmentDAO {
#Autowired
private AppointmentRepository appointmentRepository;
#Autowired
private ApptReminderRepository apptReminderRepository;
public List<Appointment> save(List<Appointment> appointments) {
appointments.forEach(a -> this.save(a));
return appointments;
}
public Appointment save(Appointment appointment) {
final Appointment appt = appointmentRepository.save(appointment);
List<ApptReminder> apptReminders = appointment.getApptReminders();
apptReminders.forEach(a -> {
a.getReminderKey().setApptId(appt.getApptId());
a.getReminderTags().forEach(t -> t.setApptId(appt.getApptId()));
a.getReminderMessages()
.forEach(m -> m.getReminderMessageKey().setApptId(appt.getApptId()));
a.getMsgQueueReminder().setApptId(appt.getApptId());
});
apptReminderRepository.saveAll(apptReminders);
return appointment;
}
}

Circular reference in entity JPA with Jackson and Spring Boot

I have two web services: "Proprietario" and "Veiculo", the "Proprietario" contains a list of "Veiculo" and "Veiculo" contains a "Proprietario".
The problem is that when I make a request calling the findAll method of "Proprietario", when trying to serialize, Jackson goes into infinite loop throwing exception. The same happens when I try to call the findAll method of "Veiculo".
I would like it when I call you to call the findAll of the "Veiculo", bring along the "Proprietario", but do not bring the "Veiculo" list inside the "Proprietario". The opposite of when I call the findAll method of "Proprietario", I'd like to bring the "Veiculo" list, but do not bring the "Proprietario" into the "Veiculo".
I tried to use some Jackson annotations, but none solves the conflict on both sides.
#Getter
#Setter
#Entity
#EqualsAndHashCode(of = "id")
public class Veiculo {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
#Column(length = 10)
private String placa;
#Column(nullable = false)
private Integer ano;
#ManyToOne
#JoinColumn
private Proprietario proprietario;
}
#Getter
#Setter
#Entity
#EqualsAndHashCode(of = "id")
public class Veiculo {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
#Column(length = 10)
private String placa;
#Column(nullable = false)
private Integer ano;
#ManyToOne
#JoinColumn
private Proprietario proprietario;
}
Try using these two annotations
#JsonManagedReference and #JsonBackReference
see http://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion

Merging parent and lazy child collection list

1 parent entity may have 0 or multiple lazy child entities
For example, there is a function changing the status column in parent and child entities, while merge(parent), parent entity is updated but child entities are insert new instead of update.
Both the child entities id, data are exactly the same as in database while debugging.
The parent object is put in #SessionAttributes in spring controller, would it be the reason?
Even I merge only the child list, merge(childList), it create new records instead of update also.
#Entity
#Table(name = "member")
public class Member implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private int id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "memberParent", cascade = CascadeType.ALL)
public List<Child> ChildList
getter setter......
}
#Entity
#Table(name = "child")
public class Child implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private int id;
#Column(name="member_id")
private int mem_id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumns({
#JoinColumn(name = "member_id", referencedColumnName = "id", insertable = false, updatable = false)
})
public Member memberParent;
getter setter......
}
//Controller
#SessionAttributes({"member"})
public class Appcontroller {
#Transactional
#RequestMapping(value = {"/update-member/{id}"}, method = RequestMethod.GET)
public String viewEditRepresetative(ModelMap model, #PathVariable ind id) {
Member member = memberService.find(id);
model.addAttributes("member", member);
}
#Transactional
#RequestMapping(value = {"/update-member"}, method = RequestMethod.POST)
public String viewEditRepresetative(ModelMap model, HttpServletRequest reques, #Valid #ModelAttribute("member") Member member, BindingResult result,
RedirectAttributes redirectAttributes, SessionStatus status) {
if (!result.hasErrors()) {
memberService.merge(member);
}
}
I can't see any parent child relationship in your snapshot code.
Please amend the code for child class with below code to create the inheritance relationship.
public class Child extends Member implements Serializable{
Extending the Child class to the Parent(Member) will reflect the required changes related to lazy loading.

JsonView - define Default View

I am working on a Spring boot (MVC, JPA) application and it is required to return different attributes on different requests. I found the #JsonView annotation and it seems to work. But do I need to annotate every attribute with a basic view?
Example:
Entity1
#Entity
public class Entity1 implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#JsonView(JsonViews.ExtendedView.class)
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "entity1", fetch = FetchType.EAGER)
List<Entity2> entities2;
#JsonView(JsonView.ExtendedView.class)
#OneToMany(cascade = CascadeType.ALL, mappedBy = "entity1", fetch = FetchType.LAZY)
List<Entity3> entities3;
}
Entity2
#Entity
public class Entity2 implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
Entity3
#Entity
public class Entity3 implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
Views
public class JsonViews {
public static class BasicView { }
public static class ExtendedView extends BasicView { }
}
Controller
#RequestMapping(method = RequestMethod.GET)
#JsonView(JsonViews.BasicView.class)
public #ResponseBody List<Entity1> index() {
return repositoryEntity1.findAll();
}
This is a trimmed example but I think it applies to the problem. I expect that the controller returns the Ids and the list of Entity2 objects. But it returns an empty object with "No Properties". If I annotate every attribute of every class involved in this request, it seems to work, but is this really needed or the best solution? Is there a way to define a "DefaultView"?
thanks
Edit: If I annotate the JpaRepository it returns the entire object including the list with Entity3 objects.
No, you do not need to define views on all properties. Insert
spring.jackson.mapper.default-view-inclusion=true
in your application.properties. This will cause properties without the #JsonView annotation to be included in the response and only the annotated properties will be filtered.
In your Controller, properties without a view or with the BasicView annotated will be returned.

Categories