LostCatBox

SpringProject-Board-CH07

Word count: 1.4kReading time: 8 min
2022/12/24 Share

Spring 게시판 프로젝트 7편 (vue+spring으로 댓글 구현)

Created Time: August 1, 2022 7:24 PM
Last Edited Time: August 3, 2022 11:07 AM
Tags: Java, Spring, Computer

댓글 작성

front-end vue

BoardDetail.vue

  • vue에서 Components로 넣어주면 <컴포넌트이름>으로 불러올수있었으며, 인자도 아래와 같이 post-id로 넘겨줄수있었다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div>
<h2>해당 게시글 댓글 목록</h2>
<Comments :post-id="this.id"/>
</div>
</template>

<script>
import Comments from "@/views/comments/Comments";

export default {
name: "BoardDetail",
components:{
Comments
},

Comments.vue

  • views/comments 위치
  • components로 넣어주고 다른 vue파일 내용이 컴포넌트로 삽입되도록 활용하였다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<template>
<div>
<div>
<br>
<strong>Comments.vue</strong>
</div>
<!-- 댓글 데이터를 불러와 반복문으로 돌려 컴포넌트에 각각 들어가도록 한다.-->
<!-- script에서 가져왔던 res.data로 이용하여 v-for반복문 돌리고 각 row마다 PrcommentListItem 사용-->
<div v-for="item in commentObj" :key="item.id">
<PrCommentListItem :post-id="this.id" :commentItem="item" :reload="reload"/>
</div>
<!-- 댓글 쓰기 창도 아래 컴포넌트에 따로 생성.-->
<div>
<PrCommentCreate :post-id="this.id" :reload="reload"/>
</div>
</div>
</template>
<script>
import PrCommentListItem from "@/views/comments/components/PrCommentListItem";
import PrCommentCreate from "@/views/comments/components/PrCommentCreate";

export default {
name:"PrCommentList",
components:{
PrCommentListItem,
PrCommentCreate
},
props:{
postId:Number,
contentId:Number,
},
data(){
return{
commentObj: null,
requestBody: this.$route.query,
id: this.postId,
token : `${localStorage.getItem("user").replace(/^"(.*)"$/, '$1')}`,
}
},
mounted() {
this.getCommentList()
},
methods:{
getCommentList(){
this.$axios.get(this.$serverUrl +"/basic/post/"+this.id+"/comments/",
{
params: this.requestBody,
headers: {
"X-AUTH-TOKEN": this.token,
},
}).then((res)=> {
console.log(res.data)
this.commentObj = res.data //res.data형태[{"id":7,"comment":"aa","createdDate":"2022.07.15 10:17","modifiedDate":"2022.07.15 10:17","user":null,"post":null,"nickname":"admin","email":"admin"}
})
}

}
}
</script>

PrCommentListItem.vue

  • views/comments/components 위치
  • vue에 장점을 못살린… window.location.reload()사용… 변경해야함
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
<!--//PrCommentListItem 댓글 속성 정의-->
<template>
<div class="commentItem">
<div>{{ name }}</div>
<div>{{ created_at }}</div>
<input type="text" v-model="comment">
<button @click="updateComment">수정</button>
<button @click="deleteComment">삭제</button>
</div>
</template>

<script>
export default {
name:"PrCommentListItem",
props:{
postId: Number,
commentItem:Object, // //[{"id":7,"comment":"aa","createdDate":"2022.07.15 10:17","modifiedDate":"2022.07.15 10:17","user":null,"post":null,"nickname":"admin","email":"admin"},{"id":8,"comment":"111","createdDate":"2022.07.15 10:20","modifiedDate":"2022.07.15 10:20","user":null,"post":null,"nickname":"admin","email":"admin"},{"id":9,"comment":"11","createdDate":"2022.07.15 10:20","modifiedDate":"2022.07.15 10:20","user":null,"post":null,"nickname":"admin","email":"admin"}]
reload:Function
},
data(){
return{
commentId: '',
name:'',
comment:'',
created_at:'',
token: `${localStorage.getItem("user").replace(/^"(.*)"$/, '$1')}`

}
},
created(){
this.commentId = this.commentItem.id
this.name = this.commentItem.nickname
this.comment= this.commentItem.comment
this.created_at=this.commentItem.createdDate

},
methods:{
updateComment(){
this.$axios.put(this.$serverUrl + '/basic/post/' + this.postId + '/comments/',{
id: this.commentId,
comment: this.comment
},{
headers: {
"X-AUTH-TOKEN": this.token
}
})
.then((res) => {
alert('댓글수정완료')
}).catch((err) => {
alert('ERR발생')
})
this.reload(true)
},
deleteComment(){
this.$axios.delete(this.$serverUrl + '/basic/post/' + this.postId + '/comments/'+this.commentId, {
headers: {
"X-AUTH-TOKEN": this.token
}
})
.then((res) => {
alert('댓글삭제완료')
}).catch((err) => {
alert('ERR발생')
})
window.location.reload(true)
}

}

}
</script>
<style>
.commentItem{
border:1px solid #000;
margin-bottom:10px;
padding:10px;
}
</style>

PrCommentCreate.vue

  • views/comments/components 위치
  • vue에 장점을 못살린… window.location.reload()사용… 변경해야함
  • input 박스로
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<template>
<div class="commentItem">
<textarea
placeholder="댓글을 입력하세요"
v-model="context"
></textarea>
<button @click="createComment">댓글달기</button>
</div>
</template>

<script>
export default {
name: "PrCommentCreate",
props: {
postId: Number,
reload: Function
},
data() {
return {
comment: '',
token: `${localStorage.getItem("user").replace(/^"(.*)"$/, '$1')}`
}
},
methods: {
createComment() {
this.$axios.post(this.$serverUrl + '/basic/post/' + this.postId + '/comments/', {
comment: this.context
}, {
headers: {
"X-AUTH-TOKEN": this.token
}
})
.then((res) => {
alert('글이 저장됨')
window.location.reload(true)
}).catch((err) => {
alert('ERR발생')
})

},

},

};;;;
</script>

오류 해결

무한 참조

기존에 사용하는 Comment엔티티로 Json응답을 할려고하니… post와 서로 참조하고있어서 오류발생.

서로 참조하고있는경우 getter로 jsonMapper가 계속 무한대로 참조하다가 stackoverflow가 발생함.

해결

  • 양방향을 단방향으로 바꾸는 방법 하나
  • @JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "무한 참조 엔티티의 필드명")
  • 아예 새로운 응답용 DTO를 만들기

