Java JPA学习与使用小记

本文介绍了JPA相关知识。JPA是Java针对ORM提出的规范,有多种注解用于映射数据库表和字段。还阐述了JPA查询、分页、排序、更新、删除等操作的使用方法,以及级联操作、延迟加载等特性,同时提及逻辑删除的实现方式。

什么是JPA

JPA之于ORM(持久层框架,如MyBatis、Hibernate等)正如JDBC之于数据库驱动。

JDBC是Java语言定义的一套标准,规范了客户端程序访问关系数据库(如MySQL、Oracle、Postgres、SQLServer等)的应用程序接口,接口的具体实现(即数据库驱动)由各关系数据库自己实现。

随着业务系统的复杂,直接用JDBC访问数据库对开发者来说变得很繁琐,代码难以维护,为解决此问题,ORM(Object Relation Mapping)框架出现了,如MyBatis、Hibernate等,百花齐放。

爱大一统的Java又出手了,Java针对ORM提出了JPA,JPA 本质上是一种 ORM 规范,不是 ORM 框架,只是定制了一些规范,提供了一些编程的 API 接口,具体实现由 ORM 厂商实现,如Hiernate、Eclipselink等都是JAP的具体实现。

Spring Data JPA 其实并不依赖于 Spring 框架。

JPA注解

**@Entity**

@Entity 标注用于实体类声明语句之前,指出该Java 类为实体类,将映射到指定的关系数据库表。(类似的,使用@Document可以映射到mongodb)

**@Table**

当实体类与其映射的数据库表名不同名时需要使用 @Table 标注说明,该标注与 @Entity 标注并列使用

  • schema属性:指定数据库名
  • name属性:指定表名,不知道时表名为类名

**@IdClass**

修饰在实体类上,指定联合主键。如:@IdClass(StudentExperimentEntityPK.class),主键类StudentExperimentEntityPK需要满足:

  1. 实现Serializable接口
  2. 有默认的public无参数的构造方法
  3. 重写equals和hashCode方法。equals方法用于判断两个对象是否相同,EntityManger通过find方法来查找Entity时,是根据equals的返回值来判断的。hashCode方法返回当前对象的哈希码

**@id**

@Id 标注用于声明一个实体类的属性映射为数据库的一个主键列

@Id标注也可置于属性的getter方法之前。以下注解也一样可以标注于getter方法前。

若同时指定了下面的@GeneratedValue则存储时会自动生成主键值,否则在存入前用户需要手动为实体赋一个主键值。主键值类型可能是:

    • Primitive types: boolean, byte, short, char, int, long, float, double.
    • Equivalent wrapper classes from package java.lang:
      Byte, Short, Character, Integer, Long, Float, Double.
    • java.math.BigInteger, java.math.BigDecimal.
    • java.lang.String.
    • java.util.Date, java.sql.Date, java.sql.Time, java.sql.Timestamp.
    • Any enum type.
    • Reference to an entity object.
    • composite of several keys above

**@EmbeddedId**

功能与@IdClass一样用于指定联合主键。不同的在于其是修饰实体内的一个主键类变量,且主键类应该被@Embeddable修饰。

此外在主键类内指定的字段在实体类内可以不再指定,若再指定则需为@Column加上insertable = false, updatable = false属性

**@GeneratedValue**

@GeneratedValue 用于标注主键的生成策略,通过 strategy 属性指定。默认情况下,JPA 自动选择一个最适合底层数据库的主键生成策略:SqlServer 对应 identity,MySQL 对应 auto increment

  • IDENTITY:采用数据库 ID自增长的方式来自增主键字段,Oracle 不支持这种方式
  • AUTO: JPA自动选择合适的策略,是默认选项
  • TABLE:通过表产生主键,框架借由表模拟序列产生主键,使用该策略可以使应用更易于数据库移植。
  • SEQUENCE:通过序列产生主键,通过 @SequenceGenerator 注解指定序列名,MySql 不支持这种方式

**@Basic**

表示一个简单的属性到数据表的字段的映射,对于没有任何标注的 getXxx() 方法,默认为 @Basic

fetch 表示属性的读取策略,有 EAGER 和 LAZY 两种,分别为主支抓取和延迟加载

