Merging parent and lazy child collection list - java

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.

Related

Java Spring Data App doesn't save sub-objects

I'm trying to build build service, which saves object with sub-objects, but getting error. In result object data fields saved, but sub-object not.
I have the next object. The main is Order and sub-object is Partner:
#Getter
#Setter
#Entity
#Table(name = "orders")
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "order_id")
private int orderId;
#OneToMany(mappedBy = "order", fetch = FetchType.EAGER,
cascade = CascadeType.ALL)
private Set<Partner> partners;
}
#Getter
#Setter
#Entity
#Table(name = "partners")
public class Partner implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "partner_id")
private int id;
#ManyToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "order_id", nullable = false)
private Order order;
}
I use standard embedded method "save" from Spring Jpa Repository:
#Repository
public interface OrdersRepository extends JpaRepository<Order, Integer> {
}
and service, which call this Repository:
#Service
public class OrdersServiceImpl implements OrdersService {
#Autowired
private OrdersRepository repository;
#Override
public Order save(Order order) {
return repository.save(order);
}
}
Does someone have an idea why Partners are not saved?
Thanks a lot!
Because the relationship owner is Partner, so that you need to save the Order first. Or you can put cascade = CascadeType.PERSIST on private Order order;

How get Null value for child in OneToMany in Spring Boot JPA

I need to get simple List of Parent objects with null values for childs.
But when i use findAll() method and then try to get child object i get
LazyInitializationException: failed to lazily initialize a collection of role: ... could not initialize proxy - no Session
I see explanation of this problem (about Lazy/Eager, JOIN FETCH), but i need to get null for child objects without query for childs.
#Entity
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "parent")
Set<Child> childs;
#Entity
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "parent_id")
Parent parent;
#Service
public class ParentService {
#Autowired
ParentRepository parentRepository;
public List<Course> getParentList() {
return parentRepository.findAll();
}
In hibernate i get correct query:
select parent0_.id as id1_0_ from parent parent0_
Before test i add few parent entities in DB, result not empty, and on this test i get error LazyInitializationException
#Test
public void checkParentListFormat() {
List<Parent> parentList = parentService.getParentList();
Assertions.assertThat(parentList.get(0).getChilds()).isNull();
}
I read about DTO, but is it possible to get simple Entity with null values? Thanks
I think you need to put your lazy initialization in the parent annotation.
#OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
Set<Child> childs;

How to use Hibernate orphanRemoval with DtoMapping and #Audited

I have audited parent and child entity classes with a one-directional OneToMany mapping. I have DTO classes with the same fields for both (including id) and a Mapper that maps from DTO to entities.
If in the DTO class id field is set, the Mapper.map(ParentDto parentDto, Parent.class) method will not instantiate a new instance of Parent entity, but load the entity from database by its id and then map fields on the loaded entity. When mapping the childs Mapper.map(ChildDto parentDto, Child.class) method also tries to load the child from database by its id.
My problem is, that before mapping the childs list, the mapper clears the list -> orphanRemoval = true triggers and deletes the children from database. They then have to be recreated by the Mapper and persisted. To prevent this I use session.detach(parent) on the parent entity before mapping, thus preventing any change to the database and later reattach it by session.saveOrUpdate(parent). This leads to a NonUniqueObjectException
The Mapper works like this:
#Stateless
public class Mapper{
#PersistenceContext(unitName = "core")
private EntityManager em;
public void map(ParentDto parentDto, Parent parent) {
Session session = em.unwrap(Session.class);
if em.contains(parent) {
session.detach(parent); //detach so that orphanRemoval will not delete childs from DB
}
...
parent.getChilds().clear;
for (ChildDto childDto : parentDto.getChilds()) {
parent.getChilds().add(mapToEntity(childDto));
}
session.saveOrUpdate(parent); //(re-)attach, so that changed fields or new childs get written to DB
}
public Parent mapToEntity(ParentDto parentDto) {
Parent parentEntity= null;
if (parentDto.id != null) {
parentEntity= loadParentEntityFromDb(parentDto.id);
}
if (parentEntity= null) {
parentEntity= new Parent();
}
map(parentDto, parentEntity);
return parentEntity;
}
public Child mapToEntity(ChildDto childDto) {
Child childEntity= null;
if (childDto.id != null) {
childEntity= loadChildEntityFromDb(childDto.id);
}
if (childEntity= null) {
childEntity= new Child();
}
map(childDto, childEntity);
return childEntity;
}
}
Parent entity:
#Entity
#Audited
public class Parent implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
#Getter
private Long id;
...
#Getter
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinTable(name = "PARENT_TO_CHILD", joinColumns = #JoinColumn(name = "PARENT_ID", referencedColumnName = "ID"), inverseJoinColumns = #JoinColumn(name = "CHILD_ID", referencedColumnName = "ID"))
private final List<Child> childs = new ArrayList<>();
}
and Child entity
#Entity
#Audited
public class Child implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
#Getter
private Long id;
...
}
the Dto Classes:
public class ParentDto implements Serializable {
private static final long serialVersionUID = 1L;
#Getter
#Setter
private Long id;
...
#Getter
private final List<ChildDto> childs = new ArrayList<>();
}
public class ChildDto implements Serializable {
private static final long serialVersionUID = 1L;
#Getter
#Setter
private Long id;
...
}
With this setup during the mapping I then get
12:35:17,422 WARN [com.arjuna.ats.arjuna] (default task-5) ARJUNA012125: TwoPhaseCoordinator.beforeCompletion - failed for SynchronizationImple< 0:ffff0a00100f:4b2caaeb:5e4cf8b3:58b38, org.wildfly.transaction.client.AbstractTransaction$AssociatingSynchronization#1149eaa7 >: org.hibernate.NonUniqueObjectException: A different object with the same identifier value was already associated with the session : [PARENT_TO_CHILDS_AUD#{REV=Revision [id=98781, timestamp=1582112117394, username=RUser], Parent_id=885752, childs_id=885754}]
What does work, is if I use em.detach(parent) and then after mapping em.merge(parent). But merge returns a copy of parent that is persistent, the parent given to the mapper stays detached. I can not change the signature of the mapping method public void map(ParentDto parentDto, Parent parent), that is why I tried using session.saveOrUpdate(parent) in the first place, as this is supposed to only work on the given object and reattach that.
So is there a way to either get the #Audit annotation working with session.saveOrUpdate(), or to prevent hibernate to delete children (that are orphans for a fraction of a second) during the mapping without changing the clearing of the list before mapping?
I am using Hibernate 5.3.6.Final.

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;
}
}

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