Managing objects with relationships in multilayered app using JPA repository with Spring Data

30 Views Asked by At

I'm trying to code my first webapp using Spring Boot. I haven't had much time to dive deep into ORMs, JPA and Hibernate, so it's hard for me to implement application layers with transfer objects. So I have this entities:

@Data
@Entity
public class Dog {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "owner_id")
    private Owner owner;
}
@Entity
@Data
public class Owner {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String firstName;
    private String lastName;

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "owner")
    private Set<Dog> dogs = new HashSet<>();

}

What I'm trying to do is to pass a name for new Dog from controller to DogService and assign an owner ID to it with DTO that looks like this:

@Data
public class CreateDogDTO {
    private String name;
    private Long ownerId;
}

In my DogService I'm fetching for OwnerDTO with OwnerService

@Data
public class OwnerDTO {
    private String firstName;
    private String lastName;

    @JsonProperty("owner_url")
    private String ownerURL;

    private Set<GetDogDTO> dogs;
}
@Data
public class GetDogDTO {
    private Long id;
    private String name;
    private OwnerDTO owner;
}

And when I'm creating a new Dog, I get a Hibernate Error:

org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing

I understand that there might be some circular definition with OwnerDTO having set<GetDogDTO> that itself have again OwnerDTO. So here's my first question: How should I construct DTOs to get maximum amount of data to use on the front of my application? Should I create simple OwnerDTO and DogDTO without any relation just to simplify things, or is it possible to force hibernate to create such object for me. I'm using MapStruct for mapping:

@Mapper
public interface DogMapper {
    DogMapper INSTANCE = Mappers.getMapper(DogMapper.class);

    GetDogDTO dogToDogDTO(Dog dog);
    Dog dogDTOToDog(GetDogDTO getDogDTO);
    @Mapping(source = "ownerId", target = "owner.id")
    Dog saveDogDTOToDog(CreateDogDTO createDogDTO);
}

Is there clear design for handling relations in such examples. I mean it's pretty common stuff but whenever I'm looking at repos with such applications I can't understand how they handle this situation when Repository returns Entity object but I have to deal with DTOs in service layer and Repository doesn't recognize object because it was mapped to DTO and I can't use this information to create a Dog.

To state my question clear. What should I do in Service Layer designed for handling Dog. Should I inject OwnerRepository to this class, get the desired Entity and be done with it, or should I overengineer this, inject OwnerService class and try to create new Dog with data only acquired from OwnerDTO - is there a way to map this object so I don't get Hibernate error.

    //Should take CreateDogDTO with ownerID and dog's name.
    @Override
    public GetDogDTO createNewDog(String dogName) {
        Dog dogToCreate = new Dog();
        dogToCreate.setName(dogName);
        //TODO: Owner must come from session or be present as a parameter in URL
        ownerService.getAllOwners().stream().findFirst().ifPresent(owner -> dogToCreate.setOwner(ownerMapper.ownerDTOToOwner(owner)));
        Dog createdDog = dogRepository.save(dogToCreate);
        System.out.println(createdDog);
        return dogMapper.dogToDogDTO(createdDog);
    }
@Mapper
public interface OwnerMapper {
    OwnerMapper INSTANCE = Mappers.getMapper(OwnerMapper.class);

    OwnerDTO ownerToOwnerDTO(Owner owner);
    Owner ownerDTOToOwner(OwnerDTO ownerDTO);
}
0

There are 0 best solutions below