Hibernate doesn't insert values into EmbeddedId with mappings - java

I have a ClassA entity, which contains set of ClassB entities.
public class ClassA {
#Id
#Column(name = "id")
#EqualsAndHashCode.Include
private Long id;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "classA")
#Cascade({org.hibernate.annotations.CascadeType.MERGE, org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.REFRESH, org.hibernate.annotations.CascadeType.DETACH})
private Set<ClassB> classB = new HashSet<>();
#PrePersist
#PreUpdate
void setClassA() {
if (nonNull(this.classB)) {
this.classB.forEach(b -> b.setA(this));
}
}
}
ClassB entity has composite key, which points to two other entities' ids.
#Embeddable
public class ClassBId implements Serializable {
private String cId;
private Long aId;
}
public class ClassB {
#AttributeOverrides({
#AttributeOverride(name = "c", column = #Column(name = "c_id")),
#AttributeOverride(name = "a", column = #Column(name = "a_id"))
})
#EmbeddedId
private ClassBId id;
#OneToOne
#Cascade({org.hibernate.annotations.CascadeType.MERGE, org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.REFRESH, org.hibernate.annotations.CascadeType.DETACH})
#JoinColumn(name = "c_id", referencedColumnName = "id")
#MapsId("cId")
private ClassC classC;
#ManyToOne
#Cascade({org.hibernate.annotations.CascadeType.MERGE, org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.REFRESH, org.hibernate.annotations.CascadeType.DETACH})
#JoinColumn(name = "a_id", referencedColumnName = "id")
#MapsId("aId")
private ClassA classA;
#PrePersist
#PreUpdate
private void setEmbeddedId() {
id.setCId(c.getId());
id.setAId(a.getId());
}
}
When I save my ClassA entity and CrudRepository returns me a result, everything is nicely filled in but that embedded id remains null, with or without setting id manually in pre-operation method.
What kind of join or mapping am I missing so on save or update I can not only have ClassC and ClassA inside of ClassB entity (which takes place already) but also somehow have that composite key consisting of PKs of ClassA and ClassC automagically filled in?

I think you have to assign the id to at least an empty object for this to work:
#EmbeddedId
private ClassBId id = new ClassBId();

Related

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

Hibernate not finding map property

So I am getting the exception:
org.hibernate.AnnotationException: mappedBy reference an unknown target entity property: se.mulander.cosmos.movies.model.Cast.starredIn in se.mulander.cosmos.movies.model.ExtendedMovie.cast
But I can't really figure out why.
The two objects that I am going to map are:
#Entity
#Table(name = "cast")
#ApiModel(description = "A cast member that has been part of making the movie")
public class Cast
{
#JsonIgnore
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.ALL})
#JoinColumn(name = "movie_id")
public ExtendedMovie starredIn;
}
and
#Entity
#Table(name = "extended_movie")
public class ExtendedMovie
{
#OneToMany(cascade = {CascadeType.ALL}, mappedBy = "starredIn", orphanRemoval = true)
#LazyCollection(LazyCollectionOption.FALSE)
public List<Cast> cast = new ArrayList<>();
}
I have stripped them of some other properties, but in essence this is the relationship that is not working.
So what I don't get is why it says that it is an unknown property, as the property is public and hibernate shouldn't have any problems mapping it.
what is it that I am missing here?
Try something like:
ExtendedMovie :
#Entity
public class ExtendedMovie implements Serializable {
private static final long serialVersionUID = 6771189878622264738L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
#JoinColumn(name = "cast_id", referencedColumnName = "id")
private Set<Cast> cast;
public Set<Cast> getCast() {
return cast;
}
public void setCast(Set<Cast> cast) {
this.cast= cast;
}
}
Cast:
#Entity
#ApiModel(description = "A cast member that has been part of making the movie")
public class Cast implements Serializable {
private static final long serialVersionUID = 6771189878622265738L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
//Remove extendedmovie from here
//other property getter and setters here
}
This will establish a one-to-many relationship between ExtendedMovie and Cast.

Spring Data Rest Projection to get value from different entity

I have two entities. One is the parent (for which I have a projection) and the other entity has FK relation with the parent. My parent entity does "not" have bidirectional relationship (mappedBy).
How do I expose/get the child entity in the projection I have for the parent.
Here is how I want.
Parent:
public class EntityA implements java.io.Serializable {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "ENTITYAID", unique = true, nullable = false)
private Integer entityAID;
......
}
Child:
public class EntityB {
#EmbeddedId
private EntityBPk entityBPk;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ENTITYAID", referencedColumnName = "ENTITYAID",insertable=false,updatable=false)
private EntityA entityA;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ENTITYCID", referencedColumnName = "ENTITYCID",insertable=false,updatable=false)
private EntityC entityC;
#Column(name = "DUMMY")
private String dummy;
}
I want to access EntityB from EntityA's projection. Something like below.
Projection for EntityA:
#Projection(name = "projEntityA", types = { EntityA.class })
public interface EntityAProjection {
.....
Set<EntityB> getEntityBs();
}
Note:
But I don't have any reference of EntityB inside EntityA (since I don't want bidirectional relationship)
This may not be needed. But just FYI.
public class EntityBPk implements Serializable {
#Column(name = "ENTITYAID", nullable = false)
private Integer entityAID;
#Column(name = "ENTITYCID", nullable = false)
private Integer entityCId;
}
Thanks
Bharath

