状态

JPA实现为Hibernate,具有三种状态:

  1. 瞬时态(Transient) 未于数据库进行关联状态
  2. 持久态(Persistent) 与数据库关联状态
  3. 脱管态(Detached) 脱离了Hibernate关联的状态

事务与缓存

事务注解 @Transcation,定义在 service 层或者 dao 层。对于 更新和删除操作必须声明事务。

默认情况下,Spring Data JPA 实现的方法都是使用事务的。针对查询类型的方法,其等价于 @Transactional(readOnly=true);

增删改类型的方法,等价于 @Transactional。可以看出,除了将查询的方法设为只读事务外,其他事务属性均采用默认值。

如果用户觉得有必要,可以在接口方法上使用 @Transactional 显式指定事务属性,该值覆盖 Spring Data JPA 提供的默认值。

同时,开发者也可以在业务层方法上使用 @Transactional 指定事务属性,这主要针对一个业务层方法多次调用持久层方法的情况。

持久层的事务会根据设置的事务传播行为来决定是挂起业务层事务还是加入业务层的事务。

缓存注解 @Cacheable 为 Hibernate 的二级缓存注解,需要开启并指定第三方缓存。

往往不如 spring 自身缓存管理好用,而且对于 hibernate 来说需要使用 @QueryHint 声明查询才行。

JPQL

基础语句

只有 select update delete 三种语句操作

update 和 delete 语句必须使用 @Modifying 注解

是否执行 insert 操作基于操作对象是否包含实体主键,如果实体具有有效的主键执行 update

否则执行 insert。

update 会更新有修改过的实体属性,并返回更新或插入后的实体对象。

注意:null 也被算做属性值之一,故更新时需要注意空值

@Modifying 注解开启自动清理缓存 @Modifying(clearAutomatically = true) 可有效清理 update 操作产生的脏数据。

save()方法与 update 语句的区别是 update 可在语句中指定更新字段,而 save() 方法则更新整个对象中所有发生过修改的字段,

尤其会导致 null 更新问题。使用save () 方法建议先查询对象 再修改对象的对应属性 最后执行 save()。

delete 返回 integer 或 int 数值,表示受影响的对象数量

select 则和 SQL 中的 select 差不多,需要注意查询时要使用首字母大写,表示对象

不能使用 * 进行查询所有属性,当不指定select 字段时,相当于 * 查询,例如:

JPQL from User u where u.id = 1 等效于 select * from user u where u.id = 1

对象通常使用 as 指定别名用于在查询条件中使用,也可用于指定投影属性别名,大部分情况可省略。

对于表之间的关联关系必须在实体中声明,单纯使用关联语句并不生效。

JPQL语句中的关联关系共 inner join join left join 三种,没有 on 。也没有 right join

join 默认为 inner join

关联方式为:join 主表别名.从表 从表别名(可省略,当使用从表字段作为查询条件时需指定)例如:

无从表查询条件 from User u join u.dept

有从表查询条件 from User u join u.dept p where p.id = :id 或者

select 和 update 所返回的结果对象,仍然为持久态对象。

当执行flush()方法时,对结果对象的修改会被同步到数据库中。

使用 spring 自带的 Bean 复制工具类 BeanUtitls 的 Copy 方法可脱离持久态。

对集合类应使用深拷贝才能脱离Hibernate的管理。

JPA 支持基础聚合函数以及排序

语句参数

JPA 支持传递普通参数和对象参数,支持 ?:参数别名 两种占位符。

? 为参数顺序占位符,例如在语句中 ?1 表示第一个参数,一旦参数顺序发生变化会出现问题,故不推荐使用。

:参数别名 为参数别名占位符 参数注解前必须添加 @Param 注解。普通参数和对象参数都必须使用该注解

示例:

@Query("from User u join u.dept p where p.id = :userid")
User queryUser(@Param ("userid") Long id);

对于对象参数,需要使用spring的SPEL表达式::#{``#参数对象别名.参数对象属性}

示例:

@Query("from User u join u.dept p where p.id = :#{#ParamUser.userid}")
User queryUser(@Param ("ParamUser") User user);

