long may the sunshine.
今天的我拿起键盘就是猛敲代码。
果然,十分钟后各种 JPA 报错开始了。跟新手党一样,看到一个错误就解决一个,没有好好思考为什么会出现这样的错误。
于是乎,遇到一个解决一个,解决一个又遇到一个,经过数十个报错的来回起伏。
敏锐的我发现苗头有些不对。全靠脑细胞的记忆,以及开始对第一个错误的解决过程开始模糊不清了。
最后,我采用了《数据库 ER 图》的方式,重新开始分析、梳理。
也就是本文的初衷。
当我写到最后的时候。我的 Junit 用例全部跑通了。赞。
以下是正文,稍微有点。。。。。。。。。。。。。长。
01 数据库 ER 图
ER 图概念
实体 entity:用矩形表示,数据模型中的数据对象。
属性 attribute:用椭圆形表示,数据对象所具有的属性(所具有的列)。其中唯一属性 unique attribute,用下划线表示。
关系 relationshop:用菱形表示,数据对象与数据对象之间的联系。
假设有两个实体集 A、B,它们有以下三种关联关系。
一对一 1:1
A 的每个实体至多与 B 的一个实体有关系。
B 的每个实体至多与 A 的一个实体有关系。
满足以上两点,即 A 与 B 的关系是一对一。
一对多 1:N
A 的每个实体至少与 B 的 N(N>0)个实体有关系。
B 的每个实体至多与 A 的一个实体有关系。
满足以上两点,即 A 与 B 的关系是一对多,B 与 A 的关系是多对一。
多对多 M:N
A 的每个实体至少与 B 的 M(M>0)个实体有关系。
B 的每个实体至少与 A 的 N(N>0)个实体有关系。
满足以上两点,即 A 与 B 的关系是多对多。
02 JPA 关联
在 JPA 中分别使用 @OneToOne、@OneToMany、@ManyToOne、@ManyToMany 注解表示一对一、一对多,多对一、多对多三种关联关系。
OneToOne
targetEntity,作为关联目标的实体类。
cascade,必须级联到关联目标的操作。
ALL,级联所有操作。
PERSIST,级联保存操作。
MERGE,级联修改操作。
REMOVE,级联删除操作。
REFRESH,级联刷新操作。
DETACH,级联分离操作。(2.0 版本开始支持)
fetch,关联是延迟加载还是必须立刻获取。
optional,关联是否为可选。
mappedBy,拥有关系的字段。仅在关联的反侧(非所有权)指定此元素。
orphanRemoval,是否将删除操作应用于已从关系中删除的实体,以及是否将删除操作级联到那些实体。
OneToMany
targetEntity、cascade、fetch、mappedBy、orphanRemoval
ManyToOne
targetEntity、cascade、fetch、orphanRemoval
ManyToMany
targetEntity、cascade、fetch、mappedBy
在以上关联注解的使用过程中,还需要 @JoinColumn 指定实体关联、元素集合的列。
例如:@ManyToOne@JoinColumn(name="ADDR_ID")public Address getAddress() { return address; }@OneToMany@JoinColumn(name="CUST_ID")public SetgetOrders() {return orders;}
03 分析
图 A - ER 图
本案例有四张数据库表,分别为导购员、商品数据、订单主数据,以及订单明细数据。(如上图所示)
导购员、商品数据是基础数据表,即不主动关联其他的实体集。
商品主数据,包含两种关联关系。
与导购员之间的关系是多对一。即 @ManyToOne,注意这里只需要级联刷新操作即可。
与订单明细数据的关系是一对多。即@OneToMany,注意这里需要级联保存、修改、删除、刷新所有的操作。
商品明细数据,也包含两种关联关系。
与商品数据之间的关系是多对一。即 @ManyToOne,注意这里只需要级联刷新操作即可。
与订单主数据的关系是多对一。即@ManyToOne,注意这里需要级联保存、修改、删除、刷新所有的操作。
04 示例代码
导购数据 UscGuideEntity
package cn.live.opos.center.entity;// 省略 import/** * usc_guide. * * @author chenxinjie * @date 2020-08-01 */@Entity@Table(name = "usc_guide", uniqueConstraints = { @UniqueConstraint(columnNames = "no") })public class UscGuideEntity implements Serializable { private static final long serialVersionUID = -5648617800765002770L; @Id @GeneratedValue(strategy = GenerationType.AUTO, generator = "jpa-uuid") @GenericGenerator(name = "jpa-uuid", strategy = "org.hibernate.id.UUIDGenerator") @Column(name = "id", length = 36) private String id; @Column(name = "no", length = 20, nullable = false) private String no; @Column(name = "name", length = 40, nullable = false) private String name; @Column(name = "gender", columnDefinition = "int default 0", nullable = false) private int gender; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @Temporal(TemporalType.TIMESTAMP) @Column(name = "ts", columnDefinition = "timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP()", nullable = false) private Date ts; // 省略 get/set 方法}
商品数据 PscSkuEntity
package cn.live.opos.center.entity;// 省略 import@Entity@Table(name = "psc_sku", uniqueConstraints = { @UniqueConstraint(columnNames = "sku") })public class PscSkuEntity implements Serializable { private static final long serialVersionUID = 8904367725209990433L; @Id @GeneratedValue(strategy = GenerationType.AUTO, generator = "jpa-uuid") @GenericGenerator(name = "jpa-uuid", strategy = "org.hibernate.id.UUIDGenerator") @Column(name = "id", length = 36) private String id; @Column(name = "sku", length = 50, nullable = false) private String sku; @Column(name = "product_no", length = 40, nullable = false) private String productNo; @Column(name = "product_name", length = 100, nullable = false) private String productName; @Column(name = "color_no", precision = 4, scale = 0, nullable = false) private int colorNo; @Column(name = "color_name", nullable = false) private String colorName; @Column(name = "size_no", precision = 4, scale = 0, nullable = false) private int sizeNo; @Column(name = "size_name", nullable = false) private String sizeName; @Column(name = "tag_price", precision = 10, scale = 0, nullable = false) private int tagPrice; @Column(name = "retail_price", precision = 10, scale = 0, nullable = false) private int retailPrice; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @Temporal(TemporalType.TIMESTAMP) @Column(name = "ts", columnDefinition = "timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP()", nullable = false) private Date ts; // 省略 get/set 方法}
订单主数据 OscOrderEntity
package cn.live.opos.center.entity;// 省略 import@Entity@EntityListeners(AuditingEntityListener.class)@Table(name = "osc_order", uniqueConstraints = { @UniqueConstraint(columnNames = "order_no") })public class OscOrderEntity implements Serializable { private static final long serialVersionUID = -4409502876337140593L; @Id @GeneratedValue(strategy = GenerationType.AUTO, generator = "jpa-uuid") @GenericGenerator(name = "jpa-uuid", strategy = "org.hibernate.id.UUIDGenerator") @Column(name = "id", length = 36) private String id; @Column(name = "order_no", length = 40, nullable = false) private String orderNo; @CreatedDate @JsonFormat(pattern = "yyyy-MM-dd") @Temporal(TemporalType.DATE) @Column(name = "order_date", nullable = false) private Date orderDate; /** * 1: sell of goods. 2: return of goods. */ @Column(name = "order_type", nullable = false) private int orderType; @Column(name = "order_status", nullable = false) private int orderStatus; @Column(name = "num", precision = 5, scale = 0, nullable = false) private int num; @Column(name = "total", precision = 10, scale = 0, nullable = false) private int total; @Column(name = "guide_no", length = 20, nullable = false) private String guideNo; @LastModifiedDate @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @Temporal(TemporalType.TIMESTAMP) @Column(name = "ts", columnDefinition = "timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP()", nullable = false) private Date ts; @OneToMany(targetEntity = OscOrderItemEntity.class, cascade = CascadeType.ALL, fetch = FetchType.EAGER) @JoinColumn(name = "order_no", referencedColumnName = "order_no", insertable = false, updatable = false) private List orderItems; @ManyToOne(targetEntity = UscGuideEntity.class, cascade = CascadeType.REFRESH) @JoinColumn(name = "guide_no", referencedColumnName = "no", insertable = false, updatable = false) private UscGuideEntity guideEntity; // 省略 get/set 方法}
订单明细数据 OscOrderItemEntity
package cn.live.opos.center.entity;// 省略 import@Entity@EntityListeners(AuditingEntityListener.class)@Table(name = "osc_order_item", uniqueConstraints = { @UniqueConstraint(columnNames = { "order_no", "sku" }) })public class OscOrderItemEntity implements Serializable { private static final long serialVersionUID = -7331381906879927968L; @Id @GeneratedValue(strategy = GenerationType.AUTO, generator = "jpa-uuid") @GenericGenerator(name = "jpa-uuid", strategy = "org.hibernate.id.UUIDGenerator") @Column(name = "id", length = 36) private String id; @Column(name = "order_no", length = 40, nullable = false) private String orderNo; @Column(name = "sku", length = 50, nullable = false) private String sku; @Column(name = "num", precision = 5, scale = 0, nullable = false) private int num; @Column(name = "tag_price", precision = 10, scale = 0, nullable = false) private int tagPrice; @Column(name = "retail_price", precision = 10, scale = 0, nullable = false) private int retailPrice; @Column(name = "total", precision = 10, scale = 0, nullable = false) private int total; @LastModifiedDate @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @Temporal(TemporalType.TIMESTAMP) @Column(name = "ts", columnDefinition = "timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP()", nullable = false) private Date ts; @ManyToOne(targetEntity = OscOrderEntity.class, cascade = CascadeType.ALL) @JoinColumn(name = "order_no", referencedColumnName = "order_no", insertable = false, updatable = false) private OscOrderEntity orderEntity; @ManyToOne(targetEntity = PscSkuEntity.class, cascade = CascadeType.REFRESH) @JoinColumn(name = "sku", referencedColumnName = "sku", insertable = false, updatable = false) private PscSkuEntity skuEntity; // 省略 get/set 方法}
05 效果
使用 JPA 查询一个订单主数据,JPA 会自动将配置好的其他表的数据实体自动查询出来。
也就是,省略了查询导购员、订单明细数据、商品数据三条 SQL 语句。
PS. 完整示例代码,见 https://github.com/FoamValue/oPos.git
06 小结
今天先写到这里。
夜深了,让我们下周再见。?
这个周末,又一次成功“强迫”自己学习。
感谢各位小伙伴的阅读,这里是一个技术人的学习与分享。