POJO to Entity mapping with ModelMapper - spring-boot

My POJO has a variable annotated like this
#JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSxxx")
#JsonDeserialize(using = ZonedDateTimeDeserializer.class)
private ZonedDateTime startTs;
//other private variables
My goal is to map this to an Entity for which I'm using ModelMapper and the sample code goes like this
public POJOEntity mapToPOJOEntity(POJO pojo) {
return modelMapper.map(pojo, POJOEntity.class);
}
After mapping when I look at the startTs variable in debug mode it has the desired format but when I check in the db startTs is saving as a whole ZonedDateTime object rather than the desired format I specified in POJO. Did anyone face this issue if yes can someone please help?
Value saved in cosmosDB:
Expected output: "startTs": "2023-01-12T08:58:32.452-06:00"
PS: CosmosDB, spring-boot, Java8, ModelMapper version: 2.3.8

Related

Using projection with a interface in `#Query` annotation with a native query gives "Target type is not an interface and no matching Converter found"

I have a table with a clob in an oracle 19 database which I try to fetch with a native query using a #Query annotation with a projection from a Spring boot 2.7.4 application. I get the following error message:
java.lang.UnsupportedOperationException: Cannot project jdk.proxy2.$Proxy281 implementing java.sql.Clob,org.hibernate.engine.jdbc.WrappedClob,java.io.Serializable to java.lang.String; Target type is not an interface and no matching Converter found
The query from my repository class:
#Query(
value = """
select submission_id as "submissionId", text as "textAnswer"
from answer
where answer_id = :answerId
""",
nativeQuery = true)
public MyDTO findDTO(Long answerId);
My interface which I use for the projection:
public interface MyDTO {
String getTextAnswer();
}
From my domain object annotated with #Entity:
private String textAnswer;
My testcase which reproduce which reproduce this error. If I comment out the line with a.getTextAnswer() it runs ok.
#Test
public void findFormPublishedAnswersInterfaceDTOById() {
FormPublishedAnswersInterfaceDTO a = answerRepository.findFormPublishedAnswersInterfaceDTOById(21540241L);
assertEquals("test", a.getTextAnswer());
}
I have tried different hints I found by the help of google :) like annotating private String textAnswer with #Lob, #Type(type = "text") and #Column(name = "text", columnDefinition = "CLOB") without any luck.
If you read the exception carefully, you should understand that the JDBC driver reports that the type is a Clob, so your DTO must look like this instead:
public interface MyDTO {
Clob getTextAnswer();
}

How to make a custom converter for a String field using Spring Data Elasticsearch?

I am using Spring Data Elasticsearch in my project. I have a class like this,
#Document("example")
class Example {
#Id
private String id;
private String name;
private String game;
private String restField;
}
What I need is when ever I am saving the Example object to elasticsearch I am removing a value. But when I am getting the data from elasticsearch I need that removed value to be appended.
save(Example example) {
example.setRestField(example.getRestField().replace("***", ""));
exampleRepository.save(example);
}
get(String id) {
Example example = exampleRepository.findById(id);
example.getRestField().concat("***");
return example;
}
Right now I am doing like the above way. But can we use a custom converter for this? I checked the converter examples for Spring Data Elasticsearch but those are for different different objects. How I can create a custom converter only for this particular String field restField? I don't want to apply this converter for other String fields.
Currently there is no better solution. The converters registered for Spring Data Elasticsearch convert from a class to a String and back. Registering a converter for your case would convert any String property of every entity.
I had thought about custom converters for properties before, I have created a ticket for this.
Edit 05.11.2021:
Implemented with https://github.com/spring-projects/spring-data-elasticsearch/pull/1953 and will be available from 4.3.RC1 on.

Ignore xml tags while serializing pojo fields to xml

I am using jackson library to map POJO to XML.
compile ('com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.9.0')
While serializing I need to ignore some of the fields. This is my POJO class. For example, the field lineNumber should be ignored.
#NoArgsConstructor
#AllArgsConstructor
#Getter
#XmlAccessorType(XmlAccessType.FIELD)
public class InvoiceLineItem {
#JacksonXmlProperty(localName = "LineNumber")
#XmlTransient
private Integer lineNumber;
#JacksonXmlProperty(localName = "ProductCode")
#XmlTransient
private String productCode;
#JacksonXmlProperty(localName = "ProductDescription")
#XmlTransient
private String productDescription;
}
I am using #XmlTransient with XmlAccessorType to ignore the fields. But the lineNumber field annotated with XmlTransient is not ignored while serializing.
Try adding the #JsonProperty(access = Access.WRITE_ONLY)
annotation to the lineNumber field.
Even thought it looks like a JSON thing,
the Jackson XmlMapper identifies the annotation and reacts accordingly.
Edit
The conclusion XmlMapper should support JSON serizlization is an example of the following, incorrect attempt at reasoning:
All men are mortal.
Socrates was mortal.
Therefore, all men are Socrates.
The XmlMapper is not a wrapper class around ObjectMapper.
It came after ObjectMapper and appears to share many features,
like the handling of some JSON annotation.

Spring Data Elasticsearch Repository query define date input parameter format

