Spring Data Rest #EmbeddedId cannot be constructed from Post Request - spring

I have a JPA entity Person and an entity Team. Both are joined by an entity PersonToTeam. This joining entity holds a many-to-one relation to Person and one to Team. It has a multi-column key consisting of the ids of the Person and the Team, which is represented by an #EmbeddedId. To convert the embedded id back and forth to the request id I have a converter. All this follows the suggestion on Spring Data REST #Idclass not recognized
The code looks like this:
#Entity
public class PersonToTeam {
#EmbeddedId
#Getter
#Setter
private PersonToTeamId id = new PersonToTeamId();
#ManyToOne
#Getter
#Setter
#JoinColumn(name = "person_id", insertable=false, updatable=false)
private Person person;
#ManyToOne
#Getter
#Setter
#JoinColumn(name = "team_id", insertable=false, updatable=false)
private Team team;
#Getter
#Setter
#Enumerated(EnumType.STRING)
private RoleInTeam role;
public enum RoleInTeam {
ADMIN, MEMBER
}
}
#EqualsAndHashCode
#Embeddable
public class PersonToTeamId implements Serializable {
private static final long serialVersionUID = -8450195271351341722L;
#Getter
#Setter
#Column(name = "person_id")
private String personId;
#Getter
#Setter
#Column(name = "team_id")
private String teamId;
}
#Component
public class PersonToTeamIdConverter implements BackendIdConverter {
#Override
public boolean supports(Class<?> delimiter) {
return delimiter.equals(PersonToTeam.class);
}
#Override
public Serializable fromRequestId(String id, Class<?> entityType) {
if (id != null) {
PersonToTeamId ptid = new PersonToTeamId();
String[] idParts = id.split("-");
ptid.setPersonId(idParts[0]);
ptid.setTeamId(idParts[1]);
return ptid;
}
return BackendIdConverter.DefaultIdConverter.INSTANCE.fromRequestId(id, entityType);
}
#Override
public String toRequestId(Serializable id, Class<?> entityType) {
if (id instanceof PersonToTeamId) {
PersonToTeamId ptid = (PersonToTeamId) id;
return String.format("%s-%s", ptid.getPersonId(), ptid.getTeamId());
}
return BackendIdConverter.DefaultIdConverter.INSTANCE.toRequestId(id, entityType);
}
}
The problem with this converter is, that the fromRequestId method gets a null as id parameter, when a post request tries to create a new personToTeam association. But there is no other information about the payload of the post. So how should an id with foreign keys to the person and the team be created then? And as a more general question: What is the right approach for dealing many-to-many associations in spring data rest?

After running into the same issue I found a solution. Your code should be fine, except I return new PersonToTeamId() instead of the DefaultIdConverter if id is null in fromRequestId().
Assuming you are using JSON in your post request you have to wrap personId and teamId in an id object:
{
"id": {
"personId": "foo",
"teamId": "bar"
},
...
}
And in cases where a part of the #EmbeddedId is not a simple data type but a foreign key:
{
"id": {
"stringId": "foo",
"foreignKeyId": "http://localhost:8080/path/to/other/resource/1"
},
...
}

Related

Post Request with Enum and composite key

