<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>농담곰담곰이의곰담농</title>
    <link>https://odlram.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sun, 28 Jun 2026 22:47:38 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>브이담곰</managingEditor>
    <image>
      <title>농담곰담곰이의곰담농</title>
      <url>https://tistory1.daumcdn.net/tistory/4951882/attach/6de68945edd34f68904bf0139ebb622b</url>
      <link>https://odlram.tistory.com</link>
    </image>
    <item>
      <title>블로그 이전</title>
      <link>https://odlram.tistory.com/notice/176</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;해당 블로그를 대학시절부터 사용하면서 정도 많이 들었는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게임 개발자가 아닌 웹 개발자로 전향하게 되면서 새로운 마음으로 새 블로그에 둥지를 틀으려고 합니다!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@ramram/posts&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@ramram/posts&lt;/a&gt;&lt;/p&gt;</description>
      <author>브이담곰</author>
      <guid isPermaLink="true">https://odlram.tistory.com/notice/176</guid>
      <pubDate>Mon, 5 Jan 2026 21:01:06 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Boot] Interceptor 프로젝트에 적용해보았다.</title>
      <link>https://odlram.tistory.com/entry/Spring-Boot-Interceptor-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%97%90-%EC%A0%81%EC%9A%A9%ED%95%B4%EB%B3%B4%EC%95%98%EB%8B%A4</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 프로젝트에서, CRUD를 구현하다가 작업을 수행할 때마다 업데이트 된 리스트를 보여주어야 하므로, 전체 List를 불러오는 코드가 메서드들에 중복되서 작성되어있다는 것을 알았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1227&quot; data-origin-height=&quot;670&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVZ1SX/btsOAD3lj25/uTQcTDgeCy8m5k6PdSUVKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVZ1SX/btsOAD3lj25/uTQcTDgeCy8m5k6PdSUVKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVZ1SX/btsOAD3lj25/uTQcTDgeCy8m5k6PdSUVKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVZ1SX%2FbtsOAD3lj25%2FuTQcTDgeCy8m5k6PdSUVKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1227&quot; height=&quot;670&quot; data-origin-width=&quot;1227&quot; data-origin-height=&quot;670&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수업시간에 배운 Interceptor을 이용해서 코드를 깔끔하게 정리할 수 있다고 생각해서, 활용해보기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Spring Interceptor란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring 프레임워크에서 제공하는 기능 중 하나로, 클라이언트의 요청을 가로채서 처리하는 역할을 한다. 이를 통해 공통적인 로직(로깅, 성능 측정, 캐싱)을 처리하거나, 보안(인증, 권한)등의 목적으로 조건을 검사하고 해당 요청을 처리하거나, 무시할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1318&quot; data-origin-height=&quot;1058&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bv5ttS/btsOBglZsge/OYnlcrNkH3m31N1muy7FOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bv5ttS/btsOBglZsge/OYnlcrNkH3m31N1muy7FOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bv5ttS/btsOBglZsge/OYnlcrNkH3m31N1muy7FOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbv5ttS%2FbtsOBglZsge%2FOYnlcrNkH3m31N1muy7FOK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1318&quot; height=&quot;1058&quot; data-origin-width=&quot;1318&quot; data-origin-height=&quot;1058&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 요청 URL에만 적용되도록 매핑할 수 있다는 점이 필터와 유사하지만, 필터와 다르게 Interceptor는 스프링 웹 애플리케이션 컨텍스트에 구성하기 때문에 컨테이너의 기능을 자유롭게 활용할 수 있으며 그 내부에 선언된 모든 빈을 참조할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Interceptor을 사용하기 위해 아래와 같은 과정으로 하였다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. WebConfig 클래스 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Interceptor를 사용하려면 WebMvcConfigurer 인터페이스를 구현하는 설정 클래스를 만들고 addInterceptors() 메소드를 오버라이드해서 등록해야한다. 중요한 점은 등록 순서에 따라 Interceptor가 실행되는 순서를 결정한다는 것이다.&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Configuration // 빈(bean)정의 클래스
public class WebConfig implements WebMvcConfigurer {

