Spring Boot Java map Entity to DTO: array literal (strings) INSTEAD of array of objects - spring

sample get request: http://localhost:3000/contact/1
What I got:
{
"id": 1,
"firstname": "First Name",
"lastname": "Last Name",
"emailaddresses": [
{
"emailaddress": "email#gmail.com"
},
{
"emailaddress": "email#g.c"
}
]
}
What I want:
{
"id": 1,
"firstname": "First Name",
"lastname": "Last Name",
"emailaddresses": ["email#gmail.com","email#g.c"]
}
The code below:
PersonDto
public class PersonDto {
private Long id;
private String firstname;
private String lastname;
private List<EmailAddressDto> emailaddresses;
//getters setters
}
EmailAddressDto
public class EmailAddressDto {
private String emailaddress;
//getters and setters
}
the Service class
public PersonDto getPerson(Long personId) { //this is the method inside the class
Optional<PersonEntity> p = peopleRepository.findById(personId);
var dto = modelMapper.map(p.get(), PersonDto.class);
return dto;
}
I also have a PersonEntity class mapped one-to-many to an EmailAddressesEntity class.
I'm really new to spring/java - I couldn't figure out how to get the JSON structure I want.

You can just annotate emailaddress field of EmailAddressDto with #JsonValue and leave everything as is.
public class EmailAddressDto {
#JsonValue
private String emailaddress;
//getters and setters
}
Using the above the output of a sample:
PersonDto personDto = new PersonDto();
personDto.setId(1L);
personDto.setFirstname("John");
personDto.setLastname("Doe");
personDto.setEmailaddresses(Arrays.asList(new EmailAddressDto("john#doe.com"), new EmailAddressDto("foo#bar.com")));
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(personDto);
System.out.println(json);
is:
{"id":1,"firstname":"John","lastname":"Doe","emailaddresses":["john#doe.com","foo#bar.com"]}

I'd suggest that you use a List of Strings instead of a List of EmailAddressDto's.
Following reasons:
Since you only have one attribute in your Dto, you can easily just directly use a List of Strings instead.
You get the second JSON-Layout as a response to your GET-Request.
When using variant number 1 (with the List of EmailAddressDto), you will achieve a JSON-Response with multiple objects for your different E-Mail addresses.
Otherwise when you use variant number 2 (with the List of String), you will achieve a JSON-Response which looks like what you want to have.
So don't forget to change your entities aswell.
public class PersonDto {
private Long id;
private String firstname;
private String lastname;
private List<String> emailAddresses;
//getters setters
}

If you can change your PersonDto that would be the easiest and cleanest way to do it.
public class PersonDto {
private Long id;
private String firstname;
private String lastname;
private List<String> emailaddresses;
//getters setters
}
While mapping your entities you would need to map EmailAddressesEntity to a String representing it (emailaddress).
If this is not possible you will need a custom converter for EmailAddressDto as follows:
public class ListEmailAddressDtoConverter extends StdConverter<List<EmailAddressDto>, List<String>> {
#Override
public List<String> convert(List<EmailAddressDto> emailAddresses) {
return emailAddresses.stream().map(EmailAddressDto::getEmailaddress).collect(Collectors.toList());
}
}
Then you need to tell Jackson to use it:
public class PersonDto {
private Long id;
private String firstname;
private String lastname;
#JsonSerialize(converter = ListEmailAddressDtoConverter.class)
private List<EmailAddressDto> emailaddresses;
//getters setters
}

Related

What causes unability to fetch entities in this code?

