Spring Data Jpa Projections

Spring Data Jpa returns an entity or collection of an entity from the underlying database. entity class consists of a lot of data but in some situations, it is required to get just a few fields only, then retrieving all the fields will be a resource-consuming process. to avoid these scenarios, Spring Data Jpa provides us Projections.
Projection is a method that helps to pick certain fields from entity class out of all. Projections are of 2 types

  • Interface-based Projections
  • class-based Projections

Interface-based Projections

interface-based projection is the simplest way to limit the result. An interface needs to be created with the getter access modifier of the required entity fields.

The Spring Data Jpa query execution engine will create a proxy instance of the interface at runtime for each entity that is returned from the underlying database and pass it to the target calling method.

drop database if exists AppleDb;
create database AppleDb;
use AppleDb;
create TABLE apple ( apple_id BIGINT NOT NULL, apple_name VARCHAR(255) DEFAULT NULL, taste VARCHAR(255) DEFAULT NULL, PRIMARY KEY (apple_id)) ENGINE=INNODB;
create TABLE hibernate_sequence ( next_val BIGINT) ENGINE=INNODB;
insert into hibernate_sequence values ( 1 );
insert into apple (apple_name, apple_id, taste) values ('Macintosh', 1, 'tangy');
insert into apple (apple_name, apple_id, taste) values ('Macintosh', 2, 'sweet');
insert into apple (apple_name, apple_id, taste) values ('Macintosh', 3, 'tangy');
insert into apple (apple_name, apple_id, taste) values ('Macintosh', 4, 'firm sweet');
insert into apple (apple_name, apple_id, taste) values ('Macintosh', 5, 'tangy');
package com.sujan.example.jpa.controller;

import com.sujan.example.jpa.entity.NamesAndTasteOnly;
import com.sujan.example.jpa.repository.AppleRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class AppleController {
    @Autowired
    private AppleRepository appleRepository;

    @GetMapping
    void projection() {
        List<NamesAndTasteOnly> findByTasteProjection = appleRepository.findByTaste("tangy");
        for (NamesAndTasteOnly namesAndTasteOnly : findByTasteProjection) {
            System.out.println("appleName- " + namesAndTasteOnly.getAppleName());
            System.out.println("taste " + namesAndTasteOnly.getTaste());
        }
    }
}
package com.sujan.example.jpa.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Data
@Entity
@ToString
@NoArgsConstructor
@AllArgsConstructor()
public class Apple {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long appleId;
    private String appleName;
    private String taste;
}
package com.sujan.example.jpa.entity;

public interface NamesAndTasteOnly {
    String getAppleName();
    String getTaste();
}
package com.sujan.example.jpa.repository;

import com.sujan.example.jpa.entity.Apple;
import com.sujan.example.jpa.entity.NamesAndTasteOnly;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface AppleRepository extends JpaRepository<Apple, Long> {
    List<NamesAndTasteOnly> findByTaste(String taste);
}
package com.sujan.example.jpa;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class JpaApplication {
    public static void main(String[] args) {
        SpringApplication.run(JpaApplication.class, args);
    }
}
spring.datasource.url=jdbc:mysql://localhost:3306/AppleDb?autoReconnect=true&useSSL=false&createDatabaseIfNotExist=true
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.show-sql=true
spring.datasource.initialization-mode=always
plugins {
    id 'org.springframework.boot' version '2.3.3.RELEASE'
    id 'io.spring.dependency-management' version '1.0.10.RELEASE'
    id 'java'
}
group = 'com.sujan'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}
repositories {
    mavenCentral()
}
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    annotationProcessor 'org.projectlombok:lombok'
    compileOnly 'org.projectlombok:lombok'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    runtimeOnly 'mysql:mysql-connector-java'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
}
test {
    useJUnitPlatform()
}
curl --location --request GET 'http://localhost:8080/'
select apple0_.apple_name as col_0_0_, apple0_.taste as col_1_0_ from apple apple0_ where apple0_.taste=?
appleName- Macintosh
taste tangy
appleName- Macintosh
taste tangy
appleName- Macintosh
taste tangy

interface-based projections allow a subset of attributes and collection of a subset of attributes as well.

