JPA PostLoad and PreUpdate with Embedded columns - java

Situation: NotificationProfile entity has collection of NotificationProfileIntegration entities which has embedded IntegrationNotificationCutOff. It is legacy database and i cannot modify it. Someone thought it is a good idea to store time as "HH:mm" strings, but i need to work with date objects. That is the reason why i have these convert callbacks and transient date fields.
When i do (for existing profile entity)
entityManager.merge(profile);
I expect that #PreUpdate is called on NotificationProfileIntegration and dates are converted to their string representation and persisted to DB. Instead #PostLoad method is called first and #PreUpdate is called after.
So if i set something in cutOff instance it is never persisted, because in #PostLoad method new instance of cutOffs is created because it is null due to this feature of hibernate
When all of the values in an #Embedded object are null, Hibernate will
set the field in the parent object to null.
How can i handle this situation? Thank you.
#Entity
#Table(name = "NOTIFICATION_PROFILES")
public class NotificationProfile extends AbstractEntity<Long> {
#OneToMany(mappedBy = "notificationProfile", cascade = CascadeType.ALL, orphanRemoval = true)
private Collection<NotificationProfileIntegration> profileIntegrations;
....
}
#Entity
#Table(name = "NOTIFICATION_PROFILE_INTEG")
public class NotificationProfileIntegration extends AbstractEntity<Long> {
#Embedded
private IntegrationNotificationCutOff cutOffs;
#Embedded
private IntegrationNotificationAverageCount averageShipments;
#PostLoad
public void initEmbeded() {
if (cutOffs == null) {
cutOffs = new IntegrationNotificationCutOff();
}
if (averageShipments == null) {
averageShipments = new IntegrationNotificationAverageCount();
}
cutOffs.convertToDates();
}
#PreUpdate
#PrePersist
private void formatCutOffs() {
if (cutOffs != null) {
cutOffs.convertToValues();
}
}
}
#Embeddable
public class IntegrationNotificationCutOff {
#Column(name = "NPI_CUT_OFF_TIME_MON")
private String monday;
#Column(name = "NPI_CUT_OFF_TIME_TUE")
private String tuesday;
#Column(name = "NPI_CUT_OFF_TIME_WED")
private String wednesday;
#Column(name = "NPI_CUT_OFF_TIME_THU")
private String thursday;
#Column(name = "NPI_CUT_OFF_TIME_FRI")
private String friday;
#Column(name = "NPI_CUT_OFF_TIME_SAT")
private String saturday;
#Column(name = "NPI_CUT_OFF_TIME_SUN")
private String sunday;
#Transient
private Date mondayDate;
#Transient
private Date tuesdayDate;
#Transient
private Date wednesdayDate;
#Transient
private Date thursdayDate;
#Transient
private Date fridayDate;
#Transient
private Date saturdayDate;
#Transient
private Date sundayDate;
public void convertToDates() {
SimpleDateFormat dateFormat = getDateFormat();
mondayDate = nullSafeConvert(monday, dateFormat);
tuesdayDate = nullSafeConvert(tuesday, dateFormat);
wednesdayDate = nullSafeConvert(wednesday, dateFormat);
thursdayDate = nullSafeConvert(thursday, dateFormat);
fridayDate = nullSafeConvert(friday, dateFormat);
saturdayDate = nullSafeConvert(saturday, dateFormat);
sundayDate = nullSafeConvert(sunday, dateFormat);
}
public void convertToValues() {
SimpleDateFormat dateFormat = getDateFormat();
monday = nullSafeFormat(mondayDate, dateFormat);
tuesday = nullSafeFormat(tuesdayDate, dateFormat);
wednesday = nullSafeFormat(wednesdayDate, dateFormat);
thursday = nullSafeFormat(thursdayDate, dateFormat);
friday = nullSafeFormat(fridayDate, dateFormat);
saturday = nullSafeFormat(saturdayDate, dateFormat);
sunday = nullSafeFormat(sundayDate, dateFormat);
}
private String nullSafeFormat(Date date, SimpleDateFormat dateFormat) {
if (date == null) {
return null;
}
return dateFormat.format(date);
}
private SimpleDateFormat getDateFormat() {
return new SimpleDateFormat("HH:mm");
}
private Date nullSafeConvert(String day, SimpleDateFormat dateFormat) {
if (day == null) {
return null;
}
try {
return dateFormat.parse(day);
} catch (ParseException e) {
return null;
}
}
}
EDIT
It seems that embedding does not have any efect on this behavior. When i refactor it to single entity problem is still here: after calling createOrUpdate - select is triggered before an update and my change to entity is somewhere "lost"