I'm working on an exercise where i have to create CRUD operations.
I have a User table, a Role table and a UserRole table where i have the primary keys of those two entities.
I also have a RoleEnum with roles that have to be assigned to the User.
The problem that i'm gettin is that every time I insert a new user is a 200ok response but the role returns null and so it doesn't add it to the UserRole table as well.
I need help in solving the problem.
User Entity
#Data
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "utente")
public class Utente implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long matricola;
#Column(nullable = false)
private String nome;
#Column(nullable = false)
private String cognome;
#Column(nullable = false)
private String email;
#Column(name = "ruoloUtente", nullable = false)
#OneToMany(mappedBy = "matricolaUtente")
#JsonIgnore
private List<UtenteRuolo> ruoloUtente;
}
Role Entity
#Data
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "ruolo")
public class Ruolo implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "tipo_ruolo")
private String nome;
#Column(name = "utente_ruolo")
#OneToMany(mappedBy = "nomeRuolo")
private Set<UtenteRuolo> utenteRuolo;
}
UserRole class
#Data
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "utente_ruolo")
public class UtenteRuolo implements Serializable {
#EmbeddedId
private UtenteRuoloId utenteRuoloId;
#ManyToOne
#MapsId("matricola")
#JoinColumn(name = "matricola_utente", nullable = false)
private Utente matricolaUtente;
#ManyToOne
#MapsId("id")
#JoinColumn(name = "nome_ruolo", nullable = false)
private Ruolo nomeRuolo;
}
UserRoleId class
#Embeddable
#Data
#EqualsAndHashCode
#AllArgsConstructor
#NoArgsConstructor
public class UtenteRuoloId implements Serializable {
#Column(name = "matricola")
private Long matricola;
#Column(name = "id")
private Long id;
}
RoleEnum class
public enum RuoliEnum {
#JsonProperty
REFERENTE("REFERENTE"),
CONSULTATORE("CONSULTATORE"),
APPROVATORE("APPROVATORE");
#JsonProperty
private String value;
RuoliEnum(String value) { this.value = value; }
#JsonCreator
public static RuoliEnum fromValue(String text) {
for (RuoliEnum ruoli: RuoliEnum.values()) {
if (String.valueOf(ruoli.value).equalsIgnoreCase(text.trim())) {
return ruoli;
}
}
return null;
}
}
I also have all DTO's and the UserDTO has the RoleEnum instead of the List of class UserRole.
UserService
#Service
public class UtenteService {
#Autowired
private UtenteRepository utenteRepository;
#Autowired
private UtenteMap utenteMap;
public UtenteDto addUtente(UtenteDto utente) {
Utente u = utenteMap.fromDtoToModel(utente);
if(u != null) {
return utenteMap.fromModelToDto(utenteRepository.save(u));
}
return null;
}
UserController
#RestController
#RequestMapping("utente")
public class UtenteController {
#Autowired
private UtenteService utenteService;
#PostMapping("/addUtente")
public ResponseEntity addUtente(#Nullable #RequestBody UtenteDto utente) {
if(utente != null) {
return ResponseEntity.ok(utenteService.addUtente(utente));
} else {
return ResponseEntity.badRequest().body("utente non inserito correttamente");
}
}
this is a postman insert example:
{
"matricola" : 11,
"nome" : "aaa",
"cognome" : "bb",
"email" : "eee#mail.com",
"ruolo" : "APPROVATORE"
}
and this is the postman response with 200ok status:
{
"matricola": 11,
"nome": "aaa",
"cognome": "bb",
"email": "eee#mail.com",
"ruolo": null
}
the code doesn't tell me about any kind of error and i need to keep the UserRole table because it's required for this kind of exercise.
I tried everything I could but since I never worked like this with enums and this kind of table relations I don't know what's missing to complete it.

How to correctly describe entities with many-to-many relationship, Spring Boot JPA

I have those entities:
#Entity
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class Tender {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(nullable = false, updatable = false)
private Long id;
private String source;
private String sourceRefNumber;
private String link;
private String title;
#Column(columnDefinition="TEXT")
private String description;
private String field;
private String client;
private LocalDate date;
private LocalDate deadline;
#ManyToMany
private List<Cpv> cpv;
}
And CPV:
#Entity
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class Cpv {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String code;
private String description;
}
Each Tender can have list of Cpv-s.
In my DB I have already list of all CPV codes with description, so when I add new Tender to DB, it should add record to tender_cpv table with tender_id and cpv_id.
But when I'm using this method in my TenderServiceImpl to set Cpv id-s from DB I got error after that when try to save Tender:
#Override
public Tender addNewTender(Tender tender) {
if(tender.getCpv() != null) {
for(Cpv cpv : tender.getCpv()) {
cpv = cpvRepository.findCpvByCode(cpv.getCode());
}
}
tenderRepository.save(tender);
return tender;
}
org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.supportportal.domain.Cpv;
I understand that somewhere in the description of the entities a mistake was made, because earlier I did not have a database with all the CPV codes and before saving the tender I saved all the CPVs, but now I need to redo the logic to use the existing CPV database.
Please advise how can I change the entity description.
addNewTender method changes solved my problem:
#Override
public Tender addNewTender(Tender tender) {
if(tender.getCpv() != null) {
List<Cpv> dbCpvs = new ArrayList<>();
for(Cpv cpv : tender.getCpv()) {
dbCpvs.add(cpvRepository.findCpvByCode(cpv.getCode()));
}
tender.setCpv(dbCpvs);
}
tenderRepository.save(tender);
return tender;
}
In order for the existing entities from the database to bind to the new object, we had to first get each of them from the database and bind to the new entity.

Issue in persisting nested comments using Spring Hibernate

I am trying to create a simple CRUD application using Spring Boot with User, UserEntity, Post, Comment entities.
-> UserEntity is super class of Comment and Post.
-> Each comment has a ManyToOne relationship to a UserEntity (which can be a Post or another Comment)
UserEntity
   |
   #ManyToOne
   createdBy - refers to user table (id)
   |
--------------------
|        |
|        |
Post    Comment
        |
        #ManytoOne
          UserEntity - refers to PK(entity_id) of user_entity table as comment can be on post or reply to another comment
On trying to save a comment on post from the CommentService class,
//Controller
#PostMapping(path = "api/v1/addComment")
public void addComment(#RequestBody Comment comment){ commentService.addCommentOnPost(comment); }
//Service
public void addCommentOnEntity(Comment comment){ commentRepos.save(comment); }
the foreign key in comment table (parent_entity_id) referring to entity_id in user_entity table is not getting updated. The value is blank.
On the other hand UserEntity has a manytoone relationship with User -- createdBy -- which is updating foriegn key user_id in user_entity table properly
Can someone guide me what could be wrong, I have been trying since yesterday night but no luck. Have checked some other answers but could not get an answer for this case.
User.java
#Entity
#Table(name="[user]")
public class User {
#Id
#SequenceGenerator(name="student_sequence",
sequenceName = "student_sequence",
allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "student_sequence")
private long id;
private String name;
private String email;
private int age;
private LocalDate DOB;
//Setters and Getters and default constructor
}
UserEntity.java
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class UserEntity {
#Id
#SequenceGenerator(sequenceName = "entity_sequence", name="entity_sequence", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "post_sequence")
private long entityId;
private char entityType;
private LocalDate createdOn;
private LocalDate modifiedOn;
#ManyToOne
#JoinColumn(name = "user_id", referencedColumnName = "id")
private User createdBy;
//Setters and Getters and default constructor
}
Post.java
#Entity
public class Post extends UserEntity{
private String postHeading;
private String postBody;
//Setters and Getters and default constructor
}
Comment.java
#Entity
public class Comment extends UserEntity{
private String comment;
#ManyToOne
#JoinColumn(name="parent_entity_id", referencedColumnName = "entityId")
private UserEntity parentEntity;
//Setters and Getters and default constructor
}
and their repositories
#NoRepositoryBean
public interface UserEntityBaseRepos<T extends UserEntity> extends JpaRepository<T, Long>{
Optional<List<T>> findByCreatedBy_Id(Long user_id);
Optional<List<T>> findByEntityId(Long entity_id);
}
#Repository
public interface UserRespository extends JpaRepository<User, Long> {
Optional<User> findUserByEmail(String email);
Optional<User> findUserByName(String name);
}
#Repository
public interface PostRepos extends UserEntityBaseRepos<Post>, JpaRepository<Post, Long> {
}
#Repository
public interface CommentRepos extends UserEntityBaseRepos<Comment>, JpaRepository<Comment, Long> {
}
Json for postComment service
{
"entityType" : "C",
"createdOn" : "2020-02-05",
"createdBy" : {
"id" : 1
},
"comment": "I am the comment",
"parentEntity" : {
"entityId" : 1
}
}
//User with id = 1 and UserEntity(Post) with entityId = 1 available in database.
Here createdBy.id (user id) is getting updated in the user_entity table, but userEntity.entityId is not getting updated in the comment table
You have very complex entity relationships, it seems to me...
Anyway, I found that you added a generator property to the UserEntity entity with a post_sequence value, but I can't find any relationship to the Post entity in your database. This is probably the reason of the breakdown. You have to connect UserEntity to Post as shown on your diagram or change the generator value.
I was able to solve the problem. The issue was in the following piece of code in Comment concrete class
#ManyToOne
#JoinColumn(name="parent_entity_id", referencedColumnName = "entityId")
private UserEntity parentEntity;
and this Json input
"parentEntity" : {
"entityId" : 1
}
It seems the parentEntity in json input was not being parsed. This was solved on placing JsonProperty("parentEntity") above parentEntity in the Json input was being parsed correctly.
However there was another issue. The parentEntity was not being deserialized to UserEntity as UserEntity is an abstract class. I had to use JacksonPolymorphicDeserialization by introducing a new field parentType("P" for post, "C" for comment) along with some Annotations like below to deserialize parentEntity to corresponding concrete class object.
public class Comment extends UserEntity{
private String comment;
#Transient
#JsonProperty("parentType")
private char parentType;
#ManyToOne
#JoinColumn(name="parent_entity_id", referencedColumnName = "entity_id", foreignKey = #ForeignKey(value=ConstraintMode.CONSTRAINT))
#JsonProperty("parentEntity")
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME , property = "parentType", include = JsonTypeInfo.As.EXTERNAL_PROPERTY)
#JsonSubTypes(value = {
#JsonSubTypes.Type(value = Comment.class, name = "C"),
#JsonSubTypes.Type(value = Post.class, name = "P")
})
private UserEntity parentEntity;
reference - Jackson Polymorphic Deserialization via field. I am not really sure how this works. Will try to make sense of it and update the answer.
If anyone knows a better way to deserialize json, do mention it in the comments or as a new answer.

