Many to Many in JPA and EBean - java

I'm trying to make a many to many relationship using EBean in Play2 and I have an issue where EBean trows an error saying my class is not registered.
Heres my mapping classes:
#Entity
public class Booking extends Model {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
//... other fields
}
#Entity
public class Store extends Model {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
//... other fields
}
#Embeddable
public class ComissionId {
#ManyToOne(targetEntity = Booking.class, optional = false)
#PrimaryKeyJoinColumn(name = "booking_id", referencedColumnName = "id")
private Booking booking;
#ManyToOne(targetEntity = Store.class, optional = false)
#PrimaryKeyJoinColumn(name = "store_id", referencedColumnName = "id")
private Store store;
}
#Entity
#AssociationOverrides({
#AssociationOverride(name = "id.booking", joinColumns = #JoinColumn(name = "booking_id")),
#AssociationOverride(name = "id.store", joinColumns = #JoinColumn(name = "store_id"))
})
public class StoreComission extends Model {
#EmbeddedId
private ComissionId id;
#Column(nullable = false)
private double value;
#Column(nullable = false)
private Date date;
}
The error:
java.lang.RuntimeException:
Error reading annotations for models.ids.ComissionId
Caused by: java.lang.RuntimeException:
Error with association to [class models.Booking] from [models.ids.ComissionId.booking].
Is class models.Booking registered?
In my application.conf I've put ebean.default="models.*" so all this classes should be registered right? (I've tried to move the ComissionId from the package models.ids to models, but the same error ocurred)

You have this error because of #ManyToOne annotations inside your ComissionId class. To make this code work you have to move these relations to StoreComission class. In ComissionId class you should leave only identifiers. In StoreComission class #ManyToOne relation are mapped to the same columns as fields from composite key. But they have attributes 'insertable' and 'updateable' set to false to prevent column duplication.
Here is corrected working code for above scenario:
StoreComission class:
#Entity
public class StoreComission extends Model {
public StoreComission() {
id = new ComissionId();
}
#EmbeddedId
private ComissionId id;
#Column(nullable = false)
public double value;
#Column(nullable = false)
public Date date;
#ManyToOne
#JoinColumn(name = "booking_id", insertable = false, updatable = false)
private Booking booking;
#ManyToOne
#JoinColumn(name = "store_id", insertable = false, updatable = false)
private Store store;
public void setBooking(Booking aBooking) {
booking = aBooking;
id.booking_id = aBooking.id;
}
public Booking getBooking() {
return booking;
}
public void setStore(Store aStore) {
store = aStore;
id.store_id = aStore.id;
}
public Store getStore() {
return store;
}
}
ComissionId class:
#Embeddable
public class ComissionId {
public int booking_id;
public int store_id;
#Override
public int hashCode() {
return booking_id + store_id;
}
#Override
public boolean equals(final Object obj) {
return super.equals(obj);
}
}

Related

how to save entities in a relation to database in spring boot

