How to disply timdstamp column with specific format in Spring Data JPA - spring

I am using Spring Data JPA and I have a table like below:
public class Apk {
#Temporal(TemporalType.TIMESTAMP)
#Column
private java.util.Date creationTime;
}
My DBMS is MySQL5.x and the above column is defined datetime type in it. I just call findAl() method in a repository class extends PaginAndSortingRepository.
public interface ApksRepository extends PaginAndSortingRepository<Apk, Long>{
}
public class ApksServiceImpl implements ApksService {
public PagingRes<Apk> findAll(PageInfo pageInfo){
PaginRes<Apk> result = new PagingRes<Apk>();
Page page = apksRepos.findAll(pageInfo.toPageRequest());
result.fromPage(page);
return result;
}
}
public class PageInfo {
private int page;
private int rp;
private String sortname;
private String sortorder;
private String query;
private String qtype;
//getters and setters
public PageRequest toPageRequest() {
Sort.Direction direction = Sort.Direction.ASC;
if (sortorder!=null && "DESC".equals(sortorder))
direction = Sort.Direction.DESC;
return new PageRequest(page-1, rp, direction, sortname);
}
}
public class PagingRes<T> {
private long total;
private int page;
private int rowPerPage;
private List<T> rows;
//getters and setters
public PagingRes<T> fromPage(Page page) {
this.page = page.getNumber();
this.rowPerPage = page.getSize();
this.total = page.getTotalElements();
this.rows = page.getCotent();
return this;
}
}
And I am trying to display data in the table including the column but when I did it, the column is shown as long type. I wan to display the column in the fomat 'dd-MM-yyyy hh:mm:ss'. How can I do this?
Thanks in Advance.

I knew that Jackson mapper for JSON response were giving weird result. So after searching, I used JsonSerializer to fix it like below:
Entity class
#JsonSerialize(using=AuDateSerializer.class)
private java.util.Date eventTime;
Custom Serializer
public class AuDateSerializerextends JsonSerializer<Date> {
#Override
public void serialize(Date value, JsonGenerator gen, SerializerProvider provider) throws IOException, JsonProcessingException {
DateFormat formatter = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
jgen.writeString(formatter.format(value));
}
}
Now it works fine. Thanks.

Related

Jackson: Multiple Serializers on the same entity when differents Rest EndPoint are called