Related

Hibernate getting and saving a date with an extra day than the record on the database

Im having an issue when I fetch data from the DB with HIBERNATE and SpringMVC, everything works fine except for the dates. When I fetch a field of Date type, I receive the date but with a previous day.
For example, Im fetching the date as "2022-08-28", but im receiving "2022-08-27"
Also have an issue when I update the data, Hibernate saves on the next day that the date that is stored on the DB.
On the same example Im fetching the date as "2022-08-28", but when saving the record appears as "2022-08-29"
This is my architechture
Entity
#Entity
#Table(name = "cliente_tickets")
public class ClienteTicket {
... other fields...
#DateTimeFormat(pattern = "yyyy-MM-dd")
#Temporal(TemporalType.DATE)
#Column(name = "created_on")
private Date createdOn;
#DateTimeFormat(pattern = "yyyy-MM-dd")
#Temporal(TemporalType.DATE)
#Column(name = "updated_on")
private Date updatedOn;
... other GettersAndSetters...
public Date getCreatedOn() {
return createdOn;
}
public void setCreatedOn(Date createdOn) {
this.createdOn = createdOn;
}
public Date getUpdatedOn() {
return updatedOn;
}
public void setUpdatedOn(Date updatedOn) {
this.updatedOn = updatedOn;
}
}
This is the method in my DAO
#Override
public ClienteTicket buscarId(long id) {
try {
return sessionFactory.getCurrentSession().createQuery("from ClienteTicket ct where ct.idTicket = :id", ClienteTicket.class)
.setParameter("id", id).getSingleResult();
} catch (NoResultException e) {
return null;
}
}
This is the Service
#Override
#Transactional
public void guardar(ClienteTicket clienteTicket) {
if (clienteTicket.getCreatedBy() == null || clienteTicket.getCreatedBy().length() == 0) {
clienteTicket.setCreatedOn(new Date());
clienteTicket.setCreatedBy(Utilidades.currentUser());
} else {
clienteTicket.setUpdatedOn(new Date());
clienteTicket.setUpdatedBy(Utilidades.currentUser());
}
this.clienteTicketDao.guardar(clienteTicket);
}
This is my controller for fetching the Object
#GetMapping("/editar")
ModelAndView editar(#RequestParam("id") String id) throws ParseException {
ClienteTicket ticket = this.ticketsService.buscarId(Long.parseLong(id));
super.mv = new ModelAndView("/cliente/ticket_formulario");
super.mv.addObject("clienteEncontrado", true);
super.mv.addObject("editar", true);
super.mv.addObject("clienteTicket", ticket);
super.mv.addObject("idClienteActual", ticket.getCliente().getIdCliente());
super.mv.addObject("estadosIncidencia", this.ticketEstadosService.listar());
super.mv.addObject("tiposIncidencia", this.ticketTiposService.listar());
super.mv.addObject("titulo", "Tickets");
//this.ticketsService.guardar(ticket);
return super.mv;
}
When I Debug I get this JAVA_DEBUG
These are the properties of the DB TABLE_PROPERTIES
Current data on DB DATA_TABLE
Date in JSP is hidden DATA_JSP
HTML EDGE_DEVTOOLS
Other data is fetched correctly OTHER_DATA
I've solved!, It was related with the Timezone.The timezone on my pc was right, the issue was the configuration on the jdbc.url. I was using serverTimezone=UTC, Im from Mexico so i needed to use CST. adding serverTimezone=CST to jdbc.url solved my issue. I hope this helps someone in the future. TY
Change
jdbc.url=jdbc:mysql://localhost:3306/sge?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true&zeroDateTimeBehavior=convertToNull
To
jdbc.url=jdbc:mysql://localhost:3306/sge?useSSL=false&serverTimezone=CST&allowPublicKeyRetrieval=true&zeroDateTimeBehavior=convertToNull

