7. (실습) 스프링 V1 (기본 - CRUD) (1)

박은서's avatar
Feb 05, 2026
7. (실습) 스프링 V1 (기본 - CRUD) (1)
💡
CRUD
  • Create (생성): 데이터베이스나 파일 시스템에 새로운 데이터를 생성하고 추가하는 기능
  • Read (읽기/조회): 저장된 데이터를 검색하거나 조회하는 기능
  • Update (갱신/수정): 기존 데이터를 수정하거나 변경하는 기능
  • Delete (삭제): 데이터를 삭제하거나 제거하는 기능
프로젝트명 : boardv1
프로젝트명 : boardv1

[참고] 참고 코드

notion image

1. mustache 파일 생성

압축 폴더 다운 받기
압축 폴더 다운 받기
7-zip으로 압축 풀기
7-zip으로 압축 풀기
파일 드래그해서 복사
파일 드래그해서 복사
templates 폴더 선택해서 ctrl+V
templates 폴더 선택해서 ctrl+V

[참고] HTML <head>에서 외부 CSS 로딩

[참고] HTML <head>에서 외부 CSS 로딩

1) header.mustache

C:\workspace\spring_lab\boardv1\src\main\resources\templates\header.mustache
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script> </head> <body> <nav class="navbar navbar-expand-sm" style="background-color: grey;"> <div class="container-fluid"> <ul class="navbar-nav"> <li class="nav-item"> <a class="nav-link" href="/" style="font-weight: bold; color: white">Metacoding</a> </li> <li class="nav-item"> <a class="nav-link" href="/boards/save-form" style="color: white">글쓰기</a> </li> <li class="nav-item"> <a class="nav-link" href="/logout" style="color: white">로그아웃</a> </li> <li class="nav-item"> <a class="nav-link" href="/join-form" style="color: white">회원가입</a> </li> <li class="nav-item"> <a class="nav-link" href="/login-form" style="color: white">로그인</a> </li> </ul> </div> </nav> </div>

2) index.mustache

C:\workspace\spring_lab\boardv1\src\main\resources\templates\index.mustache
{{> header}} <div class="container mt-3"> <table class="table table-hover"> <thead> <tr> <th>번호</th> <th>제목</th> <th>내용</th> </tr> </thead> <tbody> <tr> <td>3</td> <td> <a href="/boards/1" style="color: inherit; text-decoration: none;"> 제목3 </a> </td> <td>내용3</td> </tr> <tr> <td>2</td> <td> <a href="/boards/1" style="color: inherit; text-decoration: none;"> 제목2 </a> </td> <td>내용2</td> </tr> <tr> <td>1</td> <td> <a href="/boards/1" style="color: inherit; text-decoration: none;"> 제목1 </a> </td> <td>내용1</td> </tr> </tbody> </table> </div> </body> </html>

3) detail.mustache

C:\workspace\spring_lab\boardv1\src\main\resources\templates\board\detail.mustache
{{> header}} <div class="container p-5"> <!-- 수정삭제버튼 --> <div class="d-flex justify-content-end"> <a href = "/boards/1/update-form" class="btn btn-secondary me-1">수정</a> <button class="btn btn-outline-secondary">삭제</button> </div> <!-- 게시글내용 --> <div> <h2><b>제목</b></h2> <hr /> <div class="d-flex justify-content-end"> 작성자 : 익명 </div> <div class="m-4 p-2"> 내용 </div> </div> <!-- 댓글 --> <div class="card mt-3"> <!-- 댓글등록 --> <div class="card-body"> <form action="/replies/save" method="post"> <textarea class="form-control" rows="2" name="comment"></textarea> <div class="d-flex justify-content-end"> <button class="btn btn-secondary mt-1"> 댓글등록 </button> </div> </form> </div> <!-- 댓글목록 --> <div class="card-footer"> <b>댓글리스트</b> </div> <div class="list-group"> <!-- 댓글아이템 --> <div class="list-group-item d-flex justify-content-between align-items-center"> <div class="d-flex"> <div class="px-1 me-1 bg-secondary text-white rounded">익명</div> <div>댓글 내용2</div> </div> <form action="/replies/1/delete" method="post"> <button class="btn">🗑</button> </form> </div> <!-- 댓글아이템 --> <div class="list-group-item d-flex justify-content-between align-items-center"> <div class="d-flex"> <div class="px-1 me-1 bg-secondary text-white rounded">익명</div> <div>댓글 내용1</div> </div> <form action="/replies/1/delete" method="post"> <button class="btn">🗑</button> </form> </div> </div> </div> </div> </body> </html>

4) save-form.mustache

C:\workspace\spring_lab\boardv1\src\main\resources\templates\board\save-form.mustache
{{> header}} <div class="container p-5"> <div class="card"> <div class="card-header"><b>게시글 작성</b></div> <div class="card-body"> <form action="/boards/save" method="post" enctype="application/x-www-form-urlencoded"> <div class="mb-3"> <input type="text" class="form-control" placeholder="Enter title" name="title"> </div> <div class="mb-3"> <textarea class="form-control" rows="5" name="content"></textarea> </div> <button class="btn btn-secondary form-control">글쓰기</button> </form> </div> </div> </div> </body> </html>

5) update-form.mustache

