Contents
README.md1. DTO 생성 및 적용1) BoardRequest.java2) BoardResponse.java2) BoardController.java2. User 관련 templates 추가 및 화면 연결1) join-form.mustache2) login-form.mustache 추가3) UserControlloer.java4) 테스트3. 테이블 세팅1) User.java2) 쿼리 테스트4. 더미데이터 세팅1) data.sql 에 추가5. UserRepository 메서드 생성1) UserReoitory.java6. UserRepository 메서드 테스트1) UserRepositoryTest.java2) save_test() 결과3) save_fail_test() 결과4) findByUsername_test() 결과README.md
# 인증 블로그 v2
## 1. 기술스택
- session, cookie
- orm
- lazy loading
- response(응답) DTO (왜 필요한지?)
- Optional, Stream API(map, filter, 어부(물가, 가공, 수집))
- 권한(403)과 인증(401)
## 2. 리펙토링
- ResponseDTO 내부클래스로 수정
## 3. 기능
- 회원가입 (아이디 중복체크)
- 로그인 (쿠키)
- 게시글 쓰기 (인증이 된 사람 - 수정)
- 게시글 상세 보기 (인증/권한 체크, DTO 만들기 - 수정)
- 게시글 수정/삭제 (인증/권한 체크 - 수정)
## 4. Task
### 1. 회원가입
- 그림 다운로드 (v)
- user폴더 UserController 만들어서 그림 연결 (v)
- User 테이블 생성 - 더미데이터
- UserRepository만들어서 DB 테스트 코드
- 컨트롤러, 서비스, 레포 연결해서 기능 완료하기
### 2. 로그인
- 컨트롤러, 서비스, DTO, 세션1. DTO 생성 및 적용
1) BoardRequest.java
C:\workspace\spring_lab\boardv1\src\main\java\com\example\boardv1\board\BoardRequest.java
package com.example.boardv1.board;
import lombok.Data;
public class BoardRequest {
// 책임 : 클라이언트(브라우저)의 요청 데이터를 저장하는 클래스
@Data
public static class SaveOrUpdateDTO {
private String title;
private String content;
}
}2) BoardResponse.java
C:\workspace\spring_lab\boardv1\src\main\java\com\example\boardv1\board\BoardResponse.java
package com.example.boardv1.board;
import java.util.List;
import com.example.boardv1.reply.ReplyResponse;
import lombok.Data;
public class BoardResponse {
@Data
public static class DetailDTO {
// 화면에 보이지 않는 것 (PK는 화면에 안보여도 무조건 적어야 함!)
private int id;
private int userId;
// 화면에 보이는 것
private String title;
private String content;
private String username;
// 연산해서 만들어야 되는 것
private boolean isOwner; // 게시글의 주인인가?
public DetailDTO(Board board, Integer sessionUserId) {
this.id = board.getId();
this.userId = board.getUser().getId();
this.title = board.getTitle();
this.content = board.getContent();
this.username = board.getUser().getUsername();
this.isOwner = board.getUser().getId() == sessionUserId;
this.replies = board.getReplies().stream()
.map(reply -> new ReplyResponse.DTO(reply, sessionUserId))
.toList();
}
}
}
2) BoardController.java
C:\workspace\spring_lab\boardv1\src\main\java\com\example\boardv1\board\BoardController.java
package com.example.boardv1.board;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.example.boardv1.reply.ReplyRequest;
import com.example.boardv1.user.User;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor // final이 적혀있는 애로 생성자 만들어줌
@Controller // @Controller 적어야 리턴값이 파일이 됨 (외부진입점)
public class BoardController {
private final BoardService boardService;
private final HttpSession session;
// title : title=title7&content=content7 (x-www-form)
@PostMapping("/boards/save")
public String save(BoardRequest.SaveOrUpdateDTO reqDTO){ // new해서 넣어줌! 필드가 많을 때 상태만 추가하면 되기 때문에 매우 편함! 재사용 가능!(필드명 잘 적어야 함)
// 인증(v) 권한(x)
// 인증
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser == null)
throw new RuntimeException("인증되지 않았습니다.");
boardService.게시글쓰기(reqDTO.getTitle(), reqDTO.getContent(), sessionUser.getId());
return "redirect:/";
}
// body : title=제목&content=내용
@PostMapping("/boards/{id}/update")
public String update(@PathVariable("id") int id, BoardRequest.SaveOrUpdateDTO reqDTO){
// 인증(v) 권한(v)
// 인증
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser == null)
throw new RuntimeException("인증되지 않았습니다.");
boardService.게시글수정(id,reqDTO.getTitle(), reqDTO.getContent(), sessionUser.getId());
return "redirect:/boards/"+id;
}
@GetMapping("/")
public String index(HttpServletRequest req){
List<Board> list = boardService.게시글목록();
req.setAttribute("models", list);
return "index";
}
@GetMapping("/boards/save-form")
public String saveForm(){
// 인증(v) 권한(x)
// 인증
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser == null)
throw new RuntimeException("인증되지 않았습니다.");
return "board/save-form";
}
@GetMapping("/boards/{id}/update-form")
public String updateForm(@PathVariable("id") int id, HttpServletRequest req){
// 인증(v) 권한(v)
// 인증
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser == null)
throw new RuntimeException("인증되지 않았습니다.");
Board board = boardService.수정폼게시글정보(id, sessionUser.getId());
req.setAttribute("model", board);
return "board/update-form";
}
@GetMapping("/boards/{id}")
public String detail(@PathVariable("id") int id, HttpServletRequest req){
User sessionUser = (User) session.getAttribute("sessionUser"); // Object타입으로 반환하기 때문에 다운캐스팅해야 함
Integer sessionUserId = sessionUser == null ? null : sessionUser.getId();
BoardResponse.DetailDTO dto = boardService.상세보기(id, sessionUserId);
req.setAttribute("model", dto);
return "board/detail"; // mustache 파일의 경로
}
@PostMapping("/boards/{id}/delete")
public String delete(@PathVariable("id") int id){
// 인증(v) 권한(v)
// 인증
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser == null)
throw new RuntimeException("인증되지 않았습니다.");
boardService.게시글삭제(id, sessionUser.getId());
return "redirect:/";
}
@GetMapping("/api/boards/{id}")
public @ResponseBody BoardResponse.DetailDTO apiDetail(@PathVariable("id") int id) {
User sessionUser = (User) session.getAttribute("sessionUser");
Integer sessionUserId = sessionUser == null ? null : sessionUser.getId();
BoardResponse.DetailDTO dto = boardService.상세보기(id, sessionUserId);
return dto;
}
}
2. User 관련 templates 추가 및 화면 연결
1) join-form.mustache
C:\workspace\spring_lab\boardv1\src\main\resources\templates\user\join-form.mustache
{{> header}}
<div class="container p-5">
<div class="card">
<div class="card-header"><b>회원가입 페이지</b></div>
<div class="card-body">
<form action="/join" method="post" enctype="application/x-www-form-urlencoded">
<div class="mb-3">
<input type="text" class="form-control" placeholder="Enter username" name="username">
</div>
<div class="mb-3">
<input type="password" class="form-control" placeholder="Enter password" name="password">
</div>
<div class="mb-3">
<input type="email" class="form-control" placeholder="Enter email" name="email">
</div>
<button class="btn btn-secondary form-control">회원가입</button>
</form>
</div>
</div>
</div>
</body>
</html>2) login-form.mustache 추가
C:\workspace\spring_lab\boardv1\src\main\resources\templates\user\login-form.mustache
{{> header}}
<div class="container p-5">
<div class="card">
<div class="card-header"><b>로그인 페이지</b></div>
<div class="card-body">
<form action="/login" method="post" enctype="application/x-www-form-urlencoded">
<div class="mb-3">
<input type="text" class="form-control" placeholder="Enter username" name="username">
</div>
<div class="mb-3">
<input type="password" class="form-control" placeholder="Enter password" name="password">
</div>
<button class="btn btn-secondary form-control">로그인</button>
</form>
</div>
</div>
</div>
</body>
</html>3) UserControlloer.java
C:\workspace\spring_lab\boardv1\src\main\java\com\example\boardv1\user\UserControlloer.java
package com.example.boardv1.user;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class UserControlloer {
@GetMapping("/login-form")
public String loginForm(){
return "user/login-form";
}
@GetMapping("/join-form")
public String joinForm(){
return "user/join-form";
}
}4) 테스트