I have a spring boot application with two entities in a relationship. MeetingSetting and MeetingTime meetingSetting can have unlimited meetingTimes. So far the databases are generating without problem, but When I try to save my Entity they are saved but different from each other, they are saved independently. Meaning MeetingName which is a foreign key inside MeetingTime is not saved but seen as null (I debugged and tried finding out why but could not find anything) THe other values are saved-
could someone point me out what my error is?
this is the json I am sending:
{
"meetingName":"TEst",
"meetingPw":"",
"meetingTime":[
{
"date":"2021-05-31",
"startTime":"15:30",
"endTime":"16:30"
},
{
"date":"2021-06-21",
"startTime":"15:30",
"endTime":"17:30"
},
{
"date":"2021-06-21",
"startTime":"11:01",
"endTime":"11:01"
}
]
}
MeetingSettings:
#Entity
#Table(name = "meeting_settings")
#Data
public class MeetingsSetting {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "meeting_name", unique = true)
private String meetingName;
#Column(name = "meeting_url")
private String meetingUrl;
#Column(name = "meeting_pw")
private String meetingPw;
#OneToMany(mappedBy = "meeting_Name", cascade = CascadeType.ALL)
private Set<MeetingTime> meetingTime = new HashSet<>();
}
MeetingTime:
#Entity
#Table(name = "meeting_times")
#Data
public class MeetingTime {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "meeting_date")
private String date;
#Column(name = "start_time")
private String startTime;
#Column(name = "end_time")
private String endTime;
#ManyToOne
#JoinColumn(name = "meeting_name" ,insertable = false, updatable = false , referencedColumnName = "meeting_name")
private MeetingsSetting meeting_Name;
}
this is how I try to save the entity:
#RestController
#RequestMapping("/api/meetingSetting")
public class MeetingSettingController {
#Autowired
MeetingSettingService meetingSettingService;
#PostMapping("/")
public void saveMeeting(#RequestBody MeetingsSetting meetingsSetting){
meetingSettingService.saveMeeting(meetingsSetting);
}
}
My service calls the save method of an jpaRepository.
In a bi-directional One to Many, you have to synchronize both sides of the association.
You can simply iterate over all MeetingTime objects and set the corresponding MeetingSetting to it.
Your MeetingSettingService's saveMeeting method could do this:
public void saveMeeting(MeetingsSetting meetingsSetting) {
// ...
// here you're synchronizing both sides of the association
meetingsSetting.getMeetingTime()
.forEach(mt -> mt.setMeetingSetting(meetingSetting));
// ...
repository.save(meetingSetting);
}
Solution to my question, I am not sure if this is a good or correct way of solving this maybe someone can advice me a better solution:
#Entity
#Table(name = "meeting_times")
#Data
public class MeetingTime implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "meeting_date")
private String date;
#Column(name = "start_time")
private String startTime;
#Column(name = "meeting_name")
private String meeting_name;
THIS IS THE PART WHICH IS CALLED FROM THE METHOD INSIDE MEETINGSCONTROLLER
#Column(name = "end_time")
private String endTime;
#ManyToOne
#JoinColumn(name = "meeting_name" ,insertable = false, updatable = false, referencedColumnName = "meeting_name")
private MeetingsSetting meetingName;
}
MeetingsTime Entity:
#RestController
#RequestMapping("/api/meetingSetting")
public class MeetingSettingController {
#Autowired
MeetingSettingService meetingSettingService;
#PostMapping("/")
public void saveMeeting(#RequestBody MeetingsSetting meetingsSetting){
meetingsSetting.getMeetingTime()
.forEach(mt -> mt.setMeeting_name(meetingsSetting.getMeetingName()));
// ...
meetingSettingService.saveMeeting(meetingsSetting);
}
}

JPA populate a composite key id part from parent entity

