JPA 小方法

JPA 小方法

1.生命周期注解

JPA 生命周期事件注解的执行顺序如下:

  1. @PrePersist: 在实体对象被持久化到数据库之前调用。
  2. @PostPersist: 在实体对象被持久化到数据库之后调用。
  3. @PreUpdate: 在实体对象更新到数据库之前调用。
  4. @PostUpdate: 在实体对象更新到数据库之后调用。
  5. @PreRemove: 在实体对象从数据库中移除之前调用。
  6. @PostRemove: 在实体对象从数据库中移除之后调用。
  7. @PostLoad: 在实体对象从数据库加载到内存后调用。

1. @EntityListeners

@EntityListeners 是 Java Persistence API (JPA) 中的一个注解,用于将实体监听器(Entity Listeners)与特定的实体类关联起来。实体监听器是一种特殊的类,用于在实体对象的生命周期事件发生时执行相应的操作。@EntityListeners 注解的作用是将这些监听器与实体类进行绑定,以便在实体类触发相应事件时调用监听器中定义的方法。

import javax.persistence.*;

@Entity
@EntityListeners(EmployeeListener.class)
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String department;
}

public class EmployeeListener {
    @PrePersist
    public void prePersist(Employee employee) {
        // 在实体对象被持久化到数据库之前调用
        System.out.println("Employee prePersist: " + employee.getName());
    }

    @PostPersist
    public void postPersist(Employee employee) {
        // 在实体对象被持久化到数据库之后调用
        System.out.println("Employee postPersist: " + employee.getName());
    }

 
}

2. @PrePersist

@PrePersist 是 Java Persistence API (JPA) 的一个生命周期事件注解,用于指定在实体对象被持久化到数据库之前要执行的方法。当执行持久化操作时,例如调用 EntityManagerpersist 方法保存实体对象时,被标记了 @PrePersist 注解的方法将在实体对象被持久化之前被调用。

import javax.persistence.*;

@Entity
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String department;

    @PrePersist
    public void prePersist() {
        // 在实体对象被持久化到数据库之前调用
        System.out.println("PrePersist: " + name);
        // 可在这里执行其他逻辑操作
    }
}

3. @PostPersist

@PostPersist 是 Java Persistence API (JPA) 的一个生命周期事件注解,用于指定在实体对象被持久化到数据库之后要执行的方法。当执行持久化操作时,例如调用 EntityManagerpersist 方法保存实体对象后,被标记了 @PostPersist 注解的方法将在实体对象被持久化之后被调用。

import javax.persistence.*;

@Entity
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String department;

    @PostPersist
    public void postPersist() {
        // 在实体对象被持久化到数据库之后调用
        System.out.println("PostPersist: " + name);
        // 可在这里执行其他逻辑操作
    }
}

4. @PreUpdate

@PreUpdate 是 Java Persistence API (JPA) 的一个生命周期事件注解,用于指定在实体对象更新到数据库之前要执行的方法。当执行更新操作时,例如调用 EntityManagermerge 方法更新实体对象时,被标记了 @PreUpdate 注解的方法将在实体对象更新之前被调用

import javax.persistence.*;

@Entity
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String department;

    @PreUpdate
    public void preUpdate() {
        // 在实体对象更新到数据库之前调用
        System.out.println("PreUpdate: " + name);
        // 可在这里执行其他逻辑操作
    }
}

5. @PostUpdate

@PostUpdate 是 Java Persistence API (JPA) 的一个生命周期事件注解,用于指定在实体对象更新到数据库之后要执行的方法。当执行更新操作时,例如调用 EntityManagermerge 方法更新实体对象后,被标记了 @PostUpdate 注解的方法将在实体对象更新之后被调用

import javax.persistence.*;

@Entity
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String department;

    @PostUpdate
    public void postUpdate() {
        // 在实体对象更新到数据库之后调用
        System.out.println("PostUpdate: " + name);
        // 可在这里执行其他逻辑操作
    }
}

6. @PreRemove

@PreRemove 是 Java Persistence API (JPA) 的一个生命周期事件注解,用于指定在实体对象从数据库中移除之前要执行的方法。当执行删除操作时,例如调用 EntityManagerremove 方法删除实体对象时,被标记了 @PreRemove 注解的方法将在实体对象被移除之前被调用。

import javax.persistence.*;

