JOOQ MappingException when using multiset select - java

Hi I am attempting to perform a jooq multiset select to map a one to many relationship into my POJOs.
org.jooq.exception.MappingException: An error ocurred when mapping record to class io.comp.biz.core.task.model.TaskDetails
at org.jooq.impl.DefaultRecordMapper$MutablePOJOMapper.map(DefaultRecordMapper.java:916)
at org.jooq.impl.DefaultRecordMapper.map(DefaultRecordMapper.java:610)
at org.jooq.impl.AbstractRecord.into(AbstractRecord.java:804)
at org.jooq.impl.ResultQueryTrait.fetchOneInto(ResultQueryTrait.java:530)
at io.comp.biz.core.task.query.TaskQueryRepository.findTaskDetailsById(TaskQueryRepository.java:172)
.....
Caused by: org.jooq.exception.DataTypeException: No Converter found for types org.jooq.Converters$UnknownType and java.util.List
at org.jooq.impl.Tools.converterOrFail(Tools.java:1216)
at org.jooq.impl.Tools.converterOrFail(Tools.java:1225)
at org.jooq.impl.AbstractRecord.get(AbstractRecord.java:351)
at org.jooq.impl.DefaultRecordMapper$MutablePOJOMapper.map(DefaultRecordMapper.java:943)
at org.jooq.impl.DefaultRecordMapper$MutablePOJOMapper.map(DefaultRecordMapper.java:873)
... 112 more
These are my POJOS:
#NoArgsConstructor
#AllArgsConstructor
#Builder
#Data
public class TaskDetails {
private UUID id;
private UUID tenantId;
private String title;
private String description;
private LocalDateTime createdAt;
private String priority;
private Long numericId;
private UUID assigneeId;
private UUID statusId;
private UUID reporterId;
private Boolean deleted;
private LocalDateTime modifiedAt;
private UUID modifiedBy;
private List<TaskDate> taskDates;
}
#NoArgsConstructor
#AllArgsConstructor
#Builder
#Data
public class TaskDate {
private UUID id;
private UUID taskId;
private String type;
private LocalDate date;
private LocalDateTime createdAt;
private String createdBy;
private String modifiedBy;
private LocalDateTime modifiedAt;
}
This is my jooq query:
public Optional<TaskDetails> findTaskDetailsById(UUID taskId) {
TaskDetails taskDetails =
dslContext.select(
TASK.ID,
TASK.TENANT_ID,
TASK.TITLE,
TASK.DESCRIPTION,
TASK.CREATED_AT,
TASK.PRIORITY,
TASK.NUMERIC_ID,
TASK.ASSIGNEE_ID,
TASK.STATUS_ID,
TASK.REPORTER_ID,
TASK.DELETED,
TASK.MODIFIED_AT,
TASK.MODIFIED_BY,
multiset(dslContext.select(TASK_DATE.ID,
TASK_DATE .TASK_ID,
TASK_DATE.TYPE,
TASK_DATE.DATE,
TASK_DATE.CREATED_AT,
TASK_DATE.CREATED_BY,
TASK_DATE.MODIFIED_BY,
TASK_DATE.MODIFIED_AT).from(TASK_DATE).where(TASK_DATE.TASK_ID.eq(TASK.ID))).as("taskDates").convertFrom(r -> r.map(mapping(this::mapTaskDate))))
.from(TASK)
.where(TASK.ID.in(taskId)).fetchOneInto(TaskDetails.class);
return Optional.ofNullable(taskDetails);
}
private TaskDate mapTaskDate(UUID id, UUID taskId, String type, LocalDate taskDate, LocalDateTime createdAt, String createdBy, String modifiedBy, LocalDateTime modifiedAt) {
return TaskDate.builder()
.id(id)
.taskId(taskId)
.type(type)
.date(taskDate)
.createdAt(createdAt)
.createdBy(createdBy)
.modifiedBy(modifiedBy)
.modifiedAt(modifiedAt)
.build();
}
spring-boot: (2.6.2) with spring-boot-starter-jooq dependency
jooq version: 3.15.1
From debugging the code i can see that jooq's AbstractRecord.java is throwing the exception when attempting to get the type of taskDates but I am unsure how to resolve this exception

jOOQ added MULTISET support in version 3.15.0, just one patch release before the version that you're using. Since then, there had been a ton of bugfixes in this area, see the release notes. Until version 3.17.6, the release notes reference the word "MULTISET" 65 times. I vaguely recall a bug like this.
For example, there's #12208, which was fixed in jOOQ 3.16.0 and 3.15.3. You should definitely upgrade, at least to the latest 3.15 patch release, possibly even to 3.17.

Related

Is which the best request parameter mapping strategy in Spring Framework?