投影

投影主要解决返回的实体属性过多或需要使用自定义属性的情景

投影不同于实体,没有Hibernate的状态,修改投影不会影响数据库。

投影可以在原生 SQL 和 JPQL 中使用。

接口投影

普通接口,内声明属性的get方法。

接口投影支持嵌套投影,类投影不支持。

例如一个具有name属性的User对象:


public interface User{

getName();

}

嵌套投影中获取嵌套投影中的get方法的属性名必须为实体中关联属性名

下面示例中的 getPermissionList 方法,PermissionList 是 User 中关联实体的属性:

@Data
@Entiy
public Class User{

private String name;

@OneToMany
private List<Permission> permissionList;

}

嵌套投影示例:


public interface User{

getName();

List<Permission> getPermissionList();

}

public interface Permission{

getPermission();

}

注意get必须小写

错误示例:

getUserName(); getuserName();

类投影(DTO)

Bean类,必须提供包含全部属性的构造方法,可使用 Lombok 的 @Value 注解简化。


public class User {

private String name;

public User(String name) {
this.name = name;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

多表关联对应关系

首先 @OneToMany 的含义 @当前实体To关联实体

所以 @OneToMany 关联实体应为集合,@ManyToOne 不能为属性

默认维护关系端都为 Many 一侧,由于实体只能维护自身表结构,所以 @OneToMany 在不指定 @JoinColumn 时

将使用额外的关系表来维护关系。

对于 @OneToOne 则由 mappedBy 指定关系方来确定,默认单向关系维护方在自身。

在使用延迟加载的实体上添加 @JsonIgnoreProperties("hibernateLazyInitializer") 格式化注解可消除 hibernate 生成的延迟加载标识

单向一对一

@OneToOne

标注在关联属性对象上,表明一对一关联关系

主要参数:

  1. targetEntity 属性标时关联的实体类,默认为当前标注的实体。

  2. cascade 属性表示与此实体一对一关联的实体的级联类型。级联类型指对当前实体进行操作时关联实体执行的操作策略。

其中,在定义关系时,经常会考虑是否要定义cascade属性的问题。

若不定义,则对关系表不会产生影响,默认没有定义;

未定义级联类型又操作了关联实体将会导致异常

五种级联类型:

  • CasacdeType.PERSIST,级联新建

  • CascadeTypE.REMOVE,级联删除

  • CascadeType.REFRESH,级联刷新

  • CascadeType.MERGE,级联更新

  • CascadeType.ALL,表示选择上述四种。

  1. fetch 属性表示该级联实体的加载方式,有 LAZY 和 EAGER 两种方式
  • FetchType.LAZY:延迟加载,关联实体不会立即从数据库中加载

默认为 FetchType.EAGER 延迟加载的实体一旦被调用将会立即加载,延迟加载会导致 N+1 问题

使用 BeanUtitls 的 copy 方法的第三个参数 可忽略指定属性,可防止延迟加载失效

  • FetchType.EAGER:立即加载,关联实体立即从数据库中加载
  1. optional 属性表示关联的实体是否允许为 null,默认为true,表示可以为 null。

当 optional 为 false 时 关联实体一旦为 null 则整个查询结果为 null,生成的查询语句为 inner join

当 optional 为 true 时 即使关联实体为 null 仍能获取查询结果,只有关联属性为 null,生成的查询语句为 left join

  1. mappedBy 属性用于双向关联实体时,标注在不需要保存关联关系的实体中,值为保存实体关系的实体属性名,非表名。默认为空,不指定则关联的双方都会生成关联外键。

  2. orphanRemoval 属性用于在关联实体中自动清理无效的从表实体,且只能使用在 one 一侧。

在关联关系中单向级联中,将主表实体中的从表实体设置为 null 并不能删除从表实体,仅仅是将维护关系字段设置为 null

当 orphanRemoval 为 ture 时,就可以自动删除关系字段为 null 的从表实体了

@JoinColumn

标注在关联属性对象上,标明维护关联关系表字段的相关设置

  1. name 属性用来标记表中自动生成的关联字段的名称(通常为外键)

如果不设置,则默认为 关联表_关联表主键

  1. referencedColumnName 属性用于标明从表的关联字段,默认使用从表的主键用来作外键。

  2. foreignKey 属性用于外键建立策略例如: foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT)

表示不建立外键,该属性仅影响外键是否生成,不影响关系字段生成

