1. Board Package
1. BoardRepository(interface)
package org.example.springv3.board;
import org.example.springv3.user.User;
import jakarta.persistence.EntityManager;
import lombok.RequiredArgsConstructor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.data.repository.query.Param;
import java.util.List;
import java.util.Optional;
import java.util.Optional;
public interface BoardRepository extends JpaRepository<Board, Integer> {
//join 쿼리
//@Query(value = "select * from board_tb bt inner join user_tb on bt.user_id = ut.id wherebt.id =?", nativeQuery = true)
@Query("select b from Board b join fetch b.user u where b.id=:id")
//하나를 찾을 때는 Optional을 쓰는게 좋다.
Optional<Board> mFindById(@Param("id") int id);
@Query("select b from Board b order by b.id desc")
List<Board> mFindAll();
}
- 코드설명
BoardRepository
인터페이스 정의
java코드 복사
public interface BoardRepository extends JpaRepository<Board, Integer> {
JpaRepository
확장:BoardRepository
는JpaRepository<Board,Integer>
를 확장합니다. 여기서Board
는 엔티티 클래스이며,Integer
는 해당 엔티티의 기본 키 타입입니다. 이를 통해 기본적인 CRUD 메서드 (save, findById, findAll 등)를 사용할 수 있게 됩니다.
3. 커스텀 메서드 정의
3.1. mFindById
메서드
@Query("select b from Board b join fetch b.user u where b.id=:id")
Optional<Board> mFindById(@Param("id") int id);
- 설명: 이 메서드는
Board
엔티티와 관련된User
엔티티를 함께 조회하는 JPQL 쿼리를 사용하여 특정 ID의 게시글을 찾습니다.
@Query
어노테이션: JPQL 쿼리를 직접 정의하여 사용합니다.Board
와User
테이블을 조인하여Board
를 조회합니다.
Optional<Board>
: 주어진 ID로 게시글을 찾지 못했을 때null
대신 빈Optional
객체를 반환합니다. 이는null
처리보다 안전한 방법입니다.
:id
매개변수:@Param("id")
어노테이션을 사용하여 JPQL 쿼리 내의:id
변수와 메서드 매개변수id
를 매핑합니다.
3.2. mFindAll
메서드
@Query("select b from Board b order by b.id desc")
List<Board> mFindAll();
- 설명: 모든
Board
엔티티를 ID 내림차순으로 조회하는 메서드입니다.
@Query
어노테이션: JPQL 쿼리를 직접 정의하여 사용합니다.
- 반환 타입
List<Board>
: 여러 개의Board
엔티티를 반환합니다.
요약
이
BoardRepository
인터페이스는 Spring Data JPA를 사용하여 데이터베이스와의 상호작용을 추상화한 것입니다. 기본적인 CRUD 기능 외에도 커스텀 JPQL 쿼리를 정의하여 추가 기능을 제공하고 있습니다. @Query
어노테이션을 사용하여 복잡한 쿼리 로직을 쉽게 작성할 수 있으며, Optional
을 사용하여 안전하게 객체를 처리합니다.2. BoardService
public List<Board> 게시글목록보기() {
//Pageable pg = PageRequest.of(0, 3, Sort.Direction.DESC, "id");
Sort sort = Sort.by(Sort.Direction.DESC, "id");
List<Board> boardList = boardRepository.findAll(sort);
return boardList;
}
@Transactional
public void 게시글삭제하기(Integer id, User sessionUser) {
Board board = boardRepository.findById(id)
.orElseThrow(()-> new Exception404("게시글을 찾을 수 없습니다."));
if (board.getUser().getId() != sessionUser.getId()) {
throw new Exception403("작성자가 아닙니다.");
}
boardRepository.deleteById(id);
}
public Board 게시글수정화면(int id, User sessionUser) {
Board board = boardRepository.findById(id).orElseThrow(()-> new Exception404("게시글을 찾을 수 없습니다."));
//위 코드랑 똑같은 코드다!
// Optional<Board> boardOP = boardRepository.findById(1);
// if(boardOP.isEmpty()){
// throw new Exception404("게시글을 찾을 수 없습니다");
//}
//Board board = boardOP.get();
if (board.getUser().getId() != sessionUser.getId()) {
throw new Exception403("게시글 수정 권한이 없습니다.");
}
return board;
}
@Transactional
public void 게시글수정(int id, BoardRequest.UpdateDTO updateDTO, User sessionUser) {
// 1. 게시글 조회 (없으면 404)
Board board = boardRepository.findById(id)
.orElseThrow(()-> new Exception404("게시글을 찾을 수 없습니다."));
// 2. 권한체크
if (board.getUser().getId() != sessionUser.getId()) {
throw new Exception403("게시글을 수정할 권한이 없습니다");
}
// 3. 게시글 수정하기
board.setTitle(updateDTO.getTitle());
board.setContent(updateDTO.getContent());
}
public BoardResponse.DetailDTO 게시글상세보기(User sessionUser, Integer boardId){
Board boardPS = boardRepository.findById(boardId)
.orElseThrow(() -> new Exception404("게시글이 없습니다."));
return new BoardResponse.DetailDTO(boardPS,sessionUser);
}
}
- 코드 설명
반복되는 코드
여러 메서드에서 다음과 같은 코드가 반복되고 있습니다:
Board board = boardRepository.findById(boardId)
.orElseThrow(() -> new Exception404("게시글이 없습니다."));
코드 설명
boardRepository.findById(boardId)
:boardRepository
는JpaRepository
를 확장한 인터페이스로,Board
엔티티와 관련된 데이터베이스 작업을 수행합니다.findById(boardId)
메서드는boardId
에 해당하는Board
엔티티를 데이터베이스에서 조회합니다. 이 메서드는Optional<Board>
를 반환합니다.Optional
은 Java 8에 도입된 클래스이며, 값이 존재할 수도 있고 없을 수도 있는 컨테이너를 제공합니다. 이는null
을 처리하는 더 안전한 방법을 제공하며,NullPointerException
을 피할 수 있습니다.
orElseThrow(() -> new Exception404("게시글이 없습니다."))
:Optional
클래스의orElseThrow
메서드를 사용하여, 값이 존재하지 않을 경우(Optional
이 비어 있는 경우) 특정 예외를 던지도록 합니다.orElseThrow
메서드는Supplier
타입의 람다식을 매개변수로 받습니다. 이 람다식은 예외를 생성하여 던지는 역할을 합니다.() -> new Exception404("게시글이 없습니다.")
는 람다식으로,Supplier<Exception404>
타입입니다. 이 람다식은 예외 객체를 생성하고 반환합니다.- 만약
findById
가 비어 있는Optional
을 반환하면, 이 람다식이 실행되어Exception404
예외가 던져집니다.
람다식 설명
- 람다식 (
() -> new Exception404("게시글이 없습니다.")
): - 람다식은 Java 8에서 도입된 기능으로, 익명 함수(anonymous function)를 생성하는 간결한 문법을 제공합니다.
Supplier<Exception404>
인터페이스는 매개변수를 받지 않고 결과를 반환하는 함수형 인터페이스입니다.- 람다식에서
()
는 매개변수가 없음을 나타내고,>
뒤의 표현식은 실행 결과를 나타냅니다. new Exception404("게시글이 없습니다.")
는 실행될 코드이며,Exception404
객체를 생성합니다.- 이 람다식은
Supplier
의get()
메서드를 구현하는 것으로 간주됩니다.
예외 처리의 중요성
- 이 코드 패턴은 데이터베이스에서 조회할 때 발생할 수 있는
null
또는 비어 있는 값 처리에 유용합니다.
- 명시적으로 예외를 던짐으로써, 호출하는 쪽에서 문제 상황을 인지하고 적절한 대응을 할 수 있도록 합니다.
- 예를 들어, 게시글을 찾지 못한 경우 404 응답을 반환하거나 사용자에게 적절한 메시지를 표시할 수 있습니다.
2.core Package
1. config → WebConfig
package org.example.springv3.core.config;
import org.example.springv3.core.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
// 인터셉터 작동안되게 주석처리 (호정)
//@Configuration // IoC에 저장됨
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/api/**");
}
}
- 코드설명
Spring 프레임워크에서 **인터셉터(Interceptor)**를 등록하고 설정하는 부분입니다. Spring MVC에서 인터셉터는 컨트롤러에 요청이 전달되기 전이나 후에 특정 로직을 수행할 수 있도록 도와주는 컴포넌트입니다.
코드 설명
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/api/**");
}
1. 메서드 오버라이딩
@Override
: 이 메서드는 상위 클래스 또는 인터페이스에 정의된 메서드를 재정의(오버라이딩)하고 있음을 나타냅니다.
addInterceptors(InterceptorRegistry registry)
: Spring MVC의 설정 클래스에서 인터셉터를 추가하기 위해WebMvcConfigurer
인터페이스의addInterceptors
메서드를 오버라이드한 것입니다.
2. InterceptorRegistry
InterceptorRegistry
: 인터셉터를 등록하고 설정할 수 있는 레지스트리 객체입니다.
- 이 객체를 통해 여러 인터셉터를 체인으로 등록하거나 특정 URL 패턴에 대해 인터셉터가 작동하도록 설정할 수 있습니다.
3. 인터셉터 추가
registry.addInterceptor(new LoginInterceptor())
:LoginInterceptor
라는 이름의 인터셉터 인스턴스를 등록합니다.new LoginInterceptor()
: 실제로 인터셉터 역할을 하는 클래스의 인스턴스를 생성합니다.LoginInterceptor
는 사용자 정의 인터셉터로, Spring의HandlerInterceptor
인터페이스를 구현해야 합니다. 이 인터페이스에는preHandle
,postHandle
,afterCompletion
메서드가 포함되어 있으며, 이 메서드들을 통해 요청 전후 및 요청 완료 후의 로직을 구현할 수 있습니다.
4. URL 패턴 설정
.addPathPatterns("/api/**")
: 인터셉터가 작동할 URL 패턴을 설정합니다."/api/**"
:/api/
로 시작하는 모든 URL에 대해LoginInterceptor
가 작동하도록 지정합니다.*
는 하위 모든 경로를 포함하는 와일드카드 문자입니다. 예를 들어,/api/login
,/api/users
,/api/products/123
등의 경로가 모두 포함됩니다.
인터셉터의 역할
LoginInterceptor
는 요청이 지정된 URL 패턴과 일치할 때마다 특정 로직을 실행하는 역할을 합니다. 예를 들어, LoginInterceptor
는 다음과 같은 기능을 수행할 수 있습니다:- 로그인 확인: 사용자가 로그인된 상태인지 확인하고, 로그인되지 않았다면 로그인 페이지로 리다이렉트합니다.
- 인증 및 권한 검사: 사용자의 역할(Role)과 권한(Permission)을 검사하여, 특정 경로에 접근할 자격이 있는지 확인합니다.
- 로깅 및 감사: 요청에 대한 로그를 남기거나 감사(audit) 용도로 사용될 수 있습니다.
- 기타 사전 및 사후 처리: 요청 전후에 공통적으로 처리해야 할 로직을 포함할 수 있습니다. 예를 들어, 보안 검증, 요청 데이터를 변환 또는 포맷팅하는 작업 등을 포함할 수 있습니다.
정리
이 코드 블록은 Spring MVC의 설정 중 하나로, 특정 URL 패턴에 대해 인터셉터를 설정하여, 요청 전후에 공통 로직을 수행할 수 있도록 구성한 것입니다.
LoginInterceptor
와 같은 인터셉터는 보안, 로깅, 데이터 처리 등의 다양한 목적을 위해 활용될 수 있습니다.2. interceptor → Loginlnterceptor
package org.example.springv3.core.interceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.example.springv3.core.error.ex.Exception401;
import org.example.springv3.user.User;
import org.springframework.web.servlet.HandlerInterceptor;
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser == null) {
throw new Exception401("인증되지 않았어요");
}
return true; // false면 컨트롤러 진입안됨
}
}
- 코드설명
Spring MVC에서 사용하는 인터셉터(Interceptor) 중 하나인
LoginInterceptor
클래스의 구현입니다. LoginInterceptor
는 사용자가 특정 요청을 하기 전에 해당 요청을 가로채어 사용자 세션을 검사하고, 인증되지 않은 사용자의 요청을 막는 역할을 합니다. 이 인터셉터는 HandlerInterceptor
인터페이스를 구현하여 정의합니다.세부 설명
- 클래스 선언 및 인터페이스 구현
LoginInterceptor
클래스는 Spring의HandlerInterceptor
인터페이스를 구현합니다.HandlerInterceptor
인터페이스는 HTTP 요청 처리 전후에 공통 로직을 추가할 수 있는 메서드를 제공합니다. 여기에는 세 가지 주요 메서드가 있습니다:preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
: 컨트롤러의 핸들러 메서드가 호출되기 전에 호출됩니다.postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
: 컨트롤러의 핸들러 메서드가 성공적으로 실행된 후에 호출됩니다.afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
: 뷰가 렌더링된 후에 호출됩니다.- 이 코드에서는
preHandle
메서드만 구현되어 있습니다. 이는 요청이 컨트롤러에 도달하기 전에 실행할 로직을 정의합니다.
public class LoginInterceptor implements HandlerInterceptor
preHandle
메서드
sessionUser
가 null
이라면, 이는 사용자가 로그인하지 않았음을 의미합니다.이 경우, Exception401
예외를 던집니다. Exception401
은 사용자 정의 예외로, HTTP 401 Unauthorized 상태를 나타냅니다.리턴 값:return true; // false면 컨트롤러 진입안됨
이 메서드는 boolean
값을 반환합니다. true
를 반환하면 요청이 컨트롤러로 전달되고, false
를 반환하면 요청이 더 이상 처리되지 않습니다.인증된 사용자인 경우 true
를 반환하여 요청이 계속 진행되도록 합니다.인터셉터의 역할
LoginInterceptor
의 주된 역할은 사용자 인증 상태를 확인하고, 인증되지 않은 사용자의 요청을 차단하는 것입니다. 이를 통해 인증되지 않은 사용자가 보호된 API 엔드포인트에 접근하는 것을 방지할 수 있습니다.- 보안 강화: 인터셉터를 사용하면 보안 로직을 일관되게 적용할 수 있으며, 각 컨트롤러 메서드에 개별적으로 인증 검사를 추가할 필요가 없습니다.
- 중앙 집중식 처리: 인터셉터는 모든 요청에 대해 공통적인 사전/사후 처리를 할 수 있는 중앙 집중식 위치를 제공합니다. 이는 코드 중복을 줄이고 유지보수성을 높이는 데 도움이 됩니다.
정리
LoginInterceptor
클래스는 Spring MVC의 인터셉터를 사용하여 사용자가 특정 경로로 요청을 보낼 때마다 로그인 여부를 확인하고, 인증되지 않은 사용자의 접근을 차단합니다. 이를 통해 애플리케이션의 보안을 강화하고, 인증된 사용자만이 특정 API에 접근할 수 있도록 합니다.3. user Package
1. UserRepository(interface)
ackage org.example.springv3.user;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Integer> {
@Query("select u from User u where u.username=:username")
Optional<User> findByUsername(@Param("username") String username);
@Query("select u from User u where u.username=:username and u.password=:password")
Optional<User> findByUsernameAndPassword(@Param("username") String username, @Param("password") String password);
}
- 코드설명
- 인터페이스 선언 및
JpaRepository
상속
public interface UserRepository extends JpaRepository<User, Integer>
UserRepository
는 JpaRepository<User, Integer>
를 확장합니다.User
는 JPA 엔티티 클래스이며, Integer
는 User
엔티티의 기본 키(Primary Key) 타입입니다.JpaRepository
를 상속하면, UserRepository
는 save()
, findById()
, findAll()
, deleteById()
등 기본적인 CRUD 메서드를 자동으로 사용할 수 있게 됩니다.- 커스텀 쿼리 메서드:
findByUsername
java코드 복사
@Query("select u from User u where u.username=:username")
Optional<User> findByUsername(@Param("username") String username);
username
을 기준으로 User
엔티티를 검색하는 메서드입니다.@Query
어노테이션을 사용하여 직접 JPQL (Java Persistence Query Language) 쿼리를 작성합니다.select u from User u where u.username=:username
:User
엔티티에서username
속성이 주어진 파라미터 값과 일치하는User
엔티티를 선택합니다.
@Param("username")
: 쿼리에서 사용되는:username
파라미터에 메서드의username
매개변수를 매핑합니다.Optional<User>
:User
엔티티를 반환하는데, 해당username
에 해당하는 사용자가 없을 경우Optional.empty()
를 반환하여null
대신 안전하게 처리할 수 있습니다.
- 커스텀 쿼리 메서드:
findByUsernameAndPassword
@Query("select u from User u where u.username=:username and u.password=:password")
Optional<User> findByUsernameAndPassword(@Param("username") String username, @Param("password") String password);
username
과 password
를 기준으로 User
엔티티를 검색하는 메서드입니다.select u from User u where u.username=:username and u.password=:password
:User
엔티티에서username
과password
속성이 모두 주어진 파라미터 값과 일치하는User
엔티티를 선택합니다.
@Param("username")
과@Param("password")
: 쿼리에서 사용되는:username
및:password
파라미터에 메서드의username
과password
매개변수를 각각 매핑합니다.Optional<User>
:username
과password
가 모두 일치하는User
엔티티를 반환하며, 일치하는 사용자가 없으면Optional.empty()
를 반환합니다.
4.application-dev.properties
# 6. Query Format
spring.jpa.properties.hibernate.format_sql=true
# 7. OpenInView
spring.jpa.open-in-view=false
# 8. Logging
logging.level.org.hibernate.orm.jdbc.bind=INFO
logging.level.org.example.springv3=DEBUG
- 코드설명
Spring Boot 애플리케이션에서 JPA(Hibernate)와 로깅 관련된 설정을 정의하는 부분입니다. 각각의 설정이 무엇을 의미하는지 자세히 설명해 드리겠습니다.
1. Query Format
properties코드 복사
spring.jpa.properties.hibernate.format_sql=true
- 설명: 이 설정은 Hibernate가 실행하는 SQL 쿼리를 콘솔에 출력할 때, 쿼리를 더 읽기 쉽게 포맷팅(들여쓰기 및 줄바꿈)하도록 지시합니다.
- 효과:
true
로 설정하면, SQL 쿼리가 더 읽기 좋은 형식으로 출력됩니다. 예를 들어, 모든 SQL 키워드가 한 줄에 표시되는 대신, SQL 쿼리가 여러 줄에 걸쳐 들여쓰기되어 표시됩니다.- 개발자는 이를 통해 데이터베이스와 상호작용하는 쿼리를 더 쉽게 읽고 분석할 수 있습니다.
- 사용 예: 디버깅 시 쿼리의 구조를 이해하고, 쿼리의 복잡성을 줄이기 위해 성능 개선을 논의할 때 유용합니다.
2. OpenInView
properties코드 복사
spring.jpa.open-in-view=false
- 설명: 이 설정은 Spring의 "Open Session in View" 또는 "Open EntityManager in View" 전략을 비활성화합니다.
Open Session in View
란?:- 이 전략은 영속성 컨텍스트(Session/EntityManager)를 HTTP 요청의 시작부터 끝까지 열어 두는 것입니다.
- 기본적으로 Spring Boot는 이 전략을 활성화(
true
) 상태로 두어, 뷰 렌더링 시점까지 지연 로딩(lazy loading)을 허용합니다.
- 효과:
false
로 설정하면, 영속성 컨텍스트는 서비스 계층에서만 열리게 됩니다. 즉, 데이터베이스와의 연결이 더 빨리 종료되며, 지연 로딩은 서비스 계층에서만 가능해집니다.- 이렇게 설정하면 데이터베이스 연결을 더 효율적으로 관리할 수 있지만, 컨트롤러나 뷰에서 지연 로딩된 필드를 사용할 수 없게 됩니다. 이로 인해 "LazyInitializationException"이 발생할 수 있습니다.
- 사용 예: 데이터베이스 연결 자원을 보다 효율적으로 관리하고, 엔티티 객체를 뷰에 직접 노출하지 않도록 하여 설계상의 문제를 방지하고자 할 때 유용합니다.
3. Logging
properties코드 복사
logging.level.org.hibernate.orm.jdbc.bind=INFO
logging.level.org.example.springv3=DEBUG
- 설명: 이 설정은 로깅 수준을 정의하여 특정 패키지의 로깅 정보를 얼마나 상세하게 출력할지를 결정합니다.
3.1. Hibernate SQL 바인딩 로깅 설정
properties코드 복사
logging.level.org.hibernate.orm.jdbc.bind=INFO
- 설명: Hibernate SQL 쿼리에서 사용되는 파라미터의 값(바인딩된 값)을 로깅하는 설정입니다.
- 효과:
INFO
수준으로 설정하면, Hibernate는 SQL 쿼리에서 사용된 파라미터의 실제 값을 콘솔에 출력합니다. 예를 들어,select * from users where id = ?
와 같은 쿼리에서?
에 해당하는 실제 값이 출력됩니다.- 이는 디버깅 시, 실제 SQL 쿼리에 어떤 값들이 사용되는지 확인하는 데 매우 유용합니다.
3.2. 애플리케이션 패키지 로깅 설정
properties코드 복사
logging.level.org.example.springv3=DEBUG
- 설명:
org.example.springv3
패키지에 대한 로깅 수준을DEBUG
로 설정합니다.
- 효과:
DEBUG
수준으로 설정하면, 애플리케이션에서 실행되는 코드의 보다 상세한 실행 흐름을 로그로 출력합니다.- 이는 애플리케이션의 로직이 제대로 동작하는지, 어디에서 오류가 발생하는지, 메서드가 어떻게 호출되고 있는지 등의 정보를 제공하여 개발 및 디버깅에 도움을 줍니다.
Share article