「拥抱开源」从表设计到 JPA 实现
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 Set<Order> getOrders() {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<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 小结
今天先写到这里。
夜深了,让我们下周再见。?
这个周末,又一次成功“强迫”自己学习。
感谢各位小伙伴的阅读,这里是一个技术人的学习与分享。
- 【nginx启动】 97 Address family not supported by protocol
- jfinal 内置的handler功能
- JS 对指定iframe 全屏操作
- 【jfinal修仙系列】扩展CacheInterceptor支持Redis缓存
- 基于Redis的定时任务
- 【jfinal】扩展JFIANL 支持加载jar包中SQL模板
- 【jfinal修仙系列】扩展ShiroCacheManager 支持Redis缓存
- 【springboot】 springboot 整合mybatis-plus
- jfinal-swagger让你的应用接口更加简单
- 【springboot】 spring session 分布式会话共享
- 基于jfinal Template的Shiro 标签
- 基于Spring Cloud 少量配置完成单点登录开发
- Spring 必知概念(一)
- 如何在EHAB(EntLib)中定义”细粒度”异常策略?
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法