1

I have an entity that needs to be slightly modified before it is returned to the user. From what I understand, the best way to do that is to use a Data Transfer Object.

That is working fine, but then I added a child entity. I again created a DTO for the child entity. Here is the code for the repository:

@Repository
public interface DrawingReleaseRepository extends CrudRepository<DrawingRelease, Integer> {
    @Query("SELECT new com.engineering.dto.drawings.DrawingReleaseDTO("
            + "dwgrel.drawingReleaseID,"
            + "relType"
            + ") FROM DrawingRelease dwgrel "
            + "JOIN dwgrel.releaseType relType "
            + "WHERE dwgrel.drawingReleaseID = ?1")
    DrawingReleaseDTO getDrawingRelease(int drawingReleaseID);
}

The constructor for DrawingReleaseDTO is this:

public DrawingReleaseDTO(int drawingReleaseID, DrawingReleaseType releaseType) {
    this.drawingReleaseID = drawingReleaseID;
    this.releaseType = new DrawingReleaseTypeDTO(releaseType);
}

In my service layer, I can now manipulate the data and have that returned to the client, but the changes are not persisted to the database (which is what I want):

@Override
public DrawingReleaseDTO findByID(int id) {
    DrawingReleaseDTO dto = repository.getDrawingRelease(id);
    dto.getReleaseType().setCustom1(true);
    return dto;
}

The problem with that scenario is that it is not very efficient. What I mean is that Hibernate executes two queries - one for the DrawingRelease and one for the DrawingReleaseType.

I thought that the way to fix this would be to specify JOIN FETCH dwgrel.releaseType relType, as that should force hibernate to fetch all the data in one shot, but that produces the error:

query specified join fetching, but the owner of the fetched association was not present in the select list

Is there a way to use a DTO, include data for child entities, AND have it all use efficient queries?

Tim
  • 1,605
  • 6
  • 31
  • 47
  • TLDR; first sentence-TDO is ment to be used eg as REST output or input parameters, not to "modify" nor transform anything. You are wrong from the very beginning here. – Antoniossss Oct 31 '18 at 22:41
  • @Antoniossss Reference https://stackoverflow.com/questions/53073313/spring-data-jpa-updates-entity-when-i-dont-want-it-to/53088702#53088702 for more background as to why I am using a DTO. What else would you suggest? – Tim Oct 31 '18 at 22:45
  • I just ment that DTO are not to "transform" anything as you stated. And I kind of dont understand the connection between DTOs and the issue that something is not persisting into the database. Probably its just my fatigue. – Antoniossss Oct 31 '18 at 22:59
  • Where is the query for the DrawingRelease coming from? The query you specified only has the dwgrel.drawingReleaseID, so isn't building a DrawingRelease instance. Could your DrawingReleaseType have a reference to DrawingRelease? If so, mark it as lazy to avoid the fetch. – Chris Nov 01 '18 at 00:06
  • @Chris I think the query is building a DrawingRelease instance. It is using the drawingReleaseID from that instance to build the DrawingReleaseDTO. I don't want to avoid the fetch--I want it to happen all in one query. – Tim Nov 01 '18 at 16:39
  • @Tim, Yes, that is my point. Without knowing exactly what is causing the query, you can't optimize the fetch. If your DrawingReleaseType instance has a reference to DrawingRelease, this is what you would need to indicate needs to be fetched. As mentioned in other answers, DTO is obfuscating things a bit and probably isn't needed if all you are doing is wrapping a DrawingReleaseType instance. If DrawingReleaseType has a DrawingRelease reference, type.getDrawingRelease().getId() would return the id string anyway. – Chris Nov 01 '18 at 21:07
  • Ask your real question which is around "I have an entity that needs to be slightly modified before it is returned to the user." https://en.wikipedia.org/wiki/XY_problem . There are most likely alternative solutions to DTO. – Alan Hay Nov 02 '18 at 10:20
  • @AlanHay I did ask the real question at https://stackoverflow.com/questions/53073313/spring-data-jpa-updates-entity-when-i-dont-want-it-to Feel free to offer an alternative solution there. – Tim Nov 02 '18 at 14:15

2 Answers2

1

The problem is that you mixed DTOs and entities.

The DTO should not reference entities, and the second query comes from initializing the child entity.

Also, passing an entity to a DTO is an anti-pattern:

this.releaseType = new DrawingReleaseTypeDTO(releaseType);

You need the DTO to be constructed from the column projections instead.

Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
  • Maybe I should have included this code as well, but is it still wrong to pass an entity to a DTO if the DTO constructor has `DTOproperty1 = releaseType.getPropertyX()` and so on? I know that the second query comes from initializing the child entity, but is there a way to initialize them both in a single query? I will actually have a number of child entities, and if I can get all the data I need in a single query, that seems to be more efficient. Or is the right way to retrieve the entities and then construct the DTO? But then don't you lose the benefit of not retrieving managed entities? – Tim Nov 01 '18 at 14:29
  • Yes, it's wrong. If you need entities, use entity queries. If you need DTOs, use DTO projections. But don't mix them. – Vlad Mihalcea Nov 01 '18 at 17:21
  • Do you have an example anywhere for how to do a DTO with child DTOs? Your article just uses native types. – Tim Nov 01 '18 at 17:35
  • Yes that is a good example of a hierarchy of the same object, but what about when there are children of a different object like in my question? Is there a way to retrieve all the data in a single query (like JOIN FETCH), or do you have to just do a JOIN and let Hibernate run multiple queries to get all the child data? – Tim Nov 01 '18 at 20:06
0

That's a perfect use case for Blaze-Persistence Entity Views which will do exactly what you want and also improve the performance by generating a JPQL/HQL query that only fetches the relevant attributes. Although I don't know your model, the use of entity views for your model as far as I understood it could look like this

@EntityView(DrawingRelease.class)
public interface DrawingReleaseDTO
  int getDrawingReleaseID();
  DrawingReleaseTypeDTO getReleaseType();

  @EntityView(DrawingReleaseType.class)
  interface DrawingReleaseTypeDTO {
    boolean isCustom1();
  }
}

When properly setup, you can use that entity view with spring-data repositories

interface DrawingReleaseRepository extends JpaRepository<DrawingReleaseDTO, Long> {
}

DrawingReleaseDTO dto = drawingReleaseRepository.findById(id);

It will generate a JPQL/HQL query like this

SELECT drawingRelease.id, drawingReleaseType.custom1 
FROM DrawingRelease drawingRelease 
LEFT JOIN drawingRelease.drawingReleaseType drawingReleaseType 
WHERE drawingRelease.id = :id
Christian Beikov
  • 15,141
  • 2
  • 32
  • 58