Spring Data JPA

Starter

Spring Data JPA provides repository support for the Java Persistence API (JPA). It eases development of applications that need to access JPA data sources.

  1. JPA : 一套规范(标准接口),而Hibernate,TopLink,JDO等是JPA的一种实现产品。

  2. Spring Data JPA : 是 Spring 基于 ORM 框架、JPA 规范的基础上封装的一套JPA应用框架(底层默认实现使用的是Hibernate)。

  3. Repository : Spring Data 的核心接口,是一个空接口,也叫标记接口(不包含任何方法声明)

    public interface Repository<T,ID extends Serializable> {}
    
    • 使用:
      • 方式一:自定义一个接口extends Repository,则这个接口会被Spring容器所管理
      • 方式二: 使用注解方式:@RespositoryDefinition(domainClass=T.class,idClass=ID.class)

使用

查询

  1. 规范方法名实现查询findBy...,不需要写实现(弊端:方法名较长,无法实现复杂查询)

     //select * from Employee where name =?
     Employee findByName(String name);
    
     // where name like ?% and age <?
     public List<Employee> findByNameStartingWithAndAgeLessThan(String name, Integer age);
    
     // where name like %? and age <? 
     public List<Employee> findByNameEndingWithAndAgeLessThan(String name, Integer age);
    
     // where name in (?,?....) or age <?
     public List<Employee> findByNameInOrAgeLessThan(List<String> names, Integer age);
    
     // where name in (?,?....) and age <?
     public List<Employee> findByNameInAndAgeLessThan(List<String> names, Integer age);
    
  2. 使用@Query(HQL/JPQL/NativeSQL)注解 (加在方法上,方法名称随意,支持命名参数和索引参数的使用)

     @Query("select o from Employee o where id=(select max(id) from Employee t1)")
     public Employee getEmployeeByMaxId();
    
     @Query("select o from Employee o where o.name=?1 and o.age=?2")
     public List<Employee> queryParams1(String name, Integer age);
    
     @Query("select o from Employee o where o.name=:name and o.age=:age")
     public List<Employee> queryParams2(@Param("name")String name, @Param("age")Integer age);
    
     @Query("select o from Employee o where o.name like %?1%")
     public List<Employee> queryLike1(String name);
    
     @Query("select o from Employee o where o.name like %:name%")
     public List<Employee> queryLike2(@Param("name")String name);
    
     @Query(nativeQuery = true, value = "select count(1) from employee")
     public long getCount();
    
Keyword Sample JPQL Snippet
And findByLastnameAndFirstname ... where x.lastname=?1 and x.firstname=?2
Or findByLastnameOrFirstname ... where x.lastname=?1 or x.firstname=?2
Between findByStartDateBetween ... where x.startDate between ?1 and ?2
LessThan findByAgeGreaterThan ... where x.age<?1
GreaterThan findByAgeGreaterThan ... where x.age>?1
After findByStartDateAfter ... where x.startDate>?1
Before findByStartDateBefore ... where x.startDate<?1
IsNull findByAgeIsNull ... where x.age is null
IsNotNull,NotNull findByAgeIsNotNull ... where x.age is not null
Like findByFirstnameLike ... where x.firstname like ?1
NotLike findByFirstnameNotLike ... where x.firstname not like ?1
StartingWith findByFirstnameStartingWith ... where x.firstname like ?1 (parameter bound with appended %)
EndingWith findByFirstnameEndingWith ... where x.firstname like ?1 (parameter bound with prepended %)
Containing findByFirstnameContaining ... where x.firstname like ?1 (parameter bound wrapped in %)
OrderBy findByAgeOrderByLastnameDesc ... where x.age=? order by x.lastname desc
Not findByLastnameNot ... where x.lastname <> ?1
In findByAgeIn(Collection ages) ... where x.age in ?1
NotIn findByAgeNotIn(Collection ages) ... where x.age not in ?1
TRUE findByActiveTrue() ... where x.active=true
FALSE findByActiveFalse() ... where x.active=false

更新/删除

  1. Repository层: 方法上添加注解@Modifying
  2. Service层: 方法上添加事务注解@Transactional

