How to use joda time with JPA (eclipselink)? - java

I tried to use the DataTime in my entity class. Adding #Temporal(TemporalType.DATE) above the field, I got the error saying "The persistent field or property for a Temporal type must be of type java.util.Date, java.util.Calendar or java.util.GregorianCalendar". I can introduce the conversion back and forth; using setters and getters as follows:
#Temporal(TemporalType.DATE)
private Calendar attendanceDate;
public DateTime getAttendanceDate() {
return new DateTime(this.attendanceDate);
}
public void setAttendanceDate(DateTime attendanceDate) {
this.attendanceDate = attendanceDate.toCalendar(Locale.getDefault());
}
but I hope eclipselink to do it for me. I have gone thro' Persist Joda-time's DateTime via Hibernate. The answer suggesting to use hibernate, but I have to use eclipselink. I can use the DateTime object in my entity class with DB representation as BLOB, but I need it as Date. Is there anything like jodatime-eclipselink? Or any other suggestion? Thanks for the help.

Basic the link defines an EclipseLink Converter to convert from Joda DateTime to java.sql.Timestamp or Date.
You could use it, or define your own converter and use it through #Convert, #Converter in EclipseLink.
For DDL creation, the converter should define the initialize method and set the type on the mapping's field to java.sql.Timestamp.
Please log a bug (or vote for the existing one) on this in EclipseLink, we should have support for Joda.

I Try use joda-time-eclipselink-integration, but don't work, problably I made something wrong,
So I made more researchs and i found this link http://jcodehelp.blogspot.com.br/2011/12/persist-joda-datetime-with-eclipselink.html, they use #Converter annotation to convert the Joda Date Time.
I Try and works for me, I hope, works for you too.

