ModelMapper can map DTO to Entity, but not back again - spring

Seeing some odd behavior using ModelMapper. To demonstrate I have an AnimalDto object that looks like this:
#SuperBuilder()
#Getter
#NoArgsConstructor
#ToString
public class AnimalDto {
/**
* Type of the animal
*/
private String animalType;
/**
* Description of animal
*/
private JsonNode animalDesc;
}
Then I have an AnimalEntity that resembles this Dto object, and is able to be saved in the DB:
#Getter
#Setter
#NoArgsConstructor
#TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
#ToString
public class AnimalEntity {
/**
* Animal id for this animal
*/
#Id
private UUID id = UUID.randomUUID();
/**
* Type of animal
*/
#Column(name = "animal_type")
private String animalType;
/**
* JSON which describes the animal
*/
#Type(type = "jsonb")
#Column(name = "animal_desc")
private JsonNode animalDesc;
}
When I use ModelMapper to map between the objects I am able to successfully convert from a AnimalDto to an AnimalEntity:
JsonNode animalDesc = objectMapper.convertValue("{\"desc\": \"small penguin\"}", JsonNode.class);
AnimalDto animalDto = AnimalDto.builder().animalType("penguin").animalDesc(animalDesc).build();
AnimalEntity animalEntity = modelMapper.map(animalDto, AnimalEntity.class);
System.out.println(animalEntity);
The output will be expected:
AnimalEntity(id=47066e09-3a40-4a85-89fa-4befabdf03fa, animalType=penguin, animalDesc="{ \"desc\": \"small penguin\"}")
However, if I do the same back again:
AnimalDto testDto = modelMapper.map(animalEntity, AnimalDto.class);
System.out.println(testDto)
Output will be:
AnimalDto(animalType=null, animalDesc=null)
When I am expecting:
AnimalDto(animalType=penguin, animalDesc="{ \"desc\": \"small penguin\"}")
Why is it able to map one-way, but not the way back?
EDIT:
Fixed Code error
EDIT 2: The issue is that the Dto is missing the Setters. It can create an instance of the object but cannot set any of the fields. You need a public setter for all the fields you want to be able to set. See: link

Related

Failed to convert property value of type 'java.lang.Integer' for property

I am trying to persist a class with .save method of a #Repository or #RepositoryRestResource using springboot (both case its happening the same...)
my repo:
/**
* Repository interface to do queries for UserBackoffice data.
*/
#RepositoryRestResource(path = "userBackoffice")
public interface UserBackofficeRepository
extends PagingAndSortingRepository<UserBackoffice, UserBackofficeId> {
}
my entity is this class:
/**
* DAO for UserBackoffice table in Database.
*/
#Data
#NoArgsConstructor
#AllArgsConstructor
#JsonInclude(Include.NON_NULL)
#Entity
#Table(uniqueConstraints = {#UniqueConstraint(name = "unq_user_id_constraint_01",
columnNames = {"user_id", "core_user_id", "backoffice_id"})})
#IdClass(UserBackofficeId.class)
public class UserBackoffice implements Serializable {
/**
* serial version.
*/
private static final long serialVersionUID = 1L;
/**
* user associated to the backoffice identifier.
*/
#Id
#ToString.Exclude
#EqualsAndHashCode.Exclude
#JsonBackReference(value = "user")
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
/**
* Backoffice Id.
*/
#Id
#ToString.Exclude
#EqualsAndHashCode.Exclude
#JsonBackReference(value = "backofficeId")
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "backoffice_id")
private Backoffice backofficeId;
/**
* Core user identifier update from Backoffice.
*/
#Column(name = "core_user_id")
private String coreUserId;
}
/**
* Artificial class to support product backoffice composite key.
*/
#Data
#NoArgsConstructor
#AllArgsConstructor
public class UserBackofficeId implements Serializable {
/**
* serial version.
*/
private static final long serialVersionUID = 1L;
/**
* user associated to the backoffice identifier.
*/
private User user;
/**
* Backoffice Id.
*/
private Backoffice backofficeId;
}
/**
* DAO for backoffice table in Database.
*/
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#JsonInclude(Include.NON_NULL)
#Entity(name = "backoffice")
public class Backoffice implements Serializable {
/**
* serial version.
*/
private static final long serialVersionUID = 9077401445066055280L;
/**
* backoffice Id.
*/
#Id
#Column(name = "id")
private Integer id;
/**
* backoffice name.
*/
#NotEmpty
#Size(max = 100)
#Column(name = "name")
private String name;
/**
* list of user backoffice.
*/
#LazyCollection(LazyCollectionOption.TRUE)
// #JsonManagedReference
#OneToMany(cascade = CascadeType.ALL, targetEntity = UserBackoffice.class,
mappedBy = "backofficeId")
private List<UserBackoffice> userBackoffice;
}
when i try to do a simple test like this:
#Test
void saveUserBackOffice() throws StreamReadException, DatabindException, IOException {
PodamFactory factory = new PodamFactoryImpl(); //factory for dummy objects
User user = factory.manufacturePojo(User.class);//create a dummy user
Backoffice backoffice = factory.manufacturePojo(Backoffice.class); //create a dummy backoffice
UserBackoffice userbackoffice = new UserBackoffice(user, backoffice, "01");
UserBackoffice backofficeResponseAfterSave = userBackRepo.save(userbackoffice);
assertEquals(userbackoffice, backofficeResponseAfterSave);
}
i get this error message when save execute and the transaction is rollbacked:
"Failed to convert property value of type 'java.lang.Integer' to required type 'es.kukenbank.microservice.crypto.repository.entity.Backoffice' for property 'backofficeId'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.Integer' to required type 'es.kukenbank.microservice.crypto.repository.entity.Backoffice' for property 'backofficeId': no matching editors or conversion strategy found"
i really dont see what is happening because user and backoffice dummy object have value according his atribute types correctly.

