7

So, I have a VehicleDto:

class VehicleDto {
    private String someId
    private String vType;
    private CarDto car;
    private BikeDto bike;
}

I need to have either the CarDto or BikeDto in the request payload.

In the post request payload there will be multiple fields which are properties of VehicleDto, for example, here someId. Now, this someId is also a part of CarDto and BikeDto, and any other Dto that is a child of VehicleDto.

So when I try to save into the db, I have some issues there.

if (vehicleDto.getVType().equals("CAR")) {
    this.saveCar(vehicleDto);
}

private boolean saveCar(TicketSoldCreateDto ticketSoldCreateDto) {
    CarDto carDto = ticketSoldCreateDto.getCar();
    carDto is mapped to Car model
    // Now how do I map the rest of the fields in vehicleDto to Car model??
}

Super class Vehicle:

@MappedSuperclass
@Data
public abstract class Vehicle extends AbstractBaseEntity {
// fields same as vehicleDto
}

Child class Car:

@Entity
@Data
public class Car extends Vehicle {
// Some fields
}

How should I design such problems?

nirvair
  • 4,001
  • 10
  • 51
  • 85

1 Answers1

16

Why not use inheritance instead of association for DTO's, just like for entities? And then map these DTO's to entites and back with some mapper (I prefer mapstruct).

I've made a complete example on github.

DTO's:

@Data
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes({
        @JsonSubTypes.Type(value = CarDto.class, name = "car"),
        @JsonSubTypes.Type(value = BikeDto.class, name = "bike")
})
public class VehicleDto {
    private Long id;
    private String type;
    private Integer modelYear;
}

@Data
public class BikeDto extends VehicleDto {
    private String frameType;
}

@Data
public class CarDto extends VehicleDto {
    private Boolean isCabriolet;
}

@JsonTypeInfo and @JsonSubTypes are needed to automatically resolve DTO type in Controller. My sample controller receives VehicleDto and tries to store it in database as Bike entity with DtoMapper and VehicleService. Last step - it reads it again from database and responds with BikeDto.

@Controller
public class SampleController {

    @Autowired
    private VehicleService vehicleService;

    @Autowired
    private DtoMapper mapper;

    @PostMapping("/testDto")
    @ResponseBody
    @Transactional
    public BikeDto testDto(@RequestBody VehicleDto vehicleDto) {

        if (vehicleDto instanceof BikeDto)
            vehicleService.saveBike(mapper.toBikeEntity((BikeDto) vehicleDto));

        return mapper.toBikeDto(vehicleService.getBike(vehicleDto.getId()));
    }
}

For DtoMapper I've used Mapstruct, it converts my Bike entity to BikeDto and back:

@Mapper(componentModel = "spring")
@Component
public interface DtoMapper {

    @Mapping(target = "type", constant = "bike")
    BikeDto toBikeDto(Bike entity);

    Bike toBikeEntity(BikeDto dto);
}

And last, test class for this example. It passes BikeDto as POST body and expects it to return back.

@RunWith(SpringRunner.class)
@SpringBootTest
@ActiveProfiles("scratch")
public class SampleDataJpaApplicationTests {

    @Autowired
    private WebApplicationContext context;
    private MockMvc mvc;

    @Before
    public void setUp() {
        this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build();
    }

    @Test
    public void testDto() throws Exception {

        BikeDto bikeDto = new BikeDto();
        bikeDto.setId(42L);
        bikeDto.setType("bike");
        bikeDto.setModelYear(2019);
        bikeDto.setFrameType("carbon");

        Gson gson = new Gson();
        String json = gson.toJson(bikeDto);

        this.mvc.perform(post("/testDto").contentType(MediaType.APPLICATION_JSON).content(json))
                .andExpect(status().isOk())
                .andExpect(content().json(json));
    }
}

POST (BikeDto) body:

{
 "id":42,
 "type":"bike",
 "modelYear":2019,
 "frameType":"carbon"
}

Other classes (entities, services, repositories) you can watch in complete example on github.

Anatoly Shamov
  • 2,608
  • 1
  • 17
  • 27
  • So, how will my request be like? Will the vehicleDto have an object named bike? or the vehicle dto will be an instance of BikeDto? – nirvair Sep 20 '17 at 10:16
  • 1
    BikeDto is an instance of VehicleDto. Also, if VehicleDto alone should not be used - you can make it abstract. – Anatoly Shamov Sep 20 '17 at 10:21
  • And, what does name=bike mean here `@JsonSubTypes.Type(value = BikeDto.class, name = "bike")` ? Is it the value of `vType` ? – nirvair Sep 20 '17 at 10:41
  • Yes. Depends on value of 'type' (just renamed it from 'vType', as I use 'type' field in project), the real class of VehicleDto's child will be resolved. "bike" refers to BikeDto.class, and "car" refers to CarDto.class – Anatoly Shamov Sep 20 '17 at 10:44
  • For some reasons my code breaks at that if condition where it checks if it is an instance of BikeDto.class. It doesn't go inside if condition. – nirvair Sep 20 '17 at 10:57
  • You've tried to run my example, or made changes to your code? Show your @Controller class and POST request body, please. – Anatoly Shamov Sep 20 '17 at 11:00
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/154883/discussion-between-anatoly-shamov-and-nirvair). – Anatoly Shamov Sep 20 '17 at 11:01