@Entity
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String department;

    @PreRemove
    public void preRemove() {
        // 在实体对象从数据库中移除之前调用
        System.out.println("PreRemove: " + name);
        // 可在这里执行其他逻辑操作
    }
}

7. @PostRemove

** @PostRemove 是 Java Persistence API (JPA) 的一个生命周期事件注解,用于指定在实体对象从数据库中移除之后要执行的方法。当执行删除操作时,例如调用 EntityManagerremove 方法删除实体对象后,被标记了 @PostRemove 注解的方法将在实体对象被移除之后被调用。**

import javax.persistence.*;

@Entity
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String department;

    @PostRemove
    public void postRemove() {
        // 在实体对象从数据库中移除之后调用
        System.out.println("PostRemove: " + name);
        // 可在这里执行其他逻辑操作
    }

}

8. @PostLoad

@PostLoad 是 Java Persistence API (JPA) 的一个生命周期事件注解,用于指定在实体对象从数据库加载到内存后要执行的方法。当从数据库中检索实体对象时,被标记了 @PostLoad 注解的方法将在实体对象加载到内存后被调用。

import javax.persistence.*;

@Entity
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String department;

    @PostLoad
    public void postLoad() {
        // 在实体对象从数据库加载到内存后调用
        System.out.println("PostLoad: " + name);
        // 可在这里执行其他逻辑操作
    }

}

9. @DynamicUpdate

@DynamicUpdate 是 Hibernate 框架提供的一个注解,用于指定只更新发生变化的字段到数据库,而忽略未发生变化的字段。它可以应用于实体类上,表示在执行更新操作时,只更新被修改的字段,而不更新所有字段。

import javax.persistence.*;

@Entity
@Table(name = "employees")
@DynamicUpdate
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String department;
    private double salary;
}

执行数据操作

public class Main {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("myPersistenceUnit");
        EntityManager em = emf.createEntityManager();

        // 创建一个新的 Employee 对象
        Employee employee = new Employee();
        employee.setName("John");
        employee.setDepartment("IT");
        employee.setSalary(5000.0);

        // 将对象保存到数据库
        em.getTransaction().begin();
        em.persist(employee);
        em.getTransaction().commit();

        // 更新 Employee 对象的部门和薪水字段
        em.getTransaction().begin();
        employee.setDepartment("HR");
        employee.setSalary(6000.0);
        em.getTransaction().commit();

        em.close();
        emf.close();
    }
}

使用 @DynamicUpdate 注解时生成的更新语句(只更新发生变化的字段):

UPDATE employees SET department = 'HR', salary = 6000.0 WHERE id = 1;

未使用 @DynamicUpdate 注解时生成的更新语句(更新所有字段):

UPDATE employees SET name = 'John', department = 'HR', salary = 6000.0 WHERE id = 1;

10. @DynamicInsert

@DynamicInsert 是 Hibernate 框架提供的一个注解,用于指定在执行插入操作时,只插入非空字段到数据库,而忽略空值字段。它可以应用于实体类上,表示在执行插入操作时,只插入非空字段,而不插入空值字段。

import javax.persistence.*;

@Entity
@Table(name = "employees")
@DynamicInsert
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String department;
    private double salary;

}

执行数据操作

public class Main {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("myPersistenceUnit");
        EntityManager em = emf.createEntityManager();

        // 创建一个新的 Employee 对象
        Employee employee = new Employee();
        employee.setName("John");
        employee.setDepartment("IT");

        // 将对象保存到数据库
        em.getTransaction().begin();
        em.persist(employee);
        em.getTransaction().commit();

        em.close();
        emf.close();
    }
}

使用 @DynamicInsert 注解时生成的更新语句(只插入非空字段):

INSERT INTO employees (name, department) VALUES ('John', 'IT');

未使用 @DynamicInsert 注解时生成的更新语句(插入所有字段):

INSERT INTO employees (id, name, department, salary) VALUES (1, 'John', 'IT', 5000.0);

2. Projections(投影)

Projections 是一种功能,用于选择实体中的特定字段,以创建自定义的投影对象。投影对象是一个接口或类,仅包含您所选择的字段,而不是整个实体的所有字段。

通过使用投影,您可以在查询结果中返回仅包含所需字段的对象,而不是完整的实体对象。这有助于减少数据传输量和内存消耗,提高性能,并使查询结果更加精确和专注。

接口投影(Interface Projections)