optional 表示该属性是否允许为 null,默认为 true

**@Column**

当实体的属性与其映射的数据库表的列不同名时需要使用 @Column 标注说明,还有属性 unique、nullable、length 等

**@Transient**

表示该属性并非一个到数据库表的字段的映射,ORM 框架将忽略该属性

如果一个属性并非数据库表的字段映射,就务必将其标识为 @Transient,否则ORM 框架默认为其注解 @Basic,例如工具方法不需要映射

**@Temporal**

在 JavaAPI 中没有定义 Date 类型的精度,而在数据库中表示 Date 类型的数据类型有 Date,Time,TimeStamp 三种精度(日期,时间,两者兼具),进行属性映射的时候可以使用 @Temporal 注解调整精度。目前此注解只能用于修饰java.util.Date、java.util.Calendar类型的变量,TemporalType取DATE、TIME、TIMESTAMP时在MySQL中分别对应的DATE、TIME、DATETIME类型。示例:

    @Temporal(TemporalType.TIMESTAMP)
    @UpdateTimestamp //org.hibernate.annotations.UpdateTimestamp,用于在保存Entity时自动更新该字段值,类似的还有@CreationTimestamp
    @Column(name = "update_time")
    private Date updateTime;

**@MappedSuperClass**

用来修饰一个类,类中声明了各Entity共有的字段,也即数据库中多表中共有的字段,如create_time、update_time、id等。

标注为@MappedSuperclass的类将不是一个完整的实体类,他将不会映射到数据库表,但是他的属性都将映射到其子类的数据库字段中。

标注为@MappedSuperclass的类不能再标注@Entity或@Table注解,也无需实现序列化接口。

 

SPA使用小记

JPA查询

基本用法:通过方法名来指定查询逻辑,而不需要自己实现查询的SQL逻辑,示例:List<Student> getByName(String name)

方法名解析原理:对方法名中除了保留字(findBy、top、within等)外的部分以and为分隔符提取出条件单词,然后解析条件获取各个单词并看是否和Entity中的属性对应(不区分大小写进行比较)。

条件解析原理:假设School和Student是一对多关系,Student中有个所属的School school字段、School有个String addressCode属性,以如下查询为例: 

Studetn getByNameAndSchoolAddressCode(String studentName, String addressCode)(先说结果:JPA会自动生成条件studentName和关联条件student.school.addressCode进行查询)

  1. 由And分割得到studentName、SchoolAddressCode;
  2. 分别看Student中是否有上述两属性,显然前者有后者没有,则后者需要进一步解析(见下步)
  3. JPA按驼峰命名格式从后往前尝试分解SchoolAddressCode:先得到 [SchoolAdress、Code],由于Student没有SchoolAddress属性故继续尝试分解,得到[School、AdressCode];由于Student有School属性且School有addressCode属性故满足,最终得到条件student.school.addressCode。注:但若Student中有个SchoolAdress schoolAddress属性但schoolAddress中没有code属性,则会因找不到student.schoolAdress.code而报错,所以可通过下划线显示指定分割关系,即写成: getByNameAndSchool_AddressCode

 

在查询时,通常需要同时根据多个属性进行查询,且查询的条件也格式各样(大于某个值、在某个范围等等),Spring Data JPA 为此提供了一些表达条件查询的关键字,大致如下:

And --- 等价于 SQL 中的 and 关键字,比如 findByUsernameAndPassword(String user, Striang pwd);
Or --- 等价于 SQL 中的 or 关键字,比如 findByUsernameOrAddress(String user, String addr);
Between --- 等价于 SQL 中的 between 关键字,比如 findBySalaryBetween(int max, int min);
LessThan --- 等价于 SQL 中的 "<",比如 findBySalaryLessThan(int max);
GreaterThan --- 等价于 SQL 中的">",比如 findBySalaryGreaterThan(int min);
IsNull --- 等价于 SQL 中的 "is null",比如 findByUsernameIsNull();
IsNotNull --- 等价于 SQL 中的 "is not null",比如 findByUsernameIsNotNull();
NotNull --- 与 IsNotNull 等价;
Like --- 等价于 SQL 中的 "like",比如 findByUsernameLike(String user);
NotLike --- 等价于 SQL 中的 "not like",比如 findByUsernameNotLike(String user);
OrderBy --- 等价于 SQL 中的 "order by",比如 findByUsernameOrderBySalaryAsc(String user);
Not --- 等价于 SQL 中的 "! =",比如 findByUsernameNot(String user);
In --- 等价于 SQL 中的 "in",比如 findByUsernameIn(Collection<String> userList) ,方法的参数可以是 Collection 类型,也可以是数组或者不定长参数;
NotIn --- 等价于 SQL 中的 "not in",比如 findByUsernameNotIn(Collection<String> userList) ,方法的参数可以是 Collection 类型,也可以是数组或者不定长参数;
Containing --- 包含指定字符串
StargingWith --- 以指定字符串开头
EndingWith --- 以指定字符串结尾

 

