实体类
根据需求,我们可以抽象出以下实体类:
-
文章
类(Article) -
分类
类(Type) -
标签
类(Tag) -
评论
类(Comment) -
用户
类(User)
实体关系
不是很会画ER图,就画张草图简单示意一下:

可能有人会问了,为什么博客和用户之间没有关系呢?其实很简单,就是因为这个网站真正可以发布文章的只有我一个人,用户只是用来发表评论的,所以用户和博客(文章)之间不需要建立关系。
实体类
重构之后我们没有选择MongoDB,而是用了MySQL——原因也只有一个:成熟,解决方案多。而且数据量其实不是很大,关系也不是很复杂,这种情况下用MySQL不会带来特别大的麻烦。
实体类其实没有太多好讲的,主要是在实体关系这方面需要多关注一些。
文章
这次使用@NotNull
和@NotEmpty
注解加上了判空操作,避免因为参数校验不足而产生一个NullPointerException把后端代码整炸了。
使用懒加载是为了提高性能。
这次遵循阿里Java编码规范,使用文档注释来写每个字段的注释。
Lombok
还是要用的,对提高开发效率有很高的帮助。
@Temporal
注解可以用于指定时间的格式,这里就用TIMESTAMP
时间戳类型了。
文章内容这个字段需要使用TEXT
类型,使用方法就是加上两个注解:@Lob
和@Column
,并且指定它为TEXT类型。
/**
* 文章实体类
*
* @author jiangwen
*/
@Data
@NoArgsConstructor
@Accessors(chain = true)
@ToString
@Entity
@Table(name = "wendev_article")
public class Article {
/**
* 主键,值为自动生成
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 文章标题
*/
@NotNull(message = "标题不能为空")
@NotEmpty(message = "标题不能为空字符串")
@Column
private String title;
/**
* 文章内容,因为太长所以映射为TEXT类型
*/
@NotNull(message = "文章内容不能为空")
@NotEmpty(message = "文章内容不能为空字符串")
@Lob
@Column(columnDefinition = "TEXT")
private String content;
/**
* 文章阅读量
*/
@Column
private Integer views;
/**
* 是否已发布
* 这其实是一个保留字段,目前还用不到
*/
@Column
private Boolean published;
/**
* 是否开启评论功能
*/
@Column
private Boolean commentEnabled;
/**
* 文章的创建时间
*/
@Column
@Temporal(TemporalType.TIMESTAMP)
private Date createTime;
/**
* 文章的更新时间
*/
@Column
@Temporal(TemporalType.TIMESTAMP)
private Date updateTime;
/**
* 该文章所属的类型
*/
@ManyToOne
private Type type;
/**
* 该文章所拥有的标签,设置级联新增
*/
@ManyToMany(cascade = {CascadeType.PERSIST}, fetch = FetchType.LAZY)
private List<Tag> tags = new ArrayList<>();
/**
* 该文章对应的评论
*/
@OneToMany(mappedBy = "article", fetch = FetchType.LAZY)
private List<Comment> comments = new ArrayList<>();
/**
* 乐观锁
*/
@Version
private Long version;
}
用户
为了以后可能对接Spring Security
而保留了一个role
字段。
/**
* @author jiangwen
*/
@Data
@NoArgsConstructor
@Accessors(chain = true)
@ToString
@Entity
@Table(name = "wendev_user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column
@NotNull(message = "用户名不能为空")
@NotEmpty(message = "用户名不能为空字符串")
private String username;
@Column
@NotNull(message = "用户昵称不能为空")
@NotEmpty(message = "用户昵称不能为空字符串")
private String nickname;
@Column
@NotNull(message = "邮箱不能为空")
@NotEmpty(message = "邮箱不能为空字符串")
private String email;
@Column
@NotNull(message = "密码不能为空")
@NotEmpty(message = "密码不能为空字符串")
private String password;
/**
* 用户权限
* 这个字段是为了以后可能对接Spring Security保留的
*/
@Column
@NotNull(message = "用户角色不能为空")
@NotEmpty(message = "用户角色不能为空字符串")
private String role;
@Temporal(TemporalType.TIMESTAMP)
private Date createTime;
@Temporal(TemporalType.TIMESTAMP)
private Date updateTime;
/**
* 该用户所发表的全部评论
*/
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Comment> comments = new ArrayList<>();
@Version
private Long version;
}
文章类型
类型与文章是一对多关系,由类型端来维护。
/**
* 文章类型实体类
*
* @author jiangwen
*/
@Data
@NoArgsConstructor
@Accessors(chain = true)
@ToString
@Entity
@Table(name = "wendev_type")
public class Type {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 类型名称
*/
@NotNull(message = "类型名称不能为空")
@NotEmpty(message = "类型名称不能为空字符串")
@Column
private String name;
/**
* 该分类下属的文章
* 关系的被维护端
*/
@OneToMany(mappedBy = "type", fetch = FetchType.LAZY)
private List<Article> articles = new ArrayList<>();
@Version
private Long version;
}
文章标签
标签与文章有多对多关系,由标签端来维护。
/**
* 文章标签实体类
*
* @author jiangwen
*/
@Data
@NoArgsConstructor
@Accessors(chain = true)
@ToString
@Entity
@Table(name = "wendev_tag")
public class Tag {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 拥有该tag的文章列表
* 关系的维护端
*/
@ManyToMany(mappedBy = "tags", fetch = FetchType.LAZY)
private List<Article> articles = new ArrayList<>();
/**
* 标签名称
*/
@Column
@NotNull(message = "标签名称不能为空")
@NotEmpty(message = "标签类型不能为空")
private String name;
@Version
private Long version;
}
评论
评论也没什么好说的。但是由于它有一个自关联一对多的关系,所以需要注意一下。
其实也没有什么好注意的,当成两个实体类来写就行了。
/**
* @author jiangwen
*/
@Data
@NoArgsConstructor
@Accessors(chain = true)
@ToString
@Entity
@Table(name = "wendev_comment")
public class Comment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 该评论的发表者
*/
@ManyToOne
private User user;
/**
* 该评论所属于的文章
*/
@ManyToOne
private Article article;
/**
* 该评论下的子评论
*/
@OneToMany(mappedBy = "parentComment", fetch = FetchType.LAZY)
private List<Comment> replyComments = new ArrayList<>();
/**
* 该评论的父评论
*/
@ManyToOne
private Comment parentComment;
@Column
@NotNull(message = "评论内容不能为空")
@NotEmpty(message = "评论内容不能为空字符串")
private String content;
@Temporal(TemporalType.TIMESTAMP)
private Date createTime;
@Version
private Long version;
}
这样实体类和实体关系就构建完成了。
启动项目让JPA帮我们创建好表,再从DataGrip里查看一下表关系图就可以很清楚地看出这些实体类之间的关系了:

wendev_article_tags
是维护文章与标签多对多关系的关联表,是自动帮我们创建好的。当然也可以在实体类里使用@JoinTable
指定它的信息。
网友评论