I'm trying to avoid using the DTO antipattern when different EndPoint are called, where each returns a distinct representation of the same entity. I'd like to take advantage of the serialization that Jackson performs when I return the entity in the Rest EndPoint. This means that serialization is only done once and not twice as it would be with a DTO (entity to DTO and DTO to Json):
EndPoints example:
#GetMapping("/events")
public ResponseEntity<List<Event>> getAllEvents(){
try {
List<Event> events = (List<Event>) eventsRepository.findAll();
return new ResponseEntity<List<Event>>(
events, HttpStatus.OK);
}catch(IllegalArgumentException e) {
return new ResponseEntity<List<Event>>(HttpStatus.BAD_REQUEST);
}
}
#GetMapping("/events/{code}")
public ResponseEntity<Event> retrieveEvent(#PathVariable String code){
Optional<Event> event = eventsRepository.findByCode(code);
return event.isPresent() ?
new ResponseEntity<Event>(event.get(), HttpStatus.OK) :
new ResponseEntity<Event>(HttpStatus.BAD_REQUEST);
}
Serializer (class that extends of StdSerializer):
#Override
public void serialize(Event value, JsonGenerator gen,
SerializerProvider provider) throws IOException {
if(firstRepresentation) {
//First Representation
gen.writeStartObject();
gen.writeNumberField("id", value.getId());
gen.writeObjectField("creation", value.getCreation());
gen.writeObjectFieldStart("event_tracks");
for (EventTrack eventTrack : value.getEventsTracks()) {
gen.writeNumberField("id", eventTrack.getId());
gen.writeObjectField("startTime", eventTrack.getStartTime());
gen.writeObjectField("endTime", eventTrack.getEndTime());
gen.writeNumberField("priority", eventTrack.getPriority());
gen.writeObjectFieldStart("user");
gen.writeNumberField("id", eventTrack.getUser().getId());
gen.writeEndObject();
gen.writeObjectFieldStart("state");
gen.writeNumberField("id", eventTrack.getState().getId());
gen.writeStringField("name", eventTrack.getState().getName());
gen.writeEndObject();
}
gen.writeEndObject();
gen.writeEndObject();
}else if(secondRepresentation) {
//Second Representation
}
}
Entity:
#JsonSerialize(using = EventSerializer.class)
#RequiredArgsConstructor
#Getter
#Setter
public class Event implements Comparable<Event>{
private Long id;
#JsonIgnore
private String code;
private Timestamp creation;
#NonNull
private String description;
#JsonUnwrapped
#NonNull
private EventSource eventSource;
#NonNull
private String title;
#NonNull
private Category category;
#NonNull
#JsonProperty("event_tracks")
private List<EventTrack> eventsTracks;
#JsonProperty("protocol_tracks")
private List<ProtocolTrack> protocolTracks;
public void addEventTrack(#NonNull EventTrack eventTracks) {
eventsTracks.add(eventTracks);
}
#JsonIgnore
public EventTrack getLastEventTrack() {
return eventsTracks.get(eventsTracks.size() - 1);
}
#JsonIgnore
public int getLastPriority() {
return getLastEventTrack().getPriority();
}
public void generateUUIDCode() {
this.code = UUID.randomUUID().toString();
}
#Override
public int compareTo(Event o) {
return this.getLastPriority() - o.getLastPriority();
}
}
So, so far I have been able to serialize a representation type with a class that extend of StdDeserializer, but this doesn't give me the flexibility to extend the representations of the same entity attributes in multiple ways. Although I've tried it with Json annotations, but I realize that the more representations the entity class has, it can get very complex, something that it should be simple. Maybe some idea how I could do it.
Thank you.
If you want to define multiple representations of the same bean you could use Jackson JsonView.
With json views you can set different strategies to define which property will be serialized in the response and so use different views by endpoint.
Documentation here : https://www.baeldung.com/jackson-json-view-annotation
Just don't forget that you doing REST here....avoid expose too many representations of the same resource

Generic Search and Filter by dynamic fields for Criteria (Global Search)