JPA/Hibernate fetch

I have 2 classes with OneToMany one-directional relationship.
class A {
#Column(name = "a_id")
Integer id;
#OneToMany
#JoinColumn(name = "a_id")
private List<B> listOfB;
//getters and setters
}
class B {
Integer id;
String name;
#Column("a_id")
Integer aId;
//getters and setters
}
In database I already have saved instance of B.
I need to do:
a.listOfB.add(b);
save(a);
In object b in database I have id and name, but fk is null. I need to update fk, but before it I need to fetch object b;
How can I do it?
Only by writing custom method, or Hibernate/JPA have its own method to fetch objects?
Your mapping is not very common. Common way to have #JoinColumn in B and mappedBy = "a" in A. And you can specify cascade. In the simplest case cascade = CascadeType.ALL.
class A {
#Column(name = "a_id")
Integer id;
#OneToMany(mappedBy = "a", cascade = CascadeType.ALL)
private List<B> listOfB = new ArrayList<B>;
public void addB(B b) {
b.setA(this);
listOfB.add(b);
}
}
class B {
Integer id;
String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "a_id")
private A a;
}
To solve your issue
save(a);
b.setA(a);
saveOrUpdate(b);
And cascade will help you to do the same by almost your way with a help of addB() method
a.addB(b);
save(a);

JPA Mapping - Unique combination of parents per child

I am using:
Spring 3.2
Hibernate 4.1.9
I need to map, with JPA, three classes. Class A has a ManyToMany relationship with Class B. A unique combination of Class A and Class B need to own a collection of Class C.
Table A
foo
id | name
Table B
bar
id | name
Table C
data
id | xrefId
Join Table -- Unique Key on (fooId,barId)
xref
id | fooId | barId
Altering the existing data structure is not an option.
Edit 1:
Goal: Load a Foo, get its collection of Bars. From each Bar, get its (their!) collection of Data.
Class A
#Entity
public class Foo {
#Id
private UUID id;
#ManyToMany(optional = false)
#JoinTable(name = "xref",
joinColumns = { #JoinColumn(name = "fooId") },
inverseJoinColumns = { #JoinColumn(name = "barId") })
private List<Bar> lstBar = new ArrayList<Bar>();
}
Class B
public class Bar {
#Id
private UUID id;
#ManyToMany(mappedBy = "lstBar")
private List<Foo> lstFoo = new ArrayList<Foo>();
}
Class C
public class Data {
#Id
private UUID id;
}
Just KISS. Make another class Xref, which contains id, foo, bar and Set<Data> fields. Make a DAO method to find an Xref using two parameters foo and bar (implement it with a simple HQL). The unique requirement could be achieved by an unique constraint in the database.
It doesn't look good trying to express it just by the class hierarchy, better to use DAOs.
Your join table, xref, has an extra id field, in order to be able to create such a table with JPA you need an extra entity class XRef and then you have to map the relation between A and XRef and betweem B and XRef (both are one-to-many). Then, you can create the entity class C and map the relation between C and XRef. Do you need more help? I don't have time right now to provide the code, but if you need ask and I will try to add it as soon as possible.
Look at this example (used Integer instead of UUID for simplicity, the rest should be OK).
Bar class:
public class Bar {
#Id
private Integer id;
#Column(name = "name")
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "barId")
private Collection<Xref> xrefCollection;
}
Foo class:
public class Foo {
#Id
private Integer id;
#Column(name = "name")
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "fooId")
private Collection<Xref> xrefCollection;
}
Xref class:
public class Xref {
#Id
private Integer id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "xrefId")
private Collection<Data> dataCollection;
#JoinColumn(name = "bar_id", referencedColumnName = "id")
#ManyToOne(optional = false)
private Bar barId;
#JoinColumn(name = "foo_id", referencedColumnName = "id")
#ManyToOne(optional = false)
private Foo fooId;
}
Data Class:
public class Data {
#Id
private Integer id;
#JoinColumn(name = "xref_id", referencedColumnName = "id")
#ManyToOne(optional = false)
private Xref xrefId;
}
This code has been automatically generated by NetBeans, provided that all tables and indexes are correctly defined in the DB

Categories