Hibernate: How to display data from multiple table

I am new in spring/hibernate technologies, I have tried to find an information about it, but failed, so if you can help I will be so thankful!
I need to display a JSON response in browser of multiple tables, one of the table has primary key for another one.
My entities:
#Entity
#Table
#ToString
public class Book {
#Id
#GeneratedValue(strategy = AUTO)
#JsonView(Views.IdName.class)
private Long book_id;
#JsonView(Views.IdName.class)
private String name;
#Column(length = 1000000)
private String text;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="author_id")
#JsonView(Views.IdName.class)
private Author author;
// ....get/set methods
Another one:
#Entity
#Table
#ToString
public class Page {
#Id
#GeneratedValue(strategy = AUTO)
private Long id;
#Column(length = 1000000)
private String text;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "book_id")
private Book book;
// ...get/set methods
My controllers:
#RestController
#RequestMapping("books")
public class BookController {
private final BookRepo bookRepo;
#Autowired
public BookController(BookRepo bookRepo) {
this.bookRepo = bookRepo;
}
#GetMapping
#JsonView(Views.IdName.class)
public List<Book> getAll() {
return bookRepo.findAll();
}
#GetMapping("{id}")
public Book getOne(#PathVariable("id") Book book) {
return book;
}
}
Another one:
#RestController
#RequestMapping("authors")
public class AuthorController {
private final AuthorRepo authorRepo;
#Autowired
public AuthorController(AuthorRepo authorRepo) {
this.authorRepo = authorRepo;
}
#GetMapping
public List<Author> getAll() {
return authorRepo.findAll();
}
#GetMapping("{id}")
public Optional<Author> getOne(#PathVariable("id") Long id) {
return authorRepo.findById(id);
}
}
And also repo for interaction with DB (they are the similar):
public interface AuthorRepo extends JpaRepository<Author, Long> {
}
So when I make a request for get all books, I take the following JSON:
enter image description here
Bit I want different result, something like:
[
{
"book_id" : 1,
"name": "name 1 book",
"author" :
{
"author_id" : 1,
"name": "some_name"
}
}
]
Also, when I tried to make a request for /authors/1, I will get the following response (something like recursion) :
enter image description here
So any help how can I handle with it? Thank you!
You can use a #NoRepositoryBean
like in this example:
#NoRepositoryBean
public interface MappedTypeRepository<T extends AbstractMappedType>
extends Repository<T, Long> {
#Query("select new com.example.YourObjectWithConstructor(e.attribute, sub.sub_attribute) from entity e inner join e.subtable sub where e.attribute = ?1")
List<YourObjectWithConstructor> findAllByAttribute(String attribute);
}
My example may not be 100% correct, I did not check the syntax. Feel free to explore it
Check this also:
JPQL Create new Object In Select Statement - avoid or embrace?

