실시간 통신
HTTP 한계점
클라이언트와 서버 간 요청과 응답이 있어야만 할 수 있는 것
클라이언트 : 나 HTTP 통신인데 너랑 웹소켓 통신을 하고 싶어
→ 핸드셰이크
서버 : 알겠어, 웹소켓 통신으로 변경 해줄게

/** SessionHandshakeInterceptor 클래스
*
* WebSocketHandler가 동작하기 전/후에
* 연결된 클라이언트 세션을 가로채는 동작을 작성할 클래스
*
*/
@Component // Bean 등록
public class SessionHandshakeInterceptor implements HandshakeInterceptor {
// * Before
// 핸들러 동작 전에 수행되는 메서드
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Map<String, Object> attributes) throws Exception {
// ServerHttpRequest : HttpServletRequest의 부모 인터페이스
// ServerHttpResponse : HttpServletResponse의 부모 인터페이스
// attributes : 해당 맵에 세팅된 속성(데이터)은
// 다음에 동작할 Handler 객체에게 전달됨
// (HandshakeInterceptor -> Handler 데이터 전달하는 역할)
// 아래의 if문 : request 가 참조하는 객체가
// ServletServerHttpRequest로 다운캐스팅이 가능한가?
if(request instanceof ServletServerHttpRequest) {
// -> 부모 자식 관계가 맞다면
// 다운캐스팅
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest)request;
// 웹소켓 동작을 요청한 클라이언트의 세션을 얻어옴
HttpSession session = servletRequest.getServletRequest().getSession();
// 가로챈 세션을 Handler에 전달할 수 있게 값 세팅
attributes.put("session", session);
}
return true; // 가로채기 진행 여부 기본값 false -> true로 작성해야 세션을 가로채서 Handler에게 전달 가능
}
// * After
// 당장 할거는 없어서 내비둠
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Exception exception) {
// TODO Auto-generated method stub
}
}
/** OOOWebSocketHandler 클래스
*
* 웹소켓 동작 시 수행할 구문을 작성하는 클래스
*
*/
@Slf4j
@Component // Bean 등록
public class TestWebSocketHandler extends TextWebSocketHandler {
// WebSocketSession
// - 클라이언트 <-> 서버 간 전이중 통신을 담당하는 객체
// - SessionHandshakeInterceptor가 가로챈 연결한 클라이언트의 HttpSession을 가지고 있음
// (attributes에 추가한 값)
// 동기화된 Set 생성
private Set<WebSocketSession> sessions = Collections.synchronizedSet(new HashSet<>());
// -> 여러가지 스레드가 동작하는 환경에서 하나의 컬렉션에
// 여러 스레드가 접근(비동기)하여 의도치 않은 문제가 발생되지 않게 하기 위해
// 동기화를 진행하여 스레드가 순서대로 한 컬렉션에 접근할 수 있도록 변경
// * 클라이언트와 연결이 완료되고, 통신할 준비가 되면 실행
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
// SessionHandshakeInterceptor가 가로챈 세션
// 연결된 클라이언트의 WebSocketSession 정보를 Set에 추가
// -> 웹소켓에 연결된 클라이언트 정보를 모아둠
sessions.add(session);
}
// * 클라이언트와 연결이 종료되면 실행
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
// 웹소켓 연결이 끊긴 클라이언트 정보를 Set에서 제거
sessions.remove(session);
}
// * 클라이언트로부터 텍스트 메세지를 받았을때 실행
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
// TextMessage : 웹소켓으로 연결된 클라이언트가 전달한
// 텍스트(내용)가 담겨 있는 객체
// message.getPayload() : 통신 시 탑재된 데이터 자체
log.info("전달 받은 메시지 : {}", message.getPayload());
// 전달 받은 메시지를 현재 해당 웹소켓에 연결된 모든 클라이언트에게 보내기
for(WebSocketSession s : sessions) {
s.sendMessage(message);
}
}
}
/*
WebSocketHandler 인터페이스 :
웹소켓을 위한 메소드를 지원하는 인터페이스
-> WebSocketHandler 인터페이스를 상속받은 클래스를 이용해
웹소켓 기능을 구현
WebSocketHandler 주요 메소드
void handlerMessage(WebSocketSession session, WebSocketMessage message)
- 클라이언트로부터 메세지가 도착하면 실행
void afterConnectionEstablished(WebSocketSession session)
- 클라이언트와 연결이 완료되고, 통신할 준비가 되면 실행
void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus)
- 클라이언트와 연결이 종료되면 실행
void handleTransportError(WebSocketSession session, Throwable exception)
- 메세지 전송중 에러가 발생하면 실행
----------------------------------------------------------------------------
*TextWebSocketHandler :
WebSocketHandler 인터페이스를 상속받아 구현한
텍스트 메세지 전용 웹소켓 핸들러 클래스
handlerTextMessage(WebSocketSession session, TextMessage message)
- 클라이언트로부터 텍스트 메세지를 받았을때 실행
*BinaryWebSocketHandler:
WebSocketHandler 인터페이스를 상속받아 구현한
이진 데이터 메시지를 처리하는 데 사용.
주로 바이너리 데이터(예: 이미지, 파일)를 주고받을 때 사용.
*/
@Configuration // 서버 실행 시 작성된 메서드를 모두 수행
@EnableWebSocket // 웹소켓 활성화 설정
@RequiredArgsConstructor
public class WebSocketConfig implements WebSocketConfigurer {
// Bean으로 등록된 SessionHandshakeInterceptor가 주입됨
private final HandshakeInterceptor handshakeInterceptor;
// 웹소켓 처리 동작이 작성된 객체 의존성 주입
private final TestWebSocketHandler testWebSocketHandler;
// 웹소켓 핸들러를 등록하는 메서드
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// addHandler(웹소켓 핸들러, 웹소켓 요청 주소)
registry
.addHandler(testWebSocketHandler, "/testSock")
// ws://localhost/testSock 으로 클라이언트가 요청을 하면,
// testWebSocketHandler가 처리하도록 등록
.addInterceptors(handshakeInterceptor)
// 클라이언트 연결 시 HttpSession을 가로채 핸들러에게 전달
.setAllowedOriginPatterns("http://localhost/",
"http://127.0.0.1/",
"http://192.168.50.216/")
// 웹소켓 요청이 허용되는 IP/도메인 지정
.withSockJS();
// SockJS 지원
}
}
/* 웹소켓 테스트 */
// 1. SockJs 라이브러리 추가
// -> common.html에 작성
// 2. SockJS 객체를 생성
const testSock = new SockJS("/testSock"); // 만들자 마자 Config 내의 핸들러 안에 간거임
// - 객체 생성 시 자동으로
// ws://localhost(또는 ip)/testSock으로 연결 요청을 보냄
// 3. 생성된 SockJS 객체를 이용해서 메시지 전달
const sendMessageFn = (name, str) => {
// JSON을 이용해서 데이터를 TEXT 형태로 전달
const obj = {
"name" : name,
"str" : str
};
// 연결된 웹소켓 핸들러로 JSON을 전달
testSock.send(JSON.stringify(obj));
}
// 4. 서버로부터 현재 클라이언트에게
// 웹소켓을 이용한 메시지가 전달된 경우
testSock.addEventListener("message", e => {
// e.data : 서버로부터 전달 받은 message
const msg = JSON.parse(e.data); // JSON -> JS Object
console.log(`${msg.name} : ${msg.str}`);
// ex) 홍길동 : Hi
});