Sample:

  1. EmployeeRepository

     @Modifying
     @Query("update Employee o set o.age = :age where o.id = :id")
     public void updateById(@Param("id")Integer id, @Param("age")Integer age);
    
     @Query("delete from Employee where id=?1")
     @Modifying
     public int deleteOneById(Integer id);
    
  2. EmployeeService

     @Transactional
     public boolean update(Integer id,Integer age) {
         return this.employeeRepository.updateById(id,age);
     }
    
     @Transactional
     public boolean delete(Integer id) {
         return this.employeeRepository.deleteOneById(id);
     }
    

Repository 子接口

  1. CrudRepository extends Repository: 包含CRUD方法

     // save
     <S extends T> S save(S entity);
     <S extends T> Iterable<S> save(Iterable<S> entities);
    
     // find
     Optional<T> findById(ID id);
     boolean existsById(ID id);
     Iterable<T> findAll();
     Iterable<T> findAllById(Iterable<ID> ids);
    
     // delete
     void deleteById(ID id);
     void delete(T entity);
     void deleteAll(Iterable<? extends T> entities);
     void deleteAll();
    
     long count();
    
  2. PagingAndSortingRepository extends CrudRepository : 包含分页排序方法

     Iterable<T>  findAll(Sort sort)            // 带排序的查询
     Page<T>  findAll(Pageable pageable)     // 带排序的分页查询
    
  3. JpaRepository extends PagingAndSortingRepository: 包含Jpa规范的方法

     // save
     <S extends T> S saveAndFlush(S entity);
     <S extends T> List<S> saveAll(Iterable<S> entities);
    
     // flush
     void flush();
    
     // find
     T getOne(ID id);
     List<T> findAll();
     List<T> findAll(Sort sort);
     List<T> findAllById(Iterable<ID> ids);
     <S extends T> List<S> findAll(Example<S> example);
     <S extends T> List<S> findAll(Example<S> example, Sort sort);
    
     // delete
     void deleteInBatch(Iterable<T> entities);
     void deleteAllInBatch();
    

sample:

  1. Repository
     public interface EmployeeRepository extends JpaRepository<Employee, Integer> {
     }
    
  2. Test

     public void listByPageAndSortTest(){
         Sort sort=Sort.by(Order.desc("name"),Order.asc("id"));
         Pageable pageable=PageRequest.of(0, 10,sort);
    
         Page<Employee> page=employeeRepository.findAll(pageable);
    
         System.out.println("Total Records:"+page.getTotalElements());
         System.out.println("Total Pages:"+page.getTotalPages());
         System.out.println("Current Page:"+(page.getNumber()+1));
         System.out.println("Current Records:"+page.getNumberOfElements());
         System.out.println("Limit:"+page.getSize());
         System.out.println("Sort:"+page.getSort());
    
         List<Employee> list=page.getContent();
         for(Employee e:list) {
             System.out.println(e.getId()+":"+e.getName()+" DepartmentId:"+e.getDepartment().getId());
         }
         System.out.println("Record Size:"+list.size());
     }
    

JpaSpecificationExecutor 接口

封装JPA Criteria查询

public interface JpaSpecificationExecutor<T> {

    Optional<T> findOne(@Nullable Specification<T> spec);
    List<T> findAll(@Nullable Specification<T> spec);
    Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable);
    List<T> findAll(@Nullable Specification<T> spec, Sort sort);
    long count(@Nullable Specification<T> spec);
}
Specification<T> spec=new Specification<T>() {
    @Override
    public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
        return null;
    }
};
  • root: 可从query.from获取,常用方法:root.join,root.fetch,root.get
  • query: 可从criteriaBuilder.createQuery, criteriaBuilder.createTupleQuery获取
    • query.select (return query)
    • query.from (return root)
    • query.where (return query)
  • criteriaBuilder:
    • 可从EntityManagerEntityManagerFactory类(em.getCriteriaBuilder())中获得criteriaBuilder对象
    • 通过调用它的条件方法(equal,notEqual, gt, ge,lt, le,between,like等)和逻辑方法(and,or,not)创建Predicate对象

Sample:

  1. Repository
     public interface EmployeeRepository 
         extends JpaRepository<Employee, Integer>,JpaSpecificationExecutor<Employee> {
     }
    
  2. Test

     public void listBySpecificationTest(){
         Specification<Employee> spec=new Specification<Employee>() {
             @Override
             public Predicate toPredicate(Root<Employee> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
    
                 Predicate p1=criteriaBuilder.like(root.get("name"), "Test%");
                 Predicate p2=criteriaBuilder.gt(root.get("age"), 18);
    
                 //return criteriaBuilder.and(p1,p2);
                 query.where(criteriaBuilder.and(p1,p2))
                     .orderBy(criteriaBuilder.desc(root.get("id")));
                 return query.getRestriction();
             }
         };
    
         List<Employee> list=employeeRepository.findAll(specification);
         for(Employee e:list) {
             System.out.println(e);
         }
     }
    