I am using elasticsearch 6.5.3 and Spring Boot 2.1.6 and spring-data-elasticsearch 3.2.0.M1.
I have defined the Elasticsearch configuration as:
#Bean
public ElasticsearchOperations elasticsearchTemplate() {
return new ElasticsearchRestTemplate(client(), new CustomEntityMapper());
}
public static class CustomEntityMapper implements EntityMapper {
private final ObjectMapper objectMapper;
public CustomEntityMapper() {
//we use this so that Elasticsearch understands LocalDate and LocalDateTime objects
objectMapper = new ObjectMapper()
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
.disable(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
//MUST be registered BEFORE calling findAndRegisterModules
.registerModule(new JavaTimeModule())
.registerModule(new Jdk8Module());
//only autodetect fields and ignore getters and setters for nonexistent fields when serializing/deserializing
objectMapper.setVisibility(objectMapper.getSerializationConfig().getDefaultVisibilityChecker()
.withFieldVisibility(JsonAutoDetect.Visibility.ANY)
.withGetterVisibility(JsonAutoDetect.Visibility.NONE)
.withSetterVisibility(JsonAutoDetect.Visibility.NONE)
.withCreatorVisibility(JsonAutoDetect.Visibility.NONE));
//load the other available modules as well
objectMapper.findAndRegisterModules();
}
#Override
public String mapToString(Object object) throws IOException {
return objectMapper.writeValueAsString(object);
}
#Override
public <T> T mapToObject(String source, Class<T> clazz) throws IOException {
return objectMapper.readValue(source, clazz);
}
}
I have a repository with a method defined as:
List<AccountDateRollSchedule> findAllByNextRollDateTimeLessThanEqual(final LocalDateTime dateTime);
And the POJO AccountDateRollSchedule defines that field as:
#Field(type = FieldType.Date, format = DateFormat.date_hour_minute)
#DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm")
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm")
private LocalDateTime nextRollDateTime;
I see my index properly has that field created as declared and expected:
"nextRollDateTime": {
"type": "date",
"format": "date_hour_minute"
}
Also querying the index returns the field formatted as expected:
"nextRollDateTime" : "2019-06-27T13:34"
My repository query would translate to:
{"query":
{"bool" :
{"must" :
{"range" :
{"nextRollDateTime" :
{"from" : null,
"to" : "?0",
"include_lower" : true,
"include_upper" : true
}
}
}
}
}
}
But passing any LocalDateTime input to the method does NOT respect the format defined for the field, the FULL format is always used instead. Invoking:
findAllByNextRollDateTimeLessThanEqual(LocalDateTime.now(ZoneOffset.UTC).truncatedTo(ChronoUnit.MINUTES));
gives me the following exception (any #DateTimeFormat or #JsonFormat annotation on the method parameter in the repository is ignored):
Unrecognized chars at the end of [2019-07-22T09:07:00.000]: [:00.000]
If I instead change the repository method to accept a String and pass a String formatted exactly as expected as input to it, it works no problem.
Is it possible to somehow define the format used for the date parameter passed in input to the repository method or have Spring use the one configured on the field itself?
I would like not to wrap that method for a simple conversion like this (I did and it works), and I would also like to avoid using long type for the date field
Thanks and cheers
For reference, I also open issue on Spring JIRA
These problems are one reason why we move away from using and exposing the JacksonMapper in Spring Data Elasticsearch. From version 4.0 on all you need on your property is the one annotation:
#Field(type = FieldType.Date, format = DateFormat.date_hour_minute)
private LocalDateTime nextRollDateTime;
This will then be used in writing the index mappings, when entities are indexed and retrieved, and also when repository method and queries are processed.
But for the 3.2.x version you will have to use a workaround like the wrapping you mentioned.

BeanPropertyRowMapper does not understand joda time types anymore since upgrading to spring boot 1.4 / spring cloud camden

I have a Spring Batch Job that defines a JdbcPagingItemReader with a BeanPropertyRowMapper :
JdbcPagingItemReader<RawNotice> reader = new JdbcPagingItemReader<>();
final SqlPagingQueryProviderFactoryBean sqlPagingQueryProviderFactoryBean = new SqlPagingQueryProviderFactoryBean();
sqlPagingQueryProviderFactoryBean.setDataSource(dataSource);
sqlPagingQueryProviderFactoryBean.setSelectClause("select *");
sqlPagingQueryProviderFactoryBean.setFromClause("from a_table");
sqlPagingQueryProviderFactoryBean.setWhereClause("state = :state");
sqlPagingQueryProviderFactoryBean.setSortKey("id");
Map<String, Object> parameters = new HashMap<>();
parameters.put("state", "interesting_state");
reader.setQueryProvider(sqlPagingQueryProviderFactoryBean.getObject());
reader.setDataSource(dataSource);
reader.setPageSize(10);
// The line below is the interesting one
reader.setRowMapper(new BeanPropertyRowMapper<>(MyEntity.class));
reader.setParameterValues(parameters);
return reader;
This used to work fine, but since we upgraded to spring boot 1.4 and spring cloud Camden, it throws an exception :
org.springframework.beans.ConversionNotSupportedException: Failed to convert property value of type [java.sql.Timestamp] to required type [org.joda.time.LocalDateTime] for property 'ADateColumn'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.sql.Timestamp] to required type [org.joda.time.LocalDateTime] for property 'ADateColumn': no matching editors or conversion strategy found
The column ADateColumn is declared as a Joda LocalDateTime and stored as a java.sql.Timestamp in the database.
I'm quite aware I could add my own joda converters to the BeanPropertyRawMapper conversionService for example, or create a PropertyEditor that understands Java LocalDateTime, but that looks rather like a configuration problem, like something isn't being registered right.
Anybody with a solution/suggestion to fix this problem ?
Thanks !
This is the part of the entity that poses problem :
#Entity
#EqualsAndHashCode(of = { "..." })
#ToString(of = { .... })
public class MyEntity {
#Getter
#Setter
#Id
#GeneratedValue
private Long id;
#Getter
#Version
#Column(nullable = false)
private int version;
//<--- snip --->
#Getter
#Setter
#Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDateTime")
private LocalDateTime aDateColumn;
}
Hibernate is version 4.3.11.Final
JPA is version 2.1 with Hibernate Entity Manager 4.3.11.Final
So I finally ended up creating my own BeanPropertyRowMapper with custom Joda converters :/

Resources