接口投影(Interface Projections):使用接口来定义投影对象,并在接口中声明所需的字段访问方法。Spring Data JPA 将根据投影接口的定义来映射查询结果,并返回仅包含所需字段的投影对象。

public interface EmployeeProjection {
    String getName();
    String getDepartment();
}

List<EmployeeProjection> findByDepartment(String department);

(Closed Projections)

使用自定义的类来定义投影对象,并在类中声明所需的字段和构造方法。通过使用 Projections.constructor 方法,您可以将查询结果映射到自定义的投影类,并选择所需的字段。

public class EmployeeProjection {
    private String name;
    private String department;

    public EmployeeProjection(String name, String department) {
        this.name = name;
        this.department = department;
    }
}

List<EmployeeProjection> findByDepartment(String department);

动态投影 (Dynamic Projections)

您可以通过动态投影实现在运行时选择不同字段的能力。动态投影允许您根据查询需求在运行时选择要返回的字段,而不是在编译时固定选择字段。

public interface PersonRepository extends Repository<Person, Long> {
    // ...

    <T> T findByLastName(String lastName, Class<T> type);
}


Person person = personRepository.findByLastName("Doe", Person.class);
PersonView personView = personRepository.findByLastName("Doe", PersonView.class);
PersonDto personDto = personRepository.findByLastName("Doe", PersonDto.class);

开放式投影(open projections)

开放式投影。 这些投影使我们能够定义具有不匹配名称和在运行时计算的返回值的接口方法。

public interface PersonView {
    // ...

    @Value("#{target.firstName + ' ' + target.lastName}")
    String getFullName();
}

  PersonView findByLastName(String lastName);

3. 规范(Specification)

基本使用

使用 Spring Data JPA 进行复杂查询时,可以使用 Specification(规范)来构建动态查询条件。Specification 可以帮助您以一种类型安全的方式根据运行时条件动态构建查询。

import org.springframework.data.jpa.domain.Specification;

public class EmployeeSpecifications {
    public static Specification<Employee> hasName(String name) {
        return (root, query, criteriaBuilder) -> {
            if (name != null) {
                return criteriaBuilder.equal(root.get("name"), name);
            }
            return null;
        };
    }

    public static Specification<Employee> hasAgeGreaterThan(int age) {
        return (root, query, criteriaBuilder) -> criteriaBuilder.greaterThan(root.get("age"), age);
    }

    public static Specification<Employee> hasDepartment(String department) {
        return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("department"), department);
    }

    // 添加更多的 Specification 方法,以满足您的查询需求
}

Service

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@Transactional
public class EmployeeService {
    private final EmployeeRepository employeeRepository;

    public EmployeeService(EmployeeRepository employeeRepository) {
        this.employeeRepository = employeeRepository;
    }

    public List<Employee> findEmployees(String name, int age, String department) {
        Specification<Employee> spec = Specification.where(EmployeeSpecifications.hasName(name))
                .and(EmployeeSpecifications.hasAgeGreaterThan(age))
                .and(EmployeeSpecifications.hasDepartment(department));

        return employeeRepository.findAll(spec);
    }
}

Repository

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;

@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long>, JpaSpecificationExecutor<Employee> {
}

复杂一点的servier

    @Override
    public Page<EsAlarm> page(EsPageRspVO vo) {
        Assert.notNull(vo.getProjectId(), ResultCode.NO_PROJECT_ID.getMessage());
        //条件定义
        Specification<EsAlarm> specification = (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();


            Predicate deletedPredicate = cb.equal(root.get(EsAlarm.Fields.deleted), Deleted.UN_DELETED.getCode());
            predicates.add(deletedPredicate);

            Predicate projectPredicate = cb.equal(root.get(EsAlarm.Fields.projectId), vo.getProjectId());
            predicates.add(projectPredicate);

            if (vo.getProcessingStatus() != null) {
                Predicate predicate = cb.equal(root.get(EsAlarm.Fields.processingStatus), vo.getProcessingStatus());
                predicates.add(predicate);
            }

            if (StrUtil.isNotBlank(vo.getDeviceKeywords())) {
                Predicate predicate = cb.or(
                        cb.like(root.get(EsAlarm.Fields.deviceNo).as(String.class), StringConstant.likeString(vo.getDevice())),
                        cb.like(root.get(EsAlarm.Fields.deviceName).as(String.class), StringConstant.likeString(vo.getDevice()))
                );
                predicates.add(predicate);
            }

            return cb.and(predicates.toArray(new Predicate[0]));

        };
        Pageable pageable = PageRequest.of(vo.getPageNo() - 1, vo.getPageSize(), vo.getSortOption());
        //查询
        return esAlarmRepository.findAll(specification, pageable);
    }