更多见 官方文档Spring Data JPA


SPA List类型查询参数
: List<StudentEntity> getByIdInAndSchoolId(List<String> studentIdList, String schoolId);  ,关键在于 In 关键字。

 

 

Repository nativeQuery返回Entity:

使用nativeQuery时SQL语句查询的字段名若没as则是数据库中的字段名,如school_id,而API返回值通常是schoolId,可以在SQL里通过 school_id as schoolId取别名返回。然而若查询很多个字段值则得一个个通过as取别名,很麻烦,可以直接将返回值指定为数据库表对应的Entity,不过此法要求查询的是所有字段名,如:

    @Query(value = " select t.* from teacher t where t.school_id=?1  "// 以下为搜索字段
            + "and (?4 is NULL or name like %?4% or job_number like %?4% or bz like %?4% or phone like %?4% or email like %?4%)  "
            + " order by job_number limit ?2,  ?3 ", nativeQuery = true)
    List<TeacherEntity> myGetBySchoolIdOrderByJobNumber(String schoolId, int startIndex, Integer size,
            String searchNameOrJobnumOrBzOrPhoneOrEmai);// nativeQuery返回类型可以声明为Entity,会自动进行匹配,要求查回与Entitydb中字段对应的所有db中的字段

 

 

SPA分页或排序:可以在Repository的方法的最后加一个Sort 或者 Pageable 类型的参数,以便按规则进行排序或者分页查询(编译后会自动在语句后加order by或limit语句)。

Repository中的一个方法myGetByCourseIdAndStudentId:

    @Query("select se from StudentExperimentEntity se where se.studentId= ?2 and se.experimentId in ( select e.id from ExperimentEntity e where e.courseId= ?1 ) ")
    List<StudentExperimentEntity> myGetByCourseIdAndStudentId(String courseId, String studentId, Pageable pageable);//没有写上述@Query语句也可以加Pageable。虽然实际传值时传PageRequest对象,但若这里生命为PageRequest则不会分页,总是返回所有数据,why?

调用:

studentExperimentRepository.myGetByCourseIdAndStudentId(courseId, studentId, PageRequest.of(0, count, new Sort(Sort.Direction.DESC, "lastopertime")));

编译后会在myGetByCourseIdAndStudentId所写SQL后自动加上  order by studentexp0_.lastopertime desc limit ? 

注:上述用法也支持nativeQuery,示例:

    @Query(value = "select d.*, u.username from developer d inner join user u on d.id=u.id "
            + " where (?1 is null or d.nick_name like %?1% ) ", nativeQuery = true)
    List<DeveloperEntity> myGetByNicknameOrPhoneOrEmailOrBz(String searchNicknameOrPhoneOrEmailOrBz, Pageable pageable);

 

 

Repository中更新或创建并返回该Entity:如 UserEntity u=userRepository.save(userEntity) ,其中UserEntity包含成员变量private SchoolEntity schoolEntity。Repository的save方法会返回被save的entity,但若是第一次保存该entity(即新建一条记录)时u.schoolEntity的值会为null,解决:用saveAndFlush

 

返回Entity中的部分字段

