Bidirectional ManyToOne/OneToMany - spring-boot

I think i miss misunderstood how bidirectional relation works in JPA.
Club Enitity:
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#Entity
#Table(name = "club")
public class Club {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Long id;
#Column(name = "club_name")
private String clubName;
#OneToMany(mappedBy = "club", orphanRemoval = true)
private List<Player> players = new ArrayList<>();
}
Player Entity:
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#Entity
#Table(name = "player")
public class Player {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Long id;
#Column(name = "player_name")
private String playerName;
#ManyToOne(optional = false)
#JoinColumn(name = "club_id", nullable = false)
private Club club;
}
One club may have several players, several players can be in one club. Next i create repositiories for for these entities
public interface ClubRepository extends JpaRepository<Club, Long> {}
public interface PlayerRepository extends JpaRepository<Player, Long> { }
and controlers.
#RestController
#RequestMapping("/api/club")
public class ClubController {
#Autowired
ClubRepository clubRepository;
#GetMapping
public List<Club> getALlClubs(){
return clubRepository.findAll();
}
}
#RestController
#RequestMapping("/api/player")
public class PlayerController {
#Autowired
PlayerRepository playerRepository;
#GetMapping
public List<Player> getALlPlayers(){
return playerRepository.findAll();
}
}
I added some data to these tables, but when i call api i have error:
ERROR 11128 --- [nio-8080-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Infinite recursion (StackOverflowError);
I want to my data in endpoint looks like:
[
"id": 1
"clubName": "madrid"
"players": {
"id": 1
"player_name": "kaka"
}
]
Or something like that, i just want to be able to easily refer to particular data on the frontend side. How should I do it correctly?

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.

Why does DataJpaTest fail when saving OneToMany-related data in this pattern?

I have three Hibernate #Entity's below that mimic a failure in my production app:
#Entity
#Data
#SuperBuilder(toBuilder = true)
#EqualsAndHashCode(callSuper = true)
#NoArgsConstructor
#AllArgsConstructor
public class Dog extends Animal {
String barkType;
}
The Dog entity uses JOINED inheritance with this class, Animal:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#Data
#SuperBuilder(toBuilder = true)
#NoArgsConstructor
#AllArgsConstructor
public class Animal {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Type(type = "uuid-char")
private UUID id;
#OneToMany(cascade = CascadeType.REMOVE)
#JoinColumn(name = "animalId", referencedColumnName = "id", insertable = false, updatable = false)
#Builder.Default
private List<Toy> toys = new ArrayList<>();
}
This Toy Entity is related to the parent class, Animal
#Entity
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class Toy {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Type(type = "uuid-char")
private UUID id;
#Type(type = "uuid-char")
private UUID animalId;
private String shape;
}
And here is my implementation I am testing:
#Service
#AllArgsConstructor
public class DogService {
DogRepository repository;
ToyRepository toyRepository;
#Transactional
public Dog saveDogDTO(DogDTO dogDTO) {
Dog entity = Dog.builder()
.barkType(dogDTO.getBarkType())
.build();
repository.save(entity);
toyRepository.save(Toy.builder()
.shape(dogDTO.getToyShape())
.animalId(entity.getId())
.build());
return entity;
}
}
Here is my failing Test, which fails on the LAST line:
#DataJpaTest
class DogServiceTests {
private DogService dogService;
#Autowired
private DogRepository dogRepository;
#Autowired
private ToyRepository toyRepository;
#Test
void save_not_working_example() {
dogService = new DogService(dogRepository, toyRepository);
var dogDTO = DogDTO.builder()
.barkType("big bark")
.toyShape("some shape")
.build();
var savedDog = dogService.saveDogDTO(dogDTO);
assertThat(dogRepository.count()).isEqualTo(1);
assertThat(toyRepository.count()).isEqualTo(1);
var findByIdResult = dogRepository.findById(savedDog.getId());
assertThat(findByIdResult.get().getToys()).hasSize(1);
}
}
The test failure message:
Expected size: 1 but was: 0 in:
[]
java.lang.AssertionError:
Expected size: 1 but was: 0 in:
[]
The issue seems to be that the double JPA repository save clashes within the #Transaction. Is there a way to overcome this issue? I tried adding #Transactional(propagation = Propagation.NEVER) to the test, but then I get this failure:
failed to lazily initialize a collection of role: com.example.datajpatest.demo.models.Animal.toys, could not initialize proxy - no Session
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.example.datajpatest.demo.models.Animal.toys, could not initialize proxy - no Session
#DataJpaTest is annotated #Transactional so your test method is all wrapped in a single transaction, and hence a single EntityManager. You could make your test pass by calling EntityManager.detach() on the savedDog before querying using findById(). You could also fix it by manually setting up the dog's toys in the DogService. That would be my recommendation because otherwise sooner or later you might find the same inconsistency bug in production code - the transaction boundaries just have to shift a bit and that would be quite hard to spot. In a way #DataJpaTest has done you a favour by pointing out the problem, albeit somewhat indirectly.
Ultimately, the database state doesn't match the state of the EntityManager cache, so you have to clear the cache to get the result you want. Starting a new transaction would clear the cache too, and that's what is probably happening in production. Hibernate trusts you to make the object graph match the database state when you save (or flush). If they don't match then Hibernate has no way of knowing without querying the database, which it would regard as redundant and inefficient.
Try this mapping here instead:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#Data
#SuperBuilder(toBuilder = true)
#NoArgsConstructor
#AllArgsConstructor
public class Animal {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Type(type = "uuid-char")
private UUID id;
#OneToMany(mappedBy = "animal", cascade = CascadeType.REMOVE)
#Builder.Default
private List<Toy> toys = new ArrayList<>();
}
#Entity
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class Toy {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Type(type = "uuid-char")
private UUID id;
#ManyToOne(fetch = LAZY)
#JoinColumn(name = "animalId")
private Animal animal;
private String shape;
}

Spring data JPA: embedded ID error when saving the entity

I have the following entity classes:
#Embeddable
#Getter
#Setter
public class OrganizationCyclePlageKey implements Serializable {
#Column(name = "organization_id")
Long organizationId;
#Column(name = "cycle_plages_id")
Long cyclePlagesId;
...
equals() and hashCode() methods come here
#Entity
#Table(name = "organization_cycle_plages")
#Getter
#Setter
public class OrganizationCyclePlage {
#EmbeddedId
private OrganizationCyclePlageKey id;
#ManyToOne
#MapsId("organizationId")
#JoinColumn(name = "organization_id")
Organization organization;
#ManyToOne
#MapsId("cyclePlagesId")
#JoinColumn(name = "cycle_plages_id")
CyclePlage cyclePlage;
...
other attributes
}
#Entity
#Getter
#Setter
public class CyclePlage extends AbstractEntity {
#OneToMany(mappedBy = "cyclePlage")
private Set<OrganizationCyclePlage> organizationCyclePlages;
...
}
#Entity
#DynamicUpdate
#Getter
#Setter
public class Organization extends AbstractEntity {
#OneToMany(mappedBy = "organization")
private Set<OrganizationCyclePlage> organizationCyclePlages = new HashSet<>();
...
}
SpringBoot app starts up normally without errors.
But when I try to save an instance of OrganizationCyclePlage:
OrganizationCyclePlage ocp = new OrganizationCyclePlage();
ocp.setOrganization(organization);
ocp.setCyclePlage(cyclePlage);
organizationCyclePlageRepository.save(ocp);
it raises the error when calling organizationCyclePlageRepository.save(ocp):
org.hibernate.PropertyAccessException: Could not set field value [361] value by reflection : [class com.XXXX.OrganizationCyclePlageKey.cyclePlagesId] setter of com.XXXX.OrganizationCyclePlageKey.cyclePlagesId
What's wrong with these relations?
I had to add the constructor into the OrganizationCyclePlageKey class to init the foreign keys values as well a default constructor via #NoArgsConstructor annotation:
public OrganizationCyclePlageKey(Long organizationId, Long cyclePlagesId) {
this.organizationId = organizationId;
this.cyclePlagesId = cyclePlagesId;
}
and init the OrganizationCyclePlageKey instance in the OrganizationCyclePlage class:
public class OrganizationCyclePlage {
private OrganizationCyclePlageKey id = new OrganizationCyclePlageKey();
...
}

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

Why is my mapped DTO List null? What is the best way to map and persist Child Lists?

I have a simple problem - but I think "I am standing on the tube".
I have a spring boot rest api with JPA, Modelmapper, Entities and DTOs.
But the mapping doesn't work.
Entities:
#Getter
#Setter
#MappedSuperclass
public class AbstractEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
#Getter
#Setter
#Entity(name = "contacts")
public class Contact extends AbstractEntity {
#NotBlank
private String firstName;
#NotBlank
private String lastName;
#Valid
#OneToMany(mappedBy = "contact", cascade = CascadeType.ALL, orphanRemoval = true)
private List<PhoneNumber> phoneNumberList;
}
#Getter
#Setter
#Entity(name = "phone_numbers")
public class PhoneNumber extends AbstractEntity {
#NotBlank
private String label;
#NotBlank
private String number;
#ManyToOne
#JoinColumn(name = "contact_id", referencedColumnName = "id")
#Setter(value = AccessLevel.NONE)
private Contact contact;
}
The DTOs:
#Getter
#Setter
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class ContactDTO {
private Long id;
private String firstName;
private String lastName;
List<PhoneNumberDTO> phoneNumberDTOList = new ArrayList<>();
}
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class PhoneNumberDTO {
private Long id;
private String label;
private String number;
}
My ModelMapperConfig:
#Bean
public ModelMapper modelMapper() {
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration()
.setFieldMatchingEnabled(true)
.setFieldAccessLevel(AccessLevel.PRIVATE);
return modelMapper;
}
Repo:
public interface ContactRepository extends JpaRepository<Contact, Long{
}
Service (only the create method):
#Override
public ContactDTO createOne(ContactDTO contactDTO) {
Contact contact = modelMapper.map(contactDTO, Contact.class);
contactRepository.save(contact);
return contactDTO;
}
Is this the correct way to persist the Contact with its multiple phonenumbers?
And how can I create a simple mapping?
If i want to persist it, there comes an error:
Column 'contact_id' cannot be null

Resources