I have a scenario where I need to add Criteria to perform search and filter in Spring using mongoTemplate.
Scenario:
Lets say I have Student, Course and PotentialStudent. and I have to define only certain fields to be used for search and filter purpose. For PotentialStudent, it contains both Student and Course information that is collected before all required information is gathered to be filled to Student and Course.
Search Fields are the fields to be used for searching either of the fields. For example: get values matching in either courseName or courseType in Course.
Filter is to be used to filter specific fields for matching multiple values and the values to be filtered on field is set on FilterParams. Meaning, if I get values in FilterParams.studentType then for PotentialStudent I should
add Criteria to search inside PotentialStudent's student.type for list of values whereas if for Student add Criteria to search in Student's type.
public abstract class Model {
#Id
protected String id;
#CreatedDate
protected Date createdDateTime;
#LastModifiedDate
protected Date modifiedDateTime;
protected abstract List<String> searchFields();
protected abstract Map<String, String> filterFields();
}
#Getter
#Setter
#Document("student")
public class Student extends Model {
private String firstName;
private String lastName;
private String address;
private StudentType type;
#Override
protected List<String> searchFields() {
return Lists.newArrayList("firstName","lastName","address");
}
#Override
protected Map<String, String> filterFields() {
Map<String, String> filterMap = Maps.newHashMap();
filterMap.put("studentType", "type");
return filterMap;
}
}
#Getter
#Setter
#Document("course")
public class Course extends Model {
private String courseName;
private String courseType;
private int duration;
private Difficulty difficulty;
#Override
protected List<String> searchFields() {
return Lists.newArrayList("courseName","courseType");
}
#Override
protected Map<String, String> filterFields() {
Map<String, String> filterMap = Maps.newHashMap();
filterMap.put("courseDifficulty", "difficulty");
return filterMap;
}
}
#Getter
#Setter
#Document("course")
public class PotentialStudent extends Model {
private Student student;
private Course course;
#Override
protected List<String> searchFields() {
return Lists.newArrayList("student.firstName","student.lastName","course.courseName");
}
#Override
protected Map<String, String> filterFields() {
Map<String, String> filterMap = Maps.newHashMap();
filterMap.put("studentType", "student.type");
filterMap.put("courseDifficulty", "course.difficulty");
return filterMap;
}
}
}
public class FilterParams {
private List<StudentType> studentTypes;
private List<Difficulty> difficulties;
}
public class PageData<T extends Model> {
public void setPageRecords(List<T> pageRecords) {
this.pageRecords = pageRecords;
}
private List<T> pageRecords;
}
//Generic Search Filter Implementation Class
public class GenericSearchFilter {
public <T extends Model> PageData getRecordsWithPageSearchFilter(Integer page, Integer size, String sortName, String sortOrder, String value, FilterParams filterParams, Class<T> ormClass) {
PageRequestBuilder pageRequestBuilder = new PageRequestBuilder();
Pageable pageable = pageRequestBuilder.getPageRequest(page, size, sortName, sortOrder);
Query mongoQuery = new Query().with(pageable);
//add Criteria for the domain specified search fields
Criteria searchCriteria = searchCriteria(value, ormClass);
if (searchCriteria != null) {
mongoQuery.addCriteria(searchCriteria);
}
//Handle Filter
query.addCriteria(Criteria.where(filterFields().get("studentType")).in(filterParams.getStudentTypes()));
query.addCriteria(Criteria.where(filterFields().get("courseDifficulty")).in(filterParams.getDifficulty()));
List<T> records = mongoTemplate.find(mongoQuery, ormClass);
PageData pageData = new PageData();
pageData.setPageRecords(records);
return pageData;
}
private <T extends BaseDocument> Criteria searchCriteria(String value, Class<T> ormClass) {
try {
Criteria orCriteria = new Criteria();
if (StringUtils.isNotBlank(value)) {
BaseDocument document = ormClass.getDeclaredConstructor().newInstance();
Method method = ormClass.getDeclaredMethod("searchFields");
List<String> records = (List<String>) method.invoke(document, null);
Criteria[] orCriteriaArray = records.stream().map(s -> Criteria.where(s).regex(value, "i")).toArray(Criteria[]::new);
orCriteria.orOperator(orCriteriaArray);
}
return orCriteria;
} catch (Exception e) {
log.error(e.getMessage());
}
return null;
}
}
Given this scenario, my question is how to handle filter cases in better and dynamic way and how to implement a Global search if needed to search in all Document types for specified fields on each types.

How to reference a properties value inside the schema attribute of an entity?

There is an entity :
#Entity
#Table(name = "ITMMASTER" , schema = "TOMCTB")
public class Article {
#Id
#Column(name = "ITMREF_0")
private String code_article;
#Column(name = "ACCCOD_0")
private String acccod;
public String getCode_article() {
return code_article;
}
public void setCode_article(String code) {
this.code_article = code;
}
public String getAcccod() {
return acccod;
}
public void setAcccod(String acccod) {
this.acccod = acccod;
}
}
I want to make the schema attribute to be dynamic depending on a properties file property value , for example : env.schema = TOMEXPL.
How to achieve that ?
I didn't tried it but I guess this should work.
public class DynamicNamingStrategy extends DefaultNamingStrategy {
#Value("db.table.name")
private String name;
#Override
public String tableName(String tableName) {
return name;
}
...
}
SessionFactory sessionFactory;
Configuration config = new AnnotationConfiguration()
.configure("hibernate.cfg.xml")
.setNamingStrategy( new DynamicNamingStrategy() );
sessionFactory = config.buildSessionFactory();
session = sessionFactory.openSession();

Spring Data JPA JpaRepository only uses No Arg Constructor