cannot delete or update foreign key constraint 에러!!

해결과정

기존 DB안에서는 @OnDelete(action = OnDeleteAction.CASCADE)는 반드시 DB를 create할때 적용가능했다. spring.DB 스키마를 변경사항이있을때 업데이트하는 방식으로 활용했다.

spring.jpa.hibernate.ddl-auto=update

하지만…결과적으로 @OnDelete(action = OnDeleteAction.CASCADE) 설정은 create! 설정으로 바꿔줘야했다. 변경불가능하고, DB를 새로 작성할때 조건을 추가할수있는것같았다.

해결:

application.properties파일에서spring.jpa.hibernate.ddl-auto=create

Post.java파일에서@OnDelete(action = OnDeleteAction.CASCADE) 추가함 이것이 해결책!!

이걸 적어주면 comment db 생성시, FK생성시 cascade에 옵션 들어감

에러 발생이유

부모와 자식의 1:N 관계일 때 아래 요구사항을 충족시키는 것이 필요했다.

  • 요구사항 1 : 부모 엔티티를 삭제 할 경우 자식 엔티티는 모두 삭제되어야 한다.
  • 요구사항 2 : 자식 엔티티를 삭제해도 부모 엔티티는 삭제되면 안된다.

cascade = CascadeType.REMOVE를 설정할 경우
자식 엔티티를 삭제하는 쿼리가 먼저 실행되고, 그 다음 부모 엔티티를 삭제하는 쿼리가 실행 된다.
그리고 자식 엔티티 한 건을 삭제 하려고 하면 부모 테이블에 연관되어 있는 데이터가 존재 하므로 아래와 같이 에러가 발생한다.(???)