drop database if exists AppleDb;
create database AppleDb;
use AppleDb;
create table apple (apple_id bigint not null, apple_name varchar(255), taste varchar(255), transporter_transporter_id bigint, primary key (apple_id)) engine=InnoDB;
create table apple_vendors (apple_apple_id bigint not null, vendors_vendor_id bigint not null) engine=InnoDB;
create table hibernate_sequence (next_val bigint) engine=InnoDB;
create table transporter (transporter_id bigint not null, transporter_name varchar(255), primary key (transporter_id)) engine=InnoDB;
create table vendor (vendor_id bigint not null, vendor_name varchar(255), vendor_shop_name varchar(255), primary key (vendor_id)) engine=InnoDB;
alter table apple_vendors add constraint UK_swwrhcxyin65q89fxus4pbte1 unique (vendors_vendor_id);
alter table apple add constraint FK1wlkdwyee1wmvjc49fryjpu8o foreign key (transporter_transporter_id) references transporter (transporter_id);
alter table apple_vendors add constraint FK7qgkods6al59xy7yxpa4botkl foreign key (vendors_vendor_id) references vendor (vendor_id);
alter table apple_vendors add constraint FKbas8q5yjo4skm8y80oya4arwc foreign key (apple_apple_id) references apple (apple_id);
insert into transporter values (1,'Shri transporter');
insert into vendor values (4,'Shri Seller','Shri Shop'),(5,'Vishnu Seller','Vishnu Shop');
insert into apple values (2,'Macintosh','tangy',1);
insert into apple_vendors values (2,4),(2,5);
insert into transporter values (6,'Shri transporter');
insert into vendor values(8,'Om Seller','Om Shop'),(9,'Shri Seller','Laxmi Shop');
insert into apple values (7,'Fuji','sweet',6);
insert into apple_vendors values (7,8),(7,9);
insert into hibernate_sequence values ( 9);
package com.sujan.example.jpa.controller;

import com.sujan.example.jpa.entity.AppleSummary;
import com.sujan.example.jpa.repository.AppleRepository;
import com.sujan.example.jpa.repository.TransporterRepository;
import com.sujan.example.jpa.repository.VendorRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class AppleController {
    @Autowired
    private AppleRepository appleRepository;
    @Autowired
    private TransporterRepository transporterRepository;
    @Autowired
    private VendorRepository vendorRepository;

    @GetMapping
    void projection() {
        List<AppleSummary> findByTasteProjection = appleRepository.findByTaste("tangy");
        for (AppleSummary appleSummary : findByTasteProjection) {
            System.out.println("appleName- " + appleSummary.getAppleName());
            System.out.println("taste- " + appleSummary.getTaste());
            System.out.println("Transporter = " + appleSummary.getTransporter());
            for (AppleSummary.VendorSummary vendor : appleSummary.getVendors()) {
                System.out.println("VendorName- = " + vendor.getVendorName());
                System.out.println("VendorShopName- = " + vendor.getVendorShopName());
            }
        }
    }
}
package com.sujan.example.jpa.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import javax.persistence.*;
import java.util.List;

@Data
@Entity
@ToString
@NoArgsConstructor
@AllArgsConstructor()
public class Apple {
    @OneToMany
    List<Vendor> vendors;
    @OneToOne
    Transporter transporter;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long appleId;
    private String appleName;
    private String taste;
}
package com.sujan.example.jpa.entity;

import java.util.List;

public interface AppleSummary {
    String getAppleName();
    String getTaste();
    TransporterSummary getTransporter();
    List<VendorSummary> getVendors();

    interface VendorSummary {
        String getVendorName();
        String getVendorShopName();
    }

    interface TransporterSummary {
        String getVendorName();
        String getVendorShopName();
    }
}
package com.sujan.example.jpa.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Data
@Entity
@ToString
@NoArgsConstructor
@AllArgsConstructor()
public class Transporter {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long transporterId;
    private String transporterName;
}
package com.sujan.example.jpa.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Data
@Entity
@ToString
@NoArgsConstructor
@AllArgsConstructor()
public class Vendor {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long vendorId;
    private String vendorName;
    private String vendorShopName;
}
package com.sujan.example.jpa.repository;