Entity Design using JPA

I have 3 entities -
Course
Module
Timeline
Course is an independent entity with following attributes:
Course - (id Integer Primary Key, course_name)
#Id
#Column(name = "id")
Integer courseId;
#Column(name = "course_name")
String course_name;
Next up is another entity Module,
Every row in module is related to one course, and hence there is a one to one relationship between Module and Course.
Module - (module_id, module_name, module_type, duration)
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "module_id")
Integer module_id;
#Column(name = "module_name")
String module_name;
#Column(name = "duration")
Integer duration;
#ManyToOne
#JoinColumn(name="timeline_id", nullable=false)
private Timeline timeline;
Now, next is a timeline entity, which is also related to course i.e every timeline id belongs to one course id, but one timeline id can belong to multiple module_ids, and hence below code:
#Id
#Column(name = "timeline_id")
Integer timelineId;
#OneToMany( mappedBy = "timeline" )
private List<Module> module;
#OneToOne( cascade = CascadeType.ALL)
private Course course;
Can you please tell me what is the error over here.
ModuleRepository:
#Repository
public interface ModuleRepository extends JpaRepository<Module, Integer>{
public List<Module> findAllByTimelineTimelineId(Integer timelineId);
}
IModuleService
public interface IModuleService {
public List<Module> findByTimelineId(Integer timelineId);
}
ModuleServiceImpl
public List<Module> findByTimelineId(Integer timelineId) {
// TODO Auto-generated method stub
return moduleRepo.findAllByTimelineTimelineId(timelineId);
}
Controller
#RequestMapping("/gettimeline/{timeline_id}")
public List<Module> findByTimelineId(#PathVariable Integer timeline_id){
return moduleService.findByTimelineId(timeline_id);
}
Now when I run this url in Postman: http://localhost:8083/gettimeline/1
I get an infinite loop, I am unable to decode the error, also is there any problem with OneToMany mapping, I am new to JPA:
[{"module_id":1,"module_name":"Sleep","duration":10,"timeline":{"timelineId":1,"module":[{"module_id":1,"module_name":"Sleep","duration":10,"timeline":{"timelineId":1,"module":[{"module_id":1,"module_name":"Sleep","duration":10,"timeline":{"timelineId":1,"module":[{"module_id":1,"module_name":"Sleep","duration":10,"timeline":{"timelineId":1,"module":[{"module_id":1,"module_name":"Sleep","duration":10,"timeline":{"timelineId":1,"module":[
Please help, thank you in advance :)
The infinite loop issue is caused by the one-to-many relation. There are several ways of fixing this, but I find view model classes like shown below as the cleanest approach.
Please note that the owning side of the one-to-many relation is not included in the code below, only the many-to-one. This can be done the other way around, but from your code, I guess this is what you want.
TimelineVM class
package no.mycompany.myapp.misc;
import lombok.Data;
import lombok.NoArgsConstructor;
#Data
#NoArgsConstructor
public class TimelineVM {
private Integer timelineId;
public TimelineVM(Timeline timeline) {
this.timelineId = timeline.getTimelineId();
}
}
ModuleVM class
package no.mycompany.myapp.misc;
import lombok.Data;
import lombok.NoArgsConstructor;
#Data
#NoArgsConstructor
public class ModuleVM {
private Integer module_id;
private String module_name;
private Integer duration;
private TimelineVM timeline;
public ModuleVM(Module module) {
this.module_id = module.getModule_id();
this.module_name = module.getModule_name();
this.duration = module.getDuration();
this.timeline = new TimelineVM(module.getTimeline());
}
}
Controller method
#RequestMapping("/gettimeline/{timeline_id}")
public List<ModuleVM> findByTimelineId(#PathVariable Integer timeline_id){
return moduleService.findByTimelineId(timeline_id).stream().map(ModuleVM::new).collect(Collectors.toList());
}

Springboot add problem in oneTOMany relation

I'm writing 3 tables in the following relation:
Club class:
#Setter
#Getter
#Entity
#Table(name = "Club")
public class Club {
#Id
#GeneratedValue
private Long id;
private String name;
private String type;
private String mainPage;
private String logo;
#OneToMany(mappedBy="clubProductKey.club", cascade = CascadeType.ALL)
#JsonIgnoreProperties(value = "clubProductKey.club", allowSetters=true)
private Set<ClubProduct> clubProducts;
...
Product class:
#Setter
#Getter
#Entity
#Table(name = "Product")
public class Product {
#Id
#GeneratedValue
private Long id;
#OneToMany(mappedBy="clubProductKey.product", cascade = CascadeType.ALL)
#JsonIgnoreProperties(value = "clubProductKey.product", allowSetters=true)
private Set<ClubProduct> clubProducts;
...
ClubProduct class:
#Setter
#Getter
#Entity
#Table(name = "ClubProduct")
public class ClubProduct {
#EmbeddedId
private ClubProductKey clubProductKey;
...
ClubProductKey class:
#Setter
#Getter
#Embeddable
public class ClubProductKey implements Serializable {
#ManyToOne(cascade = {CascadeType.MERGE,CascadeType.REFRESH })
#JoinColumn(name = "club_id", referencedColumnName = "id")
#JsonIgnoreProperties(value = "clubProducts", allowSetters=true)
private Club club;
#ManyToOne(cascade = {CascadeType.MERGE,CascadeType.REFRESH })
#JoinColumn(name = "product_id", referencedColumnName = "id")
#JsonIgnoreProperties(value = "clubProducts", allowSetters=true)
private Product product;
...
ClubProductRepository class:
public interface ClubProductRepository extends JpaRepository<ClubProduct, ClubProductKey> {
public List<ClubProduct> findByClubProductKeyClub(Club club);
public List<ClubProduct> findByClubProductKeyProduct(Product product);
}
I try to save clubProduct like this:
#Service
public class ClubProductServiceImp implements ClubProductService {
#Autowired
private ClubProductRepository clubProductRepository;
...
ClubProduct savedClubProduct = clubProductRepository.save(clubProduct);
return savedClubProduct;
}
However I find that the clubProduct is not saved in the clubProducts list in the club or product entity, the list is null. Must I add lines like club.getClubProducts.add(clubProduct) or is there any other way to make it added automatically?
Thank you.
The #OnetoMany mapping in your Club class uses the attribute mappedby which means that it represents the owning side of the relation responsible for handling the mapping. However, we still need to have both sides in sync as otherwise, we break the Domain Model relationship consistency, and the entity state transitions are not guaranteed to work unless both sides are properly synchronized.
The answer is yes, you have to manage the java relations yourself so that the clubProducts gets persisted. You are using an instance of the repository class club to persist the data so , you should add a setter method like :
public void addClubProduct(ClubProduct clubProduct) {
if (clubProduct!= null) {
if (clubProduct== null) {
clubProduct= new ArrayList<ClubProduct>();
}
clubProducts.add(clubProduct);
clubProduct.setClubProduct(this);
}
}
also a method to remove it from the list and use these method in your code to set the values to the list properly before initiating save . Read related article

Spring data JPA derived query for multiple #OneToMany entities and inner entity localization

I am trying to do a simple task with Spring Data JPA derived queries and am unable to get the desired results from the query. Basically I have a Book which can have one or many Chapters with localization support for the Book as well as the Chapter. I want to create a query which would fetch a language specific book (with chapters) based on the Locale. Here are my four entities.
#Entity
#Getter
#Setter
public class Book {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private int noOfPages;
/**
* Both mappings below are unidirectional #OneToMany
*/
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "BOOK_ID", referencedColumnName = "ID")
private List<BookTranslation> bookTranslations;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "BOOK_ID", referencedColumnName = "ID")
private List<Chapter> chapters;
/**
* Constructor for JPA
*/
protected Book() {
}
public Book(int noOfPages, List<BookTranslation> bookTranslations, List<Chapter> chapters) {
this.noOfPages = noOfPages;
this.bookTranslations = bookTranslations;
this.chapters = chapters;
}
}
#Entity
#Getter
#Setter
public class BookTranslation {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Enumerated(EnumType.STRING)
private Language language;
private String name;
/**
* Constructor for JPA
*/
protected BookTranslation() {
}
public BookTranslation(Language language, String name) {
this.language = language;
this.name = name;
}
}
#Entity
#Getter
#Setter
public class Chapter {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private int chapterNumber;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "CHAPTER_ID", referencedColumnName = "ID")
private List<ChapterTranslation> chapterTranslations;
/**
* Constructor for JPA
*/
protected Chapter() {
}
public Chapter(int chapterNumber, List<ChapterTranslation> chapterTranslations) {
this.chapterNumber = chapterNumber;
this.chapterTranslations = chapterTranslations;
}
}
#Entity
#Getter
#Setter
public class ChapterTranslation {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Enumerated(EnumType.STRING)
private Language language;
private String title;
/**
* Constructor for JPA
*/
protected ChapterTranslation() {
}
public ChapterTranslation(Language language, String title) {
this.language = language;
this.title = title;
}
}
public enum Language {
EN, FR
}
Below is the sample code, I am using to persist these entities. Ignore the #GetMapping please, this is just a sample.
#GetMapping("/persist-book")
public void persistBook() {
ChapterTranslation enChapter = new ChapterTranslation(Language.EN, "What is Java persistence?");
ChapterTranslation frChapter = new ChapterTranslation(Language.FR, "Qu'est-ce que la persistance Java?");
List<ChapterTranslation> chapterOneTranslation = new ArrayList<>();
chapterOneTranslation.add(enChapter);
chapterOneTranslation.add(frChapter);
Chapter chapterOne = new Chapter(1, chapterOneTranslation);
List<Chapter> chapters = new ArrayList<>();
chapters.add(chapterOne);
BookTranslation enBook = new BookTranslation(Language.EN, "JPA WikiBook in English");
BookTranslation frBook = new BookTranslation(Language.FR, "JPA WikiBook in French");
List<BookTranslation> bookTranslations = new ArrayList<>();
bookTranslations.add(enBook);
bookTranslations.add(frBook);
Book book = new Book(500, bookTranslations, chapters);
bookRepository.save(book);
}
My BookRepository looks as follows:
public interface BookRepository extends CrudRepository<Book, Long> {
List<Book> findBooksByBookTranslations_LanguageAndChapters_ChapterTranslations_Language(Language lang1, Language lang2);
}
Sample code I am using to retrieve the result.
#GetMapping("/english-book")
public List<Book> retrieveEnglishBook() {
return bookRepository.findBooksByBookTranslations_LanguageAndChapters_ChapterTranslations_Language(
Language.EN, Language.EN
);
}
My expected output is as attached in the image below.
One thing that I noticed from the Hibernate logs is that Hibernate makes a total of four select queries and the first query output is exactly what I need. However, since this a method name based query I don't suppose I can control that.
EDIT 1: Before trying out the answer, I was getting all books with all their locales returned, after changing my query to the one given in the accepted answer I was able to get the Book with the selected locale.
Please note: I also had to change all collections from using a List to a Set, more on this can be read about in the accepted answers link.
What you describe as a desired result is a single database result.
I guess what you mean by that is you expect to get all the books but only with the translations in a single language.
You don't describe what you actually get, so assume you are getting the book with all available translations.
Your desired result is beyond the capabilities of derived queries.
The different predicates of a derived queries all limit the root entities to be returned Book in your case. They should still have all references in tact.
You could achieve your goal with an annotated query like this:
public interface BookRepository extends CrudRepository<Book, Long> {
#Query("SELECT b FROM Book b
JOIN FETCH b.bookTranslations as bt
JOIN FETCH b.chapter as c
JOIN FETCH c.chapterTranslation as ct
WHERE bt.language = :lang
AND ct.language = :lang")
List<Book> findBooksByLanguage(Language lang);
}
See also How to filter child collection in JPQL query?
Side note: query derivation should only be used when the resulting method name is VERY similar to what you would have named the method anyway.

Spring data jpa inheritance - table per class not working

I have an abstract entity.
#Entity
#Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
#EntityListeners(AuditingEntityListener.class)
public abstract class AbstractEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
protected long id;
#CreatedBy
protected String createdBy;
#CreatedDate
protected Date creationDate;
#LastModifiedBy
protected String modifiedBy;
#LastModifiedDate
protected Date lastModifiedDate;
}
And 2 concrete implementations of this class:
Class A:
#Entity
#Table(name = "A")
public class A extends AbstractEntity {
#Column(name = "NAME", nullable = false)
private String name;
#Column(name = "PRIORITY", nullable = false)
private int priority;
}
Class B:
#Entity
#Table(name = "B")
public class B extends AbstractEntity {
#Column(name = "PLACE", nullable = false)
private String place;
#Column(name = "DISTANCE", nullable = false)
private int distance;
}
And a common repository interface:
#NoRepositoryBean
public interface IRepository extends Repository<AbstractEntity, Long> {
/**
* Method to query by unique id/PK.
* #param id
* #return Entity with id "id"
*/
#Query("select entity from #{#entityName} as entity where entity.id = ?1")
public AbstractEntity findById(long id);
/**
* Insert method
* #param abstractEntity
* #return modified entity after insertion
*/
public AbstractEntity save(AbstractEntity abstractEntity);
/**
* select all records from the table
* #return list of all entities representing records in the table
*/
#Query("select entity from #{#entityName} as entity")
public List<AbstractEntity> findAll();
/**
* delete record by id
* #param id
*/
public void deleteById(long id);
}
And each class has it's own repository which extends the generic repository:
public interface ARepository extends IRepository {
}
public interface BRepository extends IRepository {
}
When I invoke findAll() on ARespository, I get the records in both ARepository and BRepository. Since, the inheritance type is specified as TABLE_PER_CLASS, I assumed that a findAll() would only pick records from that table. I even added a query to the findAll() method to detect entity type and pick records appropriately, but this doesn't seem to be doing anything. Is there something I'm missing here?
I'm using Hibernate as my underlying persistence framework and am working on HSQLDB.
Thanks,
Aarthi
The typing of your repositories is incorrect change it to.
#NoRepositoryBean
public interface IRepository<Entity extends AbstractEntity> extends Repository<Entity, Long> {
}
public interface ARepository extends IRepository<A> {
}
public interface BRepository extends IRepository<B> {
}

Resources