对于nativeQuery,直接select部分字段即可,结果默认会自动包装为Map。为了便于理解可以直接将结果声明为Map。示例:

    @Query(value = "select g.id, g.school_id as schoolId, g.name, g.createtime, g.bz, count(s.id) as stuCount from grade g left join student s "
            + " on g.name=s.grade where g.school_id=(select a.school_id from admin a where a.id=?1)" // 以下为搜索条件
            + " and (?4 is null or g.name like %?4% or g.bz like %?4% ) "
            + " group by g.id limit ?2,?3", nativeQuery = true)
    List<Map<String, Object>> myGetGradeList(String adminId, Integer page, Integer size,
            String searchGradeNameOrGradeBz);
View Code

其可以达到目的,但缺点是sql里用的直接是数据库字段名,导致耦合大,数据库字段名一变,所有相关sql都得相应改变。

对于非nativeQuery:(sql里的字段名是entity的字段名,数据库字段名改动只要改变entity中对应属性的column name即可,解决上述耦合大的问题)

当Repository返回类型为XXEntity或List<XXEntity>时通常默认包含所有字段,若要去掉某些字段,可以去掉XXEntity中该字段的get方法。此法本质上还是查出来了只是spring在返回给调用者时去掉了。治标不治本。

也可以自定义一个bean,然后在Repository的sql中new该bean。此很死板,要求new时写bean的全限定名,比较麻烦。

更好的办法是与nativeQuery时类似直接在sql里select部分字段,不过非nativeQuery默认会将结果包装为List而不是Map,故不同的是:这里需要在sql里new map,此'map'非jdk里'Map';需要为字段名取别名,否则返回的Map里key为数值0、1、2... 。示例:

//为'map'不是'Map'    
@Query("select new map(g.name as name, count(s.id) as stuCount) from GradeEntity g, StudentEntity s where g.name=s.grade and g.schoolId=?1 group by g.id")
    List<Map<String, Object>> myGetBySchoolId(String schoolId);

 

 

 

 

SPA的update、delete:(需要加@Transactional、@Modefying)

@Transactional //也可以放在service方法上
@Modifying @Query(
"delete from EngineerServices es where es.engineerId = ?1")//update与此类似 int deleteByEgId(String engineerId);

对于delete操作,可以与query的写法类似,直接(注:update不支持这样写):

@Transactional
@Modifying
int deleteByEgId(String engineerId);

甚至更直接写为: int deleteByEgId(String engineerId);  ,但此时记得需要在上层调用者部分添加@Transactional

 

SPA的count:

Integer countByName(String name);

 

级联操作(CASCADE)

Use of the cascade annotation element may be used to propagate the effect of an operation to associated entities. The cascade functionality is most typically used in parent-child relationships.

用于有依赖关系的实体间(@OneToMany、@ManyToOne、@OneToOne等)的级联操作:当对一个实体进行某种操作时,若该实体加了与该操作相关的级联标记,则该操作会传播到与该实体关联的实体(即对被级联标记的实体施加某种与级联标记对应的操作时,与该实体相关联的其他实体也会被施加该操作)。包括:

CascadeType.PERSIST:持久化,即保存

CascadeType.REMOVE:删除当前实体时,关联实体也将被删除

CascadeType.MERGE:更新或查询

CascadeType.REFRESH:级联刷新,即在保存前先更新别人的修改:如Order、Item被用户A、B同时读出做修改且B的先保存了,在A保存时会先更新Order、Item的信息再保存。

CascadeType.DETACH:级联脱离,如果你要删除一个实体,但是它有外键无法删除,你就需要这个级联权限了。它会撤销所有相关的外键关联。

CascadeType.ALL:上述所有

注:

级联应该标记在One的一方。如对于 @OneToMany的Person 和 @ManyToOne的Phone,若将CascadeType.REMOVE标记在Phone则删除Phone也会删除Person,显然是错的。

慎用CascadeType.ALL,应该根据业务需求选择所需的级联关系,否则可能酿成大祸。

 

延迟加载与立即加载(FetchType):通常可以在@OneToMany中用LAZY、在@ManyToOne/Many中用EAGER,但不绝对,看具体需要。

FetchType.LAZY:延迟加载,在查询实体A时,不查询出关联实体B,在调用getxxx方法时,才加载关联实体,但是注意,查询实体A时和getxxx必须在同一个Transaction中,不然会报错:no session