Demo

  1. Table (@Table):
    • pe_employee : id,name,remark,department_id
    • pe_department : id,name,remark
  2. Entity (@Entity):
    • Employee : id,name,remark,Department department
    • Department : id,name,remark,List employees
  3. Relationship (@ManyToOne,@OneToMany):
    • Employee -> Department : ManyToOne
    • Employee <- Department : OneToMany

Entity

  1. Employee:

     package com.cj.demo.entity;
    
     import static javax.persistence.GenerationType.IDENTITY;
    
     import javax.persistence.Column;
     import javax.persistence.Entity;
     import javax.persistence.FetchType;
     import javax.persistence.GeneratedValue;
     import javax.persistence.Id;
     import javax.persistence.JoinColumn;
     import javax.persistence.ManyToOne;
     import javax.persistence.Table;
    
     @Entity
     @Table(name = "pe_employee", catalog = "demo1")
     //@DynamicUpdate
     public class Employee {
    
         private Integer id;
         private String name;
         private Department department;
         private String remark;
    
         @Id
         @GeneratedValue(strategy = IDENTITY)
         @Column(name = "id", unique = true, nullable = false)
         public Integer getId() {
             return id;
         }
         public void setId(Integer id) {
             this.id = id;
         }
    
         public String getName() {
             return name;
         }
         public void setName(String name) {
             this.name = name;
         }
    
         @ManyToOne(fetch=FetchType.LAZY)
         @JoinColumn(name = "department_id", nullable = false)
         public Department getDepartment() {
             return department;
         }
         public void setDepartment(Department department) {
             this.department = department;
         }
    
         public String getRemark() {
             return remark;
         }
         public void setRemark(String remark) {
             this.remark = remark;
         }
    
         @Override
         public String toString() {
             return "Employee [id=" + id + ", name=" + name + ", department=" + department + ", remark=" + remark + "]";
         }
     }
    
  2. Department:

     package com.cj.demo.entity;
    
     import static javax.persistence.GenerationType.IDENTITY;
    
     import java.util.ArrayList;
     import java.util.List;
    
     import javax.persistence.Column;
     import javax.persistence.Entity;
     import javax.persistence.FetchType;
     import javax.persistence.GeneratedValue;
     import javax.persistence.Id;
     import javax.persistence.OneToMany;
     import javax.persistence.Table;
    
     @Entity
     @Table(name = "pe_department", catalog = "demo1")
     public class Department {
    
         private Integer id;
         private String name;
         private String remark;
         private List<Employee> employees=new ArrayList<Employee>();
    
         public Department() {
    
         }
    
         public Department(Integer id) {
             this.id=id;
         }
    
         @Id
         @GeneratedValue(strategy = IDENTITY)
         @Column(name = "id", unique = true, nullable = false)
         public Integer getId() {
             return id;
         }
         public void setId(Integer id) {
             this.id = id;
         }
    
         public String getName() {
             return name;
         }
         public void setName(String name) {
             this.name = name;
         }
    
         public String getRemark() {
             return remark;
         }
         public void setRemark(String remark) {
             this.remark = remark;
         }
    
         @OneToMany(fetch=FetchType.LAZY,mappedBy="department")
         public List<Employee> getEmployees() {
             return employees;
         }
         public void setEmployees(List<Employee> employees) {
             this.employees = employees;
         }
    
         @Override
         public String toString() {
             return "Department [id=" + id + ", name=" + name + ", remark=" + remark + ", employees=" + employees + "]";
         }
     }
    

Test Structure

  1. Repository:

     public interface EmployeeRepository 
         extends JpaRepository<Employee, Integer>,JpaSpecificationExecutor<Employee> {
         // ...
     }
    
  2. Service:

     @Service
     public class EmployeeService {
    
         @Autowired
         private EmployeeRepository employeeRepository;
    
         // call repository function
     }
    
  3. Test:

     @RunWith(SpringRunner.class)
     @SpringBootTest
     public class EmployeeServiceTest {
    
         @Autowired
         private EmployeeService employeeService;
    
         @Test
         public void queryTest() {
             //...
         }
     }
    