So i'm developing a REST API for my Spring appplication. I have to store all data in H2 database and i'm trying to find a correct way to do so. I'm new to JPA and databases and general and need help understanding the causes of errors here.
First, i have these entities.
Position.java:
package com.example.offerserver.offerservice.task1;
#Entity
#Table(name = "position_table")
public class Position {
public Position() {
}
public Position(UUID id, String name, Integer salary) {
this.id = id;
this.name = name;
this.salary = salary;
}
#Id
private UUID id;
#Column(name = "name")
private String name;
#Column(name = "salary")
private Integer salary;
//getters and setters
Stuff.java:
package com.example.offerserver.offerservice.task1;
#Entity
#Table(name = "stuff_table")
public class Stuff {
public Stuff(){};
public Stuff(UUID id,
String surname,
String name,
String patronymic,
boolean sex,
LocalDate birthDate,
Double salaryMultiplier,
Position position) {
this.id = id;
this.surname = surname;
this.name = name;
this.patronymic = patronymic;
this.sex = sex;
this.birthDate = birthDate;
this.salaryMultiplier = salaryMultiplier;
this.position = position;
}
#Id
private UUID id;
#Column(name="surname")
private String surname;
#Column(name="name")
private String name;
#Column(name="patronymic")
private String patronymic;
#Column(name="sex")
private boolean sex;
#Column(name="birth_date")
private LocalDate birthDate;
#Column(name="salary_multiplier")
private Double salaryMultiplier;
#OneToOne(fetch = FetchType.LAZY)
private Position position;
And JPA repositories:
package com.example.offerserver.repository;
#Repository
public interface StuffRepository extends JpaRepository<Stuff, String> {
}
package com.example.offerserver.repository;
#Repository
public interface PositionRepository extends JpaRepository<Position, UUID> {
}
And i have this request:
package com.example.offerserver.controller;
#Controller
#RequestMapping("/api/v1/stuff")
public class StuffListController {
#Autowired
StuffRepository repository;
#GetMapping("")
public ResponseEntity<List<Stuff>> getStuffList(){
List<Stuff> stuff = repository.findAll();
return new ResponseEntity<>(stuff, HttpStatus.OK);
Sending this request i'm getting this error:
Could not write JSON: Unable to find com.example.offerserver.offerservice.task1.Position with id f0bd15f2-74c1-4979-854b-ffbe44b3da59; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Unable to find com.example.offerserver.offerservice.task1.Position with id f0bd15f2-74c1-4979-854b-ffbe44b3da59 (through reference chain: java.util.ArrayList[0]->com.example.offerserver.offerservice.task1.Stuff["position"]->com.example.offerserver.offerservice.task1.Position$HibernateProxy$E63ZeIxs["id"])
org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Unable to find com.example.offerserver.offerservice.task1.Position with id f0bd15f2-74c1-4979-854b-ffbe44b3da59; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Unable to find com.example.offerserver.offerservice.task1.Position with id f0bd15f2-74c1-4979-854b-ffbe44b3da59 (through reference chain: java.util.ArrayList[0]->com.example.offerserver.offerservice.task1.Stuff["position"]->com.example.offerserver.offerservice.task1.Position$HibernateProxy$E63ZeIxs["id"])
In debug every instance of stuff in the list is initialized without its "position" field, throwing an error:
Method threw 'javax.persistence.EntityNotFoundException' exception. Cannot evaluate com.example.offerserver.offerservice.task1.Position$HibernateProxy$2ZiRYZbP.toString()
This is how position repository is initialized on launch:
public static List<Position> POSITIONS = List.of(
new Position(UUID.randomUUID(), "Junior Java Backend Developer", 60000),
new Position(UUID.randomUUID(), "Middle Machine Learning Engineer", 120000),
new Position(UUID.randomUUID(), "Senior DevOps Engineer", 200000),
new Position(UUID.randomUUID(), "Senior DevOps Engineer", 150000),
new Position(UUID.randomUUID(), "Intern System Engineer", 20000)
);
positionRepository.saveAll(POSITIONS);
Stuff repository as well. Position field for every stuff instance is randomly chosen from a POSITIONS list.

SpringBoot - 400 bad request after adding List of objects to input

I have a simple ReactJS/SpringBoot application which generates XML files used for software licenses.
This has been working fine, but now I'm trying to add an "attributes" table which has a many-to-one relationship with the license table. It will keep track of attributes specified in the front end that will be set to true on the license.
I've used these URLs as a guide for the backend (video and related code):
https://www.youtube.com/watch?v=8qhaDBCJh6I
https://github.com/Java-Techie-jt/spring-data-jpa-one2many-join-example
However, I'm getting a 400 error both on the update and the addition of a license when I try to use the updated code.
The front end seems to be working correctly.
Edit: looks like this is the culprit; although I haven't figured out why, yet.
Could not resolve parameter [0] in org.springframework.http.ResponseEntity<com.license.gen.app.model.License> com.license.gen.app.web.LicenseController.updateLicense(com.license.gen.app.model.License): JSON parse error: Cannot construct instance of `com.license.gen.app.model.Attribute` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('CONFIG_MSC_PARAMETERS'); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `com.license.gen.app.model.Attribute` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('CONFIG_MSC_PARAMETERS')
/endEdit
It's producing a JSON object with the attributes added as an array (e.g. at the end of the following object):
{
"id": 861,
"fullName": "johnsmith#abc.com",
"contact": "",
"requester": "johnsmith#abc.com",
"tag": "",
"company": "ACME",
"companyId": "ABC",
"template": "AN_4_2",
"product": "Analytics",
"expiration": "2022-04-15",
"macs": "11-11-11-11-11-11",
"typeId": "555",
"family": "FACILITY",
"systems": "2",
"licenseFilename": "license_johnsmith#abc.com.xml",
"url": "https://test-licenses.s3.amazonaws.com/license_johnsmith%40abc.com.xml",
"dateCreated": "2021-04-09T02:43:39.000+0000",
"dateUpdated": "2021-04-09T02:43:39.000+0000",
"user": {
"id": "00u560lmjop5poy624x6",
"name": "myname",
"email": "myname#gmail.com"
},
"attributes": [
"CONFIG_MSC_PARAMETERS",
"REPORTING"
]
}
Here is the updated License entity, with attributes added as a one-to-many List:
#EqualsAndHashCode
#Data
#NoArgsConstructor
#RequiredArgsConstructor
#Entity
#Table(name = "licenses")
public class License {
#Id
#GeneratedValue
private Long id;
#NonNull
private String fullName;
private String contact;
private String requester;
private String tag;
private String company;
private String companyId;
private String template;
private String product;
private String expiration;
private String macs;
private String typeId;
private String family;
private String systems;
#ManyToOne(cascade = CascadeType.PERSIST)
private User user;
#OneToMany(targetEntity = Attribute.class,cascade = CascadeType.ALL)
#JoinColumn(name ="license_fk",referencedColumnName = "id")
private List<Attribute> attributes;
// getters, setters
...
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public List<Attribute> getAttributes() {
return attributes;
}
public void setAttributes(List<Attribute> attributes) {
this.attributes = attributes;
}
}
License Repository (no change):
package com.license.gen.app.model;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface LicenseRepository extends JpaRepository<License, Long>
{
License findByFullName(String fullName);
List<License> findAllByUserId(String id);
Page<License> findAll(Pageable pageable);
#Query("SELECT l FROM License l " +
"WHERE l.company LIKE %:company% " +
"OR l.macs LIKE %:macs% " +
"OR l.requester LIKE %:requester% " +
"OR l.tag LIKE %:tag% " +
"OR l.fullName LIKE %:fullName% " +
"OR l.template LIKE %:template% " +
"OR l.expiration LIKE %:expiration% " +
"OR l.family LIKE %:family% " +
"OR l.licenseFilename LIKE %:filename% " +
"OR l.product LIKE %:product%"
)
List<License> findBySearchString(
#Param("company") String company,
#Param("macs") String macs,
#Param("requester") String requester,
#Param("tag") String tag,
#Param("fullName") String fullName,
#Param("template") String template,
#Param("expiration") String expiration,
#Param("family") String family,
#Param("filename") String filename,
#Param("product") String product);
#Query("SELECT l FROM License l " +
"WHERE l.macs LIKE %:macs%"
)
List<License> findByMacs(
#Param("macs") String macs);
#Query("SELECT l FROM License l " +
"WHERE l.fullName LIKE %:fullName%"
)
List<License> findMatchesByFullName(
#Param("fullName") String fullName);
}
Attribute Entity (new):
#Data
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class Attribute {
#Id
private Long id;
#NonNull
private String attribute;
#Temporal(TemporalType.TIMESTAMP)
private Date dateCreated = new Date();
#ManyToOne(cascade = CascadeType.PERSIST)
private License license;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getAttribute() {
return attribute;
}
public void setAttribute(String attribute) {
this.attribute = attribute;
}
public Date getDateCreated() {
return dateCreated;
}
public void setDateCreated(Date dateCreated) {
this.dateCreated = dateCreated;
}
}
Attribute Repository (new):
public interface AttributeRepository extends JpaRepository<User, Long> {
}
And the License Controller:
#RestController
#RequestMapping("/api")
class LicenseController {
private final Logger log = LoggerFactory.getLogger(LicenseController.class);
private LicenseRepository licenseRepository;
private UserRepository userRepository;
private AttributeRepository attributeRepository;
public static String bucket;
public LicenseController(LicenseRepository licenseRepository,
UserRepository userRepository,
AttributeRepository attributeRepository) {
this.licenseRepository = licenseRepository;
this.userRepository = userRepository;
this.attributeRepository = attributeRepository;
}
.....
#PostMapping("/license")
ResponseEntity<LicensePojo> createLicense(#Valid #RequestBody License license,
#AuthenticationPrincipal OAuth2User principal) throws URISyntaxException {
log.info("Request to create license: {}", license);
Map<String, Object> details = principal.getAttributes();
String userId = details.get("sub").toString();
// check to see if user already exists
Optional<User> user = userRepository.findById(userId);
license.setUser(user.orElse(new User(userId,
details.get("name").toString(), details.get("email").toString())));
if(license.getLicenseFilename() == null){
license.setLicenseFilename("");
}
License result = licenseRepository.save(license);
User myUser = license.getUser();
// Generate the license
LicensePojo licensePojo = new LicensePojo(result);
String fileName = GenLicense.genLicense(licensePojo);
AmazonS3Utils.putObject(fileName);
AmazonS3Utils.setToFileDownload(fileName);
AmazonS3Utils.setObjectPublic(fileName);
result.setLicenseFilename(fileName);
String url = AmazonS3Utils.getUrl(fileName).toString();
result.setUrl(url);
String origTypeId = String.valueOf(result.getTypeId());
String origId = String.valueOf(result.getId());
if ((origTypeId == null) || origTypeId.equalsIgnoreCase("")){
result.setTypeId(origId);
}
result = licenseRepository.save(result);
return ResponseEntity.created(new URI("/api/license/" + result.getId()))
.body(licensePojo);
}
#PutMapping("/license/{id}")
ResponseEntity<License> updateLicense(#Valid #RequestBody License license) {
List<Attribute> attributes = license.getAttributes();
License result = licenseRepository.save(license);
LicensePojo licensePojo = new LicensePojo(result);
String fileName = GenLicense.genLicense(licensePojo);
AmazonS3Utils.putObject(fileName);
AmazonS3Utils.setToFileDownload(fileName);
AmazonS3Utils.setObjectPublic(fileName);
String url = AmazonS3Utils.getUrl(fileName).toString();
result.setUrl(url);
result.setLicenseFilename(fileName);
return ResponseEntity.ok().body(result);
}
...
}
As far as I can see, there are no error messages being generated. The IDE is showing the AttributeRepository isn't being used in the controller, but they may be because it's part of the underlying SpringData JPA code to implement it.
Any ideas what the problem might be?

How to convert from entity to dto using model mapper , with conversion from string to UUID

I need some help to map Entity to DTO using Model Mapper.
Here are my two pojos
#Data
public class ClientDTO {
private UUID id;
#NotNull
private String name;
private String description;
private String contactEmail;
}
#Data
#Entity
public class Client {
#Id
private String id;
#NotNull
private String name;
private String description;
#NotNull
private String contactEmail;
}
When am trying to convert between Client to ClientDTO id is rendered as null. I tried writing a PropertyMap and a converter but none of them is working for me.
I went through the documentation and was able to find a solution to the problem. Here is the soln.
Initialization
private PropertyMap<Client, ClientDTO> clientMap;
private ModelMapper clientToClientDtoMapper;
Defining PropertyMap and Converter
clientToClientDtoMapper = new ModelMapper();
Converter<Client, UUID> uuidConverter = new AbstractConverter<Client, UUID>() {
protected UUID convert(Client source) {
return UUID.fromString(source.getId());
}
};
clientMap = new PropertyMap<Client, ClientDTO>() {
protected void configure() {
try {
using(uuidConverter).map(source).setId(null);
} catch (Exception ex) {
System.out.println("Error.");
}
}
};
clientToClientDtoMapper.addMappings(clientMap);
Helper method to convert from Entity to DTO
private ClientDTO convertToDto(Client client) {
ClientDTO clientDTO = clientToClientDtoMapper.map(client, ClientDTO.class);
return clientDTO;
}

SpringBoot concatenate search parameters browser url

I am starting working with Spring Boot. My aim is to make a limited search retrieving data from a database. I want to add multiple parameters in the query of the url.
So far I was able using the seek: http://localhost:8080/wsr/search/, to get a full search of the data in the database. But what I want is delimit the search under several conditions adding parameters in the url in the browser as for instance:
http://localhost:8080/data/search/person?name=Will&address=Highstreet&country=UK
http://localhost:8080/data/search/person?name=Will&name=Angie
http://localhost:8080/data/search/person?name=Will&name=Angie&country=UK
The problem I found is that I can't find the way to work with more than one condition. The only thing I got to make it work, is:
http://localhost:8080/data/search/person?name=Will
I surfed the web but no results for this exact problem, too much information but impossible to find this.
The code I have is:
#Entity
#Table(name = "person")
public class Person {
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Column(name = "name")
private String name;
#Column(name = "address")
private String address;
#Column(name = "country")
private String country;
public Value() {
}
public Value(int id, String name, String address, String country) {
this.id = id;
this.name = name;
this.address = address;
this.country = country;
}
//all getters and setters
}
public class Implementation {
#Autowired
private DataBase dataBase;
public List<Value> findById(#PathVariable final int id) {
return dataBase.findById(id);
}
public List<Value> findByName(#PathVariable final String name) {
return dataBase.findByName(name);
}
public List<Value> findByAddress(#PathVariable final String address) {
return dataBase.findByAddress(address);
}
public List<Value> findByCountry(#PathVariable final String country) {
return dataBase.findByCountry(country);
}
}
//#Component
#RepositoryRestResource(collectionResourceRel = "person", path = "data")
public interface DataBase extends JpaRepository<Value, Integer>{
public List<Value> findAll();
#RestResource(path = "ids", rel = "findById")
public List<Value> findById(#Param("id") int id) throws ServiceException;
#RestResource(path = "name", rel = "findByName")
public List<Value> findByName(#Param("name") String name) throws ServiceException;
#RestResource(path = "address", rel = "findByAddress")
public List<Value> findByAddress(#Param("address") String address) throws ServiceException;
#RestResource(path = "country", rel = "findByCountry")
public List<Value> findByCountry(#Param("country") String country) throws ServiceException;
}
Hope you can help me putting me in the correct way of what should do or is wrong. If possible some code will also be highly appreciated.
Best regards
You can use #RequestParam("nameParameter")annotation to map all the parameters you want. Let's say you have url like :
http://localhost:8080/data/search/person?name=Will&country=UK
then you can have an api like:
...
#RequestMapping(value = "/person")
public String api(#RequestParam("name") String name, #RequestParam("country") String country)
...

Ignoring Nested properties in Jackson OnDemand

I am working on a spring boot application with Hibernate as ORM and Jackson as JSON serialiser .
I have three model objects and CRUD operations for all three models.
Class Student{
private Teacher teacher; // Teacher of the student — to be fetched eagerly
+Getter/Setter
}
class Teacher {
private List<Subject> subject; // List of subjects associated to that user— to be fetched eagerly
+Getter/Setter
}
class Subject {
private long subjectId
//Other subject properties
+ Getter/Setter
}
Whenever I trigger a get request for student info I get the teacher info which is correct where as I also receive Subject info as well which is unnecessary for me. In the same time when I request for Teacher info, I need Subject info should be associated to that for sure. If I use #JsonBackReference for subject I am losing it all the time. I am not sure how to achieve this.
Thanks in advance for your help!!
You can also annotate like this
Class Student{
#JsonIgnoreProperties("subject")
private Teacher teacher; // Teacher of the student — to be fetched eagerly
}
You can use JSON Views
From the spring blog:
public class View {
interface Summary {}
}
public class User {
#JsonView(View.Summary.class)
private Long id;
#JsonView(View.Summary.class)
private String firstname;
#JsonView(View.Summary.class)
private String lastname;
private String email;
private String address;
private String postalCode;
private String city;
private String country;
}
public class Message {
#JsonView(View.Summary.class)
private Long id;
#JsonView(View.Summary.class)
private LocalDate created;
#JsonView(View.Summary.class)
private String title;
#JsonView(View.Summary.class)
private User author;
private List<User> recipients;
private String body;
}
and in the controller
#RestController
public class MessageController {
#Autowired
private MessageService messageService;
#JsonView(View.Summary.class)
#RequestMapping("/")
public List<Message> getAllMessages() {
return messageService.getAll();
}
#RequestMapping("/{id}")
public Message getMessage(#PathVariable Long id) {
return messageService.get(id);
}
}
PS: No link to http://fasterxml.com/ as it's currently down.

Resources