Spring Data Table of Content
Spring Data Projection
Spring Data query methods usually return one or multiple instances of the aggregate root managed by the repository. However, it might sometimes be desirable to create projections based on certain attributes of those types. Spring Data allows modeling dedicated return types, to more selectively retrieve partial views of the managed aggregates.
Projection is just a way to retrieve certain fields from the database without retrieving all fields. This improves performance as it does not retrieve all fields.
There are 3 different types of projection: Scalar, DTO, Entity projection
Scalar Projection
Scalar projection allows you to select columns you need.
public interface BookRepository extends JpaRepository<User, Long> {
@Query("SELECT u.name, u.email FROM Book b")
List<Object[]> getNameAndEmail();
@Repository
public interface BookRepository extends JpaRepository<User, Long> {
@Query("SELECT u.name, u.email FROM Book b")
List<Object[]> getNameAndEmail();
}
@Repository
public interface BookRepository extends JpaRepository<User, Long> {
@Query("SELECT u.name, u.email FROM Book b")
List<Object[]> getNameAndEmail();
}
DTO Projection
DTO projection uses a constructor which Hibernate uses to populate data from the database.
public interface UserRepository extends CrudRepository<User, Long> {
@Query("SELECT new com.kaveinga.user.dto.UserDetailDTO(u.firstName, u.lastName) FROM User user WHERE user.firstName = :firstName")
List<UserDetailDTO> findByFirstName(String firstName);
@Repository
public interface UserRepository extends CrudRepository<User, Long> {
@Query("SELECT new com.kaveinga.user.dto.UserDetailDTO(u.firstName, u.lastName) FROM User user WHERE user.firstName = :firstName")
List<UserDetailDTO> findByFirstName(String firstName);
}
@Repository
public interface UserRepository extends CrudRepository<User, Long> {
@Query("SELECT new com.kaveinga.user.dto.UserDetailDTO(u.firstName, u.lastName) FROM User user WHERE user.firstName = :firstName")
List<UserDetailDTO> findByFirstName(String firstName);
}
JPA DTO
As long as the DTO class has only one constructor and its parameter names match your entity class’s attribute names, Spring generates a query with the required constructor expression.
public interface UserRepository extends JpaRepository<User, Long> {
UserDTO findByEmail(String email);
public interface UserRepository extends JpaRepository<User, Long> {
UserDTO findByEmail(String email);
}
public interface UserRepository extends JpaRepository<User, Long> {
UserDTO findByEmail(String email);
}
public UserDTO(Long id, String uid, String name, String email, int age, Address address) {
public class UserDTO {
private Long id;
private String uid;
private String name;
private String email;
private int age;
private Address address;
public UserDTO(Long id, String uid, String name, String email, int age, Address address) {
super();
this.id = id;
this.uid = uid;
this.name = name;
this.email = email;
this.age = age;
this.address = address;
}
// getters and setters
}
public class UserDTO {
private Long id;
private String uid;
private String name;
private String email;
private int age;
private Address address;
public UserDTO(Long id, String uid, String name, String email, int age, Address address) {
super();
this.id = id;
this.uid = uid;
this.name = name;
this.email = email;
this.age = age;
this.address = address;
}
// getters and setters
}
JPA DTO as interface
Here is another way you can retrieve data from the database. Instead of having a DTO class you can use an interface. Your interface only defines getter methods for basic attributes.
public interface UserRepository extends JpaRepository<User, Long> {
UserView findByUid(String uuid);
public interface UserRepository extends JpaRepository<User, Long> {
UserView findByUid(String uuid);
}
public interface UserRepository extends JpaRepository<User, Long> {
UserView findByUid(String uuid);
}
public interface UserView {
AddressView getAddress();
public interface UserView {
Long getId();
String getName();
String getEmail();
int getAge();
String getUid();
// nested object
AddressView getAddress();
}
public interface UserView {
Long getId();
String getName();
String getEmail();
int getAge();
String getUid();
// nested object
AddressView getAddress();
}
September 15, 2020
Spring Data Query to DTO
We will show here how you can map a DTO to a query using constructor.
First create your entity class. Here we will have a User class.
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false, updatable = false, unique = true)
@Column(name = "uid", unique = true, nullable=false, updatable=false)
@JsonIgnoreProperties(value= {"user"})
@OneToOne(fetch=FetchType.EAGER, cascade=CascadeType.ALL)
@JoinColumn(name="address_id")
// TODO Auto-generated constructor stub
@Entity
@Table(name = "user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false, updatable = false, unique = true)
private Long id;
@Column(name = "uid", unique = true, nullable=false, updatable=false)
private String uid;
@Column(name = "name")
private String name;
@Column(name = "email")
private String email;
@Column(name = "age")
private int age;
@JsonIgnoreProperties(value= {"user"})
@OneToOne(fetch=FetchType.EAGER, cascade=CascadeType.ALL)
@JoinColumn(name="address_id")
private Address address;
public User() {
super();
// TODO Auto-generated constructor stub
}
// setters and getters
}
@Entity
@Table(name = "user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false, updatable = false, unique = true)
private Long id;
@Column(name = "uid", unique = true, nullable=false, updatable=false)
private String uid;
@Column(name = "name")
private String name;
@Column(name = "email")
private String email;
@Column(name = "age")
private int age;
@JsonIgnoreProperties(value= {"user"})
@OneToOne(fetch=FetchType.EAGER, cascade=CascadeType.ALL)
@JoinColumn(name="address_id")
private Address address;
public User() {
super();
// TODO Auto-generated constructor stub
}
// setters and getters
}
Second create your DTO class. In this case we will have a UserDto.
public UserDto(String uid, String name, String email, int age) {
this(uid,name,email,age,null);
public UserDto(String uid, String name, String email, int age, Address address) {
public class UserDto {
private String uid;
private String name;
private String email;
private int age;
private Address address;
public UserDto() {
this(null,null,null,0);
}
public UserDto(String uid, String name, String email, int age) {
this(uid,name,email,age,null);
}
public UserDto(String uid, String name, String email, int age, Address address) {
super();
this.uid = uid;
this.name = name;
this.email = email;
this.age = age;
this.address = address;
}
// getters and setters
}
public class UserDto {
private String uid;
private String name;
private String email;
private int age;
private Address address;
public UserDto() {
this(null,null,null,0);
}
public UserDto(String uid, String name, String email, int age) {
this(uid,name,email,age,null);
}
public UserDto(String uid, String name, String email, int age, Address address) {
super();
this.uid = uid;
this.name = name;
this.email = email;
this.age = age;
this.address = address;
}
// getters and setters
}
public interface UserRepository extends JpaRepository<User, Long> {
@Query("select new com.lovemesomecoding.dto.UserDto(user.uid,user.name,user.email,user.age) from User user where user.id = :id")
UserDto getUserProfile(@Param("id") Long id);
@Query("select new com.lovemesomecoding.dto.UserDto(user.uid,user.name,user.email,user.age,user.address) from User user where user.id = :id")
UserDto getUserProfileWithRoles(@Param("id") Long id);
public interface UserRepository extends JpaRepository<User, Long> {
@Query("select new com.lovemesomecoding.dto.UserDto(user.uid,user.name,user.email,user.age) from User user where user.id = :id")
UserDto getUserProfile(@Param("id") Long id);
@Query("select new com.lovemesomecoding.dto.UserDto(user.uid,user.name,user.email,user.age,user.address) from User user where user.id = :id")
UserDto getUserProfileWithRoles(@Param("id") Long id);
}
public interface UserRepository extends JpaRepository<User, Long> {
@Query("select new com.lovemesomecoding.dto.UserDto(user.uid,user.name,user.email,user.age) from User user where user.id = :id")
UserDto getUserProfile(@Param("id") Long id);
@Query("select new com.lovemesomecoding.dto.UserDto(user.uid,user.name,user.email,user.age,user.address) from User user where user.id = :id")
UserDto getUserProfileWithRoles(@Param("id") Long id);
}
As you can see that all fields and values must be loaded into the UserDto through the constructors like this one.
com.lovemesomecoding.dto.UserDto(user.uid,user.name,user.email,user.age,user.address)
com.lovemesomecoding.dto.UserDto(user.uid,user.name,user.email,user.age,user.address)
com.lovemesomecoding.dto.UserDto(user.uid,user.name,user.email,user.age,user.address)
May 2, 2019
Spring Data Native Query to DTO
Let’s show how you can map a native SQL query to a DTO directly.
Using AliasToBeanResultTransformer
All the fields in the DTO, in this case UserFullDto, must have the same names as the result set returned from the query. If a query field name is not found in the dto then it will through an exception.
public class UserFullDto {
// TODO Auto-generated constructor stub
public class UserFullDto {
private String name;
private String email;
private int age;
private String state;
private String zip;
public UserFullDto() {
super();
// TODO Auto-generated constructor stub
}
// getters & setters
}
public class UserFullDto {
private String name;
private String email;
private int age;
private String state;
private String zip;
public UserFullDto() {
super();
// TODO Auto-generated constructor stub
}
// getters & setters
}
String searchQuery = "SELECT u.name, u.email, u.age, addr.state FROM user as u JOIN address as addr ON u.address_id = addr.id WHERE u.id = :id";
Query query = this.entityManager.createNativeQuery(searchQuery).unwrap(org.hibernate.query.Query.class).setResultTransformer(new AliasToBeanResultTransformer( UserFullDto.class));
query.setParameter("id", userId);
UserFullDto result = (UserFullDto) query.getSingleResult();
String searchQuery = "SELECT u.name, u.email, u.age, addr.state FROM user as u JOIN address as addr ON u.address_id = addr.id WHERE u.id = :id";
Query query = this.entityManager.createNativeQuery(searchQuery).unwrap(org.hibernate.query.Query.class).setResultTransformer(new AliasToBeanResultTransformer( UserFullDto.class));
query.setParameter("id", userId);
UserFullDto result = (UserFullDto) query.getSingleResult();
String searchQuery = "SELECT u.name, u.email, u.age, addr.state FROM user as u JOIN address as addr ON u.address_id = addr.id WHERE u.id = :id";
Query query = this.entityManager.createNativeQuery(searchQuery).unwrap(org.hibernate.query.Query.class).setResultTransformer(new AliasToBeanResultTransformer( UserFullDto.class));
query.setParameter("id", userId);
UserFullDto result = (UserFullDto) query.getSingleResult();
Using ResultTransformer
If you don’t want to use the same field names in your dto as the database table then your can manually transform the field values like below.
String searchQuery = "SELECT u.name, u.email, u.age, addr.state, addr.zipcode FROM user as u JOIN address as addr ON u.address_id = addr.id WHERE u.uid = :uid";
@SuppressWarnings("deprecation")
Query query = this.entityManager.createNativeQuery(searchQuery).unwrap(org.hibernate.query.Query.class).setResultTransformer(
new ResultTransformer() {
private static final long serialVersionUID = -1L;
public UserFullDto transformTuple(Object[] tuple, String[] aliases) {
UserFullDto dto = new UserFullDto();
dto.setName((String) tuple[0]);
dto.setEmail((String) tuple[1]);
dto.setAge((int) tuple[2]);
dto.setZipcode((String) tuple[4]);
public List transformList(List collection) {
query.setParameter("uid", uid);
UserFullDto result = (UserFullDto) query.getSingleResult();
String searchQuery = "SELECT u.name, u.email, u.age, addr.state, addr.zipcode FROM user as u JOIN address as addr ON u.address_id = addr.id WHERE u.uid = :uid";
@SuppressWarnings("deprecation")
Query query = this.entityManager.createNativeQuery(searchQuery).unwrap(org.hibernate.query.Query.class).setResultTransformer(
new ResultTransformer() {
private static final long serialVersionUID = -1L;
@Override
public UserFullDto transformTuple(Object[] tuple, String[] aliases) {
UserFullDto dto = new UserFullDto();
dto.setName((String) tuple[0]);
dto.setEmail((String) tuple[1]);
dto.setAge((int) tuple[2]);
dto.setZipcode((String) tuple[4]);
return dto;
}
@Override
public List transformList(List collection) {
return collection;
}
});
query.setParameter("uid", uid);
UserFullDto result = (UserFullDto) query.getSingleResult();
String searchQuery = "SELECT u.name, u.email, u.age, addr.state, addr.zipcode FROM user as u JOIN address as addr ON u.address_id = addr.id WHERE u.uid = :uid";
@SuppressWarnings("deprecation")
Query query = this.entityManager.createNativeQuery(searchQuery).unwrap(org.hibernate.query.Query.class).setResultTransformer(
new ResultTransformer() {
private static final long serialVersionUID = -1L;
@Override
public UserFullDto transformTuple(Object[] tuple, String[] aliases) {
UserFullDto dto = new UserFullDto();
dto.setName((String) tuple[0]);
dto.setEmail((String) tuple[1]);
dto.setAge((int) tuple[2]);
dto.setZipcode((String) tuple[4]);
return dto;
}
@Override
public List transformList(List collection) {
return collection;
}
});
query.setParameter("uid", uid);
UserFullDto result = (UserFullDto) query.getSingleResult();
Using @NamedNativeQuery
All fields in the entity must be in the select statement which is not efficient. All children objects must have their ids in the select statement like getAllUsers must have address_id for its address child object.
@JsonInclude(value = Include.NON_NULL)
query = "SELECT id, name, email, age, address_id, uid FROM user",
query = "SELECT u.id, u.name, u.email " +
public class User implements Serializable ...
@JsonInclude(value = Include.NON_NULL)
@Entity
@Table(name = "user")
@NamedNativeQueries({
@NamedNativeQuery(
name = "getAllUsers",
query = "SELECT id, name, email, age, address_id, uid FROM user",
resultClass=User.class
),
@NamedNativeQuery(
name = "getAllUserById",
query = "SELECT u.id, u.name, u.email " +
"FROM user as u " +
"WHERE u.id = :id ",
resultClass=User.class
)
})
public class User implements Serializable ...
@JsonInclude(value = Include.NON_NULL)
@Entity
@Table(name = "user")
@NamedNativeQueries({
@NamedNativeQuery(
name = "getAllUsers",
query = "SELECT id, name, email, age, address_id, uid FROM user",
resultClass=User.class
),
@NamedNativeQuery(
name = "getAllUserById",
query = "SELECT u.id, u.name, u.email " +
"FROM user as u " +
"WHERE u.id = :id ",
resultClass=User.class
)
})
public class User implements Serializable ...
Source Code on Github
May 1, 2019
Spring Data Custom Select Query with Join
In this tutorial, I am going to show you how to use JOIN in custom spring JPA queries.
The join is always on the id column.
@Query("select distinct user from User user JOIN user.address addr WHERE addr.id = 1;")
User getUserById(@Param("addressId") Long addressId);
@Query("select distinct user from User user JOIN user.address addr WHERE addr.id = 1;")
User getUserById(@Param("addressId") Long addressId);
@Query("select distinct user from User user JOIN user.address addr WHERE addr.id = 1;")
User getUserById(@Param("addressId") Long addressId);
Multiple Joins
@Query("select distinct user from User user JOIN user.account acct JOIN acct.paymentMethod pm WHERE pm.id = 1;")
User getUserById(@Param("pmId") Long pmId);
@Query("select distinct user from User user JOIN user.account acct JOIN acct.paymentMethod pm WHERE pm.id = 1;")
User getUserById(@Param("pmId") Long pmId);
@Query("select distinct user from User user JOIN user.account acct JOIN acct.paymentMethod pm WHERE pm.id = 1;")
User getUserById(@Param("pmId") Long pmId);
April 24, 2019
Spring Data Exclude elements from the Many side of a One To Many relationship.
March 18, 2019