首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >「拥抱开源」从表设计到 JPA 实现

「拥抱开源」从表设计到 JPA 实现

作者头像
FoamValue
发布2020-09-01 16:02:36
1.6K0
发布2020-09-01 16:02:36
举报
文章被收录于专栏:FoamValueFoamValue

long may the sunshine.

今天的我拿起键盘就是猛敲代码。

果然,十分钟后各种 JPA 报错开始了。跟新手党一样,看到一个错误就解决一个,没有好好思考为什么会出现这样的错误。

于是乎,遇到一个解决一个,解决一个又遇到一个,经过数十个报错的来回起伏。

敏锐的我发现苗头有些不对。全靠脑细胞的记忆,以及开始对第一个错误的解决过程开始模糊不清了。

最后,我采用了《数据库 ER 图》的方式,重新开始分析、梳理。

也就是本文的初衷。

当我写到最后的时候。我的 Junit 用例全部跑通了。赞。

以下是正文,稍微有点。。。。。。。。。。。。。长。


01 数据库 ER 图

ER 图概念

  1. 实体 entity:用矩形表示,数据模型中的数据对象。
  2. 属性 attribute:用椭圆形表示,数据对象所具有的属性(所具有的列)。其中唯一属性 unique attribute,用下划线表示。
  3. 关系 relationshop:用菱形表示,数据对象与数据对象之间的联系。

假设有两个实体集 A、B,它们有以下三种关联关系。

  1. 一对一 1:1
    1. A 的每个实体至多与 B 的一个实体有关系。
    2. B 的每个实体至多与 A 的一个实体有关系。
    3. 满足以上两点,即 A 与 B 的关系是一对一。
  2. 一对多 1:N
    1. A 的每个实体至少与 B 的 N(N>0)个实体有关系。
    2. B 的每个实体至多与 A 的一个实体有关系。
    3. 满足以上两点,即 A 与 B 的关系是一对多,B 与 A 的关系是多对一。
  3. 多对多 M:N
    1. A 的每个实体至少与 B 的 M(M>0)个实体有关系。
    2. B 的每个实体至少与 A 的 N(N>0)个实体有关系。
    3. 满足以上两点,即 A 与 B 的关系是多对多。

02 JPA 关联

在 JPA 中分别使用 @OneToOne、@OneToMany、@ManyToOne、@ManyToMany 注解表示一对一、一对多,多对一、多对多三种关联关系。

OneToOne

  1. targetEntity,作为关联目标的实体类。
  2. cascade,必须级联到关联目标的操作。
    1. ALL,级联所有操作。
    2. PERSIST,级联保存操作。
    3. MERGE,级联修改操作。
    4. REMOVE,级联删除操作。
    5. REFRESH,级联刷新操作。
    6. DETACH,级联分离操作。(2.0 版本开始支持)
  3. fetch,关联是延迟加载还是必须立刻获取。
  4. optional,关联是否为可选。
  5. mappedBy,拥有关系的字段。仅在关联的反侧(非所有权)指定此元素。
  6. 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 Set<Order> getOrders() {return orders;}

03 分析

图 A - ER 图

本案例有四张数据库表,分别为导购员、商品数据、订单主数据,以及订单明细数据。(如上图所示)

导购员、商品数据是基础数据表,即不主动关联其他的实体集。

商品主数据,包含两种关联关系。

  1. 与导购员之间的关系是多对一。即 @ManyToOne,注意这里只需要级联刷新操作即可。
  2. 与订单明细数据的关系是一对多。即@OneToMany,注意这里需要级联保存、修改、删除、刷新所有的操作。

商品明细数据,也包含两种关联关系。

  1. 与商品数据之间的关系是多对一。即 @ManyToOne,注意这里只需要级联刷新操作即可。
  2. 与订单主数据的关系是多对一。即@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<OscOrderItemEntity> 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 小结

今天先写到这里。

夜深了,让我们下周再见。?

这个周末,又一次成功“强迫”自己学习。

感谢各位小伙伴的阅读,这里是一个技术人的学习与分享。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-08-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 FoamValue 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档