    @Autowired // 스프링 컨테이너에 등록된 빈(Bean) 중에서 해당 타입과 일치하는 객체를 찾아서 자동으로 주입
    CategoryListInterceptor categoryListInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(categoryListInterceptor)
                .addPathPatterns(&quot;/category/**&quot;)
                .excludePathPatterns(
                        &quot;/images/**&quot;,
                        &quot;/js/**&quot;,
                        &quot;/error&quot;
                );
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Interceptor 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HandlerInterceptor 클래스의 구현체를 생성하고 Component로 빈을 등록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;postHandler 메소드를 오버라이드해서 구현을 시작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Component
public class CategoryListInterceptor implements HandlerInterceptor {

    @Autowired
    CategoryService categoryService; // 의존성 주입

    // 메서드가 실행되기 전에 전체 리스트를 업로드 해야하므로 postHandler을 override 한다.
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

        // 카테고리 관련 뷰로 이동할 때만 가로챈다.
        if(modelAndView != null &amp;amp;&amp;amp;
                modelAndView.getViewName() != null &amp;amp;&amp;amp;
                modelAndView.getViewName().equals(&quot;category/list&quot;)){

            // Category List 조회
            List&amp;lt;CategoryDTO&amp;gt; categoryList = categoryService.findAllCategory();
            modelAndView.addObject(&quot;categoryList&quot;, categoryList);
        }

    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>Spring</category>
      <author>브이담곰</author>
      <guid isPermaLink="true">https://odlram.tistory.com/175</guid>
      <comments>https://odlram.tistory.com/entry/Spring-Boot-Interceptor-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%97%90-%EC%A0%81%EC%9A%A9%ED%95%B4%EB%B3%B4%EC%95%98%EB%8B%A4#entry175comment</comments>
      <pubDate>Fri, 13 Jun 2025 17:46:56 +0900</pubDate>
    </item>
    <item>
      <title>REST API에서 페이징 처리가 필수인 이유와 실무 구현 방법</title>
      <link>https://odlram.tistory.com/entry/REST-API%EC%97%90%EC%84%9C-%ED%8E%98%EC%9D%B4%EC%A7%95-%EC%B2%98%EB%A6%AC%EA%B0%80-%ED%95%84%EC%88%98%EC%9D%B8-%EC%9D%B4%EC%9C%A0%EC%99%80-%EC%8B%A4%EB%AC%B4-%EA%B5%AC%ED%98%84-%EB%B0%A9%EB%B2%95</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  페이징을 알게 된 계기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 REST API에 대해 배우면서 상품 목록을 조회하는 API를 만들어보았다. 그런데 문득 이런 생각이 들었다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;만약 쇼핑몰에 상품이 10만 개, 100만 개가 있다면 어떻게 될까?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 상품 정보를 한 번에 클라이언트에 보내준다면...  &lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터베이스에서 100만 개 데이터를 모두 조회&lt;/li&gt;
&lt;li&gt;서버 메모리에 100만 개 객체 로딩&lt;/li&gt;
&lt;li&gt;네트워크로 거대한 JSON 전송&lt;/li&gt;
&lt;li&gt;브라우저가 100만 개 데이터 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;분명히 서버도 클라이언트도 감당할 수 없을 것 같았다.&lt;/b&gt; 그래서 페이징 응답에 대해 더 자세히 알아보게 되었다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  페이징이 없다면 생기는 문제들&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 서버 사이드 문제&lt;/h3&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;// ❌ 이렇게 하면 큰일난다!
@GetMapping(&quot;/api/products&quot;)
public List&amp;lt;ProductDTO&amp;gt; getAllProducts() {
    return productService.findAll(); // 100만 개 상품을 모두 조회!
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;발생하는 문제들:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;메모리 부족&lt;/b&gt;: 100만 개 객체가 힙 메모리를 점유&lt;/li&gt;
&lt;li&gt;&lt;b&gt;응답 시간 지연&lt;/b&gt;: 데이터 조회 + 직렬화 + 네트워크 전송 시간 급증&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터베이스 부하&lt;/b&gt;: 한 번에 모든 데이터를 읽어야 함&lt;/li&gt;
&lt;li&gt;&lt;b&gt;서버 비용 증가&lt;/b&gt;: 불필요한 리소스 소모&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 클라이언트 사이드 문제&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;브라우저 렌더링 지연&lt;/b&gt;: 100만 개 DOM 요소 생성?&lt;/li&gt;
&lt;li&gt;&lt;b&gt;메모리 사용량 폭증&lt;/b&gt;: 모바일에서는 더욱 심각&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사용자 경험 저하&lt;/b&gt;: 페이지가 멈춘 것처럼 보임&lt;/li&gt;
&lt;li&gt;&lt;b&gt;불필요한 데이터&lt;/b&gt;: 사용자는 처음 20개 정도만 봄&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 네트워크 문제&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;대역폭 낭비&lt;/b&gt;: 수 MB~GB 급 응답 크기&lt;/li&gt;
&lt;li&gt;&lt;b&gt;모바일 데이터 소모&lt;/b&gt;: 사용자 요금 폭탄&lt;/li&gt;
&lt;li&gt;&lt;b&gt;응답 시간 증가&lt;/b&gt;: 네트워크 전송 시간 기하급수적 증가&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 페이징의 해결책&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Before: 100만 개 한 번에&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Client &amp;larr; [상품1, 상품2, ..., 상품1000000] &amp;larr; Server
          100MB 응답, 30초 대기
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;After: 20개씩 나누어서&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Client &amp;larr; [상품1~20] + 페이징정보 &amp;larr; Server
        ✨ 50KB 응답, 0.1초 완료
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심 아이디어&lt;/b&gt;: &quot;사용자는 보통 처음 1~2페이지만 본다!&quot;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; ️ 실무에서 사용하는 페이징 구조&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 기본적인 페이징 파라미터&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;GET /api/products?page=0&amp;amp;size=20&amp;amp;sort=createdAt,desc
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파라미터 설명 기본값&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;page&lt;/td&gt;
&lt;td&gt;페이지 번호 (0부터 시작)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;size&lt;/td&gt;
&lt;td&gt;페이지 크기&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sort&lt;/td&gt;
&lt;td&gt;정렬 기준&lt;/td&gt;
&lt;td&gt;createdAt,desc&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Spring Data JPA로 구현하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Repository Layer&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Repository
public interface ProductRepository extends JpaRepository&amp;lt;Product, Long&amp;gt; {
    // JpaRepository가 기본 페이징 기능 제공
    Page&amp;lt;Product&amp;gt; findAll(Pageable pageable);
    
    // 조건부 페이징도 쉽게 구현
    Page&amp;lt;Product&amp;gt; findByCategory(String category, Pageable pageable);
    Page&amp;lt;Product&amp;gt; findByNameContaining(String keyword, Pageable pageable);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Service Layer&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Service
public class ProductService {
    
    @Autowired
    private ProductRepository productRepository;
    
    public PageResponse&amp;lt;ProductDTO&amp;gt; getProducts(int page, int size) {
        // Pageable 객체 생성
        Pageable pageable = PageRequest.of(page, size, 
            Sort.by(Sort.Direction.DESC, &quot;createdAt&quot;));
        
        // 페이징 조회
        Page&amp;lt;Product&amp;gt; productPage = productRepository.findAll(pageable);
        
        // DTO 변환
        List&amp;lt;ProductDTO&amp;gt; productDTOs = productPage.getContent()
            .stream()
            .map(this::convertToDTO)
            .collect(Collectors.toList());
        
        // 페이징 응답 생성
        return PageResponse.&amp;lt;ProductDTO&amp;gt;builder()
            .content(productDTOs)
            .totalElements(productPage.getTotalElements())
            .totalPages(productPage.getTotalPages())
            .currentPage(productPage.getNumber())
            .size(productPage.getSize())
            .hasNext(productPage.hasNext())
            .hasPrevious(productPage.hasPrevious())
            .build();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Controller Layer&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@RestController
@RequestMapping(&quot;/api/products&quot;)
public class ProductController {
    
    @GetMapping
    public ResponseEntity&amp;lt;ApiResponse&amp;lt;PageResponse&amp;lt;ProductDTO&amp;gt;&amp;gt;&amp;gt; getProducts(
            @RequestParam(defaultValue = &quot;0&quot;) int page,
            @RequestParam(defaultValue = &quot;20&quot;) int size) {
        
        PageResponse&amp;lt;ProductDTO&amp;gt; pageResponse = productService.getProducts(page, size);
        
        return ResponseEntity.ok(ApiResponse.&amp;lt;PageResponse&amp;lt;ProductDTO&amp;gt;&amp;gt;builder()
            .success(true)
            .message(&quot;상품 목록 조회 성공&quot;)
            .data(pageResponse)
            .build());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 커스텀 PageResponse 클래스&lt;/h3&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;@Getter
@Builder
public class PageResponse&amp;lt;T&amp;gt; {
    // 실제 데이터
    private List&amp;lt;T&amp;gt; content;
    
    // 페이징 정보
    private long totalElements;    // 전체 데이터 개수
    private int totalPages;        // 전체 페이지 수
    private int currentPage;       // 현재 페이지 (0부터 시작)
    private int size;             // 페이지 크기
    
    // 편의 정보
    private boolean hasNext;       // 다음 페이지 존재 여부
    private boolean hasPrevious;   // 이전 페이지 존재 여부
    private boolean first;         // 첫 번째 페이지 여부
    private boolean last;          // 마지막 페이지 여부
    
    // 실제 표시되는 데이터 개수
    private int numberOfElements;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  프론트엔드가 받는 JSON 응답&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;성공적인 페이징 응답&lt;/h3&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;success&quot;: true,
  &quot;message&quot;: &quot;상품 목록 조회 성공&quot;,
  &quot;data&quot;: {
    &quot;content&quot;: [
      {
        &quot;id&quot;: 1,
        &quot;name&quot;: &quot;맥북 프로 16인치&quot;,
        &quot;price&quot;: 3500000,
        &quot;category&quot;: &quot;노트북&quot;,
        &quot;imageUrl&quot;: &quot;/images/macbook-pro-16.jpg&quot;,
        &quot;createdAt&quot;: &quot;2024-06-11T10:30:00&quot;
      },
      {
        &quot;id&quot;: 2,
        &quot;name&quot;: &quot;아이폰 15 Pro&quot;,
        &quot;price&quot;: 1500000,
        &quot;category&quot;: &quot;스마트폰&quot;,
        &quot;imageUrl&quot;: &quot;/images/iphone-15-pro.jpg&quot;,
        &quot;createdAt&quot;: &quot;2024-06-11T09:15:00&quot;
      }
    ],
    &quot;totalElements&quot;: 1547,
    &quot;totalPages&quot;: 78,
    &quot;currentPage&quot;: 0,
    &quot;size&quot;: 20,
    &quot;hasNext&quot;: true,
    &quot;hasPrevious&quot;: false,
    &quot;first&quot;: true,
    &quot;last&quot;: false,
    &quot;numberOfElements&quot;: 20
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프론트엔드에서 활용하기&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;async function loadProducts(page = 0) {
    try {
        const response = await fetch(`/api/products?page=${page}&amp;amp;size=20`);
        const result = await response.json();
        
        if (result.success) {
            const { content, totalElements, currentPage, totalPages, hasNext, hasPrevious } = result.data;
            
            // 상품 목록 렌더링
            renderProducts(content);
            
            // 페이지 정보 표시
            document.getElementById('page-info').textContent = 
                `${currentPage + 1} / ${totalPages} 페이지 (총 ${totalElements.toLocaleString()}개 상품)`;
            
            // 페이징 버튼 상태 관리
            document.getElementById('prev-btn').disabled = !hasPrevious;
            document.getElementById('next-btn').disabled = !hasNext;
            
        } else {
            console.error('상품 로딩 실패:', result.message);
        }
        
    } catch (error) {
        console.error('네트워크 에러:', error);
    }
}

// 이전/다음 페이지 이동
let currentPage = 0;

document.getElementById('prev-btn').onclick = () =&amp;gt; {
    if (currentPage &amp;gt; 0) {
        currentPage--;
        loadProducts(currentPage);
    }
};

document.getElementById('next-btn').onclick = () =&amp;gt; {
    currentPage++;
    loadProducts(currentPage);
};
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  고급 페이징: 검색과 필터링&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서는 단순 페이징보다는 &lt;b&gt;검색 + 필터링 + 페이징&lt;/b&gt;이 함께 사용된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;검색 요청 DTO&lt;/h3&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;@Getter
@Setter
public class ProductSearchRequest {
    // 페이징 파라미터
    private int page = 0;
    private int size = 20;
    
    // 정렬 파라미터
    private String sortBy = &quot;createdAt&quot;;
    private String sortDirection = &quot;DESC&quot;;
    
    // 검색 및 필터 파라미터
    private String keyword;        // 상품명 검색
    private String category;       // 카테고리 필터
    private Integer minPrice;      // 최소 가격
    private Integer maxPrice;      // 최대 가격
    private Boolean inStock;       // 재고 있는 상품만
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;복합 조건 쿼리&lt;/h3&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;@Repository
public interface ProductRepository extends JpaRepository&amp;lt;Product, Long&amp;gt; {
    
    @Query(&quot;SELECT p FROM Product p WHERE &quot; +
           &quot;(:keyword IS NULL OR LOWER(p.name) LIKE LOWER(CONCAT('%', :keyword, '%'))) AND &quot; +
           &quot;(:category IS NULL OR p.category = :category) AND &quot; +
           &quot;(:minPrice IS NULL OR p.price &amp;gt;= :minPrice) AND &quot; +
           &quot;(:maxPrice IS NULL OR p.price &amp;lt;= :maxPrice) AND &quot; +
           &quot;(:inStock IS NULL OR (:inStock = true AND p.stockQuantity
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Spring</category>
      <author>브이담곰</author>
      <guid isPermaLink="true">https://odlram.tistory.com/174</guid>
      <comments>https://odlram.tistory.com/entry/REST-API%EC%97%90%EC%84%9C-%ED%8E%98%EC%9D%B4%EC%A7%95-%EC%B2%98%EB%A6%AC%EA%B0%80-%ED%95%84%EC%88%98%EC%9D%B8-%EC%9D%B4%EC%9C%A0%EC%99%80-%EC%8B%A4%EB%AC%B4-%EA%B5%AC%ED%98%84-%EB%B0%A9%EB%B2%95#entry174comment</comments>
      <pubDate>Thu, 12 Jun 2025 12:07:35 +0900</pubDate>
    </item>
    <item>
      <title>Thymeleaf 문법</title>
      <link>https://odlram.tistory.com/entry/Thymeleaf-%EB%AC%B8%EB%B2%95</link>
      <description>&lt;p&gt;Thymeleaf는 Spring Boot에서 가장 많이 사용되는 템플릿 엔진입니다. HTML과 자연스럽게 통합되어 서버사이드 렌더링을 지원한다.&lt;/p&gt;
&lt;h2&gt;  기본 설정&lt;/h2&gt;
&lt;h3&gt;HTML 템플릿 기본 구조&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&amp;quot;ko&amp;quot; xmlns:th=&amp;quot;http://www.thymeleaf.org&amp;quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;
    &amp;lt;title&amp;gt;Thymeleaf 예제&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;!-- Thymeleaf 문법 사용 --&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Controller에서 데이터 전달&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Controller
public class HomeController {

    @GetMapping(&amp;quot;/&amp;quot;)
    public String home(Model model) {
        model.addAttribute(&amp;quot;message&amp;quot;, &amp;quot;안녕하세요!&amp;quot;);
        model.addAttribute(&amp;quot;user&amp;quot;, new User(&amp;quot;김철수&amp;quot;, 25));
        model.addAttribute(&amp;quot;products&amp;quot;, Arrays.asList(&amp;quot;상품1&amp;quot;, &amp;quot;상품2&amp;quot;, &amp;quot;상품3&amp;quot;));
        return &amp;quot;index&amp;quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;  기본 문법&lt;/h2&gt;
&lt;h3&gt;1. 텍스트 출력 (&lt;code&gt;th:text&lt;/code&gt;)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- 기본 텍스트 출력 --&amp;gt;
&amp;lt;p th:text=&amp;quot;${message}&amp;quot;&amp;gt;기본 메시지&amp;lt;/p&amp;gt;
&amp;lt;!-- 결과: &amp;lt;p&amp;gt;안녕하세요!&amp;lt;/p&amp;gt; --&amp;gt;

&amp;lt;!-- 객체의 속성 접근 --&amp;gt;
&amp;lt;p th:text=&amp;quot;${user.name}&amp;quot;&amp;gt;사용자 이름&amp;lt;/p&amp;gt;
&amp;lt;!-- 결과: &amp;lt;p&amp;gt;김철수&amp;lt;/p&amp;gt; --&amp;gt;

&amp;lt;!-- 표현식과 문자열 결합 --&amp;gt;
&amp;lt;p th:text=&amp;quot;&amp;#39;안녕하세요, &amp;#39; + ${user.name} + &amp;#39;님!&amp;#39;&amp;quot;&amp;gt;인사말&amp;lt;/p&amp;gt;
&amp;lt;!-- 결과: &amp;lt;p&amp;gt;안녕하세요, 김철수님!&amp;lt;/p&amp;gt; --&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. HTML 출력 (&lt;code&gt;th:utext&lt;/code&gt;)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- HTML 태그가 포함된 텍스트 --&amp;gt;
&amp;lt;div th:utext=&amp;quot;${htmlContent}&amp;quot;&amp;gt;기본 내용&amp;lt;/div&amp;gt;
&amp;lt;!-- htmlContent = &amp;quot;&amp;lt;strong&amp;gt;굵은 글씨&amp;lt;/strong&amp;gt;&amp;quot; --&amp;gt;
&amp;lt;!-- 결과: &amp;lt;div&amp;gt;&amp;lt;strong&amp;gt;굵은 글씨&amp;lt;/strong&amp;gt;&amp;lt;/div&amp;gt; --&amp;gt;

&amp;lt;!-- th:text와 비교 --&amp;gt;
&amp;lt;div th:text=&amp;quot;${htmlContent}&amp;quot;&amp;gt;기본 내용&amp;lt;/div&amp;gt;
&amp;lt;!-- 결과: &amp;lt;div&amp;gt;&amp;amp;lt;strong&amp;amp;gt;굵은 글씨&amp;amp;lt;/strong&amp;amp;gt;&amp;lt;/div&amp;gt; --&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 속성 값 설정 (&lt;code&gt;th:속성명&lt;/code&gt;)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- value 속성 설정 --&amp;gt;
&amp;lt;input type=&amp;quot;text&amp;quot; th:value=&amp;quot;${user.name}&amp;quot; /&amp;gt;
&amp;lt;!-- 결과: &amp;lt;input type=&amp;quot;text&amp;quot; value=&amp;quot;김철수&amp;quot; /&amp;gt; --&amp;gt;

&amp;lt;!-- href 속성 설정 --&amp;gt;
&amp;lt;a th:href=&amp;quot;&amp;#39;/user/&amp;#39; + ${user.id}&amp;quot;&amp;gt;사용자 상세&amp;lt;/a&amp;gt;
&amp;lt;!-- 결과: &amp;lt;a href=&amp;quot;/user/123&amp;quot;&amp;gt;사용자 상세&amp;lt;/a&amp;gt; --&amp;gt;

&amp;lt;!-- class 속성 설정 --&amp;gt;
&amp;lt;div th:class=&amp;quot;${user.active} ? &amp;#39;active&amp;#39; : &amp;#39;inactive&amp;#39;&amp;quot;&amp;gt;상태&amp;lt;/div&amp;gt;

&amp;lt;!-- id 속성 설정 --&amp;gt;
&amp;lt;div th:id=&amp;quot;&amp;#39;user-&amp;#39; + ${user.id}&amp;quot;&amp;gt;사용자 정보&amp;lt;/div&amp;gt;
&amp;lt;!-- 결과: &amp;lt;div id=&amp;quot;user-123&amp;quot;&amp;gt;사용자 정보&amp;lt;/div&amp;gt; --&amp;gt;

&amp;lt;!-- 여러 속성 동시 설정 --&amp;gt;
&amp;lt;input th:attr=&amp;quot;type=&amp;#39;text&amp;#39;,value=${user.name},placeholder=&amp;#39;이름을 입력하세요&amp;#39;&amp;quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;  URL 생성 (&lt;code&gt;@{...}&lt;/code&gt;)&lt;/h2&gt;
&lt;h3&gt;기본 URL 생성&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- 정적 URL --&amp;gt;
&amp;lt;a th:href=&amp;quot;@{/home}&amp;quot;&amp;gt;홈으로&amp;lt;/a&amp;gt;
&amp;lt;!-- 결과: &amp;lt;a href=&amp;quot;/home&amp;quot;&amp;gt;홈으로&amp;lt;/a&amp;gt; --&amp;gt;

&amp;lt;!-- 동적 URL (경로 변수) --&amp;gt;
&amp;lt;a th:href=&amp;quot;@{/user/{id}(id=${user.id})}&amp;quot;&amp;gt;사용자 상세&amp;lt;/a&amp;gt;
&amp;lt;!-- 결과: &amp;lt;a href=&amp;quot;/user/123&amp;quot;&amp;gt;사용자 상세&amp;lt;/a&amp;gt; --&amp;gt;

&amp;lt;!-- 쿼리 파라미터 --&amp;gt;
&amp;lt;a th:href=&amp;quot;@{/search(keyword=${keyword},page=${page})}&amp;quot;&amp;gt;검색&amp;lt;/a&amp;gt;
&amp;lt;!-- 결과: &amp;lt;a href=&amp;quot;/search?keyword=spring&amp;amp;page=1&amp;quot;&amp;gt;검색&amp;lt;/a&amp;gt; --&amp;gt;

&amp;lt;!-- 경로 변수 + 쿼리 파라미터 --&amp;gt;
&amp;lt;a th:href=&amp;quot;@{/user/{id}/orders(id=${user.id},status=${orderStatus})}&amp;quot;&amp;gt;주문 목록&amp;lt;/a&amp;gt;
&amp;lt;!-- 결과: &amp;lt;a href=&amp;quot;/user/123/orders?status=PENDING&amp;quot;&amp;gt;주문 목록&amp;lt;/a&amp;gt; --&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;폼 action URL&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- 기본 폼 --&amp;gt;
&amp;lt;form th:action=&amp;quot;@{/user/save}&amp;quot; method=&amp;quot;post&amp;quot;&amp;gt;
    &amp;lt;input type=&amp;quot;text&amp;quot; name=&amp;quot;name&amp;quot; th:value=&amp;quot;${user.name}&amp;quot; /&amp;gt;
    &amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;저장&amp;quot; /&amp;gt;
&amp;lt;/form&amp;gt;

&amp;lt;!-- 동적 폼 action --&amp;gt;
&amp;lt;form th:action=&amp;quot;@{/user/{id}/update(id=${user.id})}&amp;quot; method=&amp;quot;post&amp;quot;&amp;gt;
    &amp;lt;input type=&amp;quot;text&amp;quot; name=&amp;quot;name&amp;quot; th:value=&amp;quot;${user.name}&amp;quot; /&amp;gt;
    &amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;수정&amp;quot; /&amp;gt;
&amp;lt;/form&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;  조건문 (&lt;code&gt;th:if&lt;/code&gt;, &lt;code&gt;th:unless&lt;/code&gt;, &lt;code&gt;th:switch&lt;/code&gt;)&lt;/h2&gt;
&lt;h3&gt;if/unless 조건문&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- th:if - 조건이 true일 때 렌더링 --&amp;gt;
&amp;lt;div th:if=&amp;quot;${user.age &amp;gt;= 18}&amp;quot;&amp;gt;
    성인 사용자입니다.
&amp;lt;/div&amp;gt;

&amp;lt;!-- th:unless - 조건이 false일 때 렌더링 --&amp;gt;
&amp;lt;div th:unless=&amp;quot;${user.age &amp;gt;= 18}&amp;quot;&amp;gt;
    미성년 사용자입니다.
&amp;lt;/div&amp;gt;

&amp;lt;!-- 복합 조건 --&amp;gt;
&amp;lt;div th:if=&amp;quot;${user != null and user.active}&amp;quot;&amp;gt;
    활성 사용자입니다.
&amp;lt;/div&amp;gt;

&amp;lt;!-- 문자열 비교 --&amp;gt;
&amp;lt;div th:if=&amp;quot;${user.role == &amp;#39;ADMIN&amp;#39;}&amp;quot;&amp;gt;
    관리자 메뉴
&amp;lt;/div&amp;gt;

&amp;lt;!-- null 체크 --&amp;gt;
&amp;lt;div th:if=&amp;quot;${user?.email != null}&amp;quot;&amp;gt;
    이메일: &amp;lt;span th:text=&amp;quot;${user.email}&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;switch/case 조건문&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div th:switch=&amp;quot;${user.role}&amp;quot;&amp;gt;
    &amp;lt;p th:case=&amp;quot;&amp;#39;ADMIN&amp;#39;&amp;quot;&amp;gt;관리자입니다.&amp;lt;/p&amp;gt;
    &amp;lt;p th:case=&amp;quot;&amp;#39;USER&amp;#39;&amp;quot;&amp;gt;일반 사용자입니다.&amp;lt;/p&amp;gt;
    &amp;lt;p th:case=&amp;quot;&amp;#39;GUEST&amp;#39;&amp;quot;&amp;gt;게스트입니다.&amp;lt;/p&amp;gt;
    &amp;lt;p th:case=&amp;quot;*&amp;quot;&amp;gt;알 수 없는 역할입니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;  반복문 (&lt;code&gt;th:each&lt;/code&gt;)&lt;/h2&gt;
&lt;h3&gt;기본 반복문&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- 리스트 반복 --&amp;gt;
&amp;lt;ul&amp;gt;
    &amp;lt;li th:each=&amp;quot;product : ${products}&amp;quot; th:text=&amp;quot;${product}&amp;quot;&amp;gt;상품명&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;!-- 결과:
&amp;lt;ul&amp;gt;
    &amp;lt;li&amp;gt;상품1&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;상품2&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;상품3&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
--&amp;gt;

&amp;lt;!-- 객체 리스트 반복 --&amp;gt;
&amp;lt;table&amp;gt;
    &amp;lt;tr th:each=&amp;quot;user : ${users}&amp;quot;&amp;gt;
        &amp;lt;td th:text=&amp;quot;${user.id}&amp;quot;&amp;gt;ID&amp;lt;/td&amp;gt;
        &amp;lt;td th:text=&amp;quot;${user.name}&amp;quot;&amp;gt;이름&amp;lt;/td&amp;gt;
        &amp;lt;td th:text=&amp;quot;${user.email}&amp;quot;&amp;gt;이메일&amp;lt;/td&amp;gt;
    &amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;반복 상태 변수 (Status Variable)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;table&amp;gt;
    &amp;lt;tr th:each=&amp;quot;user, status : ${users}&amp;quot;&amp;gt;
        &amp;lt;td th:text=&amp;quot;${status.index}&amp;quot;&amp;gt;인덱스&amp;lt;/td&amp;gt;      &amp;lt;!-- 0부터 시작 --&amp;gt;
        &amp;lt;td th:text=&amp;quot;${status.count}&amp;quot;&amp;gt;순번&amp;lt;/td&amp;gt;        &amp;lt;!-- 1부터 시작 --&amp;gt;
        &amp;lt;td th:text=&amp;quot;${user.name}&amp;quot;&amp;gt;이름&amp;lt;/td&amp;gt;
        &amp;lt;td th:text=&amp;quot;${status.odd} ? &amp;#39;홀수&amp;#39; : &amp;#39;짝수&amp;#39;&amp;quot;&amp;gt;순서&amp;lt;/td&amp;gt;
        &amp;lt;td th:if=&amp;quot;${status.first}&amp;quot;&amp;gt;첫 번째&amp;lt;/td&amp;gt;
        &amp;lt;td th:if=&amp;quot;${status.last}&amp;quot;&amp;gt;마지막&amp;lt;/td&amp;gt;
    &amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Map 반복&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- Map 순회 --&amp;gt;
&amp;lt;div th:each=&amp;quot;entry : ${userMap}&amp;quot;&amp;gt;
    &amp;lt;p&amp;gt;키: &amp;lt;span th:text=&amp;quot;${entry.key}&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
    &amp;lt;p&amp;gt;값: &amp;lt;span th:text=&amp;quot;${entry.value}&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;  폼 처리&lt;/h2&gt;
&lt;h3&gt;기본 폼 바인딩&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- 객체 바인딩 --&amp;gt;
&amp;lt;form th:action=&amp;quot;@{/user/save}&amp;quot; th:object=&amp;quot;${user}&amp;quot; method=&amp;quot;post&amp;quot;&amp;gt;
    &amp;lt;input type=&amp;quot;text&amp;quot; th:field=&amp;quot;*{name}&amp;quot; placeholder=&amp;quot;이름&amp;quot; /&amp;gt;
    &amp;lt;input type=&amp;quot;email&amp;quot; th:field=&amp;quot;*{email}&amp;quot; placeholder=&amp;quot;이메일&amp;quot; /&amp;gt;
    &amp;lt;input type=&amp;quot;number&amp;quot; th:field=&amp;quot;*{age}&amp;quot; placeholder=&amp;quot;나이&amp;quot; /&amp;gt;
    &amp;lt;select th:field=&amp;quot;*{role}&amp;quot;&amp;gt;
        &amp;lt;option value=&amp;quot;USER&amp;quot;&amp;gt;사용자&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;ADMIN&amp;quot;&amp;gt;관리자&amp;lt;/option&amp;gt;
    &amp;lt;/select&amp;gt;
    &amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;저장&amp;quot; /&amp;gt;
&amp;lt;/form&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;체크박스와 라디오 버튼&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- 체크박스 --&amp;gt;
&amp;lt;input type=&amp;quot;checkbox&amp;quot; th:field=&amp;quot;*{active}&amp;quot; /&amp;gt;
&amp;lt;label th:for=&amp;quot;${#ids.prev(&amp;#39;active&amp;#39;)}&amp;quot;&amp;gt;활성 상태&amp;lt;/label&amp;gt;

&amp;lt;!-- 여러 체크박스 --&amp;gt;
&amp;lt;div th:each=&amp;quot;hobby : ${hobbies}&amp;quot;&amp;gt;
    &amp;lt;input type=&amp;quot;checkbox&amp;quot; th:field=&amp;quot;*{selectedHobbies}&amp;quot; th:value=&amp;quot;${hobby}&amp;quot; /&amp;gt;
    &amp;lt;label th:for=&amp;quot;${#ids.prev(&amp;#39;selectedHobbies&amp;#39;)}&amp;quot; th:text=&amp;quot;${hobby}&amp;quot;&amp;gt;취미&amp;lt;/label&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;!-- 라디오 버튼 --&amp;gt;
&amp;lt;div th:each=&amp;quot;gender : ${genders}&amp;quot;&amp;gt;
    &amp;lt;input type=&amp;quot;radio&amp;quot; th:field=&amp;quot;*{gender}&amp;quot; th:value=&amp;quot;${gender}&amp;quot; /&amp;gt;
    &amp;lt;label th:for=&amp;quot;${#ids.prev(&amp;#39;gender&amp;#39;)}&amp;quot; th:text=&amp;quot;${gender}&amp;quot;&amp;gt;성별&amp;lt;/label&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;선택 옵션 (Select)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- 기본 셀렉트 --&amp;gt;
&amp;lt;select th:field=&amp;quot;*{categoryId}&amp;quot;&amp;gt;
    &amp;lt;option value=&amp;quot;&amp;quot;&amp;gt;카테고리 선택&amp;lt;/option&amp;gt;
    &amp;lt;option th:each=&amp;quot;category : ${categories}&amp;quot; 
            th:value=&amp;quot;${category.id}&amp;quot; 
            th:text=&amp;quot;${category.name}&amp;quot;&amp;gt;카테고리&amp;lt;/option&amp;gt;
&amp;lt;/select&amp;gt;

&amp;lt;!-- 선택된 값 표시 --&amp;gt;
&amp;lt;select th:field=&amp;quot;*{role}&amp;quot;&amp;gt;
    &amp;lt;option th:each=&amp;quot;role : ${roles}&amp;quot; 
            th:value=&amp;quot;${role}&amp;quot; 
            th:text=&amp;quot;${role}&amp;quot;
            th:selected=&amp;quot;${role == user.role}&amp;quot;&amp;gt;역할&amp;lt;/option&amp;gt;
&amp;lt;/select&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;  CSS 클래스와 스타일&lt;/h2&gt;
&lt;h3&gt;동적 클래스 적용&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- 조건부 클래스 --&amp;gt;
&amp;lt;div th:class=&amp;quot;${user.active} ? &amp;#39;active&amp;#39; : &amp;#39;inactive&amp;#39;&amp;quot;&amp;gt;상태&amp;lt;/div&amp;gt;

&amp;lt;!-- 기존 클래스에 추가 --&amp;gt;
&amp;lt;div class=&amp;quot;base-class&amp;quot; th:classappend=&amp;quot;${user.premium} ? &amp;#39;premium&amp;#39; : &amp;#39;&amp;#39;&amp;quot;&amp;gt;
    사용자 정보
&amp;lt;/div&amp;gt;

&amp;lt;!-- 클래스 제거 --&amp;gt;
&amp;lt;div class=&amp;quot;default error&amp;quot; th:classremove=&amp;quot;${user.valid} ? &amp;#39;error&amp;#39; : &amp;#39;&amp;#39;&amp;quot;&amp;gt;
    입력 필드
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;동적 스타일 적용&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- 인라인 스타일 --&amp;gt;
&amp;lt;div th:style=&amp;quot;&amp;#39;background-color: &amp;#39; + ${user.favoriteColor}&amp;quot;&amp;gt;배경색&amp;lt;/div&amp;gt;

&amp;lt;!-- 조건부 스타일 --&amp;gt;
&amp;lt;div th:style=&amp;quot;${user.online} ? &amp;#39;color: green;&amp;#39; : &amp;#39;color: gray;&amp;#39;&amp;quot;&amp;gt;
    온라인 상태
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;  유틸리티 객체&lt;/h2&gt;
&lt;h3&gt;날짜 처리 (&lt;code&gt;#dates&lt;/code&gt;)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- 현재 날짜 --&amp;gt;
&amp;lt;p th:text=&amp;quot;${#dates.createNow()}&amp;quot;&amp;gt;현재 시간&amp;lt;/p&amp;gt;

&amp;lt;!-- 날짜 포맷팅 --&amp;gt;
&amp;lt;p th:text=&amp;quot;${#dates.format(user.createdAt, &amp;#39;yyyy-MM-dd HH:mm:ss&amp;#39;)}&amp;quot;&amp;gt;
    생성일시
&amp;lt;/p&amp;gt;

&amp;lt;!-- 날짜 계산 --&amp;gt;
&amp;lt;p th:text=&amp;quot;${#dates.daysBetween(startDate, endDate)}&amp;quot;&amp;gt;일수 차이&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;문자열 처리 (&lt;code&gt;#strings&lt;/code&gt;)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- 문자열 길이 --&amp;gt;
&amp;lt;p th:text=&amp;quot;${#strings.length(user.name)}&amp;quot;&amp;gt;이름 길이&amp;lt;/p&amp;gt;

&amp;lt;!-- 문자열 자르기 --&amp;gt;
&amp;lt;p th:text=&amp;quot;${#strings.substring(user.description, 0, 50)}&amp;quot;&amp;gt;설명&amp;lt;/p&amp;gt;

&amp;lt;!-- 대소문자 변환 --&amp;gt;
&amp;lt;p th:text=&amp;quot;${#strings.toUpperCase(user.name)}&amp;quot;&amp;gt;대문자 이름&amp;lt;/p&amp;gt;

&amp;lt;!-- 문자열 체크 --&amp;gt;
&amp;lt;div th:if=&amp;quot;${#strings.isEmpty(user.email)}&amp;quot;&amp;gt;이메일이 없습니다.&amp;lt;/div&amp;gt;
&amp;lt;div th:if=&amp;quot;${#strings.contains(user.name, &amp;#39;김&amp;#39;)}&amp;quot;&amp;gt;김씨입니다.&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;숫자 처리 (&lt;code&gt;#numbers&lt;/code&gt;)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- 숫자 포맷팅 --&amp;gt;
&amp;lt;p th:text=&amp;quot;${#numbers.formatDecimal(product.price, 0, &amp;#39;COMMA&amp;#39;, 0, &amp;#39;POINT&amp;#39;)}&amp;quot;&amp;gt;
    가격: 1,000
&amp;lt;/p&amp;gt;

&amp;lt;!-- 퍼센트 --&amp;gt;
&amp;lt;p th:text=&amp;quot;${#numbers.formatPercent(rate, 1, 2)}&amp;quot;&amp;gt;비율&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;컬렉션 처리 (&lt;code&gt;#lists&lt;/code&gt;, &lt;code&gt;#sets&lt;/code&gt;, &lt;code&gt;#maps&lt;/code&gt;)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- 리스트 크기 --&amp;gt;
&amp;lt;p th:text=&amp;quot;&amp;#39;총 &amp;#39; + ${#lists.size(products)} + &amp;#39;개의 상품&amp;#39;&amp;quot;&amp;gt;상품 개수&amp;lt;/p&amp;gt;

&amp;lt;!-- 리스트 비어있는지 확인 --&amp;gt;
&amp;lt;div th:if=&amp;quot;${#lists.isEmpty(products)}&amp;quot;&amp;gt;상품이 없습니다.&amp;lt;/div&amp;gt;

&amp;lt;!-- 리스트 포함 여부 --&amp;gt;
&amp;lt;div th:if=&amp;quot;${#lists.contains(favoriteProducts, product.id)}&amp;quot;&amp;gt;
    즐겨찾기 상품입니다.
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;  국제화 (i18n)&lt;/h2&gt;
&lt;h3&gt;메시지 사용&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- 기본 메시지 --&amp;gt;
&amp;lt;p th:text=&amp;quot;#{welcome.message}&amp;quot;&amp;gt;환영 메시지&amp;lt;/p&amp;gt;

&amp;lt;!-- 파라미터가 있는 메시지 --&amp;gt;
&amp;lt;p th:text=&amp;quot;#{welcome.user(${user.name})}&amp;quot;&amp;gt;사용자 환영 메시지&amp;lt;/p&amp;gt;

&amp;lt;!-- 조건부 메시지 --&amp;gt;
&amp;lt;p th:text=&amp;quot;#{${user.gender == &amp;#39;M&amp;#39;} ? &amp;#39;male.message&amp;#39; : &amp;#39;female.message&amp;#39;}&amp;quot;&amp;gt;
    성별별 메시지
&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;properties 파일 예시&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-properties&quot;&gt;# messages.properties
welcome.message=환영합니다!
welcome.user=환영합니다, {0}님!
male.message=남성 사용자입니다.
female.message=여성 사용자입니다.&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;  프래그먼트 (Fragment)&lt;/h2&gt;
&lt;h3&gt;프래그먼트 정의&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- header.html --&amp;gt;
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html xmlns:th=&amp;quot;http://www.thymeleaf.org&amp;quot;&amp;gt;
&amp;lt;head th:fragment=&amp;quot;head&amp;quot;&amp;gt;
    &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;
    &amp;lt;title&amp;gt;공통 헤더&amp;lt;/title&amp;gt;
    &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;/css/common.css&amp;quot;&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;header th:fragment=&amp;quot;header&amp;quot;&amp;gt;
        &amp;lt;nav&amp;gt;
            &amp;lt;a href=&amp;quot;/&amp;quot;&amp;gt;홈&amp;lt;/a&amp;gt;
            &amp;lt;a href=&amp;quot;/products&amp;quot;&amp;gt;상품&amp;lt;/a&amp;gt;
            &amp;lt;a href=&amp;quot;/about&amp;quot;&amp;gt;소개&amp;lt;/a&amp;gt;
        &amp;lt;/nav&amp;gt;
    &amp;lt;/header&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;프래그먼트 사용&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- main.html --&amp;gt;
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html xmlns:th=&amp;quot;http://www.thymeleaf.org&amp;quot;&amp;gt;
&amp;lt;head th:replace=&amp;quot;~{header :: head}&amp;quot;&amp;gt;&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;!-- 헤더 포함 --&amp;gt;
    &amp;lt;div th:replace=&amp;quot;~{header :: header}&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;

    &amp;lt;!-- 메인 컨텐츠 --&amp;gt;
    &amp;lt;main&amp;gt;
        &amp;lt;h1&amp;gt;메인 페이지&amp;lt;/h1&amp;gt;
        &amp;lt;p&amp;gt;내용...&amp;lt;/p&amp;gt;
    &amp;lt;/main&amp;gt;

    &amp;lt;!-- 푸터 포함 --&amp;gt;
    &amp;lt;div th:include=&amp;quot;~{footer :: footer}&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;파라미터가 있는 프래그먼트&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- components.html --&amp;gt;
&amp;lt;div th:fragment=&amp;quot;card(title, content)&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;
        &amp;lt;h3 th:text=&amp;quot;${title}&amp;quot;&amp;gt;제목&amp;lt;/h3&amp;gt;
        &amp;lt;p th:text=&amp;quot;${content}&amp;quot;&amp;gt;내용&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;!-- 사용 --&amp;gt;
&amp;lt;div th:replace=&amp;quot;~{components :: card(&amp;#39;공지사항&amp;#39;, &amp;#39;새로운 기능이 추가되었습니다.&amp;#39;)}&amp;quot;&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;  표현식 언어&lt;/h2&gt;
&lt;h3&gt;변수 표현식 (&lt;code&gt;${...}&lt;/code&gt;)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- 기본 변수 --&amp;gt;
&amp;lt;p th:text=&amp;quot;${user.name}&amp;quot;&amp;gt;이름&amp;lt;/p&amp;gt;

&amp;lt;!-- 중첩 객체 --&amp;gt;
&amp;lt;p th:text=&amp;quot;${user.address.city}&amp;quot;&amp;gt;도시&amp;lt;/p&amp;gt;

&amp;lt;!-- 메서드 호출 --&amp;gt;
&amp;lt;p th:text=&amp;quot;${user.getFullName()}&amp;quot;&amp;gt;전체 이름&amp;lt;/p&amp;gt;

&amp;lt;!-- null 안전 연산자 --&amp;gt;
&amp;lt;p th:text=&amp;quot;${user?.address?.city}&amp;quot;&amp;gt;도시 (null 안전)&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;선택 변수 표현식 (&lt;code&gt;*{...}&lt;/code&gt;)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- th:object와 함께 사용 --&amp;gt;
&amp;lt;div th:object=&amp;quot;${user}&amp;quot;&amp;gt;
    &amp;lt;p th:text=&amp;quot;*{name}&amp;quot;&amp;gt;이름&amp;lt;/p&amp;gt;
    &amp;lt;p th:text=&amp;quot;*{email}&amp;quot;&amp;gt;이메일&amp;lt;/p&amp;gt;
    &amp;lt;p th:text=&amp;quot;*{age}&amp;quot;&amp;gt;나이&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;링크 표현식 (&lt;code&gt;@{...}&lt;/code&gt;)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- 상대 URL --&amp;gt;
&amp;lt;a th:href=&amp;quot;@{/user/list}&amp;quot;&amp;gt;사용자 목록&amp;lt;/a&amp;gt;

&amp;lt;!-- 절대 URL --&amp;gt;
&amp;lt;a th:href=&amp;quot;@{http://www.example.com}&amp;quot;&amp;gt;외부 링크&amp;lt;/a&amp;gt;

&amp;lt;!-- 컨텍스트 상대 URL --&amp;gt;
&amp;lt;a th:href=&amp;quot;@{~/documents/report.pdf}&amp;quot;&amp;gt;문서&amp;lt;/a&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;메시지 표현식 (&lt;code&gt;#{...}&lt;/code&gt;)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- 기본 메시지 --&amp;gt;
&amp;lt;p th:text=&amp;quot;#{home.welcome}&amp;quot;&amp;gt;환영 메시지&amp;lt;/p&amp;gt;

&amp;lt;!-- 변수가 있는 메시지 --&amp;gt;
&amp;lt;p th:text=&amp;quot;#{user.greeting(${user.name})}&amp;quot;&amp;gt;인사말&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;  고급 기능&lt;/h2&gt;
&lt;h3&gt;조건부 렌더링&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- Elvis 연산자 --&amp;gt;
&amp;lt;p th:text=&amp;quot;${user.name} ?: &amp;#39;이름 없음&amp;#39;&amp;quot;&amp;gt;기본 이름&amp;lt;/p&amp;gt;

&amp;lt;!-- 삼항 연산자 --&amp;gt;
&amp;lt;p th:text=&amp;quot;${user.age &amp;gt;= 18} ? &amp;#39;성인&amp;#39; : &amp;#39;미성년자&amp;#39;&amp;quot;&amp;gt;연령대&amp;lt;/p&amp;gt;

&amp;lt;!-- No-Operation 토큰 --&amp;gt;
&amp;lt;p th:text=&amp;quot;${user.description} ?: _&amp;quot;&amp;gt;
    기본 설명입니다. (user.description이 null이면 이 텍스트 유지)
&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;데이터 속성 추가&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- 동적 데이터 속성 --&amp;gt;
&amp;lt;div th:data-user-id=&amp;quot;${user.id}&amp;quot; 
     th:data-user-role=&amp;quot;${user.role}&amp;quot;&amp;gt;
    사용자 정보
&amp;lt;/div&amp;gt;
&amp;lt;!-- 결과: &amp;lt;div data-user-id=&amp;quot;123&amp;quot; data-user-role=&amp;quot;ADMIN&amp;quot;&amp;gt;사용자 정보&amp;lt;/div&amp;gt; --&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;인라인 처리&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- 텍스트 인라인 --&amp;gt;
&amp;lt;p&amp;gt;안녕하세요, [[${user.name}]]님!&amp;lt;/p&amp;gt;

&amp;lt;!-- JavaScript 인라인 --&amp;gt;
&amp;lt;script th:inline=&amp;quot;javascript&amp;quot;&amp;gt;
    var user = /*[[${user}]]*/ {};
    var userName = /*[[${user.name}]]*/ &amp;#39;default&amp;#39;;
    console.log(&amp;#39;사용자:&amp;#39;, userName);
&amp;lt;/script&amp;gt;

&amp;lt;!-- CSS 인라인 --&amp;gt;
&amp;lt;style th:inline=&amp;quot;css&amp;quot;&amp;gt;
    .user-color {
        color: /*[[${user.favoriteColor}]]*/ blue;
    }
&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;  실전 예제&lt;/h2&gt;
&lt;h3&gt;완전한 사용자 목록 페이지&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&amp;quot;ko&amp;quot; xmlns:th=&amp;quot;http://www.thymeleaf.org&amp;quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;
    &amp;lt;title th:text=&amp;quot;#{page.user.list}&amp;quot;&amp;gt;사용자 목록&amp;lt;/title&amp;gt;
    &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;/css/bootstrap.min.css&amp;quot;&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;
        &amp;lt;h1 th:text=&amp;quot;#{page.user.list}&amp;quot;&amp;gt;사용자 목록&amp;lt;/h1&amp;gt;

        &amp;lt;!-- 검색 폼 --&amp;gt;
        &amp;lt;form th:action=&amp;quot;@{/users}&amp;quot; method=&amp;quot;get&amp;quot; class=&amp;quot;mb-3&amp;quot;&amp;gt;
            &amp;lt;div class=&amp;quot;row&amp;quot;&amp;gt;
                &amp;lt;div class=&amp;quot;col-md-4&amp;quot;&amp;gt;
                    &amp;lt;input type=&amp;quot;text&amp;quot; name=&amp;quot;search&amp;quot; 
                           th:value=&amp;quot;${param.search}&amp;quot; 
                           placeholder=&amp;quot;이름 또는 이메일 검색&amp;quot; 
                           class=&amp;quot;form-control&amp;quot;&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;div class=&amp;quot;col-md-2&amp;quot;&amp;gt;
                    &amp;lt;select name=&amp;quot;role&amp;quot; class=&amp;quot;form-control&amp;quot;&amp;gt;
                        &amp;lt;option value=&amp;quot;&amp;quot;&amp;gt;전체 역할&amp;lt;/option&amp;gt;
                        &amp;lt;option th:each=&amp;quot;role : ${roles}&amp;quot; 
                                th:value=&amp;quot;${role}&amp;quot; 
                                th:text=&amp;quot;${role}&amp;quot;
                                th:selected=&amp;quot;${param.role == role}&amp;quot;&amp;gt;역할&amp;lt;/option&amp;gt;
                    &amp;lt;/select&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;div class=&amp;quot;col-md-2&amp;quot;&amp;gt;
                    &amp;lt;button type=&amp;quot;submit&amp;quot; class=&amp;quot;btn btn-primary&amp;quot;&amp;gt;검색&amp;lt;/button&amp;gt;
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/form&amp;gt;

        &amp;lt;!-- 사용자 목록 --&amp;gt;
        &amp;lt;div th:if=&amp;quot;${#lists.isEmpty(users)}&amp;quot; class=&amp;quot;alert alert-info&amp;quot;&amp;gt;
            등록된 사용자가 없습니다.
        &amp;lt;/div&amp;gt;

        &amp;lt;table th:unless=&amp;quot;${#lists.isEmpty(users)}&amp;quot; class=&amp;quot;table table-striped&amp;quot;&amp;gt;
            &amp;lt;thead&amp;gt;
                &amp;lt;tr&amp;gt;
                    &amp;lt;th&amp;gt;#&amp;lt;/th&amp;gt;
                    &amp;lt;th&amp;gt;이름&amp;lt;/th&amp;gt;
                    &amp;lt;th&amp;gt;이메일&amp;lt;/th&amp;gt;
                    &amp;lt;th&amp;gt;역할&amp;lt;/th&amp;gt;
                    &amp;lt;th&amp;gt;상태&amp;lt;/th&amp;gt;
                    &amp;lt;th&amp;gt;가입일&amp;lt;/th&amp;gt;
                    &amp;lt;th&amp;gt;액션&amp;lt;/th&amp;gt;
                &amp;lt;/tr&amp;gt;
            &amp;lt;/thead&amp;gt;
            &amp;lt;tbody&amp;gt;
                &amp;lt;tr th:each=&amp;quot;user, status : ${users}&amp;quot;&amp;gt;
                    &amp;lt;td th:text=&amp;quot;${status.count}&amp;quot;&amp;gt;순번&amp;lt;/td&amp;gt;
                    &amp;lt;td&amp;gt;
                        &amp;lt;a th:href=&amp;quot;@{/users/{id}(id=${user.id})}&amp;quot; 
                           th:text=&amp;quot;${user.name}&amp;quot;&amp;gt;이름&amp;lt;/a&amp;gt;
                    &amp;lt;/td&amp;gt;
                    &amp;lt;td th:text=&amp;quot;${user.email}&amp;quot;&amp;gt;이메일&amp;lt;/td&amp;gt;
                    &amp;lt;td&amp;gt;
                        &amp;lt;span class=&amp;quot;badge&amp;quot; 
                              th:classappend=&amp;quot;${user.role == &amp;#39;ADMIN&amp;#39;} ? &amp;#39;badge-danger&amp;#39; : &amp;#39;badge-primary&amp;#39;&amp;quot;
                              th:text=&amp;quot;${user.role}&amp;quot;&amp;gt;역할&amp;lt;/span&amp;gt;
                    &amp;lt;/td&amp;gt;
                    &amp;lt;td&amp;gt;
                        &amp;lt;span th:if=&amp;quot;${user.active}&amp;quot; class=&amp;quot;text-success&amp;quot;&amp;gt;활성&amp;lt;/span&amp;gt;
                        &amp;lt;span th:unless=&amp;quot;${user.active}&amp;quot; class=&amp;quot;text-danger&amp;quot;&amp;gt;비활성&amp;lt;/span&amp;gt;
                    &amp;lt;/td&amp;gt;
                    &amp;lt;td th:text=&amp;quot;${#dates.format(user.createdAt, &amp;#39;yyyy-MM-dd&amp;#39;)}&amp;quot;&amp;gt;가입일&amp;lt;/td&amp;gt;
                    &amp;lt;td&amp;gt;
                        &amp;lt;a th:href=&amp;quot;@{/users/{id}/edit(id=${user.id})}&amp;quot; 
                           class=&amp;quot;btn btn-sm btn-outline-primary&amp;quot;&amp;gt;수정&amp;lt;/a&amp;gt;
                        &amp;lt;a th:href=&amp;quot;@{/users/{id}/delete(id=${user.id})}&amp;quot; 
                           class=&amp;quot;btn btn-sm btn-outline-danger&amp;quot;
                           onclick=&amp;quot;return confirm(&amp;#39;정말 삭제하시겠습니까?&amp;#39;)&amp;quot;&amp;gt;삭제&amp;lt;/a&amp;gt;
                    &amp;lt;/td&amp;gt;
                &amp;lt;/tr&amp;gt;
            &amp;lt;/tbody&amp;gt;
        &amp;lt;/table&amp;gt;

        &amp;lt;!-- 페이징 --&amp;gt;
        &amp;lt;nav th:if=&amp;quot;${totalPages &amp;gt; 1}&amp;quot;&amp;gt;
            &amp;lt;ul class=&amp;quot;pagination justify-content-center&amp;quot;&amp;gt;
                &amp;lt;li class=&amp;quot;page-item&amp;quot; th:classappend=&amp;quot;${currentPage == 0} ? &amp;#39;disabled&amp;#39;&amp;quot;&amp;gt;
                    &amp;lt;a class=&amp;quot;page-link&amp;quot; 
                       th:href=&amp;quot;@{/users(page=0, search=${param.search}, role=${param.role})}&amp;quot;&amp;gt;
                        첫 페이지
                    &amp;lt;/a&amp;gt;
                &amp;lt;/li&amp;gt;
                &amp;lt;li class=&amp;quot;page-item&amp;quot; th:classappend=&amp;quot;${currentPage == 0} ? &amp;#39;disabled&amp;#39;&amp;quot;&amp;gt;
                    &amp;lt;a class=&amp;quot;page-link&amp;quot; 
                       th:href=&amp;quot;@{/users(page=${currentPage - 1}, search=${param.search}, role=${param.role})}&amp;quot;&amp;gt;
                        이전
                    &amp;lt;/a&amp;gt;
                &amp;lt;/li&amp;gt;

                &amp;lt;li th:each=&amp;quot;pageNum : ${#numbers.sequence(0, totalPages - 1)}&amp;quot;
                    class=&amp;quot;page-item&amp;quot; 
                    th:classappend=&amp;quot;${pageNum == currentPage} ? &amp;#39;active&amp;#39;&amp;quot;&amp;gt;
                    &amp;lt;a class=&amp;quot;page-link&amp;quot; 
                       th:href=&amp;quot;@{/users(page=${pageNum}, search=${param.search}, role=${param.role})}&amp;quot;
                       th:text=&amp;quot;${pageNum + 1}&amp;quot;&amp;gt;페이지&amp;lt;/a&amp;gt;
                &amp;lt;/li&amp;gt;

                &amp;lt;li class=&amp;quot;page-item&amp;quot; th:classappend=&amp;quot;${currentPage == totalPages - 1} ? &amp;#39;disabled&amp;#39;&amp;quot;&amp;gt;
                    &amp;lt;a class=&amp;quot;page-link&amp;quot; 
                       th:href=&amp;quot;@{/users(page=${currentPage + 1}, search=${param.search}, role=${param.role})}&amp;quot;&amp;gt;
                        다음
                    &amp;lt;/a&amp;gt;
                &amp;lt;/li&amp;gt;
                &amp;lt;li class=&amp;quot;page-item&amp;quot; th:classappend=&amp;quot;${currentPage == totalPages - 1} ? &amp;#39;disabled&amp;#39;&amp;quot;&amp;gt;
                    &amp;lt;a class=&amp;quot;page-link&amp;quot; 
                       th:href=&amp;quot;@{/users(page=${totalPages - 1}, search=${param.search}, role=${param.role})}&amp;quot;&amp;gt;
                        마지막 페이지
                    &amp;lt;/a&amp;gt;
                &amp;lt;/li&amp;gt;
            &amp;lt;/ul&amp;gt;
        &amp;lt;/nav&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 가이드를 통해 Thymeleaf의 주요 문법들을 실전에서 바로 활용할 수 있습니다!  &lt;/p&gt;</description>
      <category>Spring</category>
      <author>브이담곰</author>
      <guid isPermaLink="true">https://odlram.tistory.com/173</guid>
      <comments>https://odlram.tistory.com/entry/Thymeleaf-%EB%AC%B8%EB%B2%95#entry173comment</comments>
      <pubDate>Wed, 11 Jun 2025 17:30:19 +0900</pubDate>
    </item>
    <item>
      <title>@PathVariable vs @RequestParam 완벽 가이드</title>
      <link>https://odlram.tistory.com/entry/PathVariable-vs-RequestParam-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Framework에서 HTTP 요청의 데이터를 받는 두 가지 주요 어노테이션인 &lt;code&gt;@PathVariable&lt;/code&gt;과 &lt;code&gt;@RequestParam&lt;/code&gt;의 차이점과 사용법을 알아보자.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  @PathVariable&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;URL 경로의 일부를 변수로 추출&lt;/b&gt;하는 어노테이션&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본 문법&lt;/h3&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@GetMapping(&quot;/users/{id}&quot;)
public String getUser(@PathVariable Long id) {
    // id는 URL 경로에서 추출됨
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;특징&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;URL 경로 자체가 데이터&lt;/li&gt;
&lt;li&gt;RESTful API 설계에 적합&lt;/li&gt;
&lt;li&gt;필수 데이터 (경로의 일부이므로 생략 불가)&lt;/li&gt;
&lt;li&gt;보통 리소스 식별자로 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용 예시&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 기본 사용법&lt;/h4&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Controller
@RequestMapping(&quot;/menu&quot;)
public class MenuController {

    // URL: /menu/123
    @GetMapping(&quot;/{id}&quot;)
    public String getMenu(@PathVariable Long id, Model model) {
        MenuDTO menu = menuService.findById(id);
        model.addAttribute(&quot;menu&quot;, menu);
        return &quot;menu/detail&quot;;
    }

    // URL: /menu/delete/456
    @PostMapping(&quot;/delete/{menuId}&quot;)
    public String deleteMenu(@PathVariable(&quot;menuId&quot;) Long id) {
        menuService.deleteMenu(id);
        return &quot;redirect:/menu/list&quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 여러 개의 PathVariable&lt;/h4&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;// URL: /users/123/orders/456
@GetMapping(&quot;/users/{userId}/orders/{orderId}&quot;)
public String getUserOrder(@PathVariable Long userId, 
                          @PathVariable Long orderId, 
                          Model model) {
    OrderDTO order = orderService.findByUserAndOrder(userId, orderId);
    model.addAttribute(&quot;order&quot;, order);
    return &quot;order/detail&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. Optional PathVariable&lt;/h4&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// URL: /products 또는 /products/electronics
@GetMapping({&quot;/products&quot;, &quot;/products/{category}&quot;})
public String getProducts(@PathVariable(required = false) String category, 
                         Model model) {
    if (category != null) {
        // 카테고리별 상품 조회
        model.addAttribute(&quot;products&quot;, productService.findByCategory(category));
    } else {
        // 전체 상품 조회
        model.addAttribute(&quot;products&quot;, productService.findAll());
    }
    return &quot;product/list&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  @RequestParam&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;URL의 쿼리 파라미터나 폼 데이터&lt;/b&gt;를 추출하는 어노테이션.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본 문법&lt;/h3&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@GetMapping(&quot;/search&quot;)
public String search(@RequestParam String keyword) {
    // ?keyword=값 에서 값을 추출
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;특징&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;URL 뒤에 ?key=value 형태&lt;/li&gt;
&lt;li&gt;선택적 파라미터 (기본값 설정 가능)&lt;/li&gt;
&lt;li&gt;필터링, 검색, 페이징에 주로 사용&lt;/li&gt;
&lt;li&gt;폼 데이터도 받을 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용 예시&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 기본 사용법&lt;/h4&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Controller
@RequestMapping(&quot;/menu&quot;)
public class MenuController {

    // URL: /menu/search?keyword=햄버거
    @GetMapping(&quot;/search&quot;)
    public String searchMenu(@RequestParam String keyword, Model model) {
        List&amp;lt;MenuDTO&amp;gt; menus = menuService.searchByKeyword(keyword);
        model.addAttribute(&quot;menus&quot;, menus);
        return &quot;menu/search-result&quot;;
    }

    // URL: /menu/list?page=1&amp;amp;size=10
    @GetMapping(&quot;/list&quot;)
    public String getMenuList(@RequestParam(defaultValue = &quot;1&quot;) int page,
                             @RequestParam(defaultValue = &quot;10&quot;) int size,
                             Model model) {
        PageResult&amp;lt;MenuDTO&amp;gt; result = menuService.findMenus(page, size);
        model.addAttribute(&quot;menus&quot;, result);
        return &quot;menu/list&quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 선택적 파라미터와 기본값&lt;/h4&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// URL: /products?category=electronics&amp;amp;minPrice=100&amp;amp;maxPrice=500
@GetMapping(&quot;/products&quot;)
public String getProducts(@RequestParam(required = false) String category,
                         @RequestParam(defaultValue = &quot;0&quot;) int minPrice,
                         @RequestParam(defaultValue = &quot;999999&quot;) int maxPrice,
                         @RequestParam(defaultValue = &quot;name&quot;) String sortBy,
                         Model model) {

    ProductSearchCriteria criteria = ProductSearchCriteria.builder()
        .category(category)
        .minPrice(minPrice)
        .maxPrice(maxPrice)
        .sortBy(sortBy)
        .build();

    List&amp;lt;ProductDTO&amp;gt; products = productService.search(criteria);
    model.addAttribute(&quot;products&quot;, products);
    return &quot;product/list&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 폼 데이터 받기&lt;/h4&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;// HTML 폼에서 POST 요청
@PostMapping(&quot;/register&quot;)
public String registerMenu(@RequestParam String name,
                          @RequestParam int price,
                          @RequestParam String category,
                          @RequestParam(defaultValue = &quot;Y&quot;) String status) {

    MenuDTO menu = MenuDTO.builder()
        .name(name)
        .price(price)
        .category(category)
        .status(status)
        .build();

    menuService.register(menu);
    return &quot;redirect:/menu/list&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. 배열/리스트 파라미터&lt;/h4&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;// URL: /menu/delete?ids=1&amp;amp;ids=2&amp;amp;ids=3
@PostMapping(&quot;/delete&quot;)
public String deleteMenus(@RequestParam(&quot;ids&quot;) List&amp;lt;Long&amp;gt; menuIds) {
    menuService.deleteMenus(menuIds);
    return &quot;redirect:/menu/list&quot;;
}

// URL: /products?tags=sale&amp;amp;tags=new&amp;amp;tags=popular
@GetMapping(&quot;/products&quot;)
public String getProductsByTags(@RequestParam List&amp;lt;String&amp;gt; tags, Model model) {
    List&amp;lt;ProductDTO&amp;gt; products = productService.findByTags(tags);
    model.addAttribute(&quot;products&quot;, products);
    return &quot;product/list&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;⚖️ 언제 무엇을 사용할까?&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@PathVariable 사용 시기&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;리소스 식별&lt;/b&gt;: &lt;code&gt;/users/{id}&lt;/code&gt;, &lt;code&gt;/orders/{orderId}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;계층 구조&lt;/b&gt;: &lt;code&gt;/users/{userId}/orders/{orderId}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;RESTful API&lt;/b&gt;: CRUD 작업의 대상 지정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;필수 데이터&lt;/b&gt;: 반드시 있어야 하는 값&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;// ✅ 좋은 예시
@GetMapping(&quot;/users/{id}&quot;)           // 특정 사용자 조회
@PutMapping(&quot;/products/{id}&quot;)        // 특정 상품 수정
@DeleteMapping(&quot;/orders/{id}&quot;)       // 특정 주문 삭제
@GetMapping(&quot;/categories/{categoryId}/products/{productId}&quot;) // 계층 구조&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@RequestParam 사용 시기&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;검색/필터링&lt;/b&gt;: 검색어, 필터 조건&lt;/li&gt;
&lt;li&gt;&lt;b&gt;페이징&lt;/b&gt;: page, size, sort&lt;/li&gt;
&lt;li&gt;&lt;b&gt;선택적 옵션&lt;/b&gt;: 기본값이 있는 설정들&lt;/li&gt;
&lt;li&gt;&lt;b&gt;폼 데이터&lt;/b&gt;: 사용자 입력 데이터&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;// ✅ 좋은 예시
@GetMapping(&quot;/products?category=electronics&amp;amp;minPrice=100&quot;)  // 필터링
@GetMapping(&quot;/users?page=1&amp;amp;size=20&amp;amp;sort=name&quot;)             // 페이징
@GetMapping(&quot;/search?keyword=맛있는&amp;amp;type=korean&quot;)            // 검색
@PostMapping(&quot;/contact&quot;)  // 폼 데이터                      // 폼 전송&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  혼합 사용 예시&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 프로젝트에서는 두 어노테이션을 함께 사용하는 경우가 많다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Controller
@RequestMapping(&quot;/api/v1&quot;)
public class ProductController {

    // 특정 카테고리의 상품들을 페이징과 정렬로 조회
    // URL: /api/v1/categories/electronics/products?page=1&amp;amp;size=20&amp;amp;sort=price&amp;amp;order=asc
    @GetMapping(&quot;/categories/{categoryId}/products&quot;)
    public ResponseEntity&amp;lt;PageResult&amp;lt;ProductDTO&amp;gt;&amp;gt; getCategoryProducts(
            @PathVariable Long categoryId,                    // 카테고리 ID (필수)
            @RequestParam(defaultValue = &quot;1&quot;) int page,       // 페이지 번호 (선택)
            @RequestParam(defaultValue = &quot;20&quot;) int size,      // 페이지 크기 (선택)
            @RequestParam(defaultValue = &quot;name&quot;) String sort, // 정렬 기준 (선택)
            @RequestParam(defaultValue = &quot;asc&quot;) String order  // 정렬 순서 (선택)
    ) {
        PageResult&amp;lt;ProductDTO&amp;gt; result = productService.findByCategoryId(
            categoryId, page, size, sort, order
        );
        return ResponseEntity.ok(result);
    }

    // 특정 사용자의 주문들을 상태별로 필터링
    // URL: /api/v1/users/123/orders?status=PENDING&amp;amp;startDate=2024-01-01&amp;amp;endDate=2024-12-31
    @GetMapping(&quot;/users/{userId}/orders&quot;)
    public ResponseEntity&amp;lt;List&amp;lt;OrderDTO&amp;gt;&amp;gt; getUserOrders(
            @PathVariable Long userId,                        // 사용자 ID (필수)
            @RequestParam(required = false) String status,    // 주문 상태 (선택)
            @RequestParam(required = false) String startDate, // 시작 날짜 (선택)
            @RequestParam(required = false) String endDate    // 종료 날짜 (선택)
    ) {
        OrderSearchCriteria criteria = OrderSearchCriteria.builder()
            .userId(userId)
            .status(status)
            .startDate(startDate)
            .endDate(endDate)
            .build();

        List&amp;lt;OrderDTO&amp;gt; orders = orderService.searchOrders(criteria);
        return ResponseEntity.ok(orders);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  요약 비교표&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;@PathVariable&lt;/th&gt;
&lt;th&gt;@RequestParam&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;위치&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;URL 경로 안&lt;/td&gt;
&lt;td&gt;URL 쿼리 파라미터 (?key=value)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;필수성&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;필수 (경로의 일부)&lt;/td&gt;
&lt;td&gt;선택적 (required 속성으로 제어)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;기본값&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;설정 불가&lt;/td&gt;
&lt;td&gt;defaultValue로 설정 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;용도&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;리소스 식별, RESTful API&lt;/td&gt;
&lt;td&gt;검색, 필터링, 페이징, 폼 데이터&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;예시 URL&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/users/123&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/search?keyword=spring&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;여러 값&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;여러 개 가능&lt;/td&gt;
&lt;td&gt;배열/리스트로 받기 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  베스트 프랙티스&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. RESTful API 설계&lt;/h3&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;// ✅ 권장
@GetMapping(&quot;/users/{id}&quot;)              // 사용자 조회
@GetMapping(&quot;/users/{id}/orders&quot;)       // 사용자의 주문 목록
@GetMapping(&quot;/users?search=name&amp;amp;page=1&quot;) // 사용자 검색

// ❌ 비권장
@GetMapping(&quot;/getUser?id=123&quot;)          // 비RESTful
@GetMapping(&quot;/user-orders?userId=123&quot;)  // 계층 구조 미표현&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 의미있는 변수명 사용&lt;/h3&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;// ✅ 권장
@GetMapping(&quot;/products/{productId}&quot;)
public String getProduct(@PathVariable(&quot;productId&quot;) Long id) { }

// ✅ 권장 (변수명이 같으면 value 생략 가능)
@GetMapping(&quot;/products/{id}&quot;)
public String getProduct(@PathVariable Long id) { }&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 유효성 검사 추가&lt;/h3&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@GetMapping(&quot;/users/{id}&quot;)
public String getUser(@PathVariable @Min(1) Long id) {
    // id는 1 이상이어야 함
}

@GetMapping(&quot;/search&quot;)
public String search(@RequestParam @Size(min=2, max=50) String keyword) {
    // 검색어는 2자 이상 50자 이하
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 &lt;code&gt;@PathVariable&lt;/code&gt;과 &lt;code&gt;@RequestParam&lt;/code&gt;을 적절히 사용하면 깔끔하고 이해하기 쉬운 API를 만들 수 있다.&lt;/p&gt;</description>
      <category>Spring</category>
      <category>Annotation</category>
      <category>Backend</category>
      <category>Java</category>
      <category>Spring</category>
      <author>브이담곰</author>
      <guid isPermaLink="true">https://odlram.tistory.com/172</guid>
      <comments>https://odlram.tistory.com/entry/PathVariable-vs-RequestParam-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C#entry172comment</comments>
      <pubDate>Wed, 11 Jun 2025 17:16:03 +0900</pubDate>
    </item>
    <item>
      <title>크래프톤 정글 6기 TIL - Review | 트라이( Trie )</title>
      <link>https://odlram.tistory.com/entry/%ED%81%AC%EB%9E%98%ED%94%84%ED%86%A4-%EC%A0%95%EA%B8%80-6%EA%B8%B0-TIL-Review-%ED%8A%B8%EB%9D%BC%EC%9D%B4-Trie</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;808&quot; data-origin-height=&quot;476&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ci0jaa/btsLHB7b1Xg/Vz2ubxjHogQo5RjlEvZQlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ci0jaa/btsLHB7b1Xg/Vz2ubxjHogQo5RjlEvZQlK/img.png&quot; data-alt=&quot;정글 공부 키워드를 복습하며.. 모른척 했던 주제에 맞서 보려고 한다.. 그것은&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ci0jaa/btsLHB7b1Xg/Vz2ubxjHogQo5RjlEvZQlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fci0jaa%2FbtsLHB7b1Xg%2FVz2ubxjHogQo5RjlEvZQlK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;808&quot; height=&quot;476&quot; data-origin-width=&quot;808&quot; data-origin-height=&quot;476&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;정글 공부 키워드를 복습하며.. 모른척 했던 주제에 맞서 보려고 한다.. 그것은&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;270&quot; data-origin-height=&quot;173&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/13JwJ/btsLGjGhcxd/GS2GdiTMLWp5KhfVOQSRm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/13JwJ/btsLGjGhcxd/GS2GdiTMLWp5KhfVOQSRm1/img.png&quot; data-alt=&quot;트라이.. 2주차 째 Optional 이라 되어있지만 등장하는 걸 보아선..미루면 안된다..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/13JwJ/btsLGjGhcxd/GS2GdiTMLWp5KhfVOQSRm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F13JwJ%2FbtsLGjGhcxd%2FGS2GdiTMLWp5KhfVOQSRm1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;270&quot; height=&quot;173&quot; data-origin-width=&quot;270&quot; data-origin-height=&quot;173&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;트라이.. 2주차 째 Optional 이라 되어있지만 등장하는 걸 보아선..미루면 안된다..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;유튭 강의를 하나 슥 볼랬는데 바킹독님 마지막 강의가 트라이였다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;내 최애 남잔데, 그의 최애 알고리즘이 트라이라 그래서 호감도 급 상승..&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;렛츠 기릿..&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;진심으로 바킹독님..넘 멋지다..&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;트라이( Trie )&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;문자를 효율적으로 처리하기 위한 &lt;u&gt;트리&lt;/u&gt; 자료구조&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;✅ 장점 : 단어의 저장 개수와 무관하게 삽입,탐색, 삭제가 O(|S|)의 시간 복잡도를 가진다.&lt;br /&gt;✅ 단점 : 메모리를 많이 차지한다. 트리의 정점의 index를 기록해야해서 int의 4바이트* |S| 만큼 메모리를 더 사용한다.&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;다른 자료구조와 비교!&lt;/b&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;이진 검색 트리 :&lt;/span&gt; 삽입, 탐색, 삭제가 O(logN) 이지만, 문자열을 검색할 경우 단어의 길이 S 만큼 대조를 해야하기 때문에,&lt;br /&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;최악의 경우 O( S *&amp;nbsp; logN )&lt;/span&gt; 이 걸린다.&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;832&quot; data-origin-height=&quot;109&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEnvci/btsLGhhqnm0/kNOEcPtRjOG3dfhMQtkOS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEnvci/btsLGhhqnm0/kNOEcPtRjOG3dfhMQtkOS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEnvci/btsLGhhqnm0/kNOEcPtRjOG3dfhMQtkOS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEnvci%2FbtsLGhhqnm0%2FkNOEcPtRjOG3dfhMQtkOS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;832&quot; height=&quot;109&quot; data-origin-width=&quot;832&quot; data-origin-height=&quot;109&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;해시 :&lt;/span&gt; 삽입, 탐색, 삭제가 O(1) 이지만, 문자열을 검색할 경우 &lt;span style=&quot;color: #006dd7;&quot;&gt;O( |S| )&lt;/span&gt;이 걸리고, &lt;u&gt;해시 충돌에 따라 성능이 저하 될 수 있다.&lt;/u&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;다른 자료구조와 비교했을 때 확실히 트라이가 시간이 빨라보이지만,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;실제로 구현해보면 일반적인 문자열의 삽입, 탐색, 삭제를 수행하는 상황에서&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;트라이보다 해시나 이진 검색트리가 메모리, 시간 측에서 모두 효율적이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;  트라이 삽입 로직&lt;br /&gt;&lt;/span&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;679&quot; data-origin-height=&quot;246&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYSaj5/btsLFLQXSSr/AwKX3JNimM9XQ2kvYEr0e1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYSaj5/btsLFLQXSSr/AwKX3JNimM9XQ2kvYEr0e1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYSaj5/btsLFLQXSSr/AwKX3JNimM9XQ2kvYEr0e1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYSaj5%2FbtsLFLQXSSr%2FAwKX3JNimM9XQ2kvYEr0e1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;679&quot; height=&quot;246&quot; data-origin-width=&quot;679&quot; data-origin-height=&quot;246&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;노란색&lt;/span&gt; : 현재 노드( 정점 )&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;초록색 :&lt;/span&gt; 문자열 끝을 알려주는 트리거&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;현재 노드의 자식으로 단어를 추가해주면서&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f3c000;&quot;&gt;정점&lt;/span&gt;이 갱신된다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;마지막 글자는 &lt;span style=&quot;color: #009a87;&quot;&gt;트리거&lt;/span&gt;를 갱신해 주면서, 탐색 할 때 글자의 끝임을 알려준다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt; &lt;span style=&quot;font-family: 'Noto Serif KR'; color: #333333; text-align: center;&quot;&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; 트라이 검색 로직&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;492&quot; data-origin-height=&quot;242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfq2Rq/btsLGiUVjzd/tM4hSBU3344TsfVCHViw81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfq2Rq/btsLGiUVjzd/tM4hSBU3344TsfVCHViw81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfq2Rq/btsLGiUVjzd/tM4hSBU3344TsfVCHViw81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcfq2Rq%2FbtsLGiUVjzd%2FtM4hSBU3344TsfVCHViw81%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;492&quot; height=&quot;242&quot; data-origin-width=&quot;492&quot; data-origin-height=&quot;242&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f3c000;&quot;&gt;정점&lt;/span&gt;을 이동하며 글자를 비교한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;트리거&lt;/span&gt;를 통해 단어의 끝임을 알고,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;해당 단어가 존재함을 알 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt; &lt;span style=&quot;font-family: 'Noto Serif KR'; color: #333333; text-align: center;&quot;&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; 트라이 삭제 로직&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;371&quot; data-origin-height=&quot;255&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bW9fmY/btsLHCE1Kto/jd9U923yxBoIdGXDnvJEI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bW9fmY/btsLHCE1Kto/jd9U923yxBoIdGXDnvJEI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bW9fmY/btsLHCE1Kto/jd9U923yxBoIdGXDnvJEI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbW9fmY%2FbtsLHCE1Kto%2Fjd9U923yxBoIdGXDnvJEI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;371&quot; height=&quot;255&quot; data-origin-width=&quot;371&quot; data-origin-height=&quot;255&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;트리의 구조를 변경하면 안되는 트라이 특성&lt;/span&gt;상&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;삭제 작업을 할 때, 정점을 직접적으로 삭제할 수 없다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;트리거를 지움으로 써 단어가 존재함을 간접적으로 삭제&lt;/span&gt;한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 불필요한 단어가 트리에 남아 있을 수 있으므로,&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;⭐메모리 측면에서 비효율적&lt;/u&gt;일 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;  트라이는 &lt;u&gt;삭제가 많은 작업에서는 굉장히 높은 공간 복잡도를 가질 수 있음을 알 수 있다.&lt;/u&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;✏ 트라이 구현&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;647&quot; data-origin-height=&quot;243&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcBL3r/btsLFXcDpi3/SsativDCTxY1aaYzrb2gJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcBL3r/btsLFXcDpi3/SsativDCTxY1aaYzrb2gJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcBL3r/btsLFXcDpi3/SsativDCTxY1aaYzrb2gJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcBL3r%2FbtsLFXcDpi3%2FSsativDCTxY1aaYzrb2gJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;647&quot; height=&quot;243&quot; data-origin-width=&quot;647&quot; data-origin-height=&quot;243&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;앞서서 말했 듯, 정점의 자식을 관리하는 배열들은&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;정점의 인덱스로 관리되기 때문에 &lt;u&gt;int 자료형&lt;/u&gt;을 사용한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 단순히 char 배열이라면 문자의 수 N 만큼의 1 * 1 * 단어 개수 byte의 메모리가 사용되겠지만&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;int 자료형을 사용하기 때문에 4 * 4byte * 단어 개수 의 메모리가 사용된다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;( 26개의 문자를 사용한다면 그 4배인, 104 * 단어 개수&amp;nbsp; byte배 만큼 사용된다. )&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;메모리 절약 방법&lt;br /&gt;&lt;/span&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;756&quot; data-origin-height=&quot;274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B9tAV/btsLHAHduhg/QfN3WmiBgbGYClXz3OaCqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B9tAV/btsLHAHduhg/QfN3WmiBgbGYClXz3OaCqK/img.png&quot; data-alt=&quot;출처 : https://blog.encrypted.gg/1059( BaaaaaaaarkingDog Trie 블로그 글 )&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B9tAV/btsLHAHduhg/QfN3WmiBgbGYClXz3OaCqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB9tAV%2FbtsLHAHduhg%2FQfN3WmiBgbGYClXz3OaCqK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;756&quot; height=&quot;274&quot; data-origin-width=&quot;756&quot; data-origin-height=&quot;274&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://blog.encrypted.gg/1059( BaaaaaaaarkingDog Trie 블로그 글 )&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;935&quot; data-origin-height=&quot;304&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bm8G6V/btsLFgjurbT/ejROQ038iSnyHU6SVtd8i0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bm8G6V/btsLFgjurbT/ejROQ038iSnyHU6SVtd8i0/img.png&quot; data-alt=&quot;출처 : https://blog.encrypted.gg/1059( BaaaaaaaarkingDog Trie 블로그 글 )&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bm8G6V/btsLFgjurbT/ejROQ038iSnyHU6SVtd8i0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbm8G6V%2FbtsLFgjurbT%2FejROQ038iSnyHU6SVtd8i0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;935&quot; height=&quot;304&quot; data-origin-width=&quot;935&quot; data-origin-height=&quot;304&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://blog.encrypted.gg/1059( BaaaaaaaarkingDog Trie 블로그 글 )&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;가정한 시간, 공간 복잡도와&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;실제 시간과 메모리 사용정도는 확연히 다름을 볼 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;일단 정적배열은 당연히 시간은 적게 걸리지만 메모리가 많이 사용되었고,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;동적 배열과 연결리스트&lt;/span&gt;는 정점 리스트를 검색 할 때 O(1)이 아닌 O(26) 만큼 시간이 걸리기 떄문에 기하급수적으로 오래 걸리는 것을 볼 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;이진 검색트리&lt;/span&gt;는 메모리는 정적 배열과 비슷하지만 시간은 정적 배열보다 훨씬 느린 것을 볼 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;반면, &lt;span style=&quot;color: #009a87;&quot;&gt;해시&lt;/span&gt;는 충격적인 결과가...나왔는데. 이는 아무래도 단어를 저장하는데 각각의 문자는 중복됨이 많으니 아마 해시 충돌의 발생률이 높아져서 시간이 오래 걸린 것 같다는 생각이 든다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;공간 복잡도도,, 뭐 애초에 이걸 해시로 구현하는데 웃김..( 쓰기 귀찮아서 이렇게 쓰는건 아님 )&lt;/p&gt;</description>
      <category>KRAFTON JUNGLE/JUNGLE TIL</category>
      <author>브이담곰</author>
      <guid isPermaLink="true">https://odlram.tistory.com/171</guid>
      <comments>https://odlram.tistory.com/entry/%ED%81%AC%EB%9E%98%ED%94%84%ED%86%A4-%EC%A0%95%EA%B8%80-6%EA%B8%B0-TIL-Review-%ED%8A%B8%EB%9D%BC%EC%9D%B4-Trie#entry171comment</comments>
      <pubDate>Wed, 8 Jan 2025 00:27:12 +0900</pubDate>
    </item>
    <item>
      <title>크래프톤 정글 6기 TIL - Review | 보이어-무어 알고리즘</title>
      <link>https://odlram.tistory.com/entry/%ED%81%AC%EB%9E%98%ED%94%84%ED%86%A4-%EC%A0%95%EA%B8%80-6%EA%B8%B0-TIL-Review-%EB%B3%B4%EC%9D%B4%EC%96%B4-%EB%AC%B4%EC%96%B4-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;굼뱅담곰.jpg&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;604&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o6iOE/btsLE0HO3zO/SsvTgR9uItqkrDHyOdIi61/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o6iOE/btsLE0HO3zO/SsvTgR9uItqkrDHyOdIi61/img.jpg&quot; data-alt=&quot;진짜 오랜만에 쓴다..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o6iOE/btsLE0HO3zO/SsvTgR9uItqkrDHyOdIi61/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo6iOE%2FbtsLE0HO3zO%2FSsvTgR9uItqkrDHyOdIi61%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;640&quot; height=&quot;604&quot; data-filename=&quot;굼뱅담곰.jpg&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;604&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;진짜 오랜만에 쓴다..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;정글 중반기에 들어가면서,, 할게 너무 많고 블로그에 &quot;정리&quot;를 하는거 자체도 힘들어서.. 공부하면서 노션에다 기록했더니 TIL을 안쓰는 사람처럼 보여버렸다..&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;수료를 하고나서~ 좀 쉬다가!!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;정글 콤파스 보면서 놓쳤던 부분들 복습하는 중인데, 여기다가 정리를 해보려구 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;나름 복습할 때 도움이 많이 되었다! 키키&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;✅ 보이어 - 무어를 공부하기 전에....&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;1. 부르투 포스 알고리즘&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;2. KMP 알고리즘&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;에 대해 먼저 공부하는 것을 추천 !&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Boyer - Moore algorithm( 보이어 무어 알고리즘 )&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;문자열 검색&lt;/u&gt;에 사용되는 알고리즘이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;불필요한 것을 건너뛰고. &lt;span style=&quot;color: #006dd7;&quot;&gt;검색을 빠르게 하는 것&lt;/span&gt;이 주 목표.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;워드 검색 기능에서 사용된다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;보이어-무어 알고리즘은 보통 상황에서 문자열은 앞부분보다는&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;뒷부분에서 불일치가 일어날 확률이 높다는 성질을 활용&lt;/span&gt;한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 오른쪽끝부터 비교하게 된다!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Bad Character Rule&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;- 주어진 Text에서 특정 Pattern의 문자열을 찾는 방법.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;- 일일이 비교하는 brute-force method보다 효율적.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;- 찾고자 하는 문자열 Pattern에 대한 문자열내 저장위치를 알아야 함.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;- 앞의 문자부터 비교하지 않고 뒤의 문자부터 비교.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;- 문자(Character)가 일치하지 않을 때 사용.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;아래와 같이 Text안에서 Pattern(P)가 있는지 검색을 해본다고 하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;440&quot; data-origin-height=&quot;81&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nFHXP/btsLFb3rDez/zXCcZ5yediOmK9SK57sj31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nFHXP/btsLFb3rDez/zXCcZ5yediOmK9SK57sj31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nFHXP/btsLFb3rDez/zXCcZ5yediOmK9SK57sj31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnFHXP%2FbtsLFb3rDez%2FzXCcZ5yediOmK9SK57sj31%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;440&quot; height=&quot;81&quot; data-origin-width=&quot;440&quot; data-origin-height=&quot;81&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;1. &lt;b&gt;Skip Table&lt;/b&gt;을 만든다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;문자를 비교하다가&amp;nbsp; 문자가 일치 하지 않을 때&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;얼만큼 패턴을 이동시킬지 결정하는 테이블&lt;/span&gt;이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;426&quot; data-origin-height=&quot;109&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmP5xw/btsLGMVGjD4/WVAFh6i14mof9KGZupim8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmP5xw/btsLGMVGjD4/WVAFh6i14mof9KGZupim8k/img.png&quot; data-alt=&quot;Patter의 길이는 4 이니까, 위와 같이 계산해준다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmP5xw/btsLGMVGjD4/WVAFh6i14mof9KGZupim8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmP5xw%2FbtsLGMVGjD4%2FWVAFh6i14mof9KGZupim8k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;426&quot; height=&quot;109&quot; data-origin-width=&quot;426&quot; data-origin-height=&quot;109&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Patter의 길이는 4 이니까, 위와 같이 계산해준다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;2. Pattern의&lt;span style=&quot;color: #f89009;&quot;&gt; 우측부터&lt;/span&gt; 차례로 비교를 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;불일치 할 경우 Text의 문자가 Pattern에 있는지 검색을 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;만약, &lt;u&gt;Pattern에 없을 경우 Skip table의 * 일 경우 만큼 문자를 이동&lt;/u&gt;한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;454&quot; data-origin-height=&quot;88&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/R5AEK/btsLFA2QYoe/TDEqEfkIy7IqafWGzzLqek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/R5AEK/btsLFA2QYoe/TDEqEfkIy7IqafWGzzLqek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/R5AEK/btsLFA2QYoe/TDEqEfkIy7IqafWGzzLqek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FR5AEK%2FbtsLFA2QYoe%2FTDEqEfkIy7IqafWGzzLqek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;454&quot; height=&quot;88&quot; data-origin-width=&quot;454&quot; data-origin-height=&quot;88&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;3. 다시 우측부터 비교를 하는데,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;T, A는 일치하고 다음 A는 일치하지 않는다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 A가 패턴에 존재 하는지 검사를 하는데, &lt;span style=&quot;color: #f3c000;&quot;&gt;현재 남은 패턴 문자들 내에서는 존재하지 않기 때문&lt;/span&gt;에&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;일치 하지 않는 문자의 개수 만큼 Pattern을 이동&lt;/u&gt;시킨다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;440&quot; data-origin-height=&quot;98&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cibCLP/btsLFXXG9Lr/WeOm3mapKKYDQiXiXG5ju0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cibCLP/btsLFXXG9Lr/WeOm3mapKKYDQiXiXG5ju0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cibCLP/btsLFXXG9Lr/WeOm3mapKKYDQiXiXG5ju0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcibCLP%2FbtsLFXXG9Lr%2FWeOm3mapKKYDQiXiXG5ju0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;440&quot; height=&quot;98&quot; data-origin-width=&quot;440&quot; data-origin-height=&quot;98&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;4. 다시 우측부터 비교를 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;C와 T는 일치하지 않지만, Text의 C는 Pattern에 존재한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Skip Table에서 C의 값 만큼 이동한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;436&quot; data-origin-height=&quot;92&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oAQr5/btsLFWYMCZj/HOcAFxOFGd7fmf6N7U7T7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oAQr5/btsLFWYMCZj/HOcAFxOFGd7fmf6N7U7T7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oAQr5/btsLFWYMCZj/HOcAFxOFGd7fmf6N7U7T7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoAQr5%2FbtsLFWYMCZj%2FHOcAFxOFGd7fmf6N7U7T7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;436&quot; height=&quot;92&quot; data-origin-width=&quot;436&quot; data-origin-height=&quot;92&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;5. 다시 우측부터 비교를 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;모두 일치하므로 검색은 종료된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;434&quot; data-origin-height=&quot;92&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LPDeg/btsLFQEsbtm/dXekuIP7jswl9cMF94Do0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LPDeg/btsLFQEsbtm/dXekuIP7jswl9cMF94Do0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LPDeg/btsLFQEsbtm/dXekuIP7jswl9cMF94Do0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLPDeg%2FbtsLFQEsbtm%2FdXekuIP7jswl9cMF94Do0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;434&quot; height=&quot;92&quot; data-origin-width=&quot;434&quot; data-origin-height=&quot;92&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Boyer-Moore: Bad Character Rule 효율&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;1 ) Brute-Force 방법과 달리 모든 문자를 순회하면서 비교하지 않아도 되므로 더 효율적인 비교 가능.&lt;br /&gt;2 ) &lt;u&gt;선형적 시간 복잡도&lt;/u&gt;를 가짐에도 다른 패턴 매칭 알고리즘에 비해 우수한 성능을 가짐.&lt;br /&gt;3 ) Text 길이가 N개의 문자열일 때, 패턴의 길이 M만큼 비교 수행.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;O(N*M)&lt;/span&gt;&lt;/blockquote&gt;</description>
      <category>KRAFTON JUNGLE/JUNGLE TIL</category>
      <author>브이담곰</author>
      <guid isPermaLink="true">https://odlram.tistory.com/170</guid>
      <comments>https://odlram.tistory.com/entry/%ED%81%AC%EB%9E%98%ED%94%84%ED%86%A4-%EC%A0%95%EA%B8%80-6%EA%B8%B0-TIL-Review-%EB%B3%B4%EC%9D%B4%EC%96%B4-%EB%AC%B4%EC%96%B4-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98#entry170comment</comments>
      <pubDate>Tue, 7 Jan 2025 17:27:28 +0900</pubDate>
    </item>
    <item>
      <title>10844 쉬운 계단 수</title>
      <link>https://odlram.tistory.com/entry/10844-%EC%89%AC%EC%9A%B4-%EA%B3%84%EB%8B%A8-%EC%88%98</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목을 입력해주세요_-005.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4nm6C/btsI5zKOW11/sUAn1GFVkuyH1MSyshyE70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4nm6C/btsI5zKOW11/sUAn1GFVkuyH1MSyshyE70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4nm6C/btsI5zKOW11/sUAn1GFVkuyH1MSyshyE70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4nm6C%2FbtsI5zKOW11%2FsUAn1GFVkuyH1MSyshyE70%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot; data-filename=&quot;제목을 입력해주세요_-005.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/status?user_id=yuu_ta&amp;amp;problem_id=10844&amp;amp;from_mine=1&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/status?user_id=yuu_ta&amp;amp;problem_id=10844&amp;amp;from_mine=1&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;✔ 유형 : DP&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;✔ 문제 풀이: 일의 자리 수에 어떤 수가 오느냐에 따라 경우의 수가 달라짐을 알고, 점화식을 세워 배열을 채워나간다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;684&quot; data-origin-height=&quot;276&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pbewP/btsI4tdApRH/3Sb2mWszlWJ0yTzzHphn30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pbewP/btsI4tdApRH/3Sb2mWszlWJ0yTzzHphn30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pbewP/btsI4tdApRH/3Sb2mWszlWJ0yTzzHphn30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpbewP%2FbtsI4tdApRH%2F3Sb2mWszlWJ0yTzzHphn30%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;684&quot; height=&quot;276&quot; data-origin-width=&quot;684&quot; data-origin-height=&quot;276&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드&lt;/p&gt;
&lt;pre id=&quot;code_1723618252980&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import sys
input = sys.stdin.readline

MOD = 1000000000

N = int(input())

# 1 &amp;lt;= N &amp;lt;= 100
DP = [[0 for _ in range(10)] for _ in range(N+1)] # 수의 길이와 올수있는 숫자종류 0~9 

# 초기 조건 설정 (1자리 숫자)
for i in range(1, 10):  # 0으로 시작하는 수는 계단 수가 아니므로 1부터 시작
    DP[1][i] = 1

if N &amp;gt;= 2:
    for i in range(2, N+1):
        DP[i][0] = DP[i-1][1] % MOD
        
        for j in range(1, 9):
            DP[i][j] = (DP[i-1][j-1] + DP[i-1][j+1]) % MOD
        
        DP[i][9] = DP[i-1][8] % MOD

result = sum(DP[N]) % MOD
        
print(result)&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Coding Test/Baekjoon</category>
      <author>브이담곰</author>
      <guid isPermaLink="true">https://odlram.tistory.com/169</guid>
      <comments>https://odlram.tistory.com/entry/10844-%EC%89%AC%EC%9A%B4-%EA%B3%84%EB%8B%A8-%EC%88%98#entry169comment</comments>
      <pubDate>Wed, 14 Aug 2024 15:50:55 +0900</pubDate>
    </item>
    <item>
      <title>11053 가장 긴 증가하는 부분의 수열</title>
      <link>https://odlram.tistory.com/entry/11053-%EA%B0%80%EC%9E%A5-%EA%B8%B4-%EC%A6%9D%EA%B0%80%ED%95%98%EB%8A%94-%EB%B6%80%EB%B6%84%EC%9D%98-%EC%88%98%EC%97%B4</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목을 입력해주세요_-006 (1).png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvOQQ0/btsI2DNODEe/aTPENOcfFmsUoN5qtVYzdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvOQQ0/btsI2DNODEe/aTPENOcfFmsUoN5qtVYzdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvOQQ0/btsI2DNODEe/aTPENOcfFmsUoN5qtVYzdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcvOQQ0%2FbtsI2DNODEe%2FaTPENOcfFmsUoN5qtVYzdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot; data-filename=&quot;제목을 입력해주세요_-006 (1).png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/11053&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/11053&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;✔ 유형 : DP(LIS)&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;✔ 문제 풀이:&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;LIS(Longest Increase Sequence) 의 로직과 비슷하다.&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;DP[i] 를 i 번째 까지 가장 긴 증가하는 부분 수열의 개수라고 정의했을 때.&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;i보다 앞쪽에 있는 원소들과 비교해서 i번째의 수보다 작을 때 개수를 업데이트 한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;정리하자면,&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;DP[i] : 현재까지 계산된 i위치에서의 LIS의 길이&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;DP[j] + 1 : j위치까지 LIS에 현재 원소를 추가한 길이&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 D[i] 는 위의 두 경우의 수 중 최대값으로 업데이트 해주어야한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서, &quot;현재 원소를 이전 LIS 에 추가했을 때 더 긴 LIS가 되는가?&quot;를 조건을 생각해주면 되는것이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;코드&lt;/p&gt;
&lt;pre id=&quot;code_1723520547055&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import sys
input = sys.stdin.readline

N = int(input())
arr = list(map(int, input().split()))

DP = [1] * N  # 모든 원소의 최소 길이는 1

for i in range(1, N):
    for j in range(i):
        if arr[i] &amp;gt; arr[j]:
            DP[i] = max(DP[i], DP[j] + 1)

print(max(DP))&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Coding Test/Baekjoon</category>
      <author>브이담곰</author>
      <guid isPermaLink="true">https://odlram.tistory.com/168</guid>
      <comments>https://odlram.tistory.com/entry/11053-%EA%B0%80%EC%9E%A5-%EA%B8%B4-%EC%A6%9D%EA%B0%80%ED%95%98%EB%8A%94-%EB%B6%80%EB%B6%84%EC%9D%98-%EC%88%98%EC%97%B4#entry168comment</comments>
      <pubDate>Tue, 13 Aug 2024 12:42:31 +0900</pubDate>
    </item>
    <item>
      <title>2156 포도주 시식</title>
      <link>https://odlram.tistory.com/entry/2156-%ED%8F%AC%EB%8F%84%EC%A3%BC-%EC%8B%9C%EC%8B%9D</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목을 입력해주세요_-006.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ja1kv/btsI0enMJoe/GqwQpHlPTf35oOoRmAkJvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ja1kv/btsI0enMJoe/GqwQpHlPTf35oOoRmAkJvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ja1kv/btsI0enMJoe/GqwQpHlPTf35oOoRmAkJvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJa1kv%2FbtsI0enMJoe%2FGqwQpHlPTf35oOoRmAkJvk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot; data-filename=&quot;제목을 입력해주세요_-006.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/2156&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/2156&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;✔ 유형 : DP&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;✔ 문제 풀이:&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;751&quot; data-origin-height=&quot;64&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/K9te7/btsI1G4cctL/LnqMa1AboIu3FibC94aR51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/K9te7/btsI1G4cctL/LnqMa1AboIu3FibC94aR51/img.png&quot; data-alt=&quot;위의 규칙을 따라서 마실 포도주를 선택해야한다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/K9te7/btsI1G4cctL/LnqMa1AboIu3FibC94aR51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FK9te7%2FbtsI1G4cctL%2FLnqMa1AboIu3FibC94aR51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;751&quot; height=&quot;64&quot; data-origin-width=&quot;751&quot; data-origin-height=&quot;64&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;위의 규칙을 따라서 마실 포도주를 선택해야한다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;점화식을 세우는 접근방식은 다음과 같다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1093&quot; data-origin-height=&quot;1351&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/by18kV/btsIZyG8eeB/XxI7JBOuYQWlSnr7sm32kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/by18kV/btsIZyG8eeB/XxI7JBOuYQWlSnr7sm32kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/by18kV/btsIZyG8eeB/XxI7JBOuYQWlSnr7sm32kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fby18kV%2FbtsIZyG8eeB%2FXxI7JBOuYQWlSnr7sm32kk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1093&quot; height=&quot;1351&quot; data-origin-width=&quot;1093&quot; data-origin-height=&quot;1351&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1. 맨 마지막 포도주를 무조건 선택한다 했을 때, 연속 두개를 선택하고 싶다면,&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;arr[i] ( 마지막 포도주 ) , arr[i-1]( 마지막에서 두번째 포도주), 그리고 3개를 연속해서 선택할 수 없으므로&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;arr[i-2]는 선택하지 않는다.&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2. 맨 마지막 포도주를 무조건 선택하고, 그 전 포도주는 선택하지 않을 때,&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;arr[i] ( 마지막 포도주 )를 선택하고, arr[i-1]는 선택하지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;3. 맨 마지막 포도주를 절대 선택하지 않을 때&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;DP[i-1]&amp;nbsp; 포도주의 개수가 N-1때의 최대값을 반영하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;이를 일반화 하면&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;DP[i]는 세가지의 경우의 수로 일반화 하여 식을 세울 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;DP[i] = arr[i] + arr[i-1] + &lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;DP[i-3]&lt;/span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;DP[i] = arr[i] + &lt;/span&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;DP[i-2]&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;DP[i] = &lt;/span&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;DP[i-1]&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;마실 수 있는 최대 포도주의 값을 구해야 하므로 세가지 경우의 수의 값 중 가장 큰 값을 반영하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;따라서,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;DP[i] = max( &lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;arr[i] + arr[i-1] +&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;DP[i-3]&lt;/span&gt; , &lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;rr[i] +&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;DP[i-2]&lt;/span&gt; , &lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;DP[i-1]&lt;/span&gt; )로 점화식을 세울 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;코드&lt;/p&gt;
&lt;pre id=&quot;code_1723256120395&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import sys
input = sys.stdin.readline

N = int(input())

arr = [ 0 for _ in range(N+1)] # arr도 1부터 시작

for _ in range(1,N+1):
    arr[_] = int(input())

DP = [ 0 for _ in range(N+1)]
DP[1] = arr[1]

# 초기값 예외처리
if N &amp;gt;= 2:
    DP[2] = DP[1] + arr[2] 

if N &amp;gt;= 3:
    for i in range(3, N+1):
        DP[i] = max(DP[i-1],DP[i-2] + arr[i] , DP[i-3] + arr[i] + arr[i-1])
        
print(DP[N])&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 문제는 silver3 문제인 계단 오르기문제와 비슷하다고 느꼈는데, 아래 문제도 함께 생각해보면 좋을 것 같다.&lt;/p&gt;
&lt;figure id=&quot;og_1723256597002&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;2579 계단 오르기&quot; data-og-description=&quot;https://www.acmicpc.net/problem/2579✔ 유형 : DP✔ 문제 풀이: 계단을 오를 때, 앞에서부터 계산해서 최대값을 구하는 것이 아닌, 최종 도착지를 정해놓고 그전 계단을 밟았을때 밟지 않았을 때 중의 최댓&quot; data-og-host=&quot;damgom.dev&quot; data-og-source-url=&quot;https://damgom.dev/entry/2579-%EA%B3%84%EB%8B%A8-%EC%98%A4%EB%A5%B4%EA%B8%B0&quot; data-og-url=&quot;https://damgom.dev/entry/2579-%EA%B3%84%EB%8B%A8-%EC%98%A4%EB%A5%B4%EA%B8%B0&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/gIWa1/hyWKyTEsBY/9DxlI7tIvGocJ4dpEHldek/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/SJNAS/hyWOdNQjmW/Id5mjfpLmzeE8FJD2yENuk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/CTt7h/hyWOj1BfyX/mv2smz0uBPzWZXfqLMcJq1/img.png?width=1080&amp;amp;height=1080&amp;amp;face=0_0_1080_1080&quot;&gt;&lt;a href=&quot;https://damgom.dev/entry/2579-%EA%B3%84%EB%8B%A8-%EC%98%A4%EB%A5%B4%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://damgom.dev/entry/2579-%EA%B3%84%EB%8B%A8-%EC%98%A4%EB%A5%B4%EA%B8%B0&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/gIWa1/hyWKyTEsBY/9DxlI7tIvGocJ4dpEHldek/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/SJNAS/hyWOdNQjmW/Id5mjfpLmzeE8FJD2yENuk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/CTt7h/hyWOj1BfyX/mv2smz0uBPzWZXfqLMcJq1/img.png?width=1080&amp;amp;height=1080&amp;amp;face=0_0_1080_1080');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;2579 계단 오르기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;https://www.acmicpc.net/problem/2579✔ 유형 : DP✔ 문제 풀이: 계단을 오를 때, 앞에서부터 계산해서 최대값을 구하는 것이 아닌, 최종 도착지를 정해놓고 그전 계단을 밟았을때 밟지 않았을 때 중의 최댓&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;damgom.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Coding Test/Baekjoon</category>
      <author>브이담곰</author>
      <guid isPermaLink="true">https://odlram.tistory.com/167</guid>
      <comments>https://odlram.tistory.com/entry/2156-%ED%8F%AC%EB%8F%84%EC%A3%BC-%EC%8B%9C%EC%8B%9D#entry167comment</comments>
      <pubDate>Sat, 10 Aug 2024 11:23:27 +0900</pubDate>
    </item>
  </channel>
</rss>