FetchType.EAGER:立即加载,在查询实体A时,也查询出关联的实体B

 

like查询

对于单字段的可以直接在方法名加Containing

    @Query("select s from SchoolEntity s where s.customerId=?1 "// 以下为搜索条件
            + " and (?2 is null or s.name like %?2% or s.bz like %?2%  ) ")
    List<SchoolEntity> getByCustomerId(String customerId, String searchSchoolnameOrBz, Pageable pageable);

 

 

Entity中将任意对象映射为一个数据库字段:借助JPA converter to map your Entity to the database.

在要被映射的字段上加上注解: @Convert(converter = JpaConverterJson.class) 

实现JpaConverterJson:

public class JpaConverterJson implements AttributeConverter<Object, String> {//or specialize the Object as your Column type

  private final static ObjectMapper objectMapper = new ObjectMapper();

  @Override
  public String convertToDatabaseColumn(Object meta) {
    try {
      return objectMapper.writeValueAsString(meta);
    } catch (JsonProcessingException ex) {
      return null;
      // or throw an error
    }
  }

  @Override
  public Object convertToEntityAttribute(String dbData) {
    try {
      return objectMapper.readValue(dbData, Object.class);
    } catch (IOException ex) {
      // logger.error("Unexpected IOEx decoding json from database: " + dbData);
      return null;
    }
  }

}

需要注意的是,若Entity字段是一个 JavaBean 或 JavaBean 列表(如 TimeSlice 或 List<TimeSlice> ),则反序列化时相应地会反序列化成 LinkedHashMap 或 List<LinkedHashMap>,故强转成TimeSlice或List<TimeSlice>虽然编译期不会报错但运行时就出现类型转换错误。故需要进一步转换成JavaBean,示例:

 1     public static class TimeTableConverter implements AttributeConverter<List<TimeSlice>, String> {// or specialize the Object as your Column type
 2 
 3         private final static ObjectMapper objectMapper = new ObjectMapper();
 4 
 5         @Override
 6         public String convertToDatabaseColumn(List<TimeSlice> data) {
 7             try {
 8                 return objectMapper.writeValueAsString(data);
 9             } catch (JsonProcessingException ex) {
10                 throw new ApiCustomException(ApiErrorCode.OTHER, "fail to convert list to string");
11             }
12         }
13 
14         @Override
15         public List<TimeSlice> convertToEntityAttribute(String dbData) {
16             // return objectMapper.readValue(dbData, List.class);//直接return会报ClassCastException
17 
18             Field[] fields = TimeSlice.class.getDeclaredFields();
19 
20             try {
21                 List<Map<String, Object>> tmpMapList = objectMapper.readValue(dbData, List.class);// 对于键值对类型元素,默认反序列化成LinkedListMap类型,故需进一步转换成TimeSlice
22                 List<TimeSlice> timeSliceList = null;
23                 if (null != tmpMapList) {
24                     timeSliceList = new ArrayList<>();
25                     for (Map<String, Object> map : tmpMapList) {
26                         TimeSlice tmpTimeSlice = new TimeSlice();
27                         timeSliceList.add(tmpTimeSlice);
28                         for (Field field : fields) {// 复制出所有属性
29                             try {
30                                 field.setAccessible(true);
31                                 field.set(tmpTimeSlice, map.get(field.getName()));
32                             } catch (IllegalArgumentException | IllegalAccessException e) {
33                                 e.printStackTrace();
34                             }
35                         }
36                     }
37                 }
38                 return timeSliceList;
39             } catch (IOException ex) {
40                 throw new ApiCustomException(ApiErrorCode.OTHER, "fail to convert string to list");
41             }
42         }
43     }
View Code

 

参考资料:https://stackoverflow.com/questions/25738569/jpa-map-json-column-to-java-object

 

将任意非基本数据类型(如java bean、list等)对应到数据库字段:

本质上就是将数据序列化成基本数据类型如String。如要把List<String> gradeIdList对应到数据库中的字符串类型的courseSchedule字段。