I wanted to do the same thing, and Googling around actually led me here. I was able to accomplish this using the #Access annotation. First, you annotate the class like this
#Access(AccessType.FIELD)
public class MyClass
{
....
This provides field access to everything so you don't have to annotate the fields individually. Then you create a getter and setter for the JPA to use.
#Column(name="my_date")
#Temporal(TemporalType.DATE)
#Access(AccessType.PROPERTY)
private Date getMyDateForDB()
{
return myDate.toDate();
}
private void setMyDateForDB(Date myDateFromDB)
{
myDate = new LocalDate(myDateFromDB);
}
The #Access(AccessType.PROPERTY) tells JPA to persist and retrieve through these methods.
Finally, you'll want to mark your member variable as transient as follows
#Transient
private LocalDate myDate = null;
This stops JPA from trying to persist from the field as well.
That should accomplish what you're trying to do. It worked great for me.

Ahamed, you mentioned it wasn't working for you. Additionally you need to override the initialize method of the converter to define the desired field type:
#Override
public void initialize(DatabaseMapping mapping, Session session) {
((AbstractDirectMapping) mapping)
.setFieldClassification(java.sql.Timestamp.class);
}

The following is a working example based on the answers available in the topic
Basically the easiest approach is to use EclipseLink #Converter for a DateTime field in your Entity.
The converter usage looks as follows:
import org.eclipse.persistence.annotations.Convert;
import org.eclipse.persistence.annotations.Converter;
import javax.persistence.*;
import org.joda.time.DateTime;
#Entity
public class YourEntity {
#Converter(name = "dateTimeConverter", converterClass = your.package.to.JodaDateTimeConverter.class)
#Convert("dateTimeConverter")
private DateTime date;
And the converter itself:
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.converters.Converter;
import org.eclipse.persistence.sessions.Session;
import org.joda.time.DateTime;
import java.sql.Timestamp;
public class JodaDateTimeConverter implements Converter {
private static final long serialVersionUID = 1L;
#Override
public Object convertDataValueToObjectValue(Object dataValue, Session session) {
return dataValue == null ? null : new DateTime(dataValue);
}
#Override
public Object convertObjectValueToDataValue(Object objectValue, Session session) {
return objectValue == null ? null : new Timestamp(((DateTime) objectValue).getMillis());
}
#Override
public void initialize(DatabaseMapping mapping, Session session) {
// this method can be empty if you are not creating DB from Entity classes
mapping.getField().setType(java.sql.Timestamp.class);
}
#Override
public boolean isMutable() {
return false;
}
}
I am adding this for the purpose of easy copy-and-paste solution.

Solution is here
joda-time-eclipselink-integration

Answer from Atais works well. Below an upgrade to it.
You can omit #converter annotation by registering it globally.
At persistance.xml in persitence-unit add:
<mapping-file>META-INF/xyz-orm.xml</mapping-file>
and file META-INF/xyz-orm.xml with content:
<?xml version="1.0"?>
<entity-mappings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/orm" version="2.1">
<converter class="pl.ds.wsc.storage.converter.JodaDateTimeConverter"/>
</entity-mappings>
If your config file is META-INF/orm.xml then you can omit even first step because it is default confing for all persitence units.

Related

Changing date format when sending a Response entity

We are currently using SpringBoot and PostgreSQL and we have problem with date format.
When we save or edit(sending POST request from front-end) something we need a YYYY-MM-DD format since any other type of format wont save anything to database so #JSONformat on Entity or some type of annotation like that is not possible. But when we are fetching example all users it would be nicer that we get DD-MM-YYYY in a response from server, and im not sure how to do that.
We can do it from front-end but code isn't nice so back-end solution would be better.
Thanks in advance!
You can create a mapper and map your entity object to a DTO that will be sent to your front-end.
Since you don't specify the class you are using for your dates I will use LocalDate as an example, but the same logic can be applied to your date class.
Let's say your entity class looks like that:
public class SampleEntity {
private LocalDate localDate;
}
Your DTO class will look like that:
public class SampleDto {
// the Jackson annotation to format your date according to your needs.
#DateTimeFormat(pattern = "DD-MM-YYYY")
private LocalDate localDate;
}
Then you need to create the mapper to map from SampleEntity to SampleDto and vice versa.
#Component
public class SampleMapper {
public SampleDto mapTo(final SampleEntity sampleEntity){
// do the mapping
SampleDto sampleDto = new SampleDto();
sampleDto.setLocalDate(sampleEntity.getLocalDate());
return sampleDto;
}
public SampleEntity mapFrom(final SampleDto sampleDto){
// do the mapping
SampleEntity sampleEntity = new SampleEntity();
sampleEntity.setLocalDate(sampleDto.getLocalDate());
return sampleEntity;
}
}
You can use all those in your controller like that:
#GetMapping
public ResponseEntity<SampleDto> exampleMethod() {
// service call to fetch your entity
SampleEntity sampleEntity = new SampleEntity(); // lets say this is the fetched entity
sampleEntity.setLocalDate(LocalDate.now());
SampleDto sampleDto = sampleMapper.mapTo(sampleEntity);
return ResponseEntity.ok(sampleDto);
}
With this solution, you can avoid adding Jackson annotations to your entity. Also with this solution, you can control exactly what your front-end can access. More on that here What is the use of DTO instead of Entity?

How to have ModelMapper.validate() succeed when using converters and providers instead of property mapping?

Having something like:
#Getter #Setter
public static class Entity {
private int hash;
private LocalDateTime createdTime;
}
and
#Getter #Setter
public static class DTO {
private String hash;
private String createdTime;
}
I need birectional mapping so I should be able to map Entity -> DTO -> Entity. In this example the property type happens to be LocalDateTime but could be any type that needs parsing from String or so (just to say that I am not after better way to map LocalDateTime but in general).
There are no problems in mapping. I create TypeMap, add Converter and for LocalDateTime a Provider also since it does note have public default constructor. Something like here.
If I had in my DTO also LocalDateTime createdTime(or String createdTime in my Entity) then ModelMapper.validate() would be happy. But I do not have and I need to create all the converting stuff.
All this leads to ModelMapper.validate() to complain:
Unmapped destination properties found in TypeMap[DTO -> Entity]:
org.example.test.modelmapper.validation.TestIt$Entity.setCreatedTime()
The code I currently use for validating mapping for LocalDateTime case is:
ModelMapper mm = new ModelMapper();
mm.createTypeMap(Entity.class, DTO.class);
mm.createTypeMap(DTO.class, Entity.class);
mm.createTypeMap(String.class, LocalDateTime.class)
.setPropertyProvider(localDateTimeProvider);
mm.addConverter(toStringDate);
mm.validate();
(so I am not doing any actual mapping but validating the mapping)
with
Provider<LocalDateTime> localDateTimeProvider =
new AbstractProvider<LocalDateTime>() {
#Override
public LocalDateTime get() {
return LocalDateTime.now();
}
};
and
Converter<String, LocalDateTime> toStringDate = new AbstractConverter<>() {
#Override
protected LocalDateTime convert(String source) {
return LocalDateTime.parse(source);
}
};
Ask for more details/code. I'll update question as needed
The setPropertyProvider method allows to specify a Provider to be used for providing instances of mapped properties within a TypeMap.
So when you write:
mm.createTypeMap(String.class, LocalDateTime.class)
.setPropertyProvider(localDateTimeProvider);
It does not fit the case because we are not using this provider in the mapping of a property of the String type to a property of a LocalDateTime type. It should rather be moved above to be associated with the DTO -> Entity TypeMap (The error message is by the way a good hint about that). So it should rather be.
mm.createTypeMap(DTO.class, Entity.class)
.setPropertyProvider(localDateTimeProvider);
Which makes perfect sense because we are using the provider to provide instance for the mapping of a String property of the DTO (String createdTime;) to a LocalDateTime property of the Entity (LocalDateTime createdTime;).
On the other hand the converter should be added to the ModelMapper before the corresponding provider.
Also leaving in mm.createTypeMap(String.class, LocalDateTime.class), my compiler complains that a similar typemap already exist and there is no need to create a new one. So with that I can discard it.
With these two changes, my bean looks like:
#Bean
ModelMapper demoModelMapper() {
Provider<LocalDateTime> localDateTimeProvider =
new AbstractProvider<LocalDateTime>() {
#Override
public LocalDateTime get() {
return LocalDateTime.now();
}
};
Converter<String, LocalDateTime> toStringDate = new AbstractConverter<String,
LocalDateTime>() {
#Override
protected LocalDateTime convert(String source) {
return LocalDateTime.parse(source);
}
};
ModelMapper mm = new ModelMapper();
mm.createTypeMap(Entity.class, DTO.class);
mm.addConverter(toStringDate);
mm.createTypeMap(DTO.class, Entity.class)
.setPropertyProvider(localDateTimeProvider);
mm.validate();
return mm;
}
Notice that I am calling validate() before returning the bean. This works for me. Please test and see on your side.
As in answer from alainlompo I had to move the adding of converter before the creation of type map.
But I also had to remove the provider part because it seemed to cause all string fields to be mapped as LocalDateTime so I got errors like:
org.modelmapper.MappingException: ModelMapper mapping errors:
1) The provided destination instance 2020-01-05T17:28:22.088694 is not of the required type int.
Above I think means that ModelMapper tried to populate field hash with a string representing LocalDateTime.
It seems that the provider is not needed at all. So my final code with just a converter added:
ModelMapper mm = new ModelMapper();
mm.createTypeMap(Entity.class, DTO.class);
mm.addConverter(toStringDate);
mm.createTypeMap(DTO.class, Entity.class);//.setPropertyProvider(localDateTimeProvider);
mm.validate();
This actually means that I asked a bit wrong question claiming that I need to use the provider

Update entity in redis with spring-data-redis

I'm currently using Redis (3.2.100) with Spring data redis (1.8.9) and with Jedis connector.
When i use save() function on an existing entity, Redis delete my entity and re create the entity.
In my case i need to keep this existing entity and only update attributes of the entity. (I have another thread which read the same entity at the same time)
In Spring documentation (https://docs.spring.io/spring-data/data-redis/docs/current/reference/html/#redis.repositories.partial-updates), i found the partial update feature. Unfortunately, the example in the documentation use the update() method of RedisTemplate. But this method do not exist.
So did you ever use Spring-data-redis partial update?
There is another method to update entity redis without delete before?
Thanks
To get RedisKeyValueTemplate, you can do:
#Autowired
private RedisKeyValueTemplate redisKVTemplate;
redisKVTemplate.update(entity)
You should use RedisKeyValueTemplate for make partial update.
Well, consider following docs link and also spring data tests (link) actually made 0 contribution to resulting solution.
Consider following entity
#RedisHash(value = "myservice/lastactivity")
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder
public class LastActivityCacheEntity implements Serializable {
#Id
#Indexed
#Size(max = 50)
private String user;
private long lastLogin;
private long lastProfileChange;
private long lastOperation;
}
Let's assume that:
we don't want to do complex read-write exercise on every update.
entity = lastActivityCacheRepository.findByUser(userId);
lastActivityCacheRepository.save(LastActivityCacheEntity.builder()
.user(entity.getUser())
.lastLogin(entity.getLastLogin())
.lastProfileChange(entity.getLastProfileChange())
.lastOperation(entity.getLastOperation()).build());
what if there would pop up some 100 rows? then on each update entity got to fetched and saved, quite inefficient, but still would work out.
we don't actually want complex exercises with opsForHash + ObjectMapper + configuring beans approach - it's quite hard to implement and maintain (for example link)
So we're about to use something like:
#Autowired
private final RedisKeyValueTemplate redisTemplate;
void partialUpdate(LastActivityCacheEntity update) {
var partialUpdate = PartialUpdate
.newPartialUpdate(update.getUser(), LastActivityCacheEntity.class);
if (update.getLastLogin() > 0)
partialUpdate.set("lastlastLogin", update.getLastLogin());
if (update.getLastProfileChange() > 0)
partialUpdate.set("lastProfileChange", update.getLastProfileChange());
if (update.getLastOperation() > 0)
partialUpdate.set("lastOperation", update.getLastOperation());
redisTemplate.update(partialUpdate);
}
and the thing is - it doesn't really work for this case.
That is, values getting updated but you can not query new property later on via repository entity lookup: certain lastActivityCacheRepository.findAll() will return unchanged properties.
Here's the solution:
LastActivityCacheRepository.java:
#Repository
public interface LastActivityCacheRepository extends CrudRepository<LastActivityCacheEntity, String>, LastActivityCacheRepositoryCustom {
Optional<LastActivityCacheEntity> findByUser(String user);
}
LastActivityCacheRepositoryCustom.java:
public interface LastActivityCacheRepositoryCustom {
void updateEntry(String userId, String key, long date);
}
LastActivityCacheRepositoryCustomImpl.java
#Repository
public class LastActivityCacheRepositoryCustomImpl implements LastActivityCacheRepositoryCustom {
#Autowired
private final RedisKeyValueTemplate redisKeyValueTemplate;
#Override
public void updateEntry(String userId, String key, long date) {
redisKeyValueTemplate.update(new PartialUpdate<>(userId, LastActivityCacheEntity.class)
.set(key, date));
}
}
And finally working sample:
void partialUpdate(LastActivityCacheEntity update) {
if ((lastActivityCacheRepository.findByUser(update.getUser()).isEmpty())) {
lastActivityCacheRepository.save(LastActivityCacheEntity.builder().user(update.getUser()).build());
}
if (update.getLastLogin() > 0) {
lastActivityCacheRepository.updateEntry(update.getUser(),
"lastlastLogin",
update.getLastLogin());
}
if (update.getLastProfileChange() > 0) {
lastActivityCacheRepository.updateEntry(update.getUser(),
"lastProfileChange",
update.getLastProfileChange());
}
if (update.getLastOperation() > 0) {
lastActivityCacheRepository.updateEntry(update.getUser(),
"lastOperation",
update.getLastOperation());
}
all credits to Chris Richardson and his src
If you don't want to type your field names as strings in the updateEntry method, you can use use the lombok annotation on your entity class #FieldNameConstants. This creates field name constants for you and then you can access your field names like this:
...
if (update.getLastOperation() > 0) {
lastActivityCacheRepository.updateEntry(update.getUser(),
LastActivityCache.Fields.lastOperation, // <- instead of "lastOperation"
update.getLastOperation());
...
This makes refactoring the field names more bug-proof.

JPA AttributeConverter is being ignored

I am trying to use an AttributeConverter to store the new Java 8 ZonedDateTime values in a MySQL database (DATETIME field) using Hibernate 4.3.0.
When I try to execute an update I get this error:
...Data truncation: Incorrect datetime value: '\xAC\xED\x00\x05sr\x00\x0Djava.time.Ser\x95]\x84\xBA\x1B"H\xB2\x0C\x00\x00xpw\x0D\x06\x00\x00\x07\xE0\x02\x11\x0B&\xEE\xE0\x08\x' for column...
I have read many SO answers saying to use the converter approach, so I wrote one:
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import java.sql.Date;
import java.time.LocalDate;
import java.time.ZonedDateTime;
#Converter(autoApply = true)
public class ZonedDateTimeConverter implements AttributeConverter<ZonedDateTime, Date> {
#Override
public Date convertToDatabaseColumn(ZonedDateTime zonedDateTime) {
if (zonedDateTime == null) return null;
return java.sql.Date.valueOf(zonedDateTime.toLocalDate());
}
#Override
public ZonedDateTime convertToEntityAttribute(Date date) {
if (date == null) return null;
LocalDate localDate = date.toLocalDate();
return ZonedDateTime.from(localDate);
}
}
... but it never gets called.
I have even added jpaProperties.put("hibernate.archive.autodetection", "class, hbm"); to my jpaProperties, but no luck. The ZonedDateTimeConverter class is in the same package as my Entities, so it should get scanned.
What am I missing here?
Reading JPA 2.1 spec :
"The conversion of all basic types is supported except for the following: Id attributes (including the
attributes of embedded ids and derived identities), version attributes, relationship attributes, and
attributes explicitly annotated as Enumerated or Temporal or designated as such in the XML descriptor."
any chance your ZonedDateTime field is annotated with #Id or #Temporal in your entity ?
OK, this was my mistake. Part of my application is managed by Hibernate, but part of it is NOT. The object I'm having problems with is NOT managed by Hibernate, so it makes sense that the JPA stuff isn't getting called.

Modify entity before flush

Is it possible to update some column before every flush to database? I have modifiedOn and modifiedBy columns and want to update them on every DB update, similar to DB trigger. Is it possible with JPA?
Disclaimer: The following works only with Hibernate / JPA.
You can update the modifiedOn property like this with JPA:
public class Entity {
private Date modifiedOn;
#PreUpdate
#PrePersist
public void updateModified() {
modifiedOn = new Date();
}
}
As for the modifiedBy, it is a little bit trickier since the JPA spec discourages references to other entities in the lifecycle callback methods. Furthermore, you would need some knowledge of the current user, which probably belongs to the service layer.
You could use an EntityListener like this (however, this still uses the callback methods)
#Entity
#EntityListeners({MyListener.class})
public class MyEntity {
Date modifiedOn;
User modifiedBy;
...
}
An in the EntityListener:
public class MyListener {
CurrentUserProvider provider; // Implement this and make sure it is set
#PreUpdate
#PrePersist
public void updateModifier(MyEntity entity) {
entity.setModifiedOn(new Date());
entity.setModifiedBy(provider.getCurrentUser());
}
}
Obviously JPA has Entity callbacks defined in the spec such as #PrePersist, #PreUpdate. Simple revision of the spec would give you more details

Categories