写点什么

整理了一些 JPA 常用注解

作者:yombo
  • 2021 年 12 月 30 日
  • 本文字数:2325 字

    阅读完需:约 8 分钟

整理了一些JPA常用注解

一直在用 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),        })@Entitypublic 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.javapublic class BaseEntity {    private Integer id;        //...other properties}
复制代码


发布于: 刚刚
用户头像

yombo

关注

还未添加个人签名 2017.12.20 加入

还未添加个人简介

评论

发布
暂无评论
整理了一些JPA常用注解