Let's say I have those two entities, Person & Insurance. One Person can have multiple insurances, and the insurance uniqueness is maintained by a composite key combination of (insurance type, policy number, and person id). The below code represent the the scenario...
parent class
#Entity
#Setter
#Getter
#RequiredArgsConstructor
public class Person implements Serializable {
#Id
#GeneratedValue(strategy = "GenerationType.IDENTITY")
#Column(name "person_id")
private Long personId;
#Column(name = "fst_nm")
private String fstName;
#Column(name = "lst_nm")
private String lstNm;
// ..Other columns & relationships
#OneToMany(mappedBy = "person")
private List<Insurance> insurances;
public void addInsurance(Insurance toAdd) {
getInsurances().add(toAdd);
toAdd.setPerson(this);
}
}
child class
#Entity
#Setter
#Getter
#RequiredArgsConstructor
public class Insurance implements Serializable {
#EmbeddedId
private insurancePK id;
//other data
#ManyToOne
#MapsId("personId")
private Person person;
}
composite PK class
#Setter
#Getter
#Embeddable
public class InsurancePK implements Serializable {
#Column(name = "person_id", insertable = false, updatable = false)
private Long personId;
#Column(name = "insurance_type")
private String insuranceType;
#Column(name = "pol_num")
private String polNum;
}
now, my data mapper looks something like that...
Person newPerson = new Person();
newPerson.setInsurances(new ArrayList<>());
// fill out Person Model data
// incoming insurance data
while (incomingData.hasNext()) {
Insurance insuranceData = new Insurance();
InsurancePK pk = new InsurancePK();
// set other insurance data
pk.setInsuranceType("Dental");
pk.setPolNum("123Abc00");
insuranceData.setId(pk);
person.addInsurance(insuranceData);
}
Problem is my person_id inside the composite key is always getting a null value, not sure why (shouldn't the #MapsId takes care of that value)?
I need to fetch that value dynamically, most of the JPA composite key solutions only are setting all the value manually, but that's not my scenario.
return object from saveAndflush()
{
person: {
person_id: 55,
fst_nm: blah,
lst_nm: blah,
insurances: [
{
insurance_pk: {
person_id: null,
insurance_type: "Dental",
pol_num: "123Abc00"
}
//other insurance data
}
]
}
}
any suggestions on what am I missing? Thank you in advance!
Remove the #Column(name = "person_id", insertable = false, updatable = false) annotation from the InsurancePK.personId.
Add the following annotation:
#JoinColumn(name = "name = "person_id"")
to the Insurance.person.
As mentioned in the comments, adding a cascade to my entity column started me on the right track.
just in case, that's the model that worked for me after couple of tries
Parent class
#Entity
#Setter
#Getter
#RequiredArgsConstructor
public class Person implements Serializable {
#Id
#GeneratedValue(strategy = "GenerationType.IDENTITY")
#Column(name "person_id")
private Long personId;
#Column(name = "fst_nm")
private String fstName;
#Column(name = "lst_nm")
private String lstNm;
// ..Other columns & relationships
// cascade added <-- thanks to SternK
#OneToMany(mappedBy = "person", casecade = CascadeType.ALL, orphanRemoval = true)
private List<Insurance> insurances;
public void addInsurance(Insurance toAdd) {
getInsurances().add(toAdd);
toAdd.setPerson(this);
}
}
Child class
#Entity
#Setter
#Getter
#RequiredArgsConstructor
public class Insurance implements Serializable {
#EmbeddedId
private insurancePK id;
//other data
#ManyToOne
#MapsId("personId")
// annotation added here instead of PK class <-- fix
#JoinColumn(name="person_id", nullable = false, insertable = false, updatable = false)
private Person person;
}
PK class
#Setter
#Getter
#Embeddable
public class InsurancePK implements Serializable {
//annotation removed <-- fix thanks to SternK
private Long personId;
#Column(name = "insurance_type")
private String insuranceType;
#Column(name = "pol_num")
private String polNum;
}

DuplicateMappingException Table [] contains physical column name [] referred to by multiple logical column names: [_id], [Id]

I am rewriting a project and I have a build issue.
Caused by: org.hibernate.DuplicateMappingException: Table [language] contains physical column name [person_id] referred to by multiple logical column names: [person_id], [personId]
where
#Builder
#Data
#AllArgsConstructor
#NoArgsConstructor
#Entity
#IdClass(LanguagePK.class)
#Table(name = "language")
public class Language {
#Id
#Column(name = "person_id")
private int personId;
#Id
#Column(name = "position")
private int position;
#Id
#Column(name = "language_name")
private String languageNameVal;
#Column(name = "language_level")
private String languageLevelVal;
#ManyToOne
#JoinColumn(name = "person_id", referencedColumnName = "id", nullable = false, insertable = false,updatable = false)
#JsonBackReference(value = "languages")
private Person person;
#ManyToOne
#JoinColumn(name = "language_name", referencedColumnName = "name", nullable = false, insertable = false,updatable = false)
#JsonIgnore
private LanguageName languageName;
#ManyToOne
#JoinColumn(name = "language_level", referencedColumnName = "level", nullable = false, insertable = false,updatable = false)
#JsonIgnore
private LanguageLevel languageLevel;
}
and
#Builder
#Data
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "person")
public class Person implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#JsonManagedReference(value = "languages")
#OneToMany(mappedBy = "person", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private Set<Language> languages;
I use
hibernate v. 5.4.13.Final
spring-boot v. 2.2.5.RELEASE
gradle
java 1.8
lombok
The changes are very minor like before I was using maven, SessionFactory for hibernate, traditional getters and setter.
I saw some similar issues where NamingStrategy was a problem during migration between hibernate 4 and 5, but this code works on the first project where hiberante is 5.4.10.Final.
I like IdClass because I have access to personId as primitive value and don't have to load data from object person.
I saw similar issues without insertable = false,updatable = false but I have it...
Simmilar issue found here
The solution
The issue was on the access strategy field vs property access.
In the previous project, everything was with property-based access, but after I have added lombok the class Language was field-based access.
The class LanguagePK was
public class LanguagePK implements Serializable {
private static final long serialVersionUID = 1L;
private int personId;
private int position;
private String languageNameVal;
#Column(name = "person_id")
#Id
public int getPersonId() {
return personId;
}
public void setPersonId(int personId) {
this.personId = personId;
}
#Column(name = "position")
#Id
public int getPosition() {
return position;
}
public void setPosition(int position) {
this.position = position;
}
#Column(name = "language_name")
#Id
public String getLanguageNameVal() {
return languageNameVal;
}
public void setLanguageNameVal(String languageNameVal) {
this.languageNameVal = languageNameVal;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
LanguagePK that = (LanguagePK) o;
if (personId != that.personId) return false;
if (position != that.position) return false;
if (languageNameVal != null ? !languageNameVal.equals(that.languageNameVal) : that.languageNameVal != null)
return false;
return true;
}
#Override
public int hashCode() {
int result = personId;
result = 31 * result + position;
result = 31 * result + (languageNameVal != null ? languageNameVal.hashCode() : 0);
return result;
}
}
It's enough and works
#EqualsAndHashCode
public class EducationPK implements Serializable {
private int personId;
private int position;
}

