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);
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; }
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.
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.
There are two entities and first entity is referred into second entity.
Entity 1:
#Indexed
public abstract class Yesh implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Basic(optional = false)
#Column(name = "ID")
private Long id;
#Fields({ #Field(index = Index.YES, store = Store.NO), #Field(name = "YeshName_for_sort", index = Index.YES, analyzer = #Analyzer(definition = "customanalyzer")) })
#Column(name = "NAME", length = 100)
private String name;
public Yesh () {
}
public Yesh (Long id) {
this.id = id;
}
public Yesh (Long id) {
this.id = id;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return "com.Prac.Yesh[ id=" + id + " ]";
}
}
Entity 2:
public class Kash implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Basic(optional = false)
#Column(name = "ID")
private Long id;
#IndexedEmbedded
#ManyToOne
Yesh yes; //Contain reference to first entity
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Yesh getYes() {
return yes;
}
public void setId(Yesh yes) {
this.yes = yes;
}
}
There is no annotation in Entity 2 on reference Yesh; Entity 1 have field annotated with name "YeshName_for_sort". But when i try to access above field as given in following example:
Main Class:
FullTextEntityManager ftem = Search.getFullTextEntityManager(factory.createEntityManager());
QueryBuilder qb = ftem.getSearchFactory().buildQueryBuilder().forEntity( Kash.class ).get();
org.apache.lucene.search.Query query = qb.all().getQuery();
FullTextQuery fullTextQuery = ftem.createFullTextQuery(query, Kash.class);
//fullTextQuery.setSort(new Sort(new SortField("YeshName_for_sort", SortField.STRING, true)));
The above statement is not working and i have also tried to replace YeshName_for_sort with 'yes' reference but it is not working.
fullTextQuery.setFirstResult(0).setMaxResults(150);
int size = fullTextQuery.getResultSize();
List<Yesh> result = fullTextQuery.getResultList();
for (Yeshuser : result) {
logger.info("Yesh Name:" + user.getName());
}
Sorting does not work. I also tried to change statements like:
ftem.createFullTextQuery(query, Kash.class, Yesh.class); //added entity 1
or
fullTextQuery.setSort(new Sort(new SortField("yes.name", SortField.STRING, true))); // Added property name for Yesh yes;
but it is not working.
What annotations need to be implemented in entities or any changes in main program to access the field for sorting?
You are using #IndexedEmbedded so you need to reference the field with its full path namely yes.YeshName_for_sort (or yesh if the yes was a typo).
When you use #IndexedEmbedded, you include the nested fields in your Lucene document with a dotted notation.
Thus:
FullTextQuery fullTextQuery = ftem.createFullTextQuery(query, Kash.class);
fullTextQuery.setSort(new Sort(new SortField("yes.YeshName_for_sort", SortField.STRING, true)));
should work.
Note that your code is not really consistent because you start by searching for Kash objects then you manipulate Yesh objects. I suppose it's a copy/pasto.
I recommend you to read a bit about how the indexes are built by Hibernate Search: it will then be easier for you to understand this sort of things.
I m not sure that this is going to work for you but let s give it a try :
add the following annotation to property name in the class Yesh :
#Fields( {
#Field,
#Field(name = "name_sort", analyze = Analyze.NO, store = Store.YES)
} )
private String name;
sorting a query by field requires the field to be un-analyzed. If one wants to search by words in this property and still sort it, one need to index it twice - once analyzed and once un-analyzed.
2nd in your query apply the sort you want:
ftem.createFullTextQuery(query, Kash.class, Yesh.class);
fullTextQuery.setSort(new Sort(new SortField("name_sort", SortField.STRING, true)));
I'm currently working on system that consists of Java Web app and C# client app. Web app has Java Web Service, which has method that returns entity object of Program class:
#WebMethod(operationName = "getProgram")
public Program getProgram(#WebParam(name = "macAddress") String macAddress){
Device device = DeviceManager.getInstance().getDevice(macAddress);
if(device != null){
return device.getProgram();
}
return null;
}
This return object of type Program which has many properties and relations:
#Entity
#Table(name = "PROGRAM", schema = "APP")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "Program.getProgramsByWeather", query = "SELECT p FROM Program p WHERE p.weather = :weather")})
public class Program extends DbEntity implements Serializable {
private static final long serialVersionUID = 1L;
#JoinColumn(name = "LOGO_ID", referencedColumnName = "ID")
#OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch= FetchType.EAGER)
private Logo logo;
#JoinColumn(name = "WEATHER_ID", referencedColumnName = "ID")
#ManyToOne
private Weather weather;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "program", orphanRemoval = true)
private List<ProgramPlaylist> programPlaylistList = new ArrayList<>();
#OneToMany(cascade = CascadeType.ALL, mappedBy = "program", orphanRemoval = true)
private List<ProgramTicker> programTickerList = new ArrayList<>();
#Column(name = "UPDATED")
private boolean updated;
public Program() {
}
public Program(String name, AppUser owner) {
super(name, owner);
}
public Logo getLogo() {
return logo;
}
public void setLogo(Logo logo) {
this.logo = logo;
}
public Weather getWeather() {
return weather;
}
public void setWeather(Weather weather) {
this.weather = weather;
}
public boolean isUpdated() {
return updated;
}
public void setUpdated(boolean updated) {
this.updated = updated;
}
#XmlElement
public List<ProgramPlaylist> getProgramPlaylistList() {
return programPlaylistList;
}
#XmlElement
public List<ProgramTicker> getProgramTickerList() {
return programTickerList;
}
#Override
public String toString() {
return "Program[ id=" + getId() + " ]";
}
}
Client can get this object and accessing some properties in client app like program.name, which it inherits from DbEntity, but when i try to call something like this:
program.logo.name
client throws NullReferenceException.
Same exception occurs when i try to iterate over the elements of programPlaylistList ArrayList.
I'm assuming that the object itself that is passed through to client isn't fully loaded.
How can i solve this problem, please help?!
EDIT
Ok, so I printed out XML response that client get from service and its populated correctly, but for some reason object fields aren't populated and are mostly null.
Why is this occurring?
Bye default, the fetch strategy for #OneToMany annotations is LAZY, have you tried specifying it to EAGER like in the #oneToOne field (fetch= FetchType.EAGER)?