本文使用spring boot配合JPA实现增删改查。并介绍orphanRemoval属性,使用该属性对子类实体进行增删操作。
阅读本文您需要掌握以下前提知识:
- Spring Boot基础知识
- Hibernates-JPA基础知识
- Java基础知识
什么是orphanRemoval,百度了下发现没有非常准确且简单的定义。
orphanRemoval:当子类实体不再与父类实体关联时即删除该实体。
即,当你从父实体的相应集合中删除了该子类实体时,它会被物理删除,而非只在内存中删除。
以下为实例,模拟了评论以及点赞操作:
为了让大家更加直观,先上项目结构图:
AddCommentRequestBean.java : 插入评论的request bean
AddCommentLikeRequestBean.java : 点赞的request bean
CrudCommentController.java : 添删评论或点赞的接口类
CrudCommentControllerImpl.java : 添删评论或点赞的实现类
Comment.java : 评论信息实体类
CommentLike.java : 点赞信息实体类
CommentLikePK.java : 点赞信息的联合主键
本例中的父类为评论信息,子类为点赞信息,关系图如下:
先从实体类看起:
Comment.java
可见评论信息包含点赞信息,关系为OneToMany。并设置了orphanRemoval=true。因此将某子类实体从父类中的commentLike中移除时,其就会被物理删除。添加和移除可见本类的addCommentLike以及removeCommentLike的两个方法。用的是Set集合类的add()以及remove()方法。
package com.example.demo.entity;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.validation.constraints.Size;
@Entity( name="Comment" )
@Table( name = "tb_comment_info" )
@NamedQuery(name = "Comment.findAll", query = "SELECT c FROM Comment c")
public class Comment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "comment_id",columnDefinition = "BIGINT UNSIGNED")
private Long commentId;
@Size( max = 200 )
@Column( name = "comment_body" )
private String commentBody;
@OneToMany(mappedBy = "comment", fetch = FetchType.EAGER, orphanRemoval = true, cascade = { CascadeType.PERSIST } )
private Set<CommentLike> commentLike;
public Long getCommentId() {
return commentId;
}
public void setCommentId(Long commentId) {
this.commentId = commentId;
}
public String getCommentBody() {
return commentBody;
}
public void setCommentBody(String commentBody) {
this.commentBody = commentBody;
}
public Set<CommentLike> getCommentLike() {
return commentLike;
}
public void setCommentLike(Set<CommentLike> commentLike) {
this.commentLike = commentLike;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((commentId == null) ? 0 : commentId.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Comment other = (Comment) obj;
if (commentId == null) {
if (other.commentId != null)
return false;
} else if (!commentId.equals(other.commentId))
return false;
return true;
}
@Override
public String toString() {
return "Comment [commentId=" + commentId + ", commentBody=" + commentBody + "]";
}
public CommentLike addCommentLike( CommentLike commentLike ) {
this.getCommentLike().add( commentLike );
commentLike.setComment( null );
return commentLike;
}
public CommentLike removeCommentLike( CommentLike commentLike ) {
this.getCommentLike().remove( commentLike );
commentLike.setComment( null );
return commentLike;
}
}
CommentLike.java
package com.example.demo.entity;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.JoinColumns;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
@Entity( name="CommentLike" )
@Table( name = "tb_comment_like_info" )
@NamedQuery(name = "CommentLike.findAll", query = "SELECT c FROM CommentLike c")
public class CommentLike {
@EmbeddedId
private CommentLikePK id;
@ManyToOne
@JoinColumns({
@JoinColumn(name = "comment_id",referencedColumnName = "comment_id",insertable = false, updatable = false)
})
private Comment comment;
public CommentLikePK getId() {
return id;
}
public void setId(CommentLikePK id) {
this.id = id;
}
public Comment getComment() {
return comment;
}
public void setComment(Comment comment) {
this.comment = comment;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
CommentLike other = (CommentLike) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
@Override
public String toString() {
return "CommentLike [id=" + id + "]";
}
}
点赞实体类的联合主键
CommentLikePK.java
package com.example.demo.entity;
import java.io.Serializable;
import javax.persistence.Column;
public class CommentLikePK implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
@Column(name = "comment_id",columnDefinition = "BIGINT UNSIGNED")
private Long commentId;
@Column(name = "user_id",columnDefinition = "BIGINT UNSIGNED")
private Long userId;
public Long getCommentId() {
return commentId;
}
public void setCommentId(Long commentId) {
this.commentId = commentId;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public static long getSerialversionuid() {
return serialVersionUID;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((commentId == null) ? 0 : commentId.hashCode());
result = prime * result + ((userId == null) ? 0 : userId.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
CommentLikePK other = (CommentLikePK) obj;
if (commentId == null) {
if (other.commentId != null)
return false;
} else if (!commentId.equals(other.commentId))
return false;
if (userId == null) {
if (other.userId != null)
return false;
} else if (!userId.equals(other.userId))
return false;
return true;
}
@Override
public String toString() {
return "CommentLikePK [commentId=" + commentId + ", userId=" + userId + "]";
}
}
再来看两个request bean
AddCommentRequestBean.java
添加评论时,评论不得为空
package com.example.demo.bean;
import javax.validation.constraints.NotBlank;
import lombok.Data;
@Data
public class AddCommentRequestBean {
@NotBlank
private String commentBody;
}
AddCommentLikeRequestBean.java
调用点赞接口时,评论编号以及用户ID不得为空。
package com.example.demo.bean;
import javax.validation.constraints.NotBlank;
import lombok.Data;
@Data
public class AddCommentLikeRequestBean {
@NotBlank
private Long commentId;
@NotBlank
private Long userId;
}
增删评论以及点赞信息的接口类
CrudCommentController.java
package com.example.demo.controller;
import javax.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.demo.bean.AddCommentLikeRequestBean;
import com.example.demo.bean.AddCommentRequestBean;
@RestController
@RequestMapping("/restapi/comment")
public interface CrudCommentController {
@PostMapping("/add")
public ResponseEntity<?> addComment( @Valid @RequestBody AddCommentRequestBean addCommentRequestBean );
@PostMapping("/remove/{commentId}")
public ResponseEntity<?> removeComment( @PathVariable(name="commentId", required = true ) Long commentId );
@PostMapping("/like")
public ResponseEntity<?> addCommentLike( @Valid @RequestBody AddCommentLikeRequestBean addCommentLikeRequestBean );
@PostMapping("/dislike")
public ResponseEntity<?> removeCommentLike( @Valid @RequestBody AddCommentLikeRequestBean addCommentLikeRequestBean );
}
增删评论以及点赞信息的实现类
CrudCommentControllerImpl.java
package com.example.demo.controllerimpl;
import javax.transaction.Transactional;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import com.example.demo.bean.AddCommentLikeRequestBean;
import com.example.demo.bean.AddCommentRequestBean;
import com.example.demo.controller.CrudCommentController;
import com.example.demo.entity.Comment;
import com.example.demo.entity.CommentLike;
import com.example.demo.entity.CommentLikePK;
import com.example.demo.repository.CommentRepository;
@Component
@Transactional
public class CrudCommentControllerImpl implements CrudCommentController {
@Autowired
private CommentRepository commentRepository;
@Override
public ResponseEntity<?> addComment(@Valid AddCommentRequestBean addCommentRequestBean) {
// TODO Auto-generated method stub
// 生成新的评论
Comment comment = new Comment();
// 设置评论内容
comment.setCommentBody( addCommentRequestBean.getCommentBody() );
// 使用仓库存储
commentRepository.save( comment );
return new ResponseEntity<>("成功添加评论",HttpStatus.OK);
}
@Override
public ResponseEntity<?> removeComment( Long commentId ) {
// TODO Auto-generated method stub
// 使用仓库根据评论ID删除评论
commentRepository.deleteById( commentId );
return new ResponseEntity<>("成功删除评论",HttpStatus.OK);
}
@Override
public ResponseEntity<?> addCommentLike(@Valid AddCommentLikeRequestBean addCommentLikeRequestBean) {
// TODO Auto-generated method stub
// 如果评论不存在则报错,存在则获取该实体
Comment comment = commentRepository.findById( addCommentLikeRequestBean.getCommentId() )
.orElseThrow(()-> new RuntimeException("该评论不存在"));
// 生成点赞信息的联合主键
CommentLikePK commentPK = new CommentLikePK();
commentPK.setCommentId( addCommentLikeRequestBean.getCommentId() );
commentPK.setUserId( addCommentLikeRequestBean.getUserId() );
// 生成点赞信息,并设置主键
CommentLike commentLike = new CommentLike();
commentLike.setId( commentPK );
// 当子类实体中没有该用户的点赞信息时插入
// 注意equals和hashCode是否正确,如不正确contains执行结果会错误
if( !comment.getCommentLike().contains( commentLike ) ) {
comment.addCommentLike( commentLike );
}
return new ResponseEntity<>("成功点赞",HttpStatus.OK);
}
@Override
public ResponseEntity<?> removeCommentLike(@Valid AddCommentLikeRequestBean addCommentLikeRequestBean) {
// TODO Auto-generated method stub
Comment comment = commentRepository.findById( addCommentLikeRequestBean.getCommentId() )
.orElseThrow(()-> new RuntimeException("该评论不存在"));
// 生成点赞信息的联合主键
CommentLikePK commentPK = new CommentLikePK();
commentPK.setCommentId( addCommentLikeRequestBean.getCommentId() );
commentPK.setUserId( addCommentLikeRequestBean.getUserId() );
CommentLike commentLike = new CommentLike();
commentLike.setId( commentPK );
// 当子类实体中有当前用户的点赞信息时删除该点赞信息
// 注意equals和hashCode是否正确,如不正确contains执行结果会错误
if( comment.getCommentLike().contains( commentLike ) ) {
comment.removeCommentLike( commentLike );
return new ResponseEntity<>("成功取消点赞",HttpStatus.OK);
}else {
// do nothing
return new ResponseEntity<>("已取消点赞",HttpStatus.OK);
}
}
}
YML文件
server:
port: 8083
tomcat:
uri-encoding: UTF-8
spring:
application:
name: demo
mvc:
static-path-pattern: static/**
thymeleaf:
prefix: classpath:/templates/
suffix: .html
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
#本例使用mysql,如使用其他DB请自行切换
url: jdbc:mysql://localhost/test_database?useSSL=false&serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true
#注意此处切换为自己的账号密码
username:
password:
jpa:
#为当前线程配置EntityManager
open-in-view: false
#自动执行DDL
generate-ddl: true
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
ddl-auto: update
POM
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.validation/validation-api -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
使用PostMan测试
起始状态,两个表皆为空
调用CrudCommentController的addComment()方法添加评论
确认评论数据是否被添加
再调用CrudCommentController的addCommentLike()方法为该评论添加点赞信息。
再次查看点赞表数据,点赞信息被添加。
再调用CrudCommentController的removeCommentLike()方法移除该点赞信息。
最后调用CrudCommentController的removeComment()来移除刚刚添加的评论。