Can't save entity Embeddable Id

I have EcranChamp entity
#Entity
#IdClass(EcranChampId.class)
public class EcranChamp {
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ecran")
Ecran ecran;
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "champ")
Champ champ;
...
And EcranChampId
#Embeddable
public class EcranChampId implements Serializable {
private Champ champ;
private Ecran ecran;
...
Every time i am trying to save an EcranChamp element i have this following error
2018-09-25 12:15:42.889 WARN 14216 --- [nio-8092-exec-8] .w.s.m.s.DefaultHandlerExceptionResolver : Failed to convert request element: org.springframework.beans.ConversionNotSupportedException: Failed to convert property value of type 'java.lang.Long' to required type 'com.kepler.portailclient.domain.model.Ecran' for property 'ecran'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.Long' to required type 'com.kepler.portailclient.domain.model.Ecran' for property 'ecran': no matching editors or conversion strategy found
2018-09-25 12:15:42.889 WARN 14216 --- [nio-8092-exec-8] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved exception caused by Handler execution: org.springframework.beans.ConversionNotSupportedException: Failed to convert property value of type 'java.lang.Long' to required type 'com.kepler.portailclient.domain.model.Ecran' for property 'ecran'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.Long' to required type 'com.kepler.portailclient.domain.model.Ecran' for property 'ecran': no matching editors or conversion strategy found
Try something like this:
#Entity
#Table(name = "<entity name>")
public class EcranChamp {
#EmbeddedId
#AttributeOverrides({ #AttributeOverride(name = "id_ecran", column = #Column(name =
"<column name>", nullable = false)),
#AttributeOverride(name = "id_champ", column = #Column(name = "<column name>", nullable = false)) })
EcranChampId id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "id_ecran")
Ecran ecran;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "id_champ")
Champ champ;
//getters & setters
}
#Embeddable
public class EcranChampId implements Serializable {
#Column(name = "id_champ", nullable = false)
private Long id_champ;
#Column(name = "id_ecran", nullable = false)
private Long id_ecran;
//getters & setters
}
You shoud use #EmbeddedId annotation.
Please, change your EcranChampId class to this:
#Embeddable
public class EcranChampId implements Serializable {
#ManyToOne
private Champ champ;
#ManyToOne
private Ecran ecran;
//getters and setters
}
And change your EcranChamp class to this:
#Entity
#Table(name = "champ_has_ecran_table_name")
#AssociationOverrides({
#AssociationOverride(name = "pk.champ", joinColumns = #JoinColumn(name = "champ_id"))
#AssociationOverride(name = "pk.ecran", joinColumns = #JoinColumn(name = "ecran_id"))
})
public class EcranChamp {
#EmbeddedId
private EcranChampId pk;
public EcranChamp() {
pk = new EcranChampId();
}
public EcranChampId getPk() {
return pk;
}
public void setPk(EcranChampId pk) {
this.pk = pk;
}
#Transient
public Champ getChamp() {
return pk.getChamp();
}
public void setChamp(Champ champ) {
pk.setChamp(champ);
}
#Transient
public Ecran getEcran() {
return pk.getEcran();
}
public void setChamp(Ecran ecran) {
pk.setEcran(ecran);
}
}
And use it like this:
public class Champ {
#OneToMany(mappedBy = "pk.champ")
private Collection<EcranChamp> ecranChamps;
//getters and setters
}
Also, if EcranChamp or EcranChampId has no other fields i will recommend you to use #ManyToMany annotation instead EcranChamp class like this:
public class Champ {
#ManyToMany
#JoinTable(
name = "champ_has_ecran_table_name",
joinColumns = #JoinColumn(name = "champ_id", referencedColumnName = "id", nullable = false),
inverseJoinColumns = #JoinColumn(name = "ecran_id", referencedColumnName = "id", nullable = false)
)
private Collection<Ecran> ecrans;
//getters and setters
}