import com.sujan.example.jpa.entity.Apple;
import com.sujan.example.jpa.entity.AppleSummary;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface AppleRepository extends JpaRepository<Apple, Long> {
    List<AppleSummary> findByTaste(String taste);
}
package com.sujan.example.jpa;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class JpaApplication {
    public static void main(String[] args) {
        SpringApplication.run(JpaApplication.class, args);
    }
}
spring.datasource.url=jdbc:mysql://localhost:3306/AppleDb?autoReconnect=true&useSSL=false&createDatabaseIfNotExist=true
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.show-sql=true
spring.datasource.initialization-mode=always
plugins {
    id 'org.springframework.boot' version '2.3.3.RELEASE'
    id 'io.spring.dependency-management' version '1.0.10.RELEASE'
    id 'java'
}
group = 'com.sujan'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}
repositories {
    mavenCentral()
}
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    annotationProcessor 'org.projectlombok:lombok'
    compileOnly 'org.projectlombok:lombok'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    runtimeOnly 'mysql:mysql-connector-java'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
}
test {
    useJUnitPlatform()
}
curl --location --request GET 'http://localhost:8080/'
select apple0_.apple_id as apple_id1_0_, apple0_.apple_name as apple_na2_0_, apple0_.taste as taste3_0_, apple0_.transporter_transporter_id as transpor4_0_ from apple apple0_ where apple0_.taste=?
Hibernate: select transporte0_.transporter_id as transpor1_2_0_, transporte0_.transporter_name as transpor2_2_0_ from transporter transporte0_ where transporte0_.transporter_id=?
appleName- Macintosh
taste- tangy
Transporter = Transporter(transporterId=1, transporterName=Shri transporter)
select vendors0_.apple_apple_id as apple_ap1_1_0_, vendors0_.vendors_vendor_id as vendors_2_1_0_, vendor1_.vendor_id as vendor_i1_3_1_, vendor1_.vendor_name as vendor_n2_3_1_, vendor1_.vendor_shop_name as vendor_s3_3_1_ from apple_vendors vendors0_ inner join vendor vendor1_ on vendors0_.vendors_vendor_id=vendor1_.vendor_id where vendors0_.apple_apple_id=?
VendorName- = Shri Seller
VendorShopName- = Shri Shop
VendorName- = Vishnu Seller
VendorShopName- = Vishnu Shop

Closed Projections

Spring Data Jpa requires an interface with getter access modifier of entity class required attributes for projections. If projection interface whose all access methods matchies property of the entity attributes then is known as closed projections.

If you use a closed projection, Spring Data Jpa closed projections have more optimized the query execution, because for projection proxy required attributes are already known.

drop database if exists AppleDb;
create database AppleDb;
use AppleDb;
create TABLE apple ( apple_id BIGINT NOT NULL, apple_name VARCHAR(255) DEFAULT NULL, taste VARCHAR(255) DEFAULT NULL, PRIMARY KEY (apple_id)) ENGINE=INNODB;
create TABLE hibernate_sequence ( next_val BIGINT) ENGINE=INNODB;
insert into hibernate_sequence values ( 1 );
insert into apple (apple_name, apple_id, taste) values ('Macintosh', 1, 'tangy');
insert into apple (apple_name, apple_id, taste) values ('Macintosh', 2, 'sweet');
insert into apple (apple_name, apple_id, taste) values ('Macintosh', 3, 'tangy');
insert into apple (apple_name, apple_id, taste) values ('Macintosh', 4, 'firm sweet');
insert into apple (apple_name, apple_id, taste) values ('Macintosh', 5, 'tangy');
package com.sujan.example.jpa.controller;

import com.sujan.example.jpa.entity.NamesAndTasteOnly;
import com.sujan.example.jpa.repository.AppleRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class AppleController {
    @Autowired
    private AppleRepository appleRepository;

    @GetMapping
    void projection() {
        List<NamesAndTasteOnly> findByTasteProjection = appleRepository.findByTaste("tangy");
        for (NamesAndTasteOnly namesAndTasteOnly : findByTasteProjection) {
            System.out.println("appleName- " + namesAndTasteOnly.getAppleName());
            System.out.println("taste " + namesAndTasteOnly.getTaste());
        }
    }
}
package com.sujan.example.jpa.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Data
@Entity
@ToString
@NoArgsConstructor
@AllArgsConstructor()
public class Apple {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long appleId;
    private String appleName;
    private String taste;
}
package com.sujan.example.jpa.entity;

public interface NamesAndTasteOnly {
    String getAppleName();
    String getTaste();
}
package com.sujan.example.jpa.repository;