I am Java web developer, usually develop Spring MVC.
I have been using #RequestMapping or #RequestParam for mapping to hashMap at Controller.
It is a terrible way. I should always cast type when using value.
But nowadays I try to use #ModelAttribute to write clean code at Controller.
However, there are some problem.
case 1) make DTO for each EndPoint.
We can make DTO for each EndPoint, but DTO will have many duplicated property.
#Getter
#Setter
#ToString
class GetUserInfoDTO {
private String id;
private String name;
}
#Getter
#Setter
#ToString
class PostUserInfoDTO {
private String name;
private Integer age;
private String address;
private String gender;
private String email;
private Date joinDate;
}
in controller,
#GetMapping("/user")
public ResultDTO getUserInfo (#ModelAttribute GetUserInfoDTO){
...
return ResultDTO;
}
#PostMapping("/user")
public ResultDTO postUserInfo (#ModelAttribute PostUserInfoDTO){
...
return ResultDTO;
}
In this case, we can apply independent validation strategy for each End-Point.
for example..
#Getter
#Setter
#ToString
class GetUserInfoDTO {
#NotNull
private String id;
private String name;
}
#Getter
#Setter
#ToString
class PostUserInfoDTO {
#NotNull
private String name;
#NotNull
private Integer age;
#NotEmpty
private String address;
private String gender;
private String email;
private Date joinDate;
}
like this.
But so many model classes made, and so many duplicated property exists.
case 2. make common DTO for each Controller.
We can make DTO for each Controller, and reuse them.
#Getter
#Setter
#ToString
class UserInfoDTO {
private String id;
private String name;
private Integer age;
private String address;
private String gender;
private String email;
private Date joinDate;
}
#GetMapping("/user")
public ResultDTO getUserInfo (#ModelAttribute UserInfoDTO){
//I want only id, name
...
return ResultDTO;
}
#PostMapping("/user")
public ResultDTO postUserInfo (#ModelAttribute UserInfoDTO){
...
return ResultDTO;
}
But In this case, we can only pass specific properties.
If someone send other parameter than id and name, we can't notice. ( 400 error not occur )
Code assistance can't recommend us specific properties that use at single end-point.
I don't like these cases.
In first case, I should make so many models and It's management will be so hard.
Second case, unnecessary properties exists and it hard to validate for each end-point.
Which way is the best?
Or Can you recommend another way for mapping request parameter to model object?

How to map different country/state codes to a base entity via JPA annotations?