How to set and save Date object

I am writing a JPA application and I have a Date object that needs to be set and then saved in the database.
Here's an example:
#Embeddable
public class Semester {
#Column(name = "is_started",
columnDefinition = "tinyint(1)",
nullable = false)
private Boolean isStarted;
#Column(name = "is_finished",
columnDefinition = "tinyint(1)",
nullable = false)
private Boolean isFinished;
#Column(name = "starting_date")
private Date startingDate;
#Column(name = "register_deadline_date")
private Date registerDeadlineDate;
}
I want to know How to Set a Date object to pass to my entity:
Should it be something like:
System.out.println("Enter Year: ");
String year = new Scanner(System.in).next();
System.out.println("Enter month: ");
String month = new Scanner(System.in).next();
System.out.println("Enter day: ");
String day = new Scanner(System.in).next();
Date startingDate = new Date(day, month, year);
Semester semester = new Semester();
semester.setStartingDate(startingDate);
repository.save(semester);
I did this but this constructor either doesn't work anymore or I am doing something wrong. Thanks in advance.
I succeeded at that.
Maybe, cause is that you didn't write #Entity,#Table(name = "foo").
#Setter
#Getter
#Table(name = "foo") // your name of Table
#Entity
public class Semester {
#Id
private Long id;
#Column(name = "starting_date")
private Date startingDate;
#Override
public String toString() {
return "id : "+id +",startingDate : "+ startingDate;
}
}
Repository interface
#Repository
public interface FooRepo extends JpaRepository<Semester, Long> {
}
execute class
#Component
public class FooExecute {
#Autowired
FooRepo fooRepo;
#Autowired
FooRepo repository;
public void execute() {
Date startingDate = new Date(121, 8, 29);
Semester semester = new Semester();
semester.setId(2L);
semester.setStartingDate(startingDate);
semester = repository.save(semester);
System.out.println("result:"+semester.toString());
//result:id : 2,startingDate : 2021-09-29
}
}

How to map postgres's "time without time zone" to JPA/Hibernate entity?

Current implementation:
#Getter(AccessLevel.NONE)
#Setter(AccessLevel.NONE)
#Column(name = "send_time", columnDefinition = "timestamp without time zone not null")
private LocalTime sendTime;
#Convert(converter=LocalTimeConverter.class)
public LocalTime getSendTime() {
return sendTime;
}
#Convert(converter=LocalTimeConverter.class)
public void setLocalTime(LocalTime time) {
this.sendTime = time;
}
#Converter(autoApply = true)
public class LocalTimeConverter implements AttributeConverter<LocalDateTime, Timestamp>{
#Override
public Timestamp convertToDatabaseColumn(LocalDateTime zonedDateTime) {
if(zonedDateTime == null) {
return null;
}
return Timestamp.valueOf(zonedDateTime);
}
#Override
public LocalDateTime convertToEntityAttribute(Timestamp sqlTime) {
if(sqlTime == null) {
return null;
}
return sqlTime.toLocalDateTime();
}
}
object.setSendTime(LocalTime.of(11, 00, 00));
The error I get all the time:
ERROR: column "send_time" is of type time without time zone but expression is of type bytea
Hint: You will need to rewrite or cast the expression.
You should use java.sql.Time instead. Are there a specific reason you are using LocalDateTime? Example with LocalTime:
#Converter
public class MyConverter implements AttributeConverter<LocalTime, Time> {
#Override
public Time convertToDatabaseColumn(LocalTime localTime) {
if(localTime == null){
return null;
}
// convert LocalTime to java.sql.Time
}
#Override
public LocalTime convertToEntityAttribute(Time time) {
if(time == null){
return null;
}
// convert java.sql.Time to LocalTime
}
}
You do not need a converter. You can use annotations (as per JPA 2.2). This works with Postgres.
#Column(nullable = false, columnDefinition = "TIMESTAMP WITH TIME ZONE")
private OffsetDateTime createdOn;
Instantiation can be done as per:
#PrePersist
private void prePersist() {
this.createdOn = OffsetDateTime.now(ZoneId.of("UTC"));
}
The example here uses OffsetDateTime but for LocalDateTime you can use
#Column(nullable = false, columnDefinition = "TIMESTAMP WITHOUT TIME ZONE")
private LocalDateTime createdOn;

