{{announcement.body}}
{{announcement.title}}

How to Enrich DTOs With Virtual Properties Via Spring Projections

DZone 's Guide to

How to Enrich DTOs With Virtual Properties Via Spring Projections

Learn more about how to enrich DTOs with Spring projections.

· Java Zone ·
Free Resource

In JPA, as a rule of thumb, our queries (SQLs) must extract from the database only the needed data, meaning only the data that it is prone to be modified. When the fetched data is read-only (we don't plan to modify it), then we must strive to fetch it as DTOs instead of JPA entities. 

Starting from this statement, let's consider the following JPA trivial entity:

@Entity
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

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

    private String name;
    private String surname;
    private String city;
    private String country;
    private long ssn;
    private int age;

    // getters, setters, ...
}


Extracting a DTO

Now, let's assume that we want to fetch from the database a DTO containing only the usernameandcityfor displaying them in a GUI. So, we don't plan to modify this data and we want to ignore the usersurnamecountryssn, andage.

In Spring Data, we can accomplish this task via Spring Data Projections. Spring Data Repositories are usually returning the domain model (entities), but when we need DTOs, we can rely on projections.

A projection starts with an interface definition that contains the signatures of getters corresponding to the entity fields (database columns) that should be part of the DTO. For example, an interface for fetching only the usernameandcitycan be written as follows:

public interface UserNameAndCity {

    String getName();
    String getCity();
}


Having this interface in place, we can continue by writing read-only queries that extract DTOs. For example, we can add in ourUserRepository, a query that fetches the first two users bysurname:

@Repository
public interface UserRepository extends JpaRepository<User, Long> {

    @Transactional(readOnly = true)
    List<UserNameAndCity> findFirst2BySurname(String surname);
}


Further, using Spring Data style, we can call this method and display the data:

List<UserNameAndCity> users = userRepository.findFirst2BySurname("Francisco");

logger.info(() -> "Number of users:" + users.size());

for (UserNameAndCity user : users) {
    logger.info(() -> "User:" + user.getName() + ", " + user.getCity());
}


The executed query reveals that only name and city columns have been fetched from the database:

SELECT user0_.name AS col_0_0_, user0_.city AS col_1_0_ 
FROM user user0_
WHERE user0_.surname = ? LIMIT ?


The code of this application can be found here.

Adding Virtual Properties to DTOs

By adding virtual properties to DTOs, we can remodel data. The virtual properties can:

  • enrich the final DTO with properties that point to backing bean properties from the domain model (check the livinginproperty from UserDetailDTO below)

  • enrich the final DTO with properties that don't have a match in the domain model (check thesessionid andstatusproperties fromUserDetailDTO below)

Let's consider the below DTO, UserDetail:

public interface UserDetail {

    String getName();

    @Value("#{target.city}")
    String livingin();

    @Value("#{ T(java.lang.Math).random() * 10000 }")
    int sessionid();

    @Value("online")        
    String status();
}


  • We fetch from the database only the usernameandcity 

  • In the projection interface, UserDetail , use the@Valueand Spring SpEL to point to a backing property from the domain model (in this case, the domain model propertycityis exposed via the virtual property livingin)

  • In the projection interface, UserDetail, use the@Valueand Spring SpEL to enrich the result with two virtual properties that don't have a match in the domain model (in this case,sessionidandstatus)

Now, let's write a query for extracting a List<UserDetail>:

@Repository
public interface UserRepository extends JpaRepository<User, Long> {

    @Transactional(readOnly = true)
    @Query("SELECT u.name as name, u.city as city FROM User u WHERE u.surname = ?1")            
    List<UserDetail> fetchBySurname(String surname);
}


Finally, let's display the output:

List<UserDetail> users = userRepository.fetchBySurname("Francisco");

logger.info(() -> "Number of users:" + users.size());

for (UserDetail user : users) {
    logger.info(() -> "User:" + user.getName() + ", "
        + user.livingin() + ", " + user.status() + ", " + user.sessionid());
}


The complete code can be found here.

Topics:
dto ,projections ,jpa ,Spring Boot ,Spring Data ,Java ,tutorial ,Spring Projections ,Virtual Properties ,properties

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}