I have an entity with the following fields:
private Date dateOfBirth;
private String cityOfBirth;
private Long birthStateCodeId;
private Long birthCountryCodeId;
private Boolean isUSCitizen;
private Long citizenshipCountryCodeId;
private String address1;
private String address2;
private String addressCity;
private Long addressStateCodeId;
private Long addressCountryCodeId;
private String postalCode;
As you can see from the above snippet, I have
2 properties (birthStateCodeId, addressStateCodeId) where I use a state code from a StateCodes table, and
3 properties (birthCountryCodeId, citizenshipCountryCodeId, and addressCountryCodeId) where I use a country code from a CountryCodes table.
Using JPA (with Hibernate as persistence provider), how do I map the above 5 properties (2 state codes and 3 country codes) to the two separate tables StateCodes and CountryCodes?
You could achieve it like this:
#Entity
public class PersonIdentification {
// primary key
#Id // and other annotations, see JPA Spec or tutorials
private long id;
// regular attributes
private Date dateOfBirth;
private String cityOfBirth;
private Boolean isUSCitizen;
private String address1;
private String address2;
private String addressCity;
private String postalCode;
#ManyToOne
private StateCode birthStateCode;
#ManyToOne
private StateCode addressStateCode;
#ManyToOne
private CountryCode birthCountryCode;
#ManyToOne
private CountryCode addressCountryCode;
#ManyToOne
private CountryCode citizenshipCountryCode;
// setter & getter methods as needed...
}
Next, define entity classes for both "Code" types as such:
#Entity
public class StateCode {
// primary key
#Id // and other annotations, see JPA Spec or tutorials
private long id;
private String code;
private String stateName;
// other attributes of interest
// setter & getter methods as needed...
}
#Entity
public class CountryCode {
// primary key
#Id // and other annotations, see JPA Spec or tutorials
private long id;
private String code;
private String countryName;
// other attributes of interest
// setter & getter methods as needed...
}
To reduce CnP code (as with the generic aspect of primary key handling (#Id) you can check this answer. It gives you detailed hints on how handle such cases more efficiently by introducing an AbstractEntity via the #MappedSuperClass annotation.
Hope it helps

Custom json (de)serialize with springboot

It`s possible to create one JsonSerialize and Deserialize with spring boot?
I put in my appliation.properties this line
spring.jackson.date-format=dd/MM/yyyy HH:mm:ss
but when I return one Date he allways returns a wrong value (yyyy-MM-dd) so I try to create one custom serialization following the http://docs.spring.io/spring-boot/docs/1.4.0.RELEASE/reference/htmlsingle/#boot-features-json-components
but don`t work.
this is my return:
#Entity
#Table(name = "view_atos_praticados", catalog="db_registro", schema="db_wsprefeituraatos")
public class ViewAtosPraticados {
#JsonIgnore
#Id
#Column(name="id")
private Integer id;
#Column(name="descricao_ato")
private String ato;
#JsonIgnore
#Column(name="livro")
private String livro;
#JsonIgnore
#Column(name="numero_ato")
private Integer nrAto;
#JsonIgnore
#Column(name="numero_registro")
private String nrRegistro;
#Column(name="dat_registro")
private Date registro;
#Column(name="ic_transmissao")
private String transmite;
Try #JsonFormat annotation:
#JsonFormat(pattern="dd/MM/yyyy HH:mm:ss")
#Column(name="dat_registro")
private Date registro;

Aggregation relationship via JPA annotations

I am trying to establish the aggregation relationship between two Java classes through JPA annotations in order to persist them into a database.
public class Ticket
{
private String ticketNo;
private Date releasedDate;
private boolean printed;
}
public class Discount
{
private String percentage;
private Date releasedDate;
private boolean printed;
}
Such as mentioned here, the aggregation relationship is unidirectional and thus, only it is necessary to map one side. From the solution given by this page, I think the solution will be:
public class Discount
{
private String percentage;
private Date releasedDate;
private boolean printed;
#ManyToOne(name="TICKET_ID")
private Ticket ticket;
}
However, in some examples of aggregation, the many side class appears inside the one side class. Thus, I am considering this too:
public class Ticket
{
private String ticketNo;
private Date releasedDate;
private boolean printed;
#OneToMany(mappedBy="ticket")
private List<Discount> discounts = new ArrayList<Discount>();
}
Which option is the proper one?
This how you map a unidirectional many-to-one relationship:
#Entity
public class Ticket {
#Id
#GeneratedValue
private Long id;
private String ticketNo;
private Date releasedDate;
private boolean printed;
// getters and setters
}
#Entity
public class Discount {
#Id
#GeneratedValue
private Long id;
private String percentage;
private Date releasedDate;
private boolean printed;
#ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "TICKET_ID") // you can rename the join column
private Ticket ticket;
// getters and setters
}
Note:
JoinColumn (foreign key in the database terminology) must be on the many side of the relationship (this is the Discount in your case).
The #Id annotations are also mandatory. In this case, the ID will be generated by the persistence provider automatically. If you are using database sequence or table or some other strategy you can redefine it.
That looks right to me. A discount has a ticket. You could also include the discounts accessible from the tickets like ticket.getDiscounts() if you need to access them in a query such as SELECT t FROM Ticket t WHERE t.discounts.percentage >= :discountPercentage.
#Entity
public class Ticket {
#Id
private String ticketNo;
private Date releasedDate;
private boolean printed;
#OneToMany(mappedBy = "ticket", fetch = FetchType.LAZY)
private List<Discounts> discounts;
}
#Entity
public class Discount {
private String percentage;
private Date releasedDate;
private boolean printed;
#ManytoOne(name="TICKET_ID")
private Ticket ticket;
}
However, I wouldn't recommend using #OneToMany as this can create problems serializing too much data to JSON if you are returning this as JSON results or just lazily loading too much data by accident. You should always be able to work with just #ManyToOne as an example if you did not put the #OneToMany association query can be SELECT t FROM Discount d INNER JOIN d.ticket t WHERE d.percentage >= :discountPercentage

Set disallowed fields in Spring Data Rest

I want to exclude certain fields from a POST to my repositories.
For example I want to set the version myself so users cannot set this field themselves.
For example in the class below.
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#CreatedDate
private LocalDateTime created;
#LastModifiedDate
private LocalDateTime lastModified;
private String name;
}
I have tried to use the #ReadOnlyProperty annotation and not having a setter for the version field. But nothing worked, users can still set the version fields themselves. I have also tried to implement a global initializer like below, but without success. The binder gets picked up though.
#ControllerAdvice
public class GlobalInitializer {
#InitBinder
public void globalBinder(WebDataBinder webDataBinder) {
webDataBinder.setDisallowedFields("name");
}
}
You should place #JsonIgnore on field and on setter, and place #JsonProperty("propertyName") on getter.
Just tested - works for me:
#JsonIgnore
#LastModifiedDate
private LocalDate lastUpdated;
#JsonProperty("lastUpdated")
public LocalDate getLastUpdated() {
return lastUpdated;
}
#JsonIgnore
public void setLastUpdated(LocalDate lastUpdated) {
this.lastUpdated = lastUpdated;
}

Categories