Use Java 8 optional with Mapstruct - java

I have these two classes:
public class CustomerEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String firstName;
private String lastName;
private String address;
private int age;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
and
public class CustomerDto {
private Long customerId;
private String firstName;
private String lastName;
private Optional<String> address;
private int age;
}
The problem is that Mapstruct doesn't recognize the Optional variable "address".
Anyone has an idea how to solve it and let Mapstruct map Optional fields?

This is not yet supported out of the box by Mapstruct. There is an open ticket on their Github asking for this functionality: https://github.com/mapstruct/mapstruct/issues/674
One way to solve this has been added in the comments of the same ticket: https://github.com/mapstruct/mapstruct/issues/674#issuecomment-378212135
#Mapping(source = "child", target = "kid", qualifiedByName = "unwrap")
Target map(Source source);
#Named("unwrap")
default <T> T unwrap(Optional<T> optional) {
return optional.orElse(null);
}
As pointed by #dschulten, if you want to use this workaround while also setting the option nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS, you will need to define a method with the signature boolean hasXXX() for the field XXX of type Optional inside the class which is the mapping source (explanation in the docs).

Related

ModelMapper - convert a Date inside a Collection<Object> to String (java)

I've searched a lot in this forum and other websites, but I'm still stuck with my problem.
I'm actually using modelmapper to convert an entity to a DTO.
Here is the Entity :
#Entity
public class Candidate implements Serializable {
#Id
#GeneratedValue (strategy=GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column (name = "name")
private String lastname;
#Column (name = "firstname")
private String firstname;
#Column (name = "phone")
private String phoneNumber;
#Column (name = "mail")
private String email;
#Column (name = "title")
private int title;
#OneToMany (mappedBy = "candidateId")
private Collection<Candidature> Interviews;
Here is Candidature Entity (that you find in the first Entity's collection):
public class Candidature implements Serializable {
#Id
#NotBlank
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id")
private Long id;
#ManyToOne (fetch = FetchType.LAZY)
#JoinColumn (name = "candidat_id")
private Candidate candidateId;
#Column(name = "interview")
#Temporal (TemporalType.DATE)
private Date dateInterview;
#Column(name ="status")
private String status;
And here is the DTO :
public class CandidateDTO {
private Long id;
private String lastname;
private String firstname;
private String phoneNumber;
private String email;
private String title;
private String dateLastInterview;
As you can see, there are some differences.
The problem I face is that the last attribute of DTO (dateLastInterview) comes from the Collection<Candidature> and more precisely it must be the last dateInterview converted into String.
Convert a Date into String is not a problem. Getting the last item of a Collection neither.
But I can't make it work with modelMapper.
Here is a sample code I tried :
modelMapper = new ModelMapper();
Converter<Candidate, CandidateDTO> converter = new Converter<Candidate, CandidateDTO>()
{
#Override
public CandidateDTO convert(MappingContext<Candidate, CandidateDTO> mappingContext) {
Candidate candidate = mappingContext.getSource();
CandidateDTO cdto = new CandidateDTO();
List<Candidature> list = (List) candidate.getInterviews();
Date date = list.get(list.size()-1).getDateInterview();
DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
String dateInterviewConverted = df.format(date);
mappingContext.getDestination().setTitle(mappingContext.getSource().getTitle());
mappingContext.getDestination().setDateLastInterview(dateInterviewConverted);
return cdto;
}
};
modelMapper.createTypeMap(Candidate.class, CandidateDTO.class).setConverter(converter);
(and I tried, instead of the last line above : modelMapper.addConverter(converter); but same result)
But it doesn't work, I get all attributes at null.
I previously succeded using
map().setTitle(source.getTitle());
map().setDateLastInterview(dateInterviewConverted);
And then converting the Date to String in my DTO "set" method, but it seems that it shouldn't be here, but into the ModelMapper class or the class that is using it.
Do you have an idea ? I'm new with modelMapper, and I keep browsing google and I can't find (or maybe understand ?) any response that might help me.
Thanks
Ok I think I succeded.
Using the converter was the right thing, but I wasn't using it correctly. For the converter, the two objets that you put inside <> are the ones of the attributes concerned by the converter.
For example, for the first converter, I wanted to parameter the conversion of the Collection (coming from an object Candidate) to become a String (to match the attribute of the DTO).
So then you only have to create a PropertyMap with the Class and ClassDTO, and in the configure() method you only mention the attributes that will use special parameters (the other ones are correct since they respect the standard mapping).
Converter<Collection<Candidature>, String> convertLastDateToString = new Converter<Collection<Candidature>, String>() {
public String convert(MappingContext<Collection<Candidature>, String> context) {
List<Candidature> candidatureList = (List)context.getSource();
String dateInterviewConverted = "";
if (candidatureList.size() > 0) {
Date lastInterview = candidatureList.get(0).getDateInterview();
for (int i = 0; i < candidatureList.size(); i++) {
if (candidatureList.get(i).getDateInterview().after(lastInterview)) {
lastInterview = candidatureList.get(i).getDateInterview();
}
}
// converts the Date to String
DateFormat df = new SimpleDateFormat(DATE_FORMAT);
dateInterviewConverted = df.format(lastInterview);
}
return dateInterviewConverted;
}
};
// allows custom conversion for Title attribute
// the source (Candidate) has a title attribute in int type
// the destination (CandidateDTO) has a title attributes in String type
Converter<Integer, String> convertTitleToString = new Converter<Integer, String>(){
public String convert(MappingContext<Integer, String> context){
return Title.values()[context.getSource()].toString();
}
};
// define explicit mappings between source and destination properties
// does only concernes the attributes that will need custom mapping
PropertyMap<Candidate, CandidateDTO> candidateMapping = new PropertyMap<Candidate, CandidateDTO>()
{
protected void configure()
{
// to map these two attributes, they will use the corresponding converters
using(convertTitleToString).map(source.getTitle()).setTitle(null);
using(convertLastDateToString).map(source.getCandidatures()).setDateLastInterview(null);
}
};
// add the mapping settings to the ModelMapper
modelMapper.addMappings(candidateMapping);

Why #Column might not work for methods?

I have a Spring Boot application with JPA and Hibernate Maven dependencies. Database is PosgreSQL.
I would like to create fields in a database based on methods.
So I have an entity class:
#Entity
#Table(name="test_my_entity")
public class MyEntity {
#Id
#GeneratedValue
private Long id;
protected String name;
#Transient
#XmlSchemaType(name = "dateTime")
protected XMLGregorianCalendar myXMLDate;
protected Calendar myDateProperty;
#Column(name = "my_date")
private Calendar isCalendar() {
return new GregorianCalendar(myXMLDate.getYear(), myXMLDate.getMonth(), myXMLDate.getDay());
}
#Column(name = "my_str")
public String myStr() {
return "My string";
}
public MyEntity() {
}
}
However I receive the following structure:
All annotations on methods are ignored.
Could anyone please give me some advice why it might happen and how to create needed fields properly?
The methods must follow, Java Bean convention; precisely, public Getters and Setters. Moveover, properties must exist. Try this,
#Entity
#Table(name="test_my_entity")
public class MyEntity {
private Long id;
protected String name;
private Calendar myDate;
private String myStr;
#Transient
#XmlSchemaType(name = "dateTime")
protected XMLGregorianCalendar myXMLDate;
#Id
#GeneratedValue
public Long getId() {
return id;
}
#Column(name = "my_date")
public Calendar getMyDate() {
return myDate;
}
#Column(name = "my_str")
public String getMyStr() {
return myStr;
}
// I don't get its purpose; hence not touching it.
private Calendar isCalendar() {
return new GregorianCalendar(myXMLDate.getYear(), myXMLDate.getMonth(), myXMLDate.getDay());
}
}
Refs:
List of types Hibernate understand by default.
How to make Hibernate to understand your type.
So I have found the solution. So that you can receive values from methods you should add #Access annotations and also make #Transient variables.
So Hibernate will create neccesary fields and during commit will use values from methods to fulfill them.
Also here is an example of how to convert XMLGregorianCalendar to Calendar -
the format that Hibernate can use successfully.
Here is a working example:
#Entity
#Access(AccessType.FIELD)
#Table(name="test_my_entity")
public class MyEntity {
#Id
#GeneratedValue
private Long id;
#XmlAttribute(name = "name")
#Transient
protected String name;
#XmlSchemaType(name = "dateTime")
#Transient
protected XMLGregorianCalendar myXMLDate;
#Transient
private Calendar calendarDate;
#Access(AccessType.PROPERTY)
#Column(name = "calendar_date")
private Calendar getCalendarDate() {
return new GregorianCalendar(myXMLDate.getYear(), myXMLDate.getMonth(), myXMLDate.getDay());
}
#Access(AccessType.PROPERTY)
#Column(name = "my_str_name")
public String getName() {
return "My string";
}
//...setters here
public MyEntity() {
}
}

Ebean and Play! not filtering columns with .select()

I'm trying to fetch just a part of the model using Ebean in Play! Framework, but I'm having some problems and I didn't found any solutions.
I have these models:
User:
#Entity
#Table(name = "users")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class User extends Model{
#Id
private int id;
#NotNull
#Column(name = "first_name", nullable = false)
private String firstName;
#Column(name = "last_name")
private String lastName;
#NotNull
#Column(nullable = false)
private String username;
#NotNull
#Column(nullable = false)
private String email;
private String gender;
private String locale;
private Date birthday;
private String bio;
#NotNull
#Column(nullable = false)
private boolean active;
private String avatar;
#Column(name = "created_at",nullable = false)
private Date createdAt;
#OneToMany
private List<UserToken> userTokens;
// Getters and Setters omitted for brevity
}
UserToken:
#Entity
#Table(name = "user_tokens")
public class UserToken extends Model {
#Id
private int id;
#Column(name = "user_id")
private int userId;
private String token;
#Column(name = "created_at")
#CreatedTimestamp
private Date createdAt;
#ManyToOne
private User user;
// Getters and Setters omitted for brevity
}
And then, I have a controller UserController:
public class UserController extends Controller{
public static Result list(){
User user = Ebean.find(User.class).select("firstName").where().idEq(1).findUnique();
return Results.ok(Json.toJson(user));
}
}
I expected that, when using the .select(), it would filter the fields and load a partial object, but it loads it entirely.
In the logs, there is more problems that I don't know why its happening.
It is making 3 queries. First is the one that I want. And then it makes one to fetch the whole Model, and another one to find the UserTokens. I don't know why it is doing these last two queries and I wanted just the first one to be executed.
Solution Edit
After already accepted the fact that I would have to build the Json as suggested by #biesior , I found (out of nowhere) the solution!
public static Result list() throws JsonProcessingException {
User user = Ebean.find(User.class).select("firstName").where().idEq(1).findUnique();
JsonContext jc = Ebean.createJsonContext();
return Results.ok(jc.toJsonString(user));
}
I render only the wanted fields selected in .select() after using JsonContext.
That's simple, when you using select("...") it always gets just id field (cannot be avoided - it's required for mapping) + desired fields, but if later you are trying to access the field that wasn't available in first select("...") - Ebean repeats the query and maps whole object.
In other words, you are accessing somewhere the field that wasn't available in first query, analyze your controller and/or templates, find all fields and add it to your select (even if i.e. they're commented with common HTML comment in the view!)
In the last version of Play Framework (2.6) the proper way to do this is:
public Result list() {
JsonContext json = ebeanServer.json();
List<MyClass> orders= ebeanServer.find(MyClass.class).select("id,property1,property2").findList();
return ok(json.toJson(orders));
}

How to put propper DBFLow annotation

I want to insert doctor object to database, how should I put annotations for properties?
I tried to do it with te code shown below.
But i don't know how to do it on list properties specializations and phoneNumbers.
#Table(databaseName = WMDatabase.NAME)
public class Doctor extends BaseModel{
#Column
#PrimaryKey
#Unique(unique = true)
private String doctorId;
#Column
private FullName fullName;
#Column
private String organizationId;
#Column What shuld i put here?????
private List<Specialization> specializations;
#Column What shuld i put here?????
private Contacts contacts;
}
Below are the classes I use for doctor attributes:
public class Contacts extends BaseModel {
private List<PhoneNumber> phoneNumbers;
private String email;
private String fax;
}
public class Specialization extends BaseModel {
#Column
#PrimaryKey
#Unique(unique = true)
private String doctorId;
#Unique(unique = true)
private String specializationName;
public String getSpecializationName() {
return specializationName;
}
public void setSpecializationName(String specializationName) {
this.specializationName = specializationName;
}
DBFlow is a relational database system (not a mongo-type key/value store) and doesn't support lists as columns, according to the doc here.
List : List columns are not supported and not generally proper for a relational database. However, you can get away with a non-generic List column via a TypeConverter. But again, avoid this if you can.
The documentation on relationships may help you refine the model to suit your needs.

Spring-Data-Jpa Repository - Underscore on Entity Column Name

I am using spring-data-jpa on a spring webmvc project. I am facing an issue using query creation on a Repository of one of my Entities. Below you can see my Entity, my Repository and the Exception.
My Entity:
#Entity
#Table(schema = "mainschema")
#XmlRootElement
public class Municipalperson implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(nullable = false)
private Integer id;
#Basic(optional = false)
#Column(name = "municipal_id", nullable = false)
private Integer municipal_id;
#Basic(optional = false)
#Column(nullable = false, length = 60)
private String firstname;
public Municipalperson(Integer id, Integer municipal_id, String firstname) {
this.id = id;
this.municipal_id = municipal_id;
this.firstname = firstname;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getMunicipal_id() {
return municipal_id;
}
public void setMunicipal_id(Integer municipal_id) {
this.municipal_id = municipal_id;
}
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
}
my Repository:
#Repository
public interface MunicipalpersonRepository extends JpaRepository<Municipalperson, Integer> {
List<Municipalperson> findByMunicipal_idOrderByLastnameDesc(int municipal_id);
}
and the exception,
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'municipalpersonRepository': Invocation of init method failed; nested exception is org.springframework.data.mapping.PropertyReferenceException: No property municipal found for type Municipalperson!
I tried to set municipal_id as int, then as Integer and the same for the parameter municipal_id on my Repository, but none worked. Also, I renamed the Repository to findByMunicipalidOrderByLastnameDesc and findByMunicipalIdOrderByLastnameDesc but it didn't work either.
Finally I renamed the municipal_id to municipalId (underscore removed) and also renamed getters/setters and the Repository (findByMunicipalIdOrderByLastnameDesc) and the issue was resolved.
My question is why this is happening?
I solved this error by renaming field to the name without underscore.
#Column(name = "municipal_id", nullable = false)
private Integer municipalId; // <-- field was renamed
The underscore _ is a reserved character in Spring Data query derivation (see the reference docs for details) to potentially allow manual property path description. So there are two options you have:
Stick to the Java naming conventions of using camel-case for member variable names and everything will work as expected.
Escape the _ by using an additional underscore, i.e. rename your query method to findByMunicipal__idOrderByLastnameDesc(…).
I'd recommend the former as you're not going to alienate fellow Java developers :).
Please add the following properties to application.properties file:
spring.jpa.hibernate.naming-strategy=org.hibernate.cfg.ImprovedNamingStrategy
I know the question was answered a long time ago, but it can help others in the future.
According to the Docs, underscore is a special character used by spring to separate properties names. If you really want to stick with snake case notation, you can set nativeQuery to true and solve this problem:
#Query(value = "SELECT * FROM municipalperson WHERE municipal_id=?1 ORDER BY last_name DESC", nativeQuery = true)
List<Municipalperson> findByMunicipal_idOrderByLastnameDesc(int municipal_id);
One other approach that worked for me is to use #JsonProperty to differentiate between field name used in REST request/response and that used for database.
For example:
#JsonProperty("municipalId")
private Integer municipal_id;

Categories

Resources