Spring Data Jpa Query
annotation provides a way to manually add queries. by default Query
annotation works perfectly fine with inserting or retrieving data from the underlying database. in order to manipulate the data, Spring Data Jpa provides a @Modifying
annotation.
@Modifying
annotation is only applicable for method annotated with @Query
annotation , it is not applicable for costume implemented methods or query derived from method name because they already specify the modification behavior.
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, "juicy");
insert into apple (apple_name, apple_id, taste) values ("Jonagold", 4, "sweet");
insert into apple (apple_name, apple_id, taste) values ("GrannySmith", 5, "sharp");
package com.sujan.example.jpa.controller;
import com.sujan.example.jpa.entity.Apple;
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 modifying() {
List<Apple> findByAppleName = appleRepository.findByAppleName("Macintosh");
System.out.println("findByAppleName = " + findByAppleName);
int findByAppleNameAndTasteUsingParams = appleRepository.findByAppleNameAndTasteUsingParams("Macintosh", "sharp");
System.out.println("findByAppleNameAndTasteUsingParams = " + findByAppleNameAndTasteUsingParams);
findByAppleName = appleRepository.findByAppleName("Macintosh");
System.out.println("findByAppleName = " + findByAppleName);
int updateTasteByAppleNameUsingPosition = appleRepository.updateTasteByAppleNameUsingPosition("Macintosh", "juicy");
System.out.println("updateTasteByAppleNameUsingPosition = " + updateTasteByAppleNameUsingPosition);
findByAppleName = appleRepository.findByAppleName("Macintosh");
System.out.println("findByAppleName = " + findByAppleName);
}
}
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 org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Repository
public interface AppleRepository extends JpaRepository<Apple, Long> {
List<Apple> findByAppleName(String appleName);
@Modifying
@Transactional
@Query("update Apple a set a.taste = ?1 where a.appleName = ?2")
int updateTasteByAppleNameUsingPosition(String taste, String appleName);
@Modifying
@Transactional
@Query("update Apple a set a.taste = :taste where a.appleName = :appleName")
int findByAppleNameAndTasteUsingParams(@Param("appleName") String appleName, @Param("taste") 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)]
Spring Data Jpa Modifying Derived Delete Queries
Spring Data Jpa supports derived delete queries which help to avoid writing declared JPQL queries.
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, "tangy");
insert into apple (apple_name, apple_id, taste) values ("Gala", 3, "tangy");
insert into apple (apple_name, apple_id, taste) values ("Jonagold", 4, "tangy");
insert into apple (apple_name, apple_id, taste) values ("GrannySmith", 5, "tangy");
insert into apple (apple_name, apple_id, taste) values ("Macintosh", 6, "juicy");
insert into apple (apple_name, apple_id, taste) values ("Fuji", 7, "juicy");
insert into apple (apple_name, apple_id, taste) values ("Gala", 8, "juicy");
insert into apple (apple_name, apple_id, taste) values ("Jonagold", 9, "juicy");
insert into apple (apple_name, apple_id, taste) values ("GrannySmith", 10, "juicy");
insert into apple (apple_name, apple_id, taste) values ("Macintosh", 11, "sweet");
insert into apple (apple_name, apple_id, taste) values ("Fuji", 12, "sweet");
insert into apple (apple_name, apple_id, taste) values ("Gala", 13, "sweet");
insert into apple (apple_name, apple_id, taste) values ("Jonagold", 14, "sweet");
insert into apple (apple_name, apple_id, taste) values ("GrannySmith", 15, "sweet");
package com.sujan.example.jpa.controller;
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;
@RestController
public class AppleController {
@Autowired
private AppleRepository appleRepository;
@GetMapping
void modifying() {
int deleteByAppleId = appleRepository.deleteByTaste("tangy");
System.out.println("deleteByAppleId = " + deleteByAppleId);
int deleteInBulkByRoleIdPosition = appleRepository.deleteInBulkByRoleIdPosition("juicy");
System.out.println("deleteInBulkByRoleIdPosition = " + deleteInBulkByRoleIdPosition);
int deleteInBulkByRoleIdParam = appleRepository.deleteInBulkByRoleIdParam("sweet");
System.out.println("deleteInBulkByRoleIdParam = " + deleteInBulkByRoleIdParam);
}
}
package com.sujan.example.jpa.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import javax.persistence.*;
@Data
@Entity
@ToString
@NoArgsConstructor
@AllArgsConstructor()
public class Apple {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long appleId;
private String appleName;
private String taste;
@PrePersist
public void PrePersist() {
System.out.println("Apple.PrePersist");
}
@PostPersist
public void PostPersist() {
System.out.println("Apple.PostPersist");
}
@PreRemove
public void PreRemove() {
System.out.println("Apple.PreRemove");
}
@PostRemove
public void PostRemove() {
System.out.println("Apple.PostRemove");
}
@PreUpdate
public void PreUpdate() {
System.out.println("Apple.PreUpdate");
}
@PostUpdate
public void PostUpdate() {
System.out.println("Apple.PostUpdate");
}
@PostLoad
public void PostLoad() {
System.out.println("Apple.PostLoad");
}
}
package com.sujan.example.jpa.repository;
import com.sujan.example.jpa.entity.Apple;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import javax.transaction.Transactional;
@Repository
public interface AppleRepository extends JpaRepository<Apple, Long> {
@Transactional
int deleteByTaste(String taste);
@Modifying
@Transactional
@Query("delete from Apple a where a.taste = ?1")
int deleteInBulkByRoleIdPosition(String taste);
@Modifying
@Transactional
@Query("delete from Apple a where a.taste = :taste")
int deleteInBulkByRoleIdParam(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=?
Apple.PostLoad
Apple.PostLoad
Apple.PostLoad
Apple.PostLoad
Apple.PostLoad
Apple.PreRemove
Apple.PreRemove
Apple.PreRemove
Apple.PreRemove
Apple.PreRemove
delete from apple where apple_id=?
Apple.PostRemove
delete from apple where apple_id=?
Apple.PostRemove
delete from apple where apple_id=?
Apple.PostRemove
delete from apple where apple_id=?
Apple.PostRemove
delete from apple where apple_id=?
Apple.PostRemove
deleteByAppleId = 5
delete from apple where taste=?
deleteInBulkByRoleIdPosition = 5
delete from apple where taste=?
deleteInBulkByRoleIdParam = 5
Although the deleteByTaste
method seems almost the same as the deleteInBulkByRoleIdPosition
method and deleteInBulkByRoleIdParam
method but there is a very important difference between the two methods in the way they execute.
by default, the Spring Data Jpa entity has 8 life cycle methods.
@PrePersist - called before persisting/saving an entity @PostPersist - called after persisting/saving an entity @PreRemove - called before removing/deleting an entity @PostRemove - called after removing/deleting an entity @PreUpdate - called before updating an entity @PostUpdate - called after updating an entity @PostLoad - called before loading an entity
these life cycle methods run on various stages of the entity lifecycle.
deleteByTaste
first issues a select query, iterates on each record, runs all the entity life cycle methods and deletes them one by one.
deleteInBulkByRoleIdPosition
and deleteInBulkByRoleIdParam
method issues a single JPQL query against the underlying database, which is defined in @Query annotation and does not run any entity life cycle method.