@Component
@Slf4j
@RequiredArgsConstructor
public class ChattingWebSocketHandler extends TextWebSocketHandler {
private final ChattingService service;
private Set<WebSocketSession> sessions = Collections.synchronizedSet(new HashSet<>());
// 클라이언트와 연결이 완료되고, 통신할 준비가 되면 실행된다.
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
sessions.add(session);
log.info("{} 연결됨",session.getId());
}
// 클라이언트와 연결이 종료되면 실행된다.
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
sessions.remove(session);
log.info("{} 연결끊김",session.getId());
}
// 클라이언트로부터 텍스트 메시지를 받았을 때 실행된다.
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
/* message - JS에서 전달받은 내용
var obj = {
"senderNo": loginMemberNo,
"targetNo": selectTargetNo,
"chattingNo": selectChattingNo,
"messageContent": inputChatting.value,
};
chattingSock.send(JSON.stringify(obj));
*/
// {"senderNo" : "1", "targetNo" : "2", "chattingNo" : "8", "messageContent" : "Hi"}
ObjectMapper objectMapper = new ObjectMapper();
Message msg = objectMapper.readValue(message.getPayload(), Message.class);
// Message 객체 확인
log.info("msg : {}", msg);
// DB 삽입 서비스 호출
int result = service.insertMessage(msg);
if(result > 0) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd hh:mm");
msg.setSendTime(sdf.format(new Date()));
// 전역 변수로 선언된 sessions에는 현재 접속중인 모든 회원의 세션 정보가 담겨있음
for(WebSocketSession s : sessions) {
// 가로챈 session 꺼내기
HttpSession temp = (HttpSession)s.getAttributes().get("session");
// 로그인된 회원 정보 중 회원 번호를 꺼내오기
int loginMemberNo = ((Member)temp.getAttribute("loginMember")).getMemberNo();
// 로그인 상태인 회원 중 targetNo가 일치하는 회원에게 메시지 전달
if(loginMemberNo == msg.getTargetNo() || loginMemberNo == msg.getSenderNo()) {
// 다시 DTO(VO) Object를 JSON으로 변환(JS에 보내야하니까)
String jsonData = objectMapper.writeValueAsString(msg);
s.sendMessage(new TextMessage(jsonData));
}
}
}
}
}
Chatting, Room 등 채팅 형식으로 테스트 가능하게끔 강사님 html, css, js 다운 받아 경로에 붙여 넣기 진행
const addTarget = document.querySelector("#addTarget"); // 추가 버튼
const addTargetPopupLayer = document.querySelector("#addTargetPopupLayer"); // 팝업 레이어
const closeBtn = document.querySelector("#closeBtn"); // 닫기 버튼
const targetInput = document.querySelector("#targetInput"); // 사용자 검색
const resultArea = document.querySelector("#resultArea"); // 검색 결과
const chattingContent = document.querySelector(".chatting-content"); // 채팅방 영역
const prevMessage = document.querySelector(".prev-message"); // 채팅방 선택 전 메세지
let selectChattingNo; // 선택한 채팅방 번호
let selectTargetNo; // 현재 채팅 대상
let selectTargetName; // 대상의 이름
let selectTargetProfile; // 대상의 프로필
// 검색 팝업 레이어 열기
addTarget.addEventListener("click", e => {
addTargetPopupLayer.classList.toggle("popup-layer-close");
targetInput.focus();
});
// 검색 팝업 레이어 닫기
closeBtn.addEventListener("click", e => {
addTargetPopupLayer.classList.toggle("popup-layer-close");
resultArea.innerHTML = "";
});
// 사용자 검색(ajax)
targetInput.addEventListener("input", e => {
const query = e.target.value.trim();
// 입력된게 없을 때
if(query.length == 0){
resultArea.innerHTML = ""; // 이전 검색 결과 비우기
return;
}
// 입력된게 있을 때
if(query.length > 0){
fetch("/chatting/selectTarget?query="+query)
.then(resp => resp.json())
.then(list => {
console.log(list);
resultArea.innerHTML = ""; // 이전 검색 결과 비우기
if(list.length == 0){
const li = document.createElement("li");
li.classList.add("result-row");
li.innerText = "일치하는 회원이 없습니다";
resultArea.append(li);
}
for(let member of list){
// li요소 생성(한 행을 감싸는 요소)
const li = document.createElement("li");
li.classList.add("result-row");
li.setAttribute("data-id", member.memberNo);
// 프로필 이미지 요소
const img = document.createElement("img");
img.classList.add("result-row-img");
// 프로필 이미지 여부에 따른 src 속성 선택
if(member.profileImg == null) img.setAttribute("src", "/images/user.png");
else img.setAttribute("src", member.profileImg);
let nickname = member.memberNickname;
let email = member.memberEmail;
const span = document.createElement("span");
span.innerHTML = `${nickname} ${email}`.replace(query, `<mark>${query}</mark>`);
// 요소 조립(화면에 추가)
li.append(img, span);
resultArea.append(li);
// li요소에 클릭 시 채팅방에 입장하는 이벤트 추가
li.addEventListener('click', chattingEnter);
}
})
.catch(err => console.log(err) );
}
});
// 채팅방 입장 또는 선택 함수
function chattingEnter(e){
console.log(e.target); // 실제 클릭된 요소
console.log(e.currentTarget); // 이벤트 리스트가 설정된 요소
const targetNo = e.currentTarget.getAttribute("data-id");
fetch("/chatting/enter?targetNo="+targetNo)
.then(resp => resp.text())
.then(chattingNo => {
console.log(chattingNo);
selectRoomList(); // 채팅방 목록 조회
setTimeout(()=>{
const itemList = document.querySelectorAll(".chatting-item")
for(let item of itemList) {
if(item.getAttribute("chat-no") == chattingNo){
item.focus();
item.click();
addTargetPopupLayer.classList.toggle("popup-layer-close");
targetInput.value = "";
resultArea.innerHTML = "";
return;
}
}
}, 200);
})
.catch(err => console.log(err));
}
// 비동기로 채팅방 목록 조회
function selectRoomList(){
fetch("/chatting/roomList")
.then(resp => resp.json())
.then(roomList => {
console.log(roomList);
// 채팅방 목록 출력 영역 선택
const chattingList = document.querySelector(".chatting-list");
// 채팅방 목록 지우기
chattingList.innerHTML = "";
// 조회한 채팅방 목록을 화면에 추가
for(let room of roomList){
const li = document.createElement("li");
li.classList.add("chatting-item");
li.setAttribute("chat-no", room.chattingNo);
li.setAttribute("target-no", room.targetNo);
if(room.chattingNo == selectChattingNo){
li.classList.add("select");
}
// item-header 부분
const itemHeader = document.createElement("div");
itemHeader.classList.add("item-header");
const listProfile = document.createElement("img");
listProfile.classList.add("list-profile");
if(room.targetProfile == undefined)
listProfile.setAttribute("src", "/images/user.png");
else
listProfile.setAttribute("src", room.targetProfile);
itemHeader.append(listProfile);
// item-body 부분
const itemBody = document.createElement("div");
itemBody.classList.add("item-body");
const p = document.createElement("p");
const targetName = document.createElement("span");
targetName.classList.add("target-name");
targetName.innerText = room.targetNickName;
const recentSendTime = document.createElement("span");
recentSendTime.classList.add("recent-send-time");
recentSendTime.innerText = room.sendTime;
p.append(targetName, recentSendTime);
const div = document.createElement("div");
const recentMessage = document.createElement("p");
recentMessage.classList.add("recent-message");
if(room.lastMessage != undefined){
recentMessage.innerHTML = room.lastMessage;
}
div.append(recentMessage);
itemBody.append(p,div);
// 현재 채팅방을 보고있는게 아니고 읽지 않은 개수가 0개 이상인 경우 -> 읽지 않은 메세지 개수 출력
if(room.notReadCount > 0 && room.chattingNo != selectChattingNo ){
// if(room.chattingNo != selectChattingNo ){
const notReadCount = document.createElement("p");
notReadCount.classList.add("not-read-count");
notReadCount.innerText = room.notReadCount;
div.append(notReadCount);
}else{
// 현재 채팅방을 보고있는 경우
// 비동기로 해당 채팅방 글을 읽음으로 표시
fetch("/chatting/updateReadFlag",{
method : "PUT",
headers : {"Content-Type": "application/json"},
body : JSON.stringify({"chattingNo" : selectChattingNo, "memberNo" : loginMemberNo})
})
.then(resp => resp.text())
.then(result => console.log(result))
.catch(err => console.log(err));
}
li.append(itemHeader, itemBody);
chattingList.append(li);
}
roomListAddEvent();
})
.catch(err => console.log(err));
}
// 채팅 메세지 영역
const display = document.getElementsByClassName("display-chatting")[0];
// 채팅방 목록에 이벤트를 추가하는 함수
function roomListAddEvent(){
const chattingItemList = document.getElementsByClassName("chatting-item");
for(let item of chattingItemList){
item.addEventListener("click", e => {
// 클릭한 채팅방의 번호 얻어오기
//const id = item.getAttribute("id");
//const arr = id.split("-");
// 전역변수에 채팅방 번호, 상대 번호, 상태 프로필, 상대 이름 저장
selectChattingNo = item.getAttribute("chat-no");
selectTargetNo = item.getAttribute("target-no");
selectTargetProfile = item.children[0].children[0].getAttribute("src");
selectTargetName = item.children[1].children[0].children[0].innerText;
if(item.children[1].children[1].children[1] != undefined){
item.children[1].children[1].children[1].remove();
}
// 모든 채팅방에서 select 클래스를 제거
for(let it of chattingItemList) it.classList.remove("select")
// 현재 클릭한 채팅방에 select 클래스 추가
item.classList.add("select");
// 비동기로 메세지 목록을 조회하는 함수 호출
selectChattingFn();
});
}
}
// 비동기로 메세지 목록을 조회하는 함수
function selectChattingFn() {
fetch("/chatting/selectMessage?"+`chattingNo=${selectChattingNo}&memberNo=${loginMemberNo}`)
.then(resp => resp.json())
.then(messageList => {
console.log(messageList);
prevMessage.classList.add("display-none");
chattingContent.classList.remove("display-none");
// <ul class="display-chatting">
const ul = document.querySelector(".display-chatting");
ul.innerHTML = ""; // 이전 내용 지우기
// 메세지 만들어서 출력하기
for(let msg of messageList){
//<li>, <li class="my-chat">
const li = document.createElement("li");
// 보낸 시간
const span = document.createElement("span");
span.classList.add("chatDate");
span.innerText = msg.sendTime;
// 메세지 내용
const p = document.createElement("p");
p.classList.add("chat");
p.innerHTML = msg.messageContent; // br태그 해석을 위해 innerHTML
// 내가 작성한 메세지인 경우
if(loginMemberNo == msg.senderNo){
li.classList.add("my-chat");
li.append(span, p);
}else{ // 상대가 작성한 메세지인 경우
li.classList.add("target-chat");
// 상대 프로필
// <img src="/resources/images/user.png">
const img = document.createElement("img");
img.setAttribute("src", selectTargetProfile);
const div = document.createElement("div");
// 상대 이름
const b = document.createElement("b");
b.innerText = selectTargetName; // 전역변수
const br = document.createElement("br");
div.append(b, br, p, span);
li.append(img,div);
}
ul.append(li);
display.scrollTop = display.scrollHeight; // 스크롤 제일 밑으로
}
})
.catch(err => console.log(err));
}
// ----------------------------------------------------------------------------------------------------------------
// sockjs를 이용한 WebSocket 구현
// 로그인이 되어 있을 경우에만
// /chattingSock 이라는 요청 주소로 통신할 수 있는 WebSocket 객체 생성
let chattingSock;
if(loginMemberNo != ""){
chattingSock = new SockJS("/chattingSock");
}
// 채팅 입력
const send = document.getElementById("send");
const sendMessage = () => {
const inputChatting = document.getElementById("inputChatting");
if (inputChatting.value.trim().length == 0) {
alert("채팅을 입력해주세요.");
inputChatting.value = "";
} else {
var obj = {
"senderNo": loginMemberNo,
"targetNo": selectTargetNo,
"chattingNo": selectChattingNo,
"messageContent": inputChatting.value,
};
console.log(obj)
// JSON.stringify() : 자바스크립트 객체를 JSON 문자열로 변환
chattingSock.send(JSON.stringify(obj));
inputChatting.value = "";
}
}
// 엔터 == 제출
// 쉬프트 + 엔터 == 줄바꿈
inputChatting.addEventListener("keyup", e => {
if(e.key == "Enter"){
if (!e.shiftKey) {
sendMessage();
}
}
})
// WebSocket 객체 chattingSock이 서버로 부터 메세지를 통지 받으면 자동으로 실행될 콜백 함수
chattingSock.onmessage = function(e) {
// 메소드를 통해 전달받은 객체값을 JSON객체로 변환해서 obj 변수에 저장.
const msg = JSON.parse(e.data);
console.log(msg);
// 현재 채팅방을 보고있는 경우
if(selectChattingNo == msg.chattingNo){
const ul = document.querySelector(".display-chatting");
// 메세지 만들어서 출력하기
//<li>, <li class="my-chat">
const li = document.createElement("li");
// 보낸 시간
const span = document.createElement("span");
span.classList.add("chatDate");
span.innerText = msg.sendTime;
// 메세지 내용
const p = document.createElement("p");
p.classList.add("chat");
p.innerHTML = msg.messageContent; // br태그 해석을 위해 innerHTML
// 내가 작성한 메세지인 경우
if(loginMemberNo == msg.senderNo){
li.classList.add("my-chat");
li.append(span, p);
}else{ // 상대가 작성한 메세지인 경우
li.classList.add("target-chat");
// 상대 프로필
// <img src="/resources/images/user.png">
const img = document.createElement("img");
img.setAttribute("src", selectTargetProfile);
const div = document.createElement("div");
// 상대 이름
const b = document.createElement("b");
b.innerText = selectTargetName; // 전역변수
const br = document.createElement("br");
div.append(b, br, p, span);
li.append(img,div);
}
ul.append(li)
display.scrollTop = display.scrollHeight; // 스크롤 제일 밑으로
}
selectRoomList();
}
// 문서 로딩 완료 후 수행할 기능
document.addEventListener("DOMContentLoaded", ()=>{
// 채팅방 목록에 클릭 이벤트 추가
roomListAddEvent();
// 보내기 버튼에 이벤트 추가
send.addEventListener("click", sendMessage);
});
-- 채팅
CREATE TABLE "CHATTING_ROOM" (
"CHATTING_NO" NUMBER NOT NULL,
"CH_CREATE_DATE" DATE DEFAULT SYSDATE NOT NULL,
"OPEN_MEMBER" NUMBER NOT NULL,
"PARTICIPANT" NUMBER NOT NULL
);
COMMENT ON COLUMN "CHATTING_ROOM"."CHATTING_NO" IS '채팅방 번호';
COMMENT ON COLUMN "CHATTING_ROOM"."CH_CREATE_DATE" IS '채팅방 생성일';
COMMENT ON COLUMN "CHATTING_ROOM"."OPEN_MEMBER" IS '개설자 회원 번호';
COMMENT ON COLUMN "CHATTING_ROOM"."PARTICIPANT" IS '참여자 회원 번호';
ALTER TABLE "CHATTING_ROOM" ADD CONSTRAINT "PK_CHATTING_ROOM" PRIMARY KEY (
"CHATTING_NO"
);
ALTER TABLE "CHATTING_ROOM"
ADD CONSTRAINT "FK_OPEN_MEMBER"
FOREIGN KEY ("OPEN_MEMBER") REFERENCES "MEMBER";
ALTER TABLE "CHATTING_ROOM"
ADD CONSTRAINT "FK_PARTICIPANT"
FOREIGN KEY ("PARTICIPANT") REFERENCES "MEMBER";
DROP TABLE "MESSAGE";
CREATE TABLE "MESSAGE" (
"MESSAGE_NO" NUMBER NOT NULL,
"MESSAGE_CONTENT" VARCHAR2(4000) NOT NULL,
"READ_FL" CHAR DEFAULT 'N' NOT NULL,
"SEND_TIME" DATE DEFAULT SYSDATE NOT NULL,
"SENDER_NO" NUMBER NOT NULL,
"CHATTING_NO" NUMBER NOT NULL
);
COMMENT ON COLUMN "MESSAGE"."MESSAGE_NO" IS '메세지 번호';
COMMENT ON COLUMN "MESSAGE"."MESSAGE_CONTENT" IS '메세지 내용';
COMMENT ON COLUMN "MESSAGE"."READ_FL" IS '읽음 여부';
COMMENT ON COLUMN "MESSAGE"."SEND_TIME" IS '메세지 보낸 시간';
COMMENT ON COLUMN "MESSAGE"."SENDER_NO" IS '보낸 회원의 번호';
COMMENT ON COLUMN "MESSAGE"."CHATTING_NO" IS '채팅방 번호';
ALTER TABLE "MESSAGE" ADD CONSTRAINT "PK_MESSAGE" PRIMARY KEY (
"MESSAGE_NO"
);
ALTER TABLE "MESSAGE"
ADD CONSTRAINT "FK_CHATTING_NO"
FOREIGN KEY ("CHATTING_NO") REFERENCES "CHATTING_ROOM";
ALTER TABLE "MESSAGE"
ADD CONSTRAINT "FK_SENDER_NO"
FOREIGN KEY ("SENDER_NO") REFERENCES "MEMBER";
-- 시퀀스 생성
CREATE SEQUENCE SEQ_ROOM_NO NOCACHE;
CREATE SEQUENCE SEQ_MESSAGE_NO NOCACHE;
SELECT * FROM "MEMBER";
SELECT * FROM "CHATTING_ROOM";
SELECT * FROM "MESSAGE";
INSERT INTO "CHATTING_ROOM"
VALUES(SEQ_ROOM_NO.NEXTVAL, SYSDATE, 1, 11);
COMMIT;
ROLLBACK;


