IT 프로젝트/블로그 만들기

[Spring Boot] 스프링 부트 프로젝트/블로그 만들기 - 사이드바(sidebar) 만들기

happygram 2018. 11. 16. 18:00

 

sidebar 는 특정 페이지로 이동, 메인 페이지의 교체 등의 역할을 하고 있습니다.

이번 블로그 프로젝트에서는 메인 페이지의 교체의 역할을 하는 sidebar 를 구현하려고 합니다.

 

sidebar 에는 category 를 표시할 것입니다. category 를 선택하는 경우 메인 페이지를 교체하도록 합니다.

category 는 관리자가 추가, 변경, 삭제 가능하게 할 것이므로, 어플리케이션 구현 전에 데이터베이스 설계 및 생성이 필요합니다.

관리자에 대한 기능을 추후 작성할 예정입니다.

 

데이터베이스

테이블 정의

 

 

엔티티

 

 

DDL
CREATE TABLE `category` (   `nm` varchar(100) NOT NULL COMMENT '카테고리 식별자',   `label_nm` varchar(500) NOT NULL COMMENT '카테고리 라벨명',   `order_no` int(11) NOT NULL COMMENT '카테고리 순서',   PRIMARY KEY (`nm`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 
 

Dependencies

build.gradle
dependencies {     compile('org.springframework.boot:spring-boot-starter-data-jpa')     compile('org.modelmapper:modelmapper:0.7.4')     compileOnly('org.projectlombok:lombok') }

'jpa' 이용하여 'Repository' 를 구현할 것입니다.

'modelmapper' 이용하여 'Domain' 과 'DTO' 사이의 변환을 수월하게 할 수 있도록 합니다.

'lombok' 이용하여 'Domain' 을 구현할 것입니다. 'Getter', 'Settger', 'Contructor' 등을 자동으로 생성하도록 합니다.

 

 

 

소스

common.js

 

/**
 * Global Variables
 */
var BASE_CONTEXT_PATH = $('meta[name=context-path]').attr("content");
 
/**
 * Global Functions
 * @returns
 */
 
cs

static/js 경로에 'common.js' 파일을 만들었습니다.

글로벌 변수 혹은 글로벌 기능을 선언합니다.

<head> 태그에 정의되어 있는 값을 가져와서 세팅합니다.

<head> 태그의 자세한 부분은 다음의 글을 참조 해주세요.

해피그램 - 블로그 만들기 - UI-Bootstrap-Thymeleaf

 


common-sidebar.html

 

<nav id="sidebar">
    <th:block th:include="common/common-sidebar-js"></th:block>
    <div class="sidebar-header">
        <h3><a th:href="@{/}">Category</a></h3>
    </div>
    <ul id="category" class="list-unstyled components">
    </ul>
</nav>
cs

templates/common 패키지에 common-sidebar.html 파일을 생성하고, 코딩을 하였습니다.

javascript 를 이용하여 서버에서 category 목록을 가져오도록 합니다.

javascript 는 'common-sidebar-js.html' 에 구현하여, 현재 페이지에서 포함합니다.

th:include : 특정 페이지를 포함

 

common-sidebar-js.html
 

 

<script th:inline="javascript">
$(function(){
    // Collapse button event
    $('#sidebarCollapse').on('click'function() {
        $('#sidebarCollapseBtn').toggleClass('active');
        $('#sidebar').toggleClass('active');
    });
    
    // Create sidebar from category
    $.ajax({
        method : 'GET',
        url: BASE_CONTEXT_PATH + 'category',
    }).done(function(result){
        // Append tag
        var list = '';
        var class_name = 'nav-link';
        $.each(result, function(i, category){
            list += '<li><a href="'+BASE_CONTEXT_PATH+'main?categoryNm='+category.nm+'&categoryLabelNm='+category.labelNm+'">'+category.labelNm+'</a></li>';
        });
        $('#category').html(list);
    }).fail(function(xhr, ajaxOptions, thrownError){
        alert(xhr);
    });
})
</script>
cs

 

    // Collapse button event
    $('#sidebarCollapse').on('click'function() {
        $('#sidebarCollapseBtn').toggleClass('active');
        $('#sidebar').toggleClass('active');
    });

버튼으로 sidebar 를 show/hide 하는 부분입니다.

header 를 구현하는 글에서 자세히 다루도록 하겠습니다.

 

    // Create sidebar from category
    $.ajax({
        method : 'GET',
        url: BASE_CONTEXT_PATH + 'category',
    }).done(function(result){
        // Append tag
        var list = '';
        var class_name = 'nav-link';
        $.each(result, function(i, category){
            list += '<li><a href="'+BASE_CONTEXT_PATH+'main?categoryNm='+category.nm+'&categoryLabelNm='+category.labelNm+'">'+category.labelNm+'</a></li>';
        });
        $('#category').html(list);
    }).fail(function(xhr, ajaxOptions, thrownError){
        alert(xhr);
    });

common-sidebar.html 페이지가 로딩 되면서 ajax 를 수행하도록 하였습니다.

RequestMapping 이 'category' 로 되어 있는 Controller 를 'GET' 으로 호출하고 있습니다.

결과를 성공적으로 받은 경우 <li><a> 태그를 생성 하였습니다.

<a> 태그에서는 href 속성을 이용하여 선택되는 경우, RequestMapping 이 'main' 인 Controller 를 호출하며, 파라미터로 'categoryNm', 'categoryLabelNm' 을 담아서 'GET' 으로 호출하도록 합니다.

 
WebMvcConfig.java
... 	@Bean 	public ModelMapper modelMapper() { 	    return new ModelMapper(); 	} ...

ModelMapper 클래스는 Service 에서 공통적으로 사용할 예정이므로, 'WebMvcConfig.java' 에서 Bean 으로 생성 하였습니다.

각각 Service 클래스에서는 '@Autowired' 어노테이션을 이용하여 사용합니다.

 
CategoryController.java
import java.util.List;  import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody;  import project.blog.dto.CategoryDto; import project.blog.service.CategoryService;  @Controller @RequestMapping("/category") public class CategoryController {  	@Autowired 	private CategoryService categoryService;  	@GetMapping 	public @ResponseBody List<CategoryDto> getCategories() { 		return categoryService.getCategories(); 	} }

controller 패키지에 CategoryController.java 를 생성 하였습니다.

Service를 호출하도록 합니다.

 

CategoryService.java
import java.util.List; import java.util.stream.Collectors;  import org.modelmapper.ModelMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;  import project.blog.dto.CategoryDto; import project.blog.repository.CategoryRepository;  @Service public class CategoryService { 	 	@Autowired 	private CategoryRepository categoryRepository; 	 	@Autowired 	private ModelMapper modelMapper; 	 	public List<CategoryDto> getCategories(){ 		return categoryRepository.findAll().stream().map(category -> modelMapper.map(category, CategoryDto.class)).collect(Collectors.toList()); 	} }

service 패키지에 CategoryService.java 를 생성 하였습니다.

Repository 를 호출하도록 합니다.

Java 8 에서 제공하는 lambda 식을 이용하여, Repository 에서 가져온 결과를 리스트로 변환합니다. 변환을 수월하게 해주는 ModelMapper 를 이용 하여 'Domain' 을 'DTO' 로 변환하였습니다.

 

CategoryRepository.java
import org.springframework.data.jpa.repository.JpaRepository;  import project.blog.domain.Category;  public interface CategoryRepository extends JpaRepository<Category, String> { 	Category findByNm(String nm); }

repository 패키지에 CategoryRepository.java 를 생성 하였습니다.

'JpaRepository' 를 상속 받아서 구현합니다.

Category findByNm(String nm) : 'nm' 값을 받아서 결과를 반환합니다. JPA 표준 사용법에 맞추어 작성한 것입니다.

JPA 참조 문서

https://spring.io/projects/spring-data-jpa#learn

 
Category.java
import javax.persistence.Entity; import javax.persistence.Id;  import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor;  @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @Entity public class Category {     @Id     private String nm;     private String labelNm;     private int orderNo;          @Builder     public Category(String nm, String labelNm, int orderNo) {     	this.nm = nm;     	this.labelNm = labelNm;     	this.orderNo = orderNo;     } }

domain 패키지에 Category.java 를 생성 하였습니다.

'Category' 클래스는 'category' 라는 테이블과 1:1 매핑되고, 각 멤버 변수는 테이블의 컬럼과 1:1 매핑됩니다.

'lombok' 라이브러리를 이용하여, 'Getter', 'Setter' 를 자동으로 생성 합니다.

AccessLevel.PROTECTED : 접근 제어 레벨을 설정

@Id : 'JPA' 의 어노테이션으로 'primary key' 컬럼에 지정합니다.

@Builder : 빌더 패턴으로 생성자를 지정합니다. 빌더 패턴을 이용하면, 코드를 간결하고 가독성 높게 유지할 수 있습니다.

lombok 참조 문서

https://projectlombok.org/


CategoryDto.java
import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import project.blog.domain.Category;  @Getter @Setter @NoArgsConstructor public class CategoryDto { 	private String nm; 	private String labelNm;  	private int orderNo; 	 	public Category toEntity(){         return Category.builder()         		.nm(nm)         		.labelNm(labelNm)         		.orderNo(orderNo)                 .build();     } }

dto 패키지에 CategoryDto.java 를 생성 하였습니다.

dto 는 domain 과 같은 멤버 변수를 가질 수도 있고, 다른 멤버 변수를 가질 수도 있습니다. domain 에는 존재하지 않지만, dto 에는 특정 멤버 변수가 필요한 경우 확장하여 사용합니다.

 

Service 에서는 domain 을 직접 접근하는 것보다 dto 를 이용하여 반환하도록 합니다.

@NoArgsConstructor : 생성자 없는 클래스

toEntity() : toEntity() 메소드를 이용하여 domain 을 호출할 수 있도록 빌더 패턴으로 만들었습니다.

 

결과

 

'sidebar' 에 있는 'Category' 의 메뉴 중 하나를 선택 시 메인 페이지가 변경됩니다.

 
 

참조 라이브러리 사이트 목록 - 도움에 감사를 드립니다.

https://spring.io/projects/spring-data-jpa

https://projectlombok.org/

http://modelmapper.org/getting-started/

 

참조 도구 사이트 목록 - 도움에 감사를 드립니다.

https://dbeaver.io/