3. 테이블 세팅
1) User.java
C:\workspace\spring_lab\boardv1\src\main\java\com\example\boardv1\user\User.java
package com.example.boardv1.user;
import java.time.LocalDateTime;
import org.hibernate.annotations.CreationTimestamp;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@NoArgsConstructor // object mapping을 hibernate가 할 때 디폴트 생성자를 new한다.
@Getter
@Setter
@Entity
@Table(name = "user_tb")
public class User {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Id
private Integer id;
@Column(unique = true) // 테이블의 제약조건 (pk가 unique일 때 인덱스를 만들어준다)
private String username;
@Column(nullable = false, length = 100) // password 컬럼은 null일 수 없다! 길이는 100자!
private String password;
private String email;
@CreationTimestamp
private LocalDateTime createdAt;
@Override
public String toString() {
return "User [id=" + id + ", username=" + username + ", password=" + password + ", email=" + email
+ ", createdAt=" + createdAt + "]";
}
}2) 쿼리 테스트

4. 더미데이터 세팅
1) data.sql 에 추가
insert into user_tb (username, passowrd, email) values ('ssar', '1234', 'ssar@nate.com');
insert into user_tb (username, passowrd, email) values ('cos', '1234', 'cos@nate.com');5. UserRepository 메서드 생성
1) UserReoitory.java
package com.example.boardv1.user;
import org.springframework.stereotype.Repository;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Repository
public class UserRepository {
// DI
private final EntityManager em;
// 회원가입할 때 insert
public User save(User user) {
em.persist(user);
return user;
}
// 로그인할 때 username으로 조회해서 password 검증
public User findByUsername(String username) {
Query query = em.createQuery("select u from User u where u.username = :username", User.class);
query.setParameter("username", username);
return (User) query.getSingleResult();
}
public User findById(int id) {
return em.find(User.class, id);
}
}6. UserRepository 메서드 테스트
1) UserRepositoryTest.java
package com.example.boardv1.user;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest;
import org.springframework.context.annotation.Import;
@Import(UserRepository.class)
@DataJpaTest // EntityManager가 IoC에 등록됨
public class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Test
public void save_test() {
// given
User user = new User(); // 비영속 객체
user.setUsername("love");
user.setPassword("1234");
user.setEmail("love@nate.com");
// when
User findUser = userRepository.save(user); // 영속화됨
// eye
System.out.println(findUser);
}
@Test
public void save_fail_test() {
// given
User user = new User(); // 비영속 객체
user.setUsername("ssar");
user.setPassword("1234");
user.setEmail("ssar@nate.com");
// when
User findUser = userRepository.save(user); // 영속화됨
// eye
System.out.println(findUser);
}
@Test
public void fintByUsername_test() {
// given
String useString = "ssar";
// when
User findUser = userRepository.findByUsername(useString);
// eye
System.out.println(findUser);
}
}2) save_test() 결과

3) save_fail_test() 결과

org.hibernate.exception.ConstraintViolationException: could not execute statement [Unique index or primary key violation: "PUBLIC.CONSTRAINT_2 INDEX PUBLIC.CONSTRAINT_INDEX_2 ON PUBLIC.USER_TB(USERNAME NULLS FIRST) VALUES ( /* 1 */ 'ssar' )"; SQL statement:
insert into user_tb (created_at,email,password,username,id) values (?,?,?,?,default) [23505-240]] [insert into user_tb (created_at,email,password,username,id) values (?,?,?,?,default)]
➡️ 데이터베이스의 유니크 제약조건(UNIQUE / PRIMARY KEY)을 위반했다는 뜻
4) findByUsername_test() 결과

Share article