import com.sujan.example.jpa.entity.Apple;
import com.sujan.example.jpa.entity.NamesAndTasteOnly;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface AppleRepository extends JpaRepository<Apple, Long> {
    List<NamesAndTasteOnly> findByTaste(String taste);
}
package com.sujan.example.jpa;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class JpaApplication {
    public static void main(String[] args) {
        SpringApplication.run(JpaApplication.class, args);
    }
}
spring.datasource.url=jdbc:mysql://localhost:3306/AppleDb?autoReconnect=true&useSSL=false&createDatabaseIfNotExist=true
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.show-sql=true
spring.datasource.initialization-mode=always
plugins {
    id 'org.springframework.boot' version '2.3.3.RELEASE'
    id 'io.spring.dependency-management' version '1.0.10.RELEASE'
    id 'java'
}
group = 'com.sujan'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}
repositories {
    mavenCentral()
}
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    annotationProcessor 'org.projectlombok:lombok'
    compileOnly 'org.projectlombok:lombok'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    runtimeOnly 'mysql:mysql-connector-java'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
}
test {
    useJUnitPlatform()
}
curl --location --request GET 'http://localhost:8080/'
select apple0_.apple_name as col_0_0_, apple0_.taste as col_1_0_ from apple apple0_ where apple0_.taste=?
appleName- Macintosh
taste tangy
appleName- Macintosh
taste tangy
appleName- Macintosh
taste tangy

Open Projections

The Spring Data Jpa projection has all the required access modifiers of the entity class.  it can be combined to compute new values.

Open projections have entity class as the target objects. with the help of @Value annotation, multiple target objects can be combined to create a new attribute.
Spring Data Jpa doesn't know which attribute SpEL expression could use, hence cannot apply query execution optimization for open projections.

drop database if exists AppleDb;
create database AppleDb;
use AppleDb;
create TABLE apple ( apple_id BIGINT NOT NULL, apple_name VARCHAR(255) DEFAULT NULL, taste VARCHAR(255) DEFAULT NULL, PRIMARY KEY (apple_id)) ENGINE=INNODB;
create TABLE hibernate_sequence ( next_val BIGINT) ENGINE=INNODB;
insert into hibernate_sequence values ( 1 );
insert into apple (apple_name, apple_id, taste) values ('Macintosh', 1, 'tangy');
insert into apple (apple_name, apple_id, taste) values ('Fuji', 2, 'sweet');
insert into apple (apple_name, apple_id, taste) values ('Gala', 3, 'tangy');
insert into apple (apple_name, apple_id, taste) values ('Jonagold', 4, 'sweet');
insert into apple (apple_name, apple_id, taste) values ('GrannySmith', 5, 'tangy');
package com.sujan.example.jpa.controller;

import com.sujan.example.jpa.entity.AppleSummary;
import com.sujan.example.jpa.repository.AppleRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class AppleController {
    @Autowired
    private AppleRepository appleRepository;

    @GetMapping
    void projection() {
        List<AppleSummary> findByTasteProjection = appleRepository.findByTaste("tangy");
        for (AppleSummary appleSummary : findByTasteProjection) {
            System.out.println("Apple Name And Taste- " + appleSummary.getAppleNameAndTaste());
        }
    }
}
package com.sujan.example.jpa.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Data
@Entity
@ToString
@NoArgsConstructor
@AllArgsConstructor()
public class Apple {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long appleId;
    private String appleName;
    private String taste;
}
package com.sujan.example.jpa.entity;

import org.springframework.beans.factory.annotation.Value;

public interface AppleSummary {
    @Value("#{target.appleName + ' ' + target.taste}")
    String getAppleNameAndTaste();
}
package com.sujan.example.jpa.repository;

import com.sujan.example.jpa.entity.Apple;
import com.sujan.example.jpa.entity.AppleSummary;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface AppleRepository extends JpaRepository<Apple, Long> {
    List<AppleSummary> findByTaste(String taste);
}
package com.sujan.example.jpa;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class JpaApplication {
    public static void main(String[] args) {
        SpringApplication.run(JpaApplication.class, args);
    }
}
spring.datasource.url=jdbc:mysql://localhost:3306/AppleDb?autoReconnect=true&useSSL=false&createDatabaseIfNotExist=true
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.show-sql=true
spring.datasource.initialization-mode=always
plugins {
    id 'org.springframework.boot' version '2.3.3.RELEASE'
    id 'io.spring.dependency-management' version '1.0.10.RELEASE'
    id 'java'
}
group = 'com.sujan'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}
repositories {
    mavenCentral()
}
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    annotationProcessor 'org.projectlombok:lombok'
    compileOnly 'org.projectlombok:lombok'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    runtimeOnly 'mysql:mysql-connector-java'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
}
test {
    useJUnitPlatform()
}
curl --location --request GET 'http://localhost:8080/'
select apple0_.apple_id as apple_id1_0_, apple0_.apple_name as apple_na2_0_, apple0_.taste as taste3_0_ from apple apple0_ where apple0_.apple_name=?
findByAppleName = [Apple(appleId=1, appleName=Macintosh, taste=tangy)]
update apple set taste=? where apple_name=?
findByAppleNameAndTasteUsingParams = 1
select apple0_.apple_id as apple_id1_0_, apple0_.apple_name as apple_na2_0_, apple0_.taste as taste3_0_ from apple apple0_ where apple0_.apple_name=?
findByAppleName = [Apple(appleId=1, appleName=Macintosh, taste=tangy)]
update apple set taste=? where apple_name=?
updateTasteByAppleNameUsingPosition = 0
select apple0_.apple_id as apple_id1_0_, apple0_.apple_name as apple_na2_0_, apple0_.taste as taste3_0_ from apple apple0_ where apple0_.apple_name=?
findByAppleName = [Apple(appleId=1, appleName=Macintosh, taste=tangy)]

 