I have this simple REST API that i created with Spring Boot.
In this app, I have a a POJO called Expense with 4 fields. I have a no Argument constructor and another constructor that takes only two inputs. One String value "item" and one Integer value "amount". The date is set using the LocalData.now() method and the id is set automatically in a MySql db running in the server.
Here's my Entity class
#Entity
public class Expense {
#Id
#GeneratedValue (strategy = GenerationType.AUTO)
private Integer id;
private String date;
private String item;
private Integer amount;
//No Arg Construction required by JPA
public Expense() {
}
public Expense(String item, Integer amount) {
this.date = LocalDate.now().toString();
this.item = item;
this.amount = amount;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public String getItem() {
return item;
}
public void setItem(String item) {
this.item = item;
}
public Integer getAmount() {
return amount;
}
public void setAmount(Integer amount) {
this.amount = amount;
}
}
I have another class with RestController annotation where i have set a method to post Expense object with a post method using Request Mapping annotation.
#RestController
public class ExpController {
private ExpService expService;
private ExpenseRepo expenseRepo;
#Autowired
public ExpController(ExpService expService, ExpenseRepo expenseRepo) {
this.expService = expService;
this.expenseRepo = expenseRepo;
}
#RequestMapping(path = "/addExp", method=RequestMethod.POST)
public void addExp(Expense expense){
expenseRepo.save(expense);
}
}
Now finally i am using PostMan to make the HTTP Post Request. I have made a simple Json Format text to send Item and Amount
{
"item":"Bread",
"amount": 75
}
After I make the post request, all i can see is that a new Entry is created but all values are set to null.
I have done some experimentation and found out that the expenseRepo.save(expense) method is only using the default no Arg constructor to save the data. But it's not using the second constructor that takes the two parameters that I am passing through Postman
How to solve this issue. Please help
Change your controller method like this
#RequestMapping(path = "/addExp", method=RequestMethod.POST)
public void addExp(#RequestBody Expense expense){
expenseRepo.save(expense);
}
You need to use #RequestBody

Serializing an entity returns junk value for time stamp in Spring

I'm new to Spring, so I apologize if the question is basic.
I'm writing an application which fetches data from postgres and returns as JSON. The table structure is as below:
table student{
id int,
created_time Timestamp
}
I have an entry in the table as:
id | created_time
---+---------------------------
1 | 2015-02-19 23:58:23.579761
I'm having my entity object as:
package main.java;
import java.sql.Timestamp;
import javax.persistence.Entity;
import javax.persistence.Id;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
#Entity
#JsonIgnoreProperties(ignoreUnknown = true)
public class Student {
#Id
protected int id;
protected Timestamp created_time;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Timestamp getCreated_time() {
return created_time;
}
public void setCreated_time(Timestamp created_time) {
this.created_time = created_time;
}
}
And this is how I'm returning the data:
#RestController
#RequestMapping(value = "/student", produces = MediaType.APPLICATION_JSON_VALUE)
public class StudentController {
#Autowired
protected StudentRepository studentRepository;
#RequestMapping(value = "/{id}")
public Student student(#PathVariable int id) {
return studentRepository.findOne(id);// uses the findOne() method inherited from CrudRepository
}
However, the json I get is:
{"id":1,"created_time":1424419103579}
Why is it returning junk value for time stamp? How do I get the original value in same format as is in the table?
Thanks in advance.
This is the default behaviour.
By default Jackson uses default strategy to determine the date formatting when serializating into JSON, so you get date in milliseconds.
To override this with custom behavior, you can use #JsonSerialize annotation.
You will need a custom Date serializer, for example
#Component
public class JsonDateSerializer extends JsonSerializer<Timestamp> {
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
#Override
public void serialize(Timestamp date, JsonGenerator gen, SerializerProvider provider)
throws IOException, JsonProcessingException {
String formattedDate = dateFormat.format(date);
gen.writeString(formattedDate);
}
}
And then, in your entity you can do
#Entity
#JsonIgnoreProperties(ignoreUnknown = true)
public class Student {
#Id
protected int id;
protected Timestamp created_time;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#JsonSerialize(using=JsonDateSerializer.class)
public Timestamp getCreated_time() {
return created_time;
}
public void setCreated_time(Timestamp created_time) {
this.created_time = created_time;
}
}
P.S. If you named your field as created_time only to be able to serialize your property into json having created_time as a name, better to use instead #JsonProperty.
#JsonProperty("created_time")
#JsonSerialize(using=JsonDateSerializer.class)
protected Timestamp createdTime;

Resources