Getting errors while trying to persist child entity (MsgRetry) when trying to get an entity of parent entity (Msg) where the parent PK (msg_id) is the FK in the child entity.
Errors like: org.hibernate.id.IdentifierGenerationException: attempted to assign id from null one-to-one property
The parent entity, does not need to know about the child entity (at least i don't think it needs to, to work). Once the child entity is persisted I'm trying to also persist the parent entity. I can work around this by not having the parent entity in the child entity and the call the associated repositories. However, I don't think it's as clean as what I'm attempting but obviously more difficult/ complex.
Thanks for any advice on best practices or how to achieve this if this is a good solution.
tables:
msg
msg_id
pk
msg_status
msg_status
msg_retry
msg_id
fk
count
timestamp
model:
#Entity
#Table(name="msg")
public class Msg {
#Id
#Column(name = "msg_id")
#GeneratedValue(strategy = generationtype.sequence, generator = "msg_id_seq_gen")
#SequenceGenerator(name = "msg_id_seq_gen", sequencename = "msg_id_seq", allocationsize = 1)
private Long msgId;
#Column(name = "msg_status", nullable = false)
private String msgStatus;
...
//getters setters
}
#Entity
#Table(name = "msg_retry")
public class MsgRetry implements Serializable{
private static final long serialVersionUID = -7637385223556379976L;
#Id
#Column(name = "msg_id")
private Long msgId;
#OneToOne
#JoinColumn(name="msg_id", referencedColumnName = "msg_id")
private Msg msg;
#Column(name = "count")
private Long count;
#Generated(value = GenerationTime.ALWAYS)
#Column(name = "timestamp")
private Date timestamp;
public MsgRetry() {
}
public MsgRetry(Msg msg, Long count) {
this.msg = msg;
this.count = count;
}
public MsgRetry(Long msgId, Long count) {
this.msgId = msgId;
this.count = count;
}
public Msg getMsg() {
return msg;
}
public void setMsg(Msg msg) {
this.msg = msg;
}
#Repository
public interface MsgRetryRepository extends JpaRepository<MsgRetry, Long>{
}
#Test
public void testSaveMsgByMsgIdRetry() {
msgRetryRepository.deleteAll();
List<Msg> msgs = msgRepository.findAll();
MsgRetry msgRetry = new MsgRetry(msgs.get(0).getMsgId(), 1L);
msgRetry = msgRetryRepository.save(msgRetry);
assertNotNull(msgRetry.getMsg()); // fails to load Msg entity
LOG.info("msgRetry: {}", msgRetry);
}
#Test
public void testSaveMsgRetryByMsg() {
msgRetryRepository.deleteAll();
List<Msg> msgs = msgRepository.findAll();
MsgRetry msgRetry = new MsgRetry(msgs.get(0), 1L);
msgRetry = msgRetryRepository.save(msgRetry);
assertNotNull(msgRetry.getMsg());
LOG.info("msgRetry: {}", msgRetry);
}
Errors out: org.springframework.orm.jpa.JpaSystemException: ids for this class must be manually assigned before calling save(): msgtest.MsgRetry;
First, straighten out your IDs for MsgRetry. The FK should be good enough.
#Entity
#Table(name = "msg_retry")
public class MsgRetry {
#Id
#ManyToOne(optional = false)
#JoinColumn(name = "msg_id")
private Msg msg;
#Column(name = "count")
private Long count;
#Generated(value = GenerationTime.ALWAYS)
#Column(name = "timestamp")
private Date timestamp;
public Msg getMsg() { return msg; }
public void setMsg(Msg msg) { this.msg = msg; }
}
Next, be sure MsgRetryRepository is properly sub-classed:
public interface MsgRetryRepository extends CrudRepository<MsgRetry, Msg>
{
// Empty for now
}
Lastly, query the MsgRetry in the right way:
public class BizLogic {
MsgRetryRepository retryRepo
public MsgRetry retry(Msg msg, String msgStatus) {
MsgRetry mr = retryRepo.findById(msg);
if (mr !=null) {
// XXX I cannot tell from your logic what you are doing here.
retryRepo.save(mr);
}
}
}
What is your persistence layer? Maybe you can turn on debugging and visit the logs to see what is happening under the hood.
Related
Trying to update an item on the database using a PUT request but im getting this error
org.hibernate.HibernateException: identifier of an instance of com.pluralsight.conferencedemo.models.Sessions was altered from 102 to null
Need help
Controller :
#RequestMapping(value = "{id}", method = RequestMethod.PUT)//PUT VS PATCH, you know it
public Sessions update(#PathVariable Long id, #RequestBody Sessions sessions) {
//TODO: Add validation that all attributes are passed in, otherwise return a 400 bad payload
Sessions existingSessions = sessionsRepository.getReferenceById(id);
BeanUtils.copyProperties(sessions, existingSessions, "sessions_id"); //ignoring the primary key to be updated or else it will be null and invalid
return sessionsRepository.saveAndFlush(existingSessions);
}
Model :
#Entity
#JsonIgnoreProperties({"hibernateLazyInitializer","handler"})
#Table(name = "sessions", schema = "public")
public class Sessions {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long session_id;
private String session_name;
#ManyToMany
#JoinTable(
name = "session_speakers",
joinColumns = #JoinColumn (name = "session_id"),
inverseJoinColumns = #JoinColumn(name = "speaker_id") //connection between Session and Speaker Class
)
private List<Speakers> speakers;
public List<Speakers> getSpeaker() {
return speakers;
}
public void setSpeaker(List<Speakers> speaker) {
this.speakers = speaker;
}
public Long getSession_id() {
return session_id;
}
public void setSession_id(Long session_id) {
this.session_id = session_id;
}
public String getSession_name() {
return session_name;
}
public void setSession_name(String session_name) {
this.session_name = session_name; }
Guys hope you doing well under the lockdown?
I have an issue occur in my work project, the problem is Hibernate won't update my child entity when I update it from the View.
my code parent class has:
#OneToMany(cascade = CascadeType.ALL, mappedBy = "permitType", fetch = FetchType.EAGER)
private Set<BlockDate> blockDates;
.....
public Set<BlockDate> getBlockDates() {return blockDates;}
public void setBlockDates(Set<BlockDate> blockDates) {this.blockDates = blockDates;}
My child class has :
#Entity
#Table(name="block_date")
public class BlockDate implements GenericModel<Long> {
#Id
#GeneratedValue(strategy = GenerationType.TABLE, generator = "block_date")
#TableGenerator(name = "block_date", allocationSize = 30, initialValue = 10000)
private Long id;
#Column(name = "effective_date", nullable = false)
private DateTime effectiveDate;
#ManyToOne
#JoinColumn(name = "permit_type_fk")
private PermitType permitType;
#Override
public Long getId() {return id;}
public void setId(Long id) {this.id = id;}
#JsonSerialize(using = CustomDateTimeSerializer.class)
public DateTime getEffectiveDate() {return effectiveDate;}
public void setEffectiveDate(DateTime effectiveDate) {this.effectiveDate = effectiveDate;}
public PermitType getPermitType() {return permitType;}
public void setPermitType(PermitType permitType) {this.permitType = permitType;}
}
My DTO code to control the child entity and pull the entry from the view is:
if (getBlockDates() != null ) {
Set<BlockDate> blockDates = new HashSet<>();
DateTime effectiveDate;
DateTimeZone timeZone = DateTimeZone.forID(permitType.getPermitArea().getOrganisation().getTimezone());
for (String blockDateString : getBlockDates()){
effectiveDate = DATEPICKER_FORMATTER.parseDateTime(blockDateString).withZone(timeZone).withTimeAtStartOfDay();
blockDates.add(getBlockDateAtt(permitType, effectiveDate));
System.out.println("test dates " + blockDateString); //this is testing how many entry from user
}
permitType.setBlockDates(blockDates);
System.out.println("test the new child entity " + permitType.getBlockDates().size()); //it sets the new record on child entity if i print to consol but it wont applies on the DB
} else{
permitType.setBlockDates(new HashSet<>());
System.out.println("test null " + permitType.getBlockDates().size()); // this show 0 but in the DB wont updated the child, it still have the old record
}
and the controller simply have:
permitAreaDao.update(permitArea);
I have two entities which are linked via a OneToMany relationship:
#Entity
#Table(name="bookcase")
public class BookCase {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Transient
#Getter #Setter private Long oldId;
/*
https://vladmihalcea.com/a-beginners-guide-to-jpa-and-hibernate-cascade-types/
*/
#OneToMany(mappedBy = "bookCase", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Bookshelf> bookShelves = new HashSet<>();
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public Set<Bookshelf> getBookShelves() { return bookShelves; }
public void setBookShelves(Set<Bookshelf> bookShelves) { this.bookShelves = bookShelves; }
}
#Entity
#Table(name="bookshelf")
public class Bookshelf {
private static final Logger log = LoggerFactory.getLogger(Bookshelf.class);
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Transient
#Getter #Setter private Long oldId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "bookcase_id")
private BookCase bookCase;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public BookCase getBookCase() { return bookCase; }
public void setBookCase(BookCase bookCase) {
this.bookCase = bookCase;
bookCase.getBookShelves().add(this);
}
#Transient
#Setter private OldIdListener oldIdListener;
/*
When the id is saved, listening DTOs can update their ids
*/
#PostPersist
public void triggerOldId() {
log.info("Postpersist triggered for {}", id);
if (oldIdListener != null) {
oldIdListener.updateId(oldId, id);
}
}
}
public interface OldIdListener {
void updateId(long oldId, long newId);
}
The following test fails:
#Test
public void testThatCascadingListenerIsTriggered() {
var mock = mock(OldIdListener.class);
var mock2 = mock(OldIdListener.class);
var mock3 = mock(OldIdListener.class);
var bookcase = new BookCase();
var shelf1 = new Bookshelf();
shelf1.setOldId(-5L);
shelf1.setBookCase(bookcase);
shelf1.setOldIdListener(mock);
var shelf2 = new Bookshelf();
shelf2.setOldId(-6L);
shelf2.setBookCase(bookcase);
shelf2.setOldIdListener(mock2);
var saved = bookCaseRepository.save(bookcase);
verify(mock).updateId(eq(-5L), anyLong());
verify(mock2).updateId(eq(-6L), anyLong());
var savedBookCase = bookCaseRepository.findById(saved.getId()).get();
assertThat(savedBookCase.getBookShelves()).hasSize(2);
var shelf3 = new Bookshelf();
shelf3.setOldId(-10L);
shelf3.setBookCase(savedBookCase);
shelf3.setOldIdListener(mock3);
savedBookCase.getBookShelves().add(shelf3);
bookCaseRepository.save(savedBookCase);
verify(mock3).updateId(eq(-10L), anyLong());
}
mock3 is never called.
When debugging the code, I can see that the transient fields oldId and oldIdListener are set to null when the #PostPersist method is called on object shelf3, not on shelf1 and 2.
I think this is because I am modifying the Set object; but the object is correctly persisted, it just loses all transient fields. This does not happen when the entire tree is persisted for the first time.
Is this the wrong way to insert a new element to a OneToMany set or where is the error here?
I'm using Spring Boot 2.1.
Thanks!
The field which annotation with #Transient will not persist to the database, so if you want it to persist, you must remove #Transient.
I have a User entity, a UserToApplication entity, and an Application entity.
A single User can have access to more than one Application. And a single Application can be used by more than one User.
Here is the User entity.
#Entity
#Table(name = "USER", schema = "UDB")
public class User {
private Long userId;
private Collection<Application> applications;
private String firstNm;
private String lastNm;
private String email;
#SequenceGenerator(name = "generator", sequenceName = "UDB.USER_SEQ", initialValue = 1, allocationSize = 1)
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "generator")
#Column(name = "USER_ID", unique = true, nullable = false)
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
public Collection<Application> getApplications() {
return applications;
}
public void setApplications(Collection<Application> applications) {
this.applications = applications;
}
/* Other getters and setters omitted for brevity */
}
Here is the UserToApplication entity.
#Entity
#Table(name = "USER_TO_APPLICATION", schema = "UDB")
public class Application {
private Long userToApplicationId;
private User user;
private Application application;
#SequenceGenerator(name = "generator", sequenceName = "UDB.USER_TO_APP_SEQ", initialValue = 0, allocationSize = 1)
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "generator")
#Column(name = "USER_TO_APPLICATION_ID", unique = true, nullable = false)
public Long getUserToApplicationId() {
return userToApplicationId;
}
public void setUserToApplicationId(Long userToApplicationId) {
this.userToApplicationId = userToApplicationId;
}
#ManyToOne
#JoinColumn(name = "USER_ID", referencedColumnName = "USER_ID", nullable = false)
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
#ManyToOne
#JoinColumn(name = "APPLICATION_ID", nullable = false)
public Application getApplication() {
return application;
}
}
And here is the Application entity.
#Entity
#Table(name = "APPLICATION", schema = "UDB")
public class Application {
private Long applicationId;
private String name;
private String code;
/* Getters and setters omitted for brevity */
}
I have the following Specification that I use to search for a User by firstNm, lastNm, and email.
public class UserSpecification {
public static Specification<User> findByFirstNmLastNmEmail(String firstNm, String lastNm, String email) {
return new Specification<User>() {
#Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
final Predicate firstNmPredicate = null;
final Predicate lastNmPredicate = null;
final Predicate emailPredicate = null;
if (!StringUtils.isEmpty(firstNm)) {
firstNmPredicate = cb.like(cb.lower(root.get(User_.firstNm), firstNm));
}
if (!StringUtils.isEmpty(lastNm)) {
lastNmPredicate = cb.like(cb.lower(root.get(User_.lastNm), lastNm));
}
if (!StringUtils.isEmpty(email)) {
emailPredicate = cb.like(cb.lower(root.get(User_.email), email));
}
return cb.and(firstNmPredicate, lastNmPredicate, emailPredicate);
}
};
}
}
And here is the User_ metamodel that I have so far.
#StaticMetamodel(User.class)
public class User_ {
public static volatile SingularAttribute<User, String> firstNm;
public static volatile SingularAttribute<User, String> lastNm;
public static volatile SingularAttribute<User, String> email;
}
Now, I would like to also pass in a list of application IDs to the Specification, such that its method signature would be:
public static Specification<User> findByFirstNmLastNmEmailApp(String firstNm, String lastNm, String email, Collection<Long> appIds)
So, my question is, if I add the #OneToMany mapping to the User_ metamodel for the Collection<Application> applications field of my User entity, then how would I reference it in the Specification?
My current Specification would be similar to the following SQL query:
select * from user u
where lower(first_nm) like '%firstNm%'
and lower(last_nm) like '%lastNm%'
and lower(email) like '%email%';
And what I would like to achieve in the new Specification would be something like this:
select * from user u
join user_to_application uta on uta.user_id = u.user_id
where lower(u.first_nm) like '%firstNm%'
and lower(u.last_nm) like '%lastNm%'
and lower(u.email) like '%email%'
and uta.application_id in (appIds);
Is it possible to do this kind of mapping in the metamodel, and how could I achieve this result in my Specification?
I found a solution. To map a one to many attribute, in the metamodel I added the following:
public static volatile CollectionAttribute<User, Application> applications;
I also needed to add a metamodel for the Application entity.
#StaticMetamodel(Application.class)
public class Application_ {
public static volatile SingularAttribute<Application, Long> applicationId;
}
Then in my Specification, I could access the applications for a user, using the .join() method on the Root<User> instance. Here is the Predicate I formed.
final Predicate appPredicate = root.join(User_.applications).get(Application_.applicationId).in(appIds);
Also, it is worth noting that my Specification as it is written in the question will not work if any of the input values are empty. A null Predicate passed to the .and() method of CriteriaBuilder will cause a NullPointerException. So, I created an ArrayList of type Predicate, then added each Predicate to the list if the corresponding parameter was non-empty. Finally, I convert the ArrayList to an array to pass it to the .and() function of the CriteriaBuilder. Here is the final Specification:
public class UserSpecification {
public static Specification<User> findByFirstNmLastNmEmailApp(String firstNm, String lastNm, String email, Collection<Long> appIds) {
return new Specification<User>() {
#Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
final Collection<Predicate> predicates = new ArrayList<>();
if (!StringUtils.isEmpty(firstNm)) {
final Predicate firstNmPredicate = cb.like(cb.lower(root.get(User_.firstNm), firstNm));
predicates.add(firstNmPredicate);
}
if (!StringUtils.isEmpty(lastNm)) {
final Predicate lastNmPredicate = cb.like(cb.lower(root.get(User_.lastNm), lastNm));
predicates.add(lastNmPredicate);
}
if (!StringUtils.isEmpty(email)) {
final Predicate emailPredicate = cb.like(cb.lower(root.get(User_.email), email));
predicates.add(emailPredicate);
}
if (!appIds.isEmpty()) {
final Predicate appPredicate = root.join(User_.applications).get(Application_.applicationId).in(appIds);
predicates.add(appPredicate);
}
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
}
};
}
}
I am new to hibernate and having a tough time trying to wrap my head around setting up Joined inheritance with composite Primary Key. With my current setup, I get a:
JDBCException: could not insert: LandHolidayPackage
I am essentially looking for two things:
Are the inheritance annotations in place ?
Is the composite PK setup properly ?
DB Design:
Reference
Here are my classes and the annotations involved:
#Entity
#Table(name = "HOLIDAYPACKAGE")
public final class HolidayPackage {
private Integer idPackage;
private String name;
private Set<HolidayPackageVariant> holidayPackageVariants = new HashSet<HolidayPackageVariant>(0);
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "IDHOLIDAYPACKAGE", nullable = false)
public Integer getIdPackage() {
return idPackage;
}
#OneToMany(fetch=FetchType.LAZY, cascade={CascadeType.ALL}, mappedBy = "holidayPackage")
public Set<HolidayPackageVariant> getHolidayPackageVariants() {
return holidayPackageVariants;
}
// ommitted other part of the code
}
#Entity
#Inheritance(strategy=InheritanceType.JOINED)
#Table(name="HOLIDAYPACKAGEVARIANT")
public abstract class HolidayPackageVariant {
private Integer idHolidayPackageVariant;
private HolidayPackage holidayPackage;
private String typeHolidayPackage;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="IDHOLIDAYPACKAGEVARIANT", nullable=false)
public Integer getIdHolidayPackageVariant() {
return idHolidayPackageVariant;
}
#ManyToOne(fetch=FetchType.LAZY, cascade={CascadeType.ALL})
#JoinColumn(name="IDHOLIDAYPACKAGE", nullable=false)
public HolidayPackage getHolidayPackage() {
return holidayPackage;
}
#Column(name="TYPEHOLIDAYPACKAGE", nullable=true)
public String getTypeHolidayPackage() {
return typeHolidayPackage;
}
// ommitted setters, equals hashCode
}
#Entity
#Table(name="LANDHOLIDAYPACKAGEVARIANT")
public final class LandHolidayPackageVariant extends HolidayPackageVariant{
private static final String LAND = "LAND";
protected LandHolidayPackageVariant() {}
public LandHolidayPackageVariant(HolidayPackage holidayPackage) {
super(holidayPackage, LAND);
}
}
#Entity
#Table(name="FLIGHTHOLIDAYPACKAGEVARIANT")
public final class FlightHolidayPackageVariant extends HolidayPackageVariant{
private static final String FLIGHT = "FLIGHT";
private Destination originCity;
protected FlightHolidayPackageVariant(){}
public FlightHolidayPackageVariant(HolidayPackage holidayPackage,
Destination originCity) {
super(holidayPackage, FLIGHT);
setOriginCity(originCity);
}
#ManyToOne(fetch=FetchType.LAZY, cascade={CascadeType.ALL})
#JoinColumn(name="IDDESTINATION", nullable=false)
public Destination getOriginCity() {
return originCity;
}
// ommited other setters etc functions
}
You annotated the properties in stead of the fields. JPA by default tries to access the fields. If you want JPA to use the fields you have to annotate the class with #AccessType(AccessType.Field).