How to Sort based on embedded Property and limit the no of results with Specifications?

I am trying to understand and figure out the solution for the following use case
These are my entity classes
User
#Entity
#Table(name = "USER")
public class User {
private UserID id;
private Set<UserAddress> addresses = new HashSet<UserAddress>(0);
#EmbeddedId
#AttributeOverrides( {
#AttributeOverride(name = "userId", column = #Column(name = "USER_ID", nullable = false, length = 32)),
#AttributeOverride(name = "userType", column = #Column(name = "USER_TYPE", nullable = false, precision = 12, scale = 0)) })
public User getId() {
return this.id;
}
#OneToMany(fetch = FetchType.EAGER, mappedBy = "user", cascade={CascadeType.ALL})
#BatchSize(size=50)
public Set<UserAddress> getAddresses() {
return this.addresses;
}
........
}
UserAddress
#Entity
#Table(name = "USERADDRESS")
public class UserAddress {
private UserID id;
Private User user;
private String address;
#EmbeddedId
#AttributeOverrides( {
#AttributeOverride(name = "userId", column = #Column(name = "USER_ID", nullable = false, length = 32)),
#AttributeOverride(name = "userType", column = #Column(name = "USER_TYPE", nullable = false, precision = 12, scale = 0)) })
public User getId() {
return this.id;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns( {
#JoinColumn(name = "userId", referencedColumnName = "USER_ID", nullable = false, insertable=false, updatable=false),
#JoinColumn(name = "userType", referencedColumnName = "USER_TYPE", nullable = false, insertable=false, updatable=false) })
public User getUser() {
return this.user;
}
........
}
UserId
#Embeddable
public class UserId implements Serializable {
private String userNo;
private Long UserType;
.......
.......
}
I have created a staticmetamodel class for User, UserID and UserAddress and created query based on Specifications.
Metamodel class for User
#StaticMetamodel(User.class)
public abstract class User_ {
public static volatile SetAttribute<User, UserAddress> addresses;
public static volatile SingularAttribute<User, UserID> id;
}
Metamodel for UserId
#StaticMetamodel(UserID.class)
public abstract class UserID_ {
public static volatile SingularAttribute<UserID, String> userNo;
public static volatile SingularAttribute<UserID, Long> UserType;
}
I am trying to retrieve maximum of 10 User objects ordered by UserType and searched based on userId. The query has to retrieve the UserAddresses as an eager fetch.
My Specification Object is
UserSpecification
public class UserSpecifications {
public static Specification<User> userNoIs(String userNo) {
return (root, query, cb) -> {
root.fetch(User_.addresses);
return cb.equal(root.get(User_.id).get(UserID_.userNo),userNo);
};
}
}
DAO Function:
Sort sortInstructions = new Sort(Sort.Direction.DESC, "id.userNo");
Specifications<User> specifications = Specifications.where(userNoIs(input.getUserNo()));
List<User> userList = userRepository.findAll(specifications,sortInstructions);
I am getting the following exception
java.lang.ClassCastException: org.hibernate.jpa.internal.metamodel.SingularAttributeImpl$Identifier cannot be cast to javax.persistence.metamodel.ManagedType
at org.hibernate.jpa.criteria.path.AbstractFromImpl.locateManagedType(AbstractFromImpl.java:139)
at org.hibernate.jpa.criteria.path.AbstractFromImpl.locateAttributeInternal(AbstractFromImpl.java:133)
at org.hibernate.jpa.criteria.path.AbstractPathImpl.locateAttribute(AbstractPathImpl.java:221)
at org.hibernate.jpa.criteria.path.AbstractPathImpl.get(AbstractPathImpl.java:194)
at org.springframework.data.jpa.repository.query.QueryUtils.toJpaOrder(QueryUtils.java:287)
at org.springframework.data.jpa.repository.query.QueryUtils.toOrders(QueryUtils.java:261)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.getQuery(SimpleJpaRepository.java:441)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll(SimpleJpaRepository.java:294)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:322)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:307)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
The problem is with root.fetch(User_.userAddresses); - in you User class you have addresses not userAddresses so probably you wrongly generated metamodel for that.
Another thing that addresses should be ListAttribute not SingularAttribute.
UPDATE EDIT:
Try to use newer version, problem you described was solved in spring-data 1.5

Categories

Resources