Why do not I get the id when mapping from DTO to entity with mapstruct?

I have an API with spring boot and I use mapstruct and I just want to update the Person entity. For this, having PersonDto update Person.
What I have so far:
Mapper:
#Mapper
public interface PersonMapper {
PersonDto toPersonDto(Person person);
Person toPerson(PersonDto personDto);
Person updatePersonFromDto(PersonDto persoonDto, #MappingTarget
Person document);
}
In service layer:
Find Person:
public PersonDto updatePerson(Long personId) {
PersonDto personDto = personService.findById(personId)
.orElseThrow(() -> new PersonNotFoundException(id));
personDto.set(...) //set others properties
Person person = personMapper.toPerson(personDto);
person = personMapper.updatePersonFromDto(personDto, person);
personRepository.save(person);
return personMapper.toPersonDto(person);
}`
My question, is there a strategy or a better way of update an entity from a DTO?
Edit:
I was able to solve a part, now I do not lose the id, but I still create a new object instead of updating it. The id is in AbstractPersistableEntity.
#Entity
#Getter
#Setter
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class Person extends AbstractPersistableEntity<ID> implements Serializable {
#Column
private String name;
#Column
private String lastName;
#Column
private Integer age;
}
public class PersonDto {
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
private Long id;
private String name;
private String lastName;
private Integer age;
}
Maybe it is not the most optimal or correct solution but it works. I made the following modifications to the mapper:
#BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE,
nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS)
Person updatePersonFromDto(PersonDto persoonDto, #MappingTarget
Person document);
#ObjectFactory
default Person updatePerson(PersonDto personDto, Person person){
if (personDto != null){
Long id = person.getId();
Person resultPerson = updatePersonFromDto(personDto, person);
resultPerson.setId(id);
return resultPerson;
}
return null;
}
Then from the service just call:
person = personMapper.updatePerson(personDto, person);

Resources