Open projections are not checked at compile-time, hence at run time there might be a scenario that the target object might not exist in the entity class and the application will throw exceptions.

Property or field 'attribute' cannot be found on object of type 'Entity' - maybe not public or not valid?

to avoid these scenarios, it's better to use getter access modifier to create a new attribute.

drop database if exists AppleDb;
create database AppleDb;
use AppleDb;
create TABLE apple ( apple_id BIGINT NOT NULL, apple_name VARCHAR(255) DEFAULT NULL, taste VARCHAR(255) DEFAULT NULL, PRIMARY KEY (apple_id)) ENGINE=INNODB;
create TABLE hibernate_sequence ( next_val BIGINT) ENGINE=INNODB;
insert into hibernate_sequence values ( 1 );
insert into apple (apple_name, apple_id, taste) values ('Macintosh', 1, 'tangy');
insert into apple (apple_name, apple_id, taste) values ('Fuji', 2, 'sweet');
insert into apple (apple_name, apple_id, taste) values ('Gala', 3, 'tangy');
insert into apple (apple_name, apple_id, taste) values ('Jonagold', 4, 'sweet');
insert into apple (apple_name, apple_id, taste) values ('GrannySmith', 5, 'tangy');
package com.sujan.example.jpa.controller;

import com.sujan.example.jpa.entity.AppleSummary;
import com.sujan.example.jpa.repository.AppleRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class AppleController {
    @Autowired
    private AppleRepository appleRepository;

    @GetMapping
    void projection() {
        List<AppleSummary> findByTasteProjection = appleRepository.findByTaste("tangy");
        for (AppleSummary appleSummary : findByTasteProjection) {
            System.out.println("Apple Name And Taste- " + appleSummary.getAppleNameAndTaste());
        }
    }
}
package com.sujan.example.jpa.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Data
@Entity
@ToString
@NoArgsConstructor
@AllArgsConstructor()
public class Apple {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long appleId;
    private String appleName;
    private String taste;
}
package com.sujan.example.jpa.repository;

import com.sujan.example.jpa.entity.Apple;
import com.sujan.example.jpa.entity.AppleSummary;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface AppleRepository extends JpaRepository<Apple, Long> {
    List<AppleSummary> findByTaste(String taste);
}
package com.sujan.example.jpa.entity;

public interface AppleSummary {
    String getAppleName();
    String getTaste();

    default String getAppleNameAndTaste() {
        return getAppleName().concat(" ").concat(getTaste());
    }
}
package com.sujan.example.jpa;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class JpaApplication {
    public static void main(String[] args) {
        SpringApplication.run(JpaApplication.class, args);
    }
}
spring.datasource.url=jdbc:mysql://localhost:3306/AppleDb?autoReconnect=true&useSSL=false&createDatabaseIfNotExist=true
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.show-sql=true
spring.datasource.initialization-mode=always
plugins {
    id 'org.springframework.boot' version '2.3.3.RELEASE'
    id 'io.spring.dependency-management' version '1.0.10.RELEASE'
    id 'java'
}
group = 'com.sujan'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}
repositories {
    mavenCentral()
}
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    annotationProcessor 'org.projectlombok:lombok'
    compileOnly 'org.projectlombok:lombok'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    runtimeOnly 'mysql:mysql-connector-java'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
}
test {
    useJUnitPlatform()
}
curl --location --request GET 'http://localhost:8080/'
select apple0_.apple_id as apple_id1_0_, apple0_.apple_name as apple_na2_0_, apple0_.taste as taste3_0_ from apple apple0_ where apple0_.taste=?
MyBean.getAppleNameAndTaste
Apple Name And Taste- Macintosh tangy
MyBean.getAppleNameAndTaste
Apple Name And Taste- Gala tangy
MyBean.getAppleNameAndTaste
Apple Name And Taste- GrannySmith tangy
body_4