Query

  1. interface exist function:

    • T getOne(ID id)
      • 懒加载(lazyLoad), 返回一个代理对象 E_$$_jvstxxx (获取对象属性值时再触发执行query)
      • 当没有找到匹配记录时,抛出EntityNotFoundException
    • Optional<T> findById(ID id)/ List<T> findAll()
      • 马上触发执行query
      • 当没有找到匹配记录时,不会抛出EntityNotFoundException
  2. named query function:

     public List<Employee> findByDepartmentId(Integer id);
     public List<Employee> findByDepartment(Department department);
     // query:
     //    select p from Employee p left join p.department c where c.id=?
     // return:
     //    List<Employee>
     //    [ Employee: id,name,remark,department - proxy: `department_$$_xxxx (id)`]
     // Note:
     //    `findByDepartment`只会根据department的key(id)来查询,department对象的其它属性(eg:name)不会用到
    
  3. JPQL(@Query)

     /* 1 */
     @Query("from Employee e where e.department.id=?1")
     public List<Employee> list(Integer departmentId);    
    
     @Query("from Employee e where e.department.id=?1")
     public Page<Employee> listByPage(Integer departmentId,Pageable pageable);
     // query: 
     //    select e from Employee e where e.department.id=?1
     // countQuery: 
     //    select count(id) from Employee e where e.department.id=?1
     // Note: The JPQL should use alias for tables.
     //     + `from Employee where department.id=?1`: wrong!
     //     + `from Employee e where e.department.id=?1`: success!
     //     + `from Employee where name like ?1`: ok!
    
     /* 2. Employee-Department(ManyToOne) */
     @Query("from Employee e left join e.department where e.department.id=?1")    
     public List<Employee listWithDepartment(Integer departmentId);
     // use `left join`
     // return : `e` & `e.department` 
     //    => store in Employee: id,name,remark,department(id,name,remark,employees:empty)
     // Note:
     //    + if use `select e from ...`,won't get department
     //    + can't use `select * from ...`
     //    + for ManyToOne and no Pageable, `left join` is enough,`fetch` is not necessary.
    
     @Query("from Employee e left join e.department where e.department.id=?1")    
     public Page<Object[]> listWithDepartmentByPage(Integer departmentId,Pageable pageable);
     // query: 
     //    select e,e.department from Employee e left join e.department where e.department.id=?1
     // countQuery: 
     //    select count(e) from Employee e left join e.department where e.department.id=?1
     // return : `e` & `e.department`
     //    + Pageable: content won't store in Employee, get two objects List: `List<Object[]>`
     //    + no Pageable: content will store in Employee,get one object List: `List<Employee>`
     // Note:
     //    if use `select e from ...`,won't get department
     //    can't use `select * from ...`
    
     @Query( value="from Employee e left join fetch e.department where e.department.id=?1",
             countQuery="select count(e) from Employee e left join e.department where e.department.id=?1"
             //countQuery="from Employee e where e.department.id=?1"
             ) // countQuery can't use fetch !!
     public Page<Employee> listFetchWithDepartmentByPage(Integer departmentId,Pageable pageable);
     // use `left join fetch`
     // query: 
     //    select e,e.department from Employee e left join fetch e.department where e.department.id=?1
     // countQuery: 
     //    select count(e) from Employee e left join e.department where e.department.id=?1
     //     也可使用`select count(e) from Employee e where e.department.id=?1`
     // return : `e` & `e.department` 
     //    => store in Employee,get one object List:`List<Employee>`
     // Note: 
     //    can't use `fetch` for countQuery 
     //    or will throw org.hibernate.QueryException (query specified join fetching, but the owner of the fetched association was not present in the select list )
    
     /* 3. Department-Employee (OneToMany) */
     @Query("select distinct p from Department p left join fetch p.employees")
     public List<Department> list()
     // query: 
     //    select * from department left outer join employee;
     // return: p & p.employee
     //    => store in Department(包含Employee对象): id,name,remark,employees
     // Note: 
     //    for OneToMany, use `select distinct p` for root
    
  4. Specification (extends JpaSpecificationExecutor<T>)

    • Employee(Department): ManyToOne

        criteriaBuilder.equal(root.get("department").get("id"), 1);
        // Note: can't use `root.get("department.id")`
        // select * from Employee where department.id=? and name like ?; 
      
        criteriaBuilder.equal(root.get("department").get("name"), "QA");
        // will auto `inner join` Department
        // select * from Employee e join e.department where department.name=? and name like ?
      
        criteriaBuilder.equal(root.join("department",JoinType.LEFT).get("name"),"QA");
        // will `left join` Department
        // select * from Employee e left join e.department where department.name=? and name like ?;
      
        root.fetch("department",JoinType.LEFT); 
        criteriaBuilder.like(root.get("name"), "Test%")
        ...
        // will `fetch` Department into Employee
        // select * from Employee e left join fetch e.department where e.name like ?
        // Note: 
        // if use Pageable, countQuery can't use fetch, need to use the `CriteriaQuery.getResultType()` to check whether the query is projection or not
      
        /* Sample: */
         Specification<Employee> spec=new Specification<Employee>() {
            @Override
            public Predicate toPredicate(Root<Employee> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
      
                // to check whether it's a count query
                if(!Long.class.equals(query.getResultType())){
                    root.fetch("department",JoinType.LEFT);
                }
                Predicate p1=criteriaBuilder.equal(root.get("department").get("id"), 1);
                Predicate p2=criteriaBuilder.like(root.get("name"), "Test%");
                query.where(criteriaBuilder.and(p1,p2))
                    .orderBy(criteriaBuilder.desc(root.get("id")));
                return query.getRestriction();
            }
        };
      
    • Department(Employee): OneToMany

        /*
         1. need to use 'join' to set Many's conditions.
             criteriaBuilder.like(root.get("employees").get("name"), "Test%");    //error
         2. If want to fetch Many:
                 - Method1: root.fetch,then cast to join
                - Method2: use @EntityGraph + root.join
         3. Note: won't limit records,and the total records are not correct for `Pageable`.
        */
        Specification<Department> spec=new Specification<Department>() {
            @Override
            public Predicate toPredicate(Root<Department> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
      
                Predicate p1=null;
                // to check whether it's a count query -- no need if used @EntityGraph
                if(!Long.class.equals(query.getResultType()) && !long.class.equals(query.getResultType())){
                    // fetch many: use root.fetch,then cast to join
                    Fetch<Department,Employee> ep=root.fetch("employees",JoinType.LEFT);
                    Join<Department,Employee> join=(Join<Department,Employee>)ep;
                    p1=criteriaBuilder.like(join.get("name"), "Emp%");
                }else{
                    p1=criteriaBuilder.like(root.join("employees",JoinType.LEFT).get("name"), "Test%");
                }
                Predicate p2=criteriaBuilder.like(root.get("name"), "Dep%");
                query.where(criteriaBuilder.and(p1,p2))
                    .orderBy(criteriaBuilder.desc(root.get("id")));
                return query.getRestriction();
            }
        };
      
        /* Use @EntityGraph Sample: */
      
        // In DepartmentRepository:
        @EntityGraph(attributePaths="employees")
        public Page<Department> findAll(Specification<Department> spec,Pageable pageable);
      
        // Specification: 
        Specification<Department> spec=new Specification<Department>() {
            @Override
            public Predicate toPredicate(Root<Department> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                Join<Department,Employee> join=root.join("employees",JoinType.LEFT);
                Predicate p = criteriaBuilder.isNull(join);
                Predicate p1=criteriaBuilder.like(join.get("name"), "Test%");
                Predicate p2=criteriaBuilder.like(root.get("name"), "QA%");
      
                query.where(criteriaBuilder.and(criteriaBuilder.or(p,p1),p2))
                    .orderBy(criteriaBuilder.desc(root.get("id")));
      
                return query.getRestriction();
            }
        };
      
  5. @EntityGraph

    • Employee-Department(ManyToOne)

        /* 1. use @Query */
        @EntityGraph(attributePaths="department"/*,type = EntityGraph.EntityGraphType.FETCH*/)
        @Query("from Employee e")
        public Page<Employee> listByGraphAndPage(Pageable pageable);
        // query: 
        //    select * from Employee left join e.department
        // countQuery: 
        //    select count(e.id) from Employee
        // return: 
        //    e & e.department stored in Employee
        //  (will get correct Pagable records)
      
        /* 2. use Specification */
        @EntityGraph(attributePaths="department")
        public Page<Employee> findAll(Specification<Employee> spec,Pageable pageable);
        // Test
        // Note:
        //    + no need to set fetch manually
        //    + Specification中不需要再使用`CriteriaQuery.getResultType()`来检查区分是否是countQuery了
        Specification<Employee> spec=new Specification<Employee>() {
            @Override
            public Predicate toPredicate(Root<Employee> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                // to check whether it's a count query
                // if(!Long.class.equals(query.getResultType())){
                //        root.fetch("department",JoinType.LEFT);
                //    }
                Predicate p1=criteriaBuilder.equal(root.get("department").get("id"), 1);
                Predicate p2=criteriaBuilder.like(root.get("name"), "Test%");
                query.where(criteriaBuilder.and(p1,p2))
                    .orderBy(criteriaBuilder.desc(root.get("id")));
                return query.getRestriction();
            }
        };
        Sort sort=Sort.by(Order.desc("name"),Order.asc("id"));
        Pageable pageable=PageRequest.of(0, 3,sort);
        Page<Employee> page=this.employeeService.listAll(spec,pageable);
      
    • Department-Employee(OneToMany)
        @EntityGraph(attributePaths="employees")
        @Query("from Department p left join p.employees e where e.name like ?1")
        public Page<Department> listByGraphAndPage(String empName,Pageable pageable);
        // query: 
        //    select * from Department p left join p.employees e where e.name like ?1
        // countQuery: 
        //    select count(e.id) from Department p left join p.employees e where e.name like ?1
        // return: 
        //    p & p.employees stored in Department
        // (will get correct Pagable records)
      