法1:可以在业务层写代码将gradeIdList序列化成String: String res=objectMapper.writeValueAsString(gradeIdList);// 借助objectMapper.writeValueAsString(data); ,之后保存即可。从数据库中读取时: List<String> gradeIdList=objectMapper.readValue(dbData, List<String>.class); 。此法可以解决问题,但每个字段都得自己手动写此过程。

法2:实现一个AttributeConverter,并应用于Entity字段。此法相当于指定了AttributeConverter后让框架去自动做转换

@Column(name = "course_schedule")
    @Convert(converter = MyJpaConverterJson.class)
    private  List<String> courseSchedule;



public class MyJpaConverterJson implements AttributeConverter<List<String>, String> {// or specialize the Object as your
    // Column type

    private final static ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public String convertToDatabaseColumn(List<String> data) {
        try {
            return objectMapper.writeValueAsString(data);
        } catch (JsonProcessingException ex) {
            throw new ApiCustomException(ApiErrorCode.OTHER, "fail to convert list to string");
        }
    }

    @Override
    public List<String> convertToEntityAttribute(String dbData) {
        try {
            return objectMapper.readValue(dbData, List.class);
        } catch (IOException ex) {
            throw new ApiCustomException(ApiErrorCode.OTHER, "fail to convert string to list");
        }
    }
}
View Code

 

 

枚举示例

    @Column(name = "sex")
    @Enumerated(EnumType.ORDINAL)//持久化为0,1
    private Sex sex;

    @Column(name = "type")
    @Enumerated(EnumType.STRING)//持久化为字符串
    private Role role;

 

复杂条件(多条件和多表)查询和分页:Specification

示例:

    @Override
    public Page<CourseDeveloperEntity> listJoinedDevelopersOfGivenCourse(Integer page, Integer size, String courseId,
            String optionalExcludedDeveloperId, String optionalEmailOrNickName) {

        PageRequest pageRequest = PageRequest.of(page, size, Sort.by(Sort.Order.desc("developer.nickName"), Sort.Order.asc("role")));

        return courseDeveloperRepository.findAll((Specification<CourseDeveloperEntity>) (root, query, criteriaBuilder) -> {
            List<Predicate> predicateList = new ArrayList<Predicate>();

            predicateList.add(criteriaBuilder.equal(root.get("course").get("id"), courseId));
            if (null != optionalExcludedDeveloperId) {// 排除指定开发者
                predicateList.add(criteriaBuilder.notEqual(root.get("developer").get("id"), optionalExcludedDeveloperId));
            }
            if (null != optionalEmailOrNickName && optionalEmailOrNickName.trim().length() > 0) {
                // Predicate p1 = criteriaBuilder.equal(root.get("developer").get("nickName"), optionalEmailOrNickName);
                // Predicate p2 = criteriaBuilder.equal(root.get("developer").get("email"), optionalEmailOrNickName);
                Predicate p1 = criteriaBuilder.like(root.get("developer").get("nickName"), "%" + optionalEmailOrNickName + "%");
                Predicate p2 = criteriaBuilder.like(root.get("developer").get("email"), "%" + optionalEmailOrNickName + "%");
                predicateList.add(criteriaBuilder.or(p1, p2));
            }

            return criteriaBuilder.and(predicateList.toArray(new Predicate[predicateList.size()]));

        }, pageRequest);

    }
View Code

 

 

JPA Repository的save(xxx)方法

通过show-sql=true参数打印sql语句,可以发现其内部先是按被保存的Entity的主键查出该Entity,若存在则更新并保存、否则插入。

save方法以Entity为参数用于保存。其效果可能是插入新数据或修改已有数据,在执行时会根据Entity参数自动判断:如果该Entity参数中的主键值(可能是联合主键)在DB中已存在则是更新、否则为插入。因此,在某些场景下会有问题。

举个例子,我们有这么个场景:Entity为CourseDeveloperEntity,该Entity中有单独的id字段作为主键、且有course_id、developer_id联合唯一索引、还有个is_delete用于逻辑删除。