  • ConstraintMode.CONSTRAINT,建立外键

  • ConstraintMode.NO_CONSTRAINT,不建立外键

  • ConstraintMode.PROVIDER_DEFAULT,默认配置

  1. insertable 和 updatable 可插入可更新,默认为 true。两个属性同时设置为 false 一般用在 @ManyToOne 中避免字段重复映射

  2. unique 唯一约束 默认为 false 默认不唯一

  3. nullable 空值约束 默认ture 默认可为空

@JoinColumns

可传入多个 @JoinColumn ,和一个 foreignKey 指定复杂的维护关系,示例:

@JoinColumns(value = { @JoinColumn(name = "A"),@JoinColumn(name = "B") },foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))

双向一对一

@OneToOne

大部分注解同一对一,注意需要指定关系维护实体,配置 mappedBy 指定关系维护方

在双向一对一中,延迟加载在不维护关系的实体一方不生效 解决办法为更换成 @OneToMany 影响最小

或者不声明 mappedBy 主从实体都维护关联关系,此缺点为级联操作时将影响两个表。

单向一对多

@OneToMany

属性与@OneToOne相同

默认为延迟加载

需要标注在集合属性上且必须指定泛型类型

如果没有使用 @JoinColumn 的 name 属性指定本表维护关系的字段,JPA会生成额外的中间表来维护关系。

清空关联集合时应get集合实体的 clear() 方法,由于放入新的空集合不受Hibernate管理将导致异常

双向一对多

@OneToMany 声明在多端,实体属性必须为集合

@ManyToOne 声明在一端,实体属性不可为集合,没有 mapperby 属性

@ManyToOne 中要使用 FetchType.LAZY,否则会导致性能降低。

属性与 @OneToOne 相同

需要指定关系维护实体,配置 mappedBy 指定关系维护方

多对多

@JoinTable

  1. name 属性为连接两个表的表名称,若不指定,则使用默认的表名称 表1_表2

  2. joinColumn 属性表示,在保存关系的表中,所保存关联关系的外键的字段,并配合 @JoinColumn 标记使用

  3. inverseJoinColumn 属性与 joinColumn 类似,它保存的是保存关系的另外一个外键字段

  4. atalog 和 schema 属性表示实体指定点目录名称或数据库名称

  5. uniqueConstraints 属性表示该实体所关联的唯一约束条件,一个实体可以有多个唯一约束条件,默认没有约束

其他注意事项

@Query 中识别 SQL IFNULL 的解决办法

使用 coalesce() 函数

@Embedded @Embeddable 注解的使用

无限递归错误

在使用Json来序列化对象时,会产生无限递归(Infinite recursion)的错误。这里有2个解决方法:

  1. 在@ManyToOne下面使用 @JsonIgnore 。

  2. 在@OneToMany下面使用 @JsonManagedReference ,在 @ManyToOne 下面使用 @JsonBackReference。

@JsonBackReference 和 @JsonManagedReference:

@JsonBackReference 标注的属性在序列化(serialization)时,会被忽略。

@JsonManagedReference 标注的属性则会被序列化。在序列化时,@JsonBackReference 的作用相当于@JsonIgnore,

此时可以没有@JsonManagedReference。但在反序列化(deserialization)时,如果没有@JsonManagedReference,

则不会自动注入@JsonBackReference标注的属性;如果有@JsonManagedReference,则会自动注入@JsonBackReference标注的属性。

@JsonIgnore:直接忽略某个属性,以断开无限递归,序列化或反序列化均忽略。当然如果标注在get、set方法中,则可以分开控制,序列化对应的是get方法,反序列化对应的是set方法。