Converting date from timestamp to human readable in entity constructor

Currently, the format of the Date requestDate variable stored looks like: 2017-02-17 00:00:00.0. I want to convert this into, for example: Friday, February 17, 2017. I would like to do the conversion here in my entity and return it so that when it's displayed it is more human readable. This will likely happen in the constructor, at this line: this.setRequestDate(doDateConversion(requestDate));. How can I make this conversion?
My Request entity:
#Entity
#Table(name = "Request")
public class RequestDO implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="request_id")
private Long id;
private Date requestDate;
private String description;
private RequestStatus status;
/*private Boolean read;*/
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="user_id", nullable = false)
private Users users;
public RequestDO() {}
public RequestDO(Users user, Date requestDate) {
this.setUsers(user);
this.setRequestDate(requestDate);
}
#Override
public String toString() {
return String.format(
"RequestDO[id=%d, inital='%s', requestDate='%s']",
getId()
, getUsers().getInitialName()
, getRequestDate());
}
public Date getRequestDate() {
return requestDate;
}
public void setRequestDate(Date requestDate) {
this.requestDate = requestDate;
}
}
You can use SimpleDateFormat to convert your Date to a readable String of your choice.
The time format String for your example is EEEE, MMMM, dd, yyyy. You have to create a new SimpleDateFormat object and format your date to a String. Examples...
But Spring provides some specials out of the box. For example you can use Jackson for date format: #JsonFormat(pattern="yyyy-MM-dd") more. It is also possible to add a data format in application.properties file : spring.jackson.date-format
Using SimpleDateFormat:
java.sql.Date date = new Date(System.currentTimeMillis());
System.out.println(new SimpleDateFormat("EEEE, MMMM dd, YYYY").format(date));
See this for more details.
I solved the problem by changing the dates as they are read in my controller, using SimpleDateFormat:
#RequestMapping(value = "/requests", method = RequestMethod.GET)
public String getAllRequests(Model model, RequestModel requestModel) throws ParseException {
List<RequestDO> requestDOArrayList = new ArrayList<RequestDO>();
for (RequestDO requestDO : requestRepository.findAll()) {
log.info(requestDO.toString());
// Display all dates in Requests list in human-readable form
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = sdf.parse(requestDO.getRequestDate().toString());
log.info(String.valueOf(date));
requestDO.setRequestDate(date);
requestDOArrayList.add(requestDO);
}
model.addAttribute("requests", requestDOArrayList);
log.info(requestDOArrayList.toString());
return "requests";
}

Timestamp invalid hour in Java

I am using JPA with my Java project, and the timestamp is not working very well : it only shows 2015-08-12 00:00:00.0 (the day is correct but the hour is not)
#Entity
public class Session implements Serializable {
..
#Temporal(TemporalType.DATE)
private Date timestamp;
..
public Session(String sessionId) {
super();
this.sessionId = sessionId;
this.timestamp = new Date();
}
public Session() {
super();
this.timestamp = new Date();
}
}
Do you know how to fix this?
You should use TemporalType.TIMESTAMP that will map the field to a java.sql.Timestamp, hence it will contain also time related info, not only regarding date. In comparison, the type you used, TemporalType.DATE are mapped to java.sql.Date, class containing information like day, month year.
So, your code will transform in:
#Entity
public class Session implements Serializable {
..
#Temporal(TemporalType.TIMESTAMP)
private Date timestamp;
..
public Session(String sessionId) {
this.sessionId = sessionId;
this.timestamp = new Date();
}
public Session() {
this.timestamp = new Date();
}
}

Categories

Resources