在该场景下,若我们删除了一个CourseDeveloperEntity,则该Entity被删除了(is_delete标记为true以逻辑删除,数据库中实际上还在,但上层业务查不到),因此若之后再插入(course_id、developer_id)一样的Entity但没指定id一样,则会因违背联合唯一索引而duplicate key的错。相关代码:

        // 保存
        CourseDeveloperEntity courseDeveloperEntity = courseDeveloperRepository.getByCourseIdAndDeveloperId(courseId, developerEntity.getId());//查不到被逻辑删除的记录
        if (null == courseDeveloperEntity) {
            courseDeveloperEntity = new CourseDeveloperEntity();
        }
        courseDeveloperEntity.setCourseId(courseId);
        courseDeveloperEntity.setDeveloperId(developerEntity.getId());
        courseDeveloperEntity.setRole(developerRoleInTheCourse);
        return courseDeveloperRepository.save(courseDeveloperEntity);//由于主键自动新生成故courseDeveloperEntity被当成新的,故是执行insert而不是update,从而报错

 

JPA Repository的删除操作

方法名包含条件的删除操作,如 Integer deleteByNameAndSId(String name, String uuid); ,其执行时与save类似,也是先根据条件查出目标Entity再执行删除操作。对于 void delete(T entity); 则直接根据Entity的主键操作而不用先查。

 

 

逻辑删除

借助org.hibernate.annotations旳 @Where、@SQLDelete、@SQLDeleteALL 这三个注解来实现。

1、定义一个字段用于标识记录是否被逻辑删除。这里通过JPA的@MappedSuperclass定义各Entity共有的字段(该注解修饰的Entity不会对应数据库表,但其内定义的字段会被继承该Entity的子Entity对应到数据库字段),包含is_delete:

@Data
@NoArgsConstructor
@MappedSuperclass
public abstract class BaseEntity {

    @Setter(value = AccessLevel.PRIVATE)
    @Temporal(TemporalType.TIMESTAMP)
    @CreationTimestamp
    @Column(name = "create_time", nullable = false)
    private Date createTime;

    @Setter(value = AccessLevel.PRIVATE)
    @Temporal(TemporalType.TIMESTAMP)
    @UpdateTimestamp
    @Column(name = "update_time", nullable = false)
    private Date updateTime;

    @Getter(value = AccessLevel.PRIVATE)
    @Setter(value = AccessLevel.PRIVATE)
    @Column(name = constant.ISDELETE_COLUMN_NAME, nullable = false)
    private Boolean isDelete = false;
}
View Code

2、通过@Where、@SQLDelete、@SQLDeleteALL 三个注解修饰对应数据库表的Entity来实现逻辑删除: 

@SQLDelete(sql = "update " + StudentEntity.tableName + " set " + constant.ISDELETE_COLUMN_NAME + " =true where sid=?") // 对非nativeQuery 旳delete起作用,包括形如deleteByName等,下同
@SQLDeleteAll(sql = "update " + StudentEntity.tableName + " set " + constant.ISDELETE_COLUMN_NAME + " =true where sid=?")
@Where(clause = constant.ISDELETE_COLUMN_NAME + " = false") // 对非nativeQuery的select起作用(如count、非nativeQuery的String myGetNameByName等,前者本质上也是select)
@Data
@Entity
@Table(name = StudentEntity.tableName)
public class StudentEntity extends BaseEntity {
    public static final String tableName = "student";
    @Id
    @Column(name = "sid", length = 36)
    private String sId;

    @Column(name = "name", length = 36)
    private String name;

    @Column(name = "age")
    private Integer age;

}
View Code

需要注意的是:

  • @Where会自动在查询语句后拼接@Where中指定的条件;该注解对所有的非nativeQuery的查询其作用,如getByName、count、自己写查询语句但非nativeQuery的myGetByName等。
  • @SQLDelete会自动将删除语句替换为@SQLDelete中指定的sql操作;该注解对所有非nativeQuery的删除操作其作用,如delete(StudenEntity entity)、deleteBySId、deleteByName等,但由于指定的sql操作中条件不得不写死,所以要按期望起作用的话@SQLDelete中的sql操作应以Entity的主键为条件,且删除语句按上述前两者写法写(对于delete(StudenEntity entity)会自动取entity的主键给sid),而不能用第三种(会将name参数值传给sid)

 

更多参考资料:

Spring Data JPA 官方文档

使用SPA简化开发-IBM

 

转载于:https://www.cnblogs.com/z-sm/p/9176636.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值