above method required access modifier getters to be public, but there might be a scnerio when attribute has some sensetive information or public getter is not accessible. For these situations, Spring data Jpa provides another way to implement the logic in spring bean and invoke it using SpEL in projectoin interface.

drop database if exists AppleDb;
create database AppleDb;
use AppleDb;
create TABLE apple ( apple_id BIGINT NOT NULL, apple_name VARCHAR(255) DEFAULT NULL, taste VARCHAR(255) DEFAULT NULL, PRIMARY KEY (apple_id)) ENGINE=INNODB;
create TABLE hibernate_sequence ( next_val BIGINT) ENGINE=INNODB;
insert into hibernate_sequence values ( 1 );
insert into apple (apple_name, apple_id, taste) values ('Macintosh', 1, 'tangy');
insert into apple (apple_name, apple_id, taste) values ('Fuji', 2, 'sweet');
insert into apple (apple_name, apple_id, taste) values ('Gala', 3, 'tangy');
insert into apple (apple_name, apple_id, taste) values ('Jonagold', 4, 'sweet');
insert into apple (apple_name, apple_id, taste) values ('GrannySmith', 5, 'tangy');
package com.sujan.example.jpa.controller;

import com.sujan.example.jpa.entity.AppleSummary;
import com.sujan.example.jpa.repository.AppleRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class AppleController {
    @Autowired
    private AppleRepository appleRepository;

    @GetMapping
    void projection() {
        List<AppleSummary> findByTasteProjection = appleRepository.findByTaste("tangy");
        for (AppleSummary appleSummary : findByTasteProjection) {
            System.out.println("Apple Name And Taste- " + appleSummary.getAppleNameAndTaste());
        }
    }
}
package com.sujan.example.jpa.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Data
@Entity
@ToString
@NoArgsConstructor
@AllArgsConstructor()
public class Apple {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long appleId;
    private String appleName;
    private String taste;
}
package com.sujan.example.jpa.entity;

import org.springframework.stereotype.Component;

@Component
public class AppleService {
    public String getAppleNameAndTaste(Apple apple) {
        return apple.getAppleName().concat(" ").concat(apple.getTaste());
    }
}
package com.sujan.example.jpa.entity;

import org.springframework.beans.factory.annotation.Value;

public interface AppleSummary {
    @Value("#{@appleService.getAppleNameAndTaste(target)}")
    String getAppleNameAndTaste();
}
package com.sujan.example.jpa.repository;

import com.sujan.example.jpa.entity.Apple;
import com.sujan.example.jpa.entity.AppleSummary;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface AppleRepository extends JpaRepository<Apple, Long> {
    List<AppleSummary> findByTaste(String taste);
}
package com.sujan.example.jpa;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class JpaApplication {
    public static void main(String[] args) {
        SpringApplication.run(JpaApplication.class, args);
    }
}
spring.datasource.url=jdbc:mysql://localhost:3306/AppleDb?autoReconnect=true&useSSL=false&createDatabaseIfNotExist=true
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.show-sql=true
spring.datasource.initialization-mode=always
plugins {
    id 'org.springframework.boot' version '2.3.3.RELEASE'
    id 'io.spring.dependency-management' version '1.0.10.RELEASE'
    id 'java'
}
group = 'com.sujan'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}
repositories {
    mavenCentral()
}
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    annotationProcessor 'org.projectlombok:lombok'
    compileOnly 'org.projectlombok:lombok'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    runtimeOnly 'mysql:mysql-connector-java'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
}
test {
    useJUnitPlatform()
}
curl --location --request GET 'http://localhost:8080/'
select apple0_.apple_id as apple_id1_0_, apple0_.apple_name as apple_na2_0_, apple0_.taste as taste3_0_ from apple apple0_ where apple0_.taste=?
MyBean.getAppleNameAndTaste
Apple Name And Taste- Macintosh tangy
MyBean.getAppleNameAndTaste
Apple Name And Taste- Gala tangy
MyBean.getAppleNameAndTaste
Apple Name And Taste- GrannySmith tangy

follow us on