始终在用 myBatis,尽管有 Generator,但还是感觉写 SQL 是一件很麻烦的事件。而且从实际来看,咱们总是习惯于,间接依照接口所需格局,去编写 SQL。这样搞的后端很没有存在感,好像就是前端的数据库代理工具而已。加之三层架构,Transaction script 式的代码,后端货真价实的搬砖,货真价实的 CRUD,何来的业务思考。
我抉择 JPA,是想换思路。当拿到一个需要时,不是先思考其数据库该如何设计,而是如何应用代码可能精确表白业务用意,最终再转化为数据库设计。说到底,数据库只是一种存储计划。面对业务时,咱们或者应该疏忽底层存储如何,配合 JPA,咱们更能不便抉择存储计划,关系型数据库或者是非关系型数据库。并且同时不必去批改 DAO 层代码。这一点,是 myBatis 所不能的。
本文没有深刻介绍 JPA 原理,或者是 Hibernate 原理。只是我这一段时间应用 JPA 遇到的问题和 JPA 的解决形式。
一些乏味的配置
enable_lazy_load_no_trans
某次当我应用 Springboot 的异步工作时(@Async 标记的办法), 获取某个实体的 children,或者间接应用 JpaRepository 进行操作,程序总是抛出异样,具体是哪个异样我记不清了。大略的意思就是无奈从以后 session 中获取到无效数据库连贯,也有可能是以后线程的 hibernate session 有效之类的提醒吧。解决这个问题也很简略,增加一个配置spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
default_constraint_mode
默认状况下,JPA 创立数据库表,表之间的束缚它都会创立一个外键。这其实没错儿,但有时候波及到表构造变更,或者数据不残缺的时候,就很让人焦躁。有没有什么方法能够只建设逻辑外键呢?据说有两种形式
第一种形式
@JoinColumn 注解 foreignKey 设置 ConstraintMode=NO_CONSTRAINT
然而须要每处都显式设置,太麻烦;
b. 第二种形式,全局配置。但如同对 JPA 版本有要求,最低反对到哪个版本没细究,集体我的项目 Springboot 版本是 2.4.3spring.jpa.properties.hibernate.hbm2ddl.default_constraint_mode=NO_CONSTRAINT
一些乏味的注解
@LazyCollection
比方我有个订单业务,前端显示的时候须要显示订单所含商品数量。我该怎么办?两种方法:
间接把所有订单商品查到一个汇合中,而后计算汇合的 size。毛病就是得查一次所有商品;@LazyCollection(LazyCollectionOption.EXTRA)
用这个注解标记在实体关系中 Set 属性上@Formula("select count(*)")
,这个注解到某个办法上,然而我没有胜利;没工夫细究,就抉择了第二种形式
@DynamicInsert & @DynamicUpdate
@DynamicInsert
属性:设置为 true,示意 insert 对象的时候,生成动静的 insert 语句,如果这个字段的值是 null 就不会退出到 insert 语句中,默认 false。比方心愿数据库插入日期或工夫戳字段时,在对象字段为空定的状况下,表字段能主动填写以后的 sysdate。
@DynamicUpdate
属性:设置为 true,示意 update 对象的时候,生成动静的 update 语句,如果这个字段的值是 null 就不会被退出到 update 语句中,默认 false。比方只想更新某个属性,然而却把整个属性都更改了,这并不是咱们心愿的后果,咱们心愿的后果是:我更改了哪写字段,只有更新我批改的字段就够了。
@Formula
有时候须要计算总价这里的数据,比方某个商品实体,有单价,有数量;但它的总价我并不想存入到数据库中,而是偏向基于单价 * 数量的形式动静计算。
一种常见的形式是,提供一个 getter 办法,办法返回计算结果。一种举荐的形式,则是通过 @Formula 注解实现
private long grossIncome;
private int taxInPercents;
@Formula("grossIncome * taxInPercents / 100")
private long tax;
@Formula 还能够子查问、调用数据库函数、存储过程等。如果咱们写 JQL,Hibernate 可能帮忙咱们转换成 SQL,从而实现定义 SQL 查问后果。
@Where
如果须要对某个实体全局过滤,比方假如在一个逻辑删除业务背景下,如果只想查问那些被标记为’deleted‘的数据,@Where 能够全局过滤,标记在 Entity Class 上即可。如果对于某个 OneToMany 汇合要过滤,也能够应用 @Where 标记。然而 @Where 局限性是,条件是固定的,当须要动静执行条件筛选时,则须要借助 @Filter。
@Any & @AnyMetaDef
绝对于 @ManyToOne、@OneToOne,@Any 示意属性对应的类型是任意的,这是从字面的了解。
想想一个场景,假如我有个审批记录,这条审批记录可能来自于 A、B、C 三个不同的实体,但我又只想用一张表来保留审批记录,同时可能保障每条审批记录能找到其对应的 A、或 B、或 C。见以下代码
@AnyMetaDef(name = "InOutBoundSourceObjectDef", metaType = "string", idType = "int",
metaValues = {@MetaValue(value = "PURCHASE", targetEntity = A.class),
@MetaValue(value = "ALLOCATION", targetEntity = B.class),
@MetaValue(value = "OTHER", targetEntity = C.class),
})
@Entity
public class InOutBoundApprove extends BaseEntity {
// 对应到数据库,sourceObjectType 存储的值,A、B、C,具体哪个值,取决于 sourceObject 的类型
@Column(name = "source_object_type", insertable = false, updatable = false)
private String sourceObjectType;
// 对应到数据库,存储的是外键 id,即 A、B、C 的 id。// 同时要指定 Def,是 @AnyMetaDef 的 name
//@metaColumn 指定通过哪个字段来判断 sourceObject 的 class 类型
@Any(fetch = FetchType.LAZY, metaDef = "InOutBoundSourceObjectDef", metaColumn = @Column(name = "source_object_type"))
@JoinColumn(name = "source_object_id")
private BaseEntity sourceObject;
// BaseEntity.java
public class BaseEntity {
private Integer id;
//...other properties
}