(부모와 연관된 자식이 한건이라면 이 에러가 발생하지 않는다.)(Comment에서 User와 Post 둘다 FK가짐)

그래서 부모를 제거시 casecade=CascadeType.REMOVE 설정이있었으므로 자식 데이터에 대한 DELETE쿼리가 실행되는것이 정상이다.. 하지만 cannot delete or update foregin key constraint 오류가 떳다. 부모 delete가 먼저 동작하여 오류가 난것이다.
(부모 delete쿼리 이후 관계잇던 자식 데이터 delete쿼리 날아가는순서)

@OnDelete 는 데이터 베이스 레벨에서 동작하므로 주의하자.

지금 오류 재현

  • post.java
1
2
3
4
@OneToMany(mappedBy="post", fetch = FetchType.LAZY, cascade =CascadeType.REMOVE, orphanRemoval=true)
// @OnDelete(action = OnDeleteAction.CASCADE) 이것이 해결책!! 이걸 적어주면 comment db 생성시, FK생성시 cascade에 옵션 들어감
@OrderBy("id asc") //댓글 정렬
private List<Comment> comments;
  • comments.java
1
2
3
4
@ManyToOne
@JoinColumn(name = "post_id")
@JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "comments") //Json반환시 무한 get루프 벗어나는법
private Post post;

DB에서는 comments 테이블에서 post_id를 foreign key로 잡고있을테니, post를 삭제할려고 시도한다면, 당연히 DB에서는 무결성을 지키지 못하는 행동이기때문에 오류가 났었다.

그래서 foreign key를 지정할때 on Delete Cascade와 on update cascade를 지정하여, 외래키를 가진녀석이 부모가 삭제시, 어떤 행동을 해야하는지 알려줘야한다.

가령 comment에서 FK키 설정시 on delete cascade 옵션을 추가한다면, 해당 참조하는 post가 삭제시도를 할때, 이를 외래키로 가지는 모든 애들을 삭제하는 동작을 취한다.!!

관련지식

@OnDelete

  • DDMS 레벨에서 작동
  • @OnDelete(action = OnDeleteAction.CASCADE)
  • DDL 생성시 cascade 제약 조건이 생성 됨.

여기서 DDL이란 Data Definition Language로 데이터 베이스를 정의하는 언어이며, CREATE, ALTER, DROP, TRUNCATE를 말한다.

casecade=CascadeType.REMOVE

  • JPA 레벨에서 작동
  • JPA가 부모 엔티티를 삭제할 때 연관된 자식 데이터에 대한 DELETE 쿼리를 실행 함

문제 검증

select * from information_schema.table_constraints where table_name = "comments";

로 현재 comments에 제한 사항을 확인후 지우고, 다시 on delete cascade옵션을 추가하여 fk 제한사항을 다시 만들어주었다.

alter table comments drop constraint FKbqnvawwwv4gtlctsi3o7vs131;

alter table comments add constraint comments_fk_post_id foreign Key(post_id) References post(id) on delete cascade;

문제없이 동작하는것을 확인하였다..

on delete on update 현 옵션은 따로 볼수있는 방법을 몰라 아쉽다.

참고 이슈

JPA cascade 공부 필요

CATALOG
  1. 1. Spring 게시판 프로젝트 7편 (vue+spring으로 댓글 구현)
  2. 2. 댓글 작성
    1. 2.1. front-end vue
      1. 2.1.1. BoardDetail.vue
      2. 2.1.2. Comments.vue
      3. 2.1.3. PrCommentListItem.vue
      4. 2.1.4. PrCommentCreate.vue
  3. 3. 오류 해결
    1. 3.1. 무한 참조
      1. 3.1.1. 해결
    2. 3.2. cannot delete or update foreign key constraint 에러!!
      1. 3.2.1. 해결과정
      2. 3.2.2. 에러 발생이유
      3. 3.2.3. 지금 오류 재현
      4. 3.2.4. 관련지식
      5. 3.2.5. @OnDelete
      6. 3.2.6. casecade=CascadeType.REMOVE
      7. 3.2.7. 문제 검증
      8. 3.2.8. 참고 이슈
      9. 3.2.9. JPA cascade 공부 필요