C:\workspace\spring_lab\boardv1\src\main\resources\templates\board\update-form.mustache
{{> header}} <div class="container p-5"> <div class="card"> <div class="card-header"><b>게시글 수정</b></div> <div class="card-body"> <form action="/boards/1/update" method="post" enctype="application/x-www-form-urlencoded"> <div class="mb-3"> <input type="text" class="form-control" placeholder="Enter title" name="title"> </div> <div class="mb-3"> <textarea class="form-control" rows="5" name="content"></textarea> </div> <button class="btn btn-secondary form-control">글수정하기</button> </form> </div> </div> </div> </body> </html>

2. application.properties 설정

💡

application.properties의 용도

  • Spring Framework / Spring Boot 애플리케이션의 전역 설정(configuration)을 정의하는 핵심 설정 파일
  • 애플리케이션의 동작 방식, 실행 환경, 외부 리소스 연결 정보 등을 코드와 분리해서 관리

1) application.properties

C:\workspace\spring_lab\boardv1\src\main\resources\application.properties
# ===== Server ===== server.port=8080 spring.servlet.encoding.charset=UTF-8 spring.servlet.encoding.enabled=true spring.servlet.encoding.force=true # ===== Mustache ===== spring.mustache.servlet.expose-session-attributes=true spring.mustache.servlet.expose-request-attributes=true # ===== ansi ===== spring.output.ansi.enabled=always # ===== H2 Datasource ===== spring.datasource.driver-class-name=org.h2.Driver spring.datasource.url=jdbc:h2:mem:test spring.datasource.username=sa spring.datasource.password= spring.h2.console.enabled=true
💡
H2 Datasource는 새로 열 때마다 초기화되고 Board.java 대로 테이블 생성
테스트할 때는 좋지만 실제로는 사용하지 않음!

2) 테스트

notion image
notion image
notion image
notion image
notion image
notion image

3. 테이블 세팅

1) Board.java

C:\workspace\spring_lab\boardv1\src\main\java\com\example\boardv1\board\Board.java
package com.example.boardv1.board; import java.sql.Timestamp; import jakarta.persistence.Entity; import jakarta.persistence.Table; import lombok.Data; import lombok.NoArgsConstructor; /** * 데이터베이스 세상의 테이블을 자바 세상에 모델링한 결과 = 엔티티 */ @NoArgsConstructor // 디폴트 생성자 @Data // getter, setter, toString @Entity // 해당 어노테이션을 보고 컴퍼넌트 스캔 후 데이터베이스에 테이블을 생성 @Table(name = "board_tb") // 테이블명 설정 public class Board { private Integer id; private String title; private String content; private Timestamp createdAt; // import 주의!! (java.sql) }

2) 테스트 (에러)

notion image
notion image
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/hibernate/autoconfigure/HibernateJpaConfiguration.class]: Entity 'com.example.boardv1.board.Board' has no identifier (every '@Entity' class must declare or inherit at least one '@Id' or '@EmbeddedId' property)
➡️ 빈을 생성하는 과정에서 Board 엔티티가 @Id(또는 @EmbeddedId)로 지정된 Primary Key를 가지고 있지 않아, JPA 규칙을 위반하여 빈 생성이 실패한 오류
➡️ Board.java에서 적어도 하나는 @Id를 붙여야 함
※ Bean = new된 객체

3) Board.java - 수정

package com.example.boardv1.board; import java.sql.Timestamp; import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.Data; import lombok.NoArgsConstructor; /** * 데이터베이스 세상의 테이블을 자바 세상에 모델링한 결과 = 엔티티 */ @NoArgsConstructor // 디폴트 생성자 @Data // getter, setter, toString @Entity // 해당 어노테이션을 보고 컴퍼넌트 스캔 후 데이터베이스에 테이블을 생성 @Table(name = "board_tb") // 테이블명 설정 public class Board { @Id // id를 primary key로 설정 @GeneratedValue(strategy = GenerationType.IDENTITY) // auto increment 설정 private Integer id; private String title; private String content; private Timestamp createdAt; // import 주의!! (java.sql) }
💡
@Entity, @Table 안 붙이면 @Id도 안 붙여도 됨!
💡
@Table 을 안 붙이면 클래스명이 테이블명이 됨
단, 테이블명으로 만들 수 없는 이름들이 있기 때문에 그럴 경우 테이블 안 만들어짐!
그래서 @Table로 테이블명 설정해줘야 함!

4) 테스트 (성공)

notion image
notion image
 
notion image

4. 더미데이터 세팅

1) data.sql

C:\workspace\spring_lab\boardv1\src\main\resources\db\data.sql
insert into board_tb (title, content, created_at) values ('title1', 'content1', now()); insert into board_tb (title, content, created_at) values ('title2', 'content2', now());
💡
Table.java에는 카멜 표기법으로 적었지만, 실제 화면에는 언더 스코어
더미 데이터 세팅 시에도 언더 스코어로 적어야 함! 카멜 표기법으로 적으면 더미 데이터 안 들어감!

2) application.properties 에 추가

# ===== SQL Init (data.sql) ===== spring.sql.init.data-locations=classpath:db/data.sql spring.jpa.defer-datasource-initialization=true
💡
Hibernate(JPA)가 테이블을 생성한 뒤에 INSERT SQL을 실행

3) 테스트

notion image

4) data.sql - 수정 (더미데이터 6건)

insert into board_tb (title, content, created_at) values ('title1', 'content1', now()); insert into board_tb (title, content, created_at) values ('title2', 'content2', now()); insert into board_tb (title, content, created_at) values ('title3', 'content3', now()); insert into board_tb (title, content, created_at) values ('title4', 'content4', now()); insert into board_tb (title, content, created_at) values ('title5', 'content5', now()); insert into board_tb (title, content, created_at) values ('title6', 'content6', now());
Share article