工具类

基础工具类


import cn.hutool.core.collection.CollUtil;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 *
 * @author yz
 * @date 2024/03/15
 */

public class JpaSpecification<T> {

    private final List<Specification<T>> specifications;
    private Sort sort;

    public JpaSpecification() {
        specifications = new ArrayList<>();
    }

    public static <T> JpaSpecification<T> getInstance() {
        return new JpaSpecification<>();
    }

    public JpaSpecification<T> addSpecification(Specification<T> specification) {
        if (specification != null) {
            specifications.add(specification);
        }

        return this;
    }

    public JpaSpecification<T> withSort(Sort sort) {
        this.sort = sort;
        return this;
    }


    public Specification<T> build() {
        return (Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) -> {
            Predicate[] predicates = specifications.stream()
                    .map(spec -> spec.toPredicate(root, query, criteriaBuilder))
                    .toArray(Predicate[]::new);

            query.where(predicates);

            if (sort != null) {
                query.orderBy(sort.stream()
                        .map(order -> {
                            String property = order.getProperty();
                            Expression<String> orderExpr = root.get(property);
                            if (order.isIgnoreCase()) {
                                orderExpr = criteriaBuilder.lower(orderExpr);
                            }
                            return order.isAscending() ? criteriaBuilder.asc(orderExpr) : criteriaBuilder.desc(orderExpr);
                        })
                        .toArray(javax.persistence.criteria.Order[]::new));
            }

            return criteriaBuilder.and(predicates);
        };
    }

    public static <T> Specification<T> hasCreateTime(List<Date> queryTime) {
        if (CollUtil.isEmpty(queryTime)) {
            return null;
        }
        return (root, query, criteriaBuilder) -> criteriaBuilder.between(root.get("createTime"), queryTime.get(0), queryTime.get(1));
    }


    public static <T> Specification<T> hasDelete() {
        return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("deleted"), 0);
    }

    public static <T> Specification<T> hasDeviceId(Integer deviceId) {
        if (deviceId == null) {
            return null;
        }
        return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("deviceId"), deviceId);
    }

    public static <T> Specification<T> hasProjectId(Integer projectId) {
        return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("projectId"), projectId);
    }
}

模块通用工具类


public class ElevatorSpecification<T> {

    private final JpaSpecification<T> jpaSpecifications;


    public ElevatorSpecification() {
        jpaSpecifications = new JpaSpecification<>();
    }

    public static <T> ElevatorSpecification<T> getInstance() {
        return new ElevatorSpecification<>();
    }

    public static Specification<AirConditionerRecord> hasAirOperationType(String code) {
        if (StrUtil.isNotBlank(code)) {
            return null;
        }
        return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get(AirConditionerRecord.Fields.operationType), code);
    }

    public ElevatorSpecification<T> withSort(Sort sort) {
        jpaSpecifications.withSort(sort);
        return this;
    }

    public ElevatorSpecification<T> addSpecification(Specification<T> specification) {
        jpaSpecifications.addSpecification(specification);
        return this;
    }

    public Specification<T> build() {
        return jpaSpecifications.build();
    }

    public static <T> Specification<T> hasAirStatus(Integer value) {
        return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get(AirConditionerRecord.Fields.status), value);
    }


}

使用方法

    default Optional<AirConditionerRecord> findLastSwitchRecord(Integer deviceId, Integer value, Integer projectId) {
        Specification<AirConditionerRecord> spec = ElevatorSpecification.<AirConditionerRecord>getInstance()
                .addSpecification(JpaSpecification.hasDelete())
                .addSpecification(JpaSpecification.hasProjectId(projectId))
                .addSpecification(JpaSpecification.hasDeviceId(deviceId))
                .addSpecification(ElevatorSpecification.hasAirOperationType(AirConditionerOperationTypeEnums.SWITCH.code))
                .addSpecification(ElevatorSpecification.hasAirStatus(value))
                .withSort(Sort.by(Sort.Direction.DESC, AirConditionerRecord.Fields.id))
                .build();
        return findOne(spec);
    }