T Save(T e)

  • e: a transient object:
    • no primaryKey: do insert
      • if associate obj(eg: Department) is not exist,will throw Exception when do insert.
      • return saved obj(同save时传入的对象),eg: Employee: id,name,remark,department(id,name:null,remark:null,employees:empty)
    • has PrimaryKey: do select -> do insert/update/nothing:
      • not exist => do insert
      • exist & different => do update (update all columnes)
      • exist & no different => do nothing
      • return : selected persist object(不同save时传入的对象). eg: Employee: id,name,remark,department - proxy : department_$$_xxxx (id)
  • e: a persist object: do update/nothing (wont't execute select at first)
    • has change => do update, return the updated persist obj
    • no change => do nothing, return : the persist obj
  • Note: For Hibernate implentation, if add @DynamicUpdate on the entity,the update would be dynamic,just update the changed column value

Summary:

  1. Create:
    • a transient object & no primaryKey
    • a transient object & a unused primaryKey (will do select first)
  2. Update:
    • a transient object & a exist obj's primaryKey & different (will do select first)
    • a persist object & has change

Create/Update/Delete

  1. Create:

    • T save(T e):
      • a transient object & no primaryKey
      • a transient object & a unused primaryKey (will do select first)
  2. Update:

    • T save(T e):
      • a transient object & a exist obj's primaryKey & different (will do select first)
      • a persist object & has change
    • @Modifying+@Query("update ...")

      • Note: only use void or int/Integer as return type

        /* interface Repository : */
        @Query("update Employee set name=?2 where id=?1")
        @Modifying
        public int updateName(Integer id,String newName);
        
        /* class Service : */
        @Transactional
        public int updateName(Integer id,String newName){
          this.employeeRepository.updateName(id,newName);
        }
        
  3. Delete:

    • Exist function:
      • void delete(T entity): do select by Id -> do delete/insert
      • void delete(ID id): do select by Id -> do delete/ throw EmptyResultDataAccessException
      • Note: no return
    • Named function:
      • deleteByXxx,removeByXxxx
      • Process: select all ids, then do delete one by one
      • Note: could return int/Integer/removed entities
        public List<Employee> deleteByDepartmentId(Integer departmentId);
        public List<Employee> removeByDepartmentId(Integer departmentId);
        // Step1. `select` all ids:
        //    select e from Employee e left join e.department d where d.id=?
        //     => employee ids
        // Step2. do `delete` one by one:
        //     delete from Employee where id=?
        //     delete from Employee where id=?
        //    ...
        
    • JPQL function: @Modifiing + @Query("delete from ...")

      • Note: could return void/int/Integer(changed row count)

        /* interface Repository : */
        @Query("delete from Employee where department.id=?1")
        @Modifying
        public int deleteDirectlyByDepartmentId(Integer departmentId);
        
        /* class Service : */
        @Transactional
        public int delete(Integer departmentId){
          this.employeeRepository.deleteDirectlyByDepartmentId(departmentId);
        }
        

Custom Repository

  1. interface EmployeeRepositoryCustom:

     public interface EmployeeRepositoryCustom<T,ID> {
          public boolean update(T entity,String...properties); 
          public int delete(T entity,String...properties);     
     }
    
  2. interface EmployeeRepository (extends EmployeeRepositoryCustom ) :

     public interface EmployeeRepository 
         extends JpaRepository<Employee, Integer>,EmployeeRepositoryCustom<Employee, Integer> {
     }
    
  3. class EmployeeRepositoryImpl (implements EmployeeRepositoryCustom):

     public class EmployeeRepositoryImpl implements EmployeeRepositoryCustom<Employee,Integer>{
    
         @PersistenceContext
         private EntityManager em;
    
         @Override
         public boolean update(Employee entity, String... properties) {
             System.out.println("Update dynamic....");
             if(properties==null || properties.length==0)
                 return false;
    
             try {
                 EntityManagerFactory factory=em.getEntityManagerFactory();
    
                 Object idValue=factory.getPersistenceUnitUtil().getIdentifier(entity);
                 if(idValue==null)
                     return false;
    
                 Metamodel metamodel=factory.getMetamodel();
                 EntityType<? extends Object> entityType=metamodel.entity(entity.getClass());
    
                 String entityName = entityType.getJavaType().getSimpleName();
                 String idProperty=entityType.getId(Integer.class).getName();
    
                 String hql="Update "+entityName+" t set ";
                 for(String property:properties) {
                     if(property!=null && properties.length!=0)
                         hql+="t."+property+"=?,";
                 }
                 hql=hql.substring(0,hql.length()-1)+" where t."+idProperty+"=?";
    
                 Query query=em.createQuery(hql);
                 for(int i=0;i<properties.length;i++) {
                     String property=properties[i];
                     query.setParameter(i, PropertyUtils.getProperty(entity, property));
                 }
                 query.setParameter(properties.length, idValue);
                 return query.executeUpdate()>0;
    
             }catch(Exception ex) {
                 System.out.println(ex.getMessage());
                 return false;
             }
         }
    
         @Override
         public int delete(Employee entity, String... properties) {
             System.out.println("Delete dynamic....");
    
             if(properties==null || properties.length==0)
                 return 0;
    
             try {
                 EntityManagerFactory factory=em.getEntityManagerFactory();
    
                 Metamodel metamodel=factory.getMetamodel();
                 EntityType<? extends Object> entityType=metamodel.entity(entity.getClass());
    
                 String entityName = entityType.getJavaType().getSimpleName();
    
                 String hql="delete from "+entityName+" t where ";
                 for(String property:properties) {
                     if(property!=null && properties.length!=0)
                         hql+="t."+property+"=? and ";
                 }
                 hql=hql+" 1=1";
    
                 Query query=em.createQuery(hql);
                 for(int i=0;i<properties.length;i++) {
                     String property=properties[i];
                     query.setParameter(i, PropertyUtils.getProperty(entity, property));
                 }
                 return query.executeUpdate();
    
             }catch(Exception ex) {
                 System.out.println(ex.getMessage());
                 return 0;
             }
         }
     }
    
  4. Service:

     @Service
     public class EmployeeService {
    
         @Autowired
         private EmployeeRepository employeeRepository;
    
         // Update: Dynamic: em + HQL 
         @Transactional
         public boolean updateDynamic(Employee e,String...properties) {
             return this.employeeRepository.update(e, properties);
         }
    
         // Delete: Dynamic: em + HQL
         @Transactional
         public int deleteDynamic(Employee e,String...properties) {
             return this.employeeRepository.delete(e, properties);
         }
     }
    
  5. Test:

     /*
      * Update by Custom Implementation
      * 
      * Use em and build dynamic "update" hql by properties
      * ( No need add @Modify  on Repository)
      * 
      * */
     @Test
     public void updateDynamicByHQLTest() {
         Employee e = new Employee();
         e.setId(3);
         e.setName("Hello-Dynamic4");
         e.setDepartment(new Department(1));
    
         boolean result=this.employeeService.updateDynamic(e, "name");
         System.out.println(result);
     }
    
     /*
      * Delete by Custom Implementation
      * 
      * Use em and build dynamic "delete" hql by properties
      * ( No need add @Modify on Repository)
      * 
      * */
     @Test
     public void deleteDynamicTest() {
         Employee e = new Employee();
         e.setId(6);
         e.setName("Test");
         int result=this.employeeService.deleteDynamic(e,"id","name");
         System.out.println(result);
     }
    

Reference

my demo