在上一篇文章里我为了编写印花税功能,实际上又新开了一个stamp
分支,现在可以把这个分支也合并进主分支,然后来开始正式编写业务了。
整体数据结构
这个由于有了之前的Django
项目作为基础,还是比较容易的设计出来了:
其实本质上一切网站都是内容管理系统,我们也不例外,就是要建立一个这样的体系。
这里相比我原来的Django
项目的设计变更点,就是在合同与预算的对应关系上,采取了更灵活的多对多关系,而不是一对一关系,这是为了后边如果出现合同对应多个预算条目的问题,所以先在基础结构上做好准备。
这里的箭头方向都是从主控表指向被控表,即Hibernate
的映射关系所在的表。
经过考虑,我觉得系统比较简单,还不用太多考虑性能,还是决定采取外键设置,暂时不采取业务代码来控制。只不过在创建详细表的时候,还是要合理的分离每个表最关键的数据和可以暂时不取的数据。
设计数据表 - PO
类的映射关系
借这个机会我就要重新思考一下映射关系。
在之前的User
-Role
-UserInfo
中,我是把外键和控制关系都放在了User
表中。这里的特殊之处在于,由于项目和预算是一对多关系,一般来说外键都在多的那一方,但是我又想通过项目查询所有预算,所以很显然,也要进行一个反向映射,其他也是类似,都要进行双向映射。
Project
类基础信息
现在所写的功能是项目管理,我们就把这个功能命名为pms
,也是所有子包的名称。我们逐渐把所有数据表的骨架搭起来,。首先是Project
类,放在cc.conyli.fms.dataobject.pms.entity
包下:
@Entity
@Table(name = "project")
public class Project implements Serializable {
private static final long serialVersionUID = 7L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Integer id;
@NotEmpty(message = "项目名称不能为空")
@Length(max = 255, message = "长度不能超过255个字符")
@Column(name = "name", nullable = false)
private String projectName;
@NotEmpty(message = "地址不能为空")
@Length(max = 255, message = "长度不能超过255个字符")
@Column(name = "address", nullable = false)
private String projectAddress;
@NotEmpty(message = "项目经理名称不能为空")
@Length(max = 10, message = "长度不能超过255个字符")
@Column(name = "manager", nullable = false, length = 10)
private String projectManager;
@NotEmpty(message = "项目描述不能为空")
@Length(max = 1000, message = "长度不能超过1000个字符")
@Column(name = "description", nullable = false, length = 1000)
private String projectDescription;
@Temporal(TemporalType.DATE)
@Column(name = "start_time")
private Date startTime;
@Temporal(TemporalType.DATE)
@Column(name = "complete_time")
private Date completeTime;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "create_time", updatable = false)
@CreationTimestamp
private Date createTime;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "update_time")
@UpdateTimestamp
protected Date updateTime;
}
省略了其中的其他方法。项目由于数据不多,而且经常要展示,就不再分另外一个表来映射了。
接下来是预算。
Budget
类基础信息
这里也先把预算类的骨架搭好,根据我最新的预算表来搭建:
@Entity
@Table(name = "budget")
public class Budget implements Serializable {
private static final long serialVersionUID = 8L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Integer id;
@NotEmpty(message = "预算条目名称不能为空")
@Length(max = 255, message = "长度不能超过255个字符")
@Column(name = "name", nullable = false)
private String budgetName;
@NotNull(message = "含税收入不能为空")
@Digits(integer = 14, fraction = 2)
private BigDecimal grossIncome;
@NotNull(message = "不含税收入不能为空")
@Digits(integer = 14, fraction = 2)
private BigDecimal netIncome;
@NotNull(message = "含税成本不能为空")
@Digits(integer = 14, fraction = 2)
private BigDecimal grossCost;
@NotNull(message = "不含税成本不能为空")
@Digits(integer = 14, fraction = 2)
private BigDecimal netCost;
@NotNull(message = "费用不能为空")
@Digits(integer = 14, fraction = 2)
private BigDecimal expenditure;
@Column(name = "virtual")
@ColumnDefault("false")
private boolean virtual;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "create_time", updatable = false)
@CreationTimestamp
private Date createTime;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "update_time")
@UpdateTimestamp
protected Date updateTime;
}
这里有一个布尔字段用来设置这个预算条目是不是虚拟条目,可以把一些纯结转的凭证和合同,都对应在这个虚拟条目中,合同也会有对应的虚拟合同。
Project
与Budget
的映射关系
我需要在使用类似于project.budgets()
的方法来根据Project
查询对应的预算,也需要类似于budget.project()
来获取一个预算条目对应的项目,这就需要在两方都做好映射。
由于项目和预算是一对多的关系,因此我把外键放在预算这边,做一个多对一映射,然后还需要在项目那边做一个一对多映射。
先来配置Budget
中的外键映射,这里可以参考我之前的博客:Hibernate
关系映射:
@Entity
@Table(name = "budget")
public class Budget {
@ManyToOne(optional = false, fetch = FetchType.LAZY)
@JoinColumn(name = "project_id")
private Project project;
}
这实际上就在budget
表中创建了一个名称为project_id
的外键,并且不允许为null
,这实际上表示不允许有脱离项目存在的预算,这本来也是我们的目的。这也意味在代码层面,需要先有一个已经持久化的项目,才能持久化一个对应的预算,顺序不能颠倒。
现在来做另外一个映射,就是从项目中获取对应的所有预算条目:
@Entity
@Table(name = "project")
public class Project implements Serializable {
@OneToMany(mappedBy = "project", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<Budget> budgets;
}
这个映射不会在project
表中添加任何新外键,Hibernate
会依照这个对应关系进行查询。既然那边是多对一映射,这里就是采用一对多映射,其中的mappedBy = "project"
中字符串一定要与Budget
类中映射多对一关系的变量名一致,此外,还需要设置cascade
,为什么不在多对一的多那边设置呢。这是因为级联操作要先对当前对象进行操作,所以需要在先写入的那一边设置,才能级联保存,在多那边是无法先把Budget
写入数据库,再去写项目的。
再添加上对应的getter
和setter
方法之后,我们的映射就做好了。接下来在编制整个业务流程的时候,其实也就是这样下去,先编制一层映射关系,再来编写对应的增删改查。
后续操作
当然到这里两个类远远没有结束,还有很多方法在等着我们编写,比如一个项目要获取完整的当前财务状态,一个预算条目需要获取对应的成本,支付情况等等,都需要进行计算,这里两边的关系都设置为了LAZY
,就是想尽量减少查询次数。
URL
路径设计
最基础的是项目,路径设计如下:
pms/project/list
,用于列出所有项目清单pms/project/detail/{id}
,用于单个项目详情pms/project/add
,用于新增项目pms/project/edit/{id}
,用于编辑项目pms/project/delete
,用于删除项目
由于我们的结构是一层一层的往下去的,这里顺便就把预算的一起设计了,其中的{pid}
表示项目的id
:
pms/budget/{pid}/list
,用于列出某个项目对应的所有预算pms/budget/{pid}/detail/{id}
,用于列出某个预算条目的详情pms/budget/{pid}/add
,用于新增预算pms/budget/{pid}/edit/{id}
,用于编辑某个预算条目pms/budget/delete
,用于删除项目
由于预算不能脱离项目而存在,所以所有的查、删、改都要依托于项目。我这里设计URL
主要是再做一层校验,而不是直接接受POST
请求。
然后在实际操作的时候,会严格控制预算的新建,由于新创建一个预算条目一定要对应预算,所以在创建预算的时候直接就要指定对应的项目,这也是我们强关联关系的提现。下边就先来写项目的增删改查吧。