origoni's Blog from Millky

origoni의 스프링 블로그 입니다.

[블로그개발_15] 카테고리 추가 (@ManyToOne, stream())

블로그개발 시리즈 - 다른글 : http://millky.com/@origoni/folder/30/post/list

라이브 데모 : http://blog.millky.com/post/list

자바 웹 개발 시작하기 : http://www.slideshare.net/origoni/presentations




이번시간은 블로그에 카테고리 기능을 추가 하려 한다.

우리는 지금까지 글을 쓰고, 읽고, 수정하고, 삭제 하는 기능을 만들어 보았다.

아직은 Post 테이블 하나만 사용하여. 그렇게 복잡하지 않았었다.

이번에 Category 테이블이 추가되면서. 복잡도가 조금 증가하지만. 차근차근 보면 그렇게 어렵지는 않을 것이다.


그럼 시작해보자.

시작하자.

시작!


음.. 이렇게 목표는 생겼는데. 어디부터 접근해야 할까?

고민하다 카테고리 추가 부분부터 개발해보기로 했다.

사실 나는 UI나 컨트롤러부터 역으로 개발하는 방법도 많이 사용한다.

필요한 클래스, 메서드가 존재하지 않아도. 일단 적어주면... IDE가 자동으로 만들어(?) 주기 때문에.. 흐름에 따라 가기가 좋다.

하지만.. 문서로 설명하자니... 좀 일반적인(?) 순서로 진행하겠다.



카테고리를 추가하기 위해서는 많은 해야 할 일들이 있지만.

우선 Entity를 추가해 보자.

package com.millky.blog.domain.model.entity;

import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import lombok.Data;

@Data
@Entity
public class Category {
	@Id
	@GeneratedValue
	private int id;

	@Column(unique = true)
	private String name;

	private Date regDate;

	private int postCount;

	private int publicPostCount;
}

위와 같이 추가 하였다.


다른부분은 이전 포스트와 크게 다른점이 없는데.

카테고리 명에는 유니크를 붙였다. (같은 이름 카테고리가 여러개면 이상하니까...)

@Column(unique = true)
private String name;


이렇게 Entity가 준비 되었으면. 이번에는 카테고리의 DAO를 만들어보자.

우리는 스프링 데이터를 사용하기때문에. 역시 또 간단하게... 가능하다.

package com.millky.blog.infrastructure.dao;

import org.springframework.data.jpa.repository.JpaRepository;
import com.millky.blog.domain.model.entity.Category;

public interface CategoryDao extends JpaRepository<Category>, Integer>{

}


이제 DAO까지 되었으니.. 실제 추가할 수 있는 컨트롤러를 만들어 보자.

package com.millky.blog.presentation.controller;

import java.util.Date;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.millky.blog.domain.model.entity.Category;
import com.millky.blog.infrastructure.dao.CategoryDao;

@Controller
public class CategoryController {

	@Autowired
	private CategoryDao categoryDao;

	@ResponseBody
	@RequestMapping(value = "/category/add")// , method = RequestMethod.POST)
	public Category add(@RequestParam(value = "categoryName", required = true) String categoryName) {

		Category category = new Category();
		category.setName(categoryName);
		category.setRegDate(new Date());

		return categoryDao.save(category);
	}
}


/category/add?categoryName=카테고리이름


이런식으로 질의를 날리면 카테고리를 생성해준다.

글쓰기 폼에서 Ajax로 받을꺼라 POST방식만 사용하면 되지만 일단 테스트 차원에서 모든 메서드를 허용한다.


잘 동작하는지 확인이 되었으면...

글쓰기 폼에 붙여보자.


<form action="/category/add" method="post" id="add_category" >
	<input type="text" name="categoryName" class="form-control" placeholder="새로운 카테고리">
	<input type="hidden" name="_csrf" value="${_csrf.token}">
	<button type="submit" class="form-control">추가</button>
</form>

...

<script type="text/javascript">
	$('#add_category').submit(function(event) {
		var form = $(this);
		$.ajax({
			type : form.attr('method'),
			url : form.attr('action'),
			data : form.serialize()
		}).done(function(c) {				
			$("#category").append("<option value=" + c.id + ">" + c.name + "</option>");
			$("#category").val(c.id);
			
			alert(c.name + " 카테고리가 추가되었습니다.");
		}).fail(function() {
			alert('error');
		});
		event.preventDefault(); 
	});
</script>

이렇게 추가가 되었으면 Post컨트롤러를 확인해보자.


글 작성 폼에 카테고리 리스트를 내려주는 부분을 만들어보자.

@Slf4j
@Controller
@RequestMapping("/post")
public class PostController {

	@Autowired
	private PostDao postDao;

	@Autowired
	private CategoryDao categoryDao;

	@RequestMapping(value = "/write", method = RequestMethod.GET)
	public String form(Post post, Model model) {

		List<Category> categoryList = categoryDao.findAll();

		log.debug("categoryList = {}", categoryList);

		model.addAttribute("categoryMap", categoryList.stream().collect(Collectors.toMap(Category::getId, Category::getName)));

		return "post/form";
	}
...


우선 폼 메서드를 보면.


List<Category> categoryList = categoryDao.findAll();

이렇게 카테고리를 모두 불러오는 부분이 추가 되었다.


그리고 그부분을 categoryMap에 아이디와 이름만 뽑아서 담는다.

이부분은 spring form tag를 사용하다보니..


뷰쪽에 이렇게 구현을 해야 정상적으로 동작을 한다....

<div class="form-group" style="height: 30px;">
	<label for="category" class="col-sm-2 control-label">Category</label>
	<div class="col-sm-10">
		<form:select path="categoryId" items="${categoryMap}" id="category" class="form-control"/>
		<form:errors path="categoryId" cssClass="error" />
	</div>
</div>


그래서 리스트를 Map으로 만들어야 하는데.

Java8 스트림을 사용해 보았다.


categoryList.stream().collect(Collectors.toMap(Category::getId, Category::getName));


categoryList의 스트림을 생성해서.. 카테고리 아이디와 이름을 뽑아 맵으로... 정말 간단하다.


여기에 보면 몇가지 예제가 있으니 참고 바란다.

https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html

http://millky.com/@origoni/post/1125


자바8 이전에 컬랙션 작업하던것들을 자바8 람다(stream)을 사용하면 정말 간단하게 작업할 수 있다.

필터링, 정렬 같은 것도 정말 쉽게 개발할 수 있다.

이 블로그 강좌중에는 몇번 사용할지 모르겠지만. 공부해두길 강추한다.


뭐 일단 이렇게 해서. 폼에 카테고리 정보를 내릴 수 있다.





이렇게 카테고리는 추가가 되었고.

이제 Post와 엮어야 한다.


그럼 이번에는 Post Entity 를 수정해보자.


public class Post {

...

	@Min(value = 1)
	private int categoryId;

	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "categoryId", insertable = false, updatable = false)
	private Category category;
}


카테고리 하나에 글이 여러개 들어갈 수 있다. N:1 관계이다. 따라서 @ManyToOne 으로 두었다.


지금까지의 ERD는 위와 같다. (외래키 foreignKey = @ForeignKey(name = "fk_category") 요런식으로 이름을 정할 수 있다. 유니크 키도 물정할 수 있다.. 하지만 우리는 가장 간단하게.. 진행해보자. 나중에 이쁘게 수정할 날이 올 것이다?!)


JPA에 대한 부분은... 김영한님의 "자바 ORM 표준 JPA 프로그래밍" 을 읽어보도록 하자.. ^^; 처음 JPA접할때 영한님의 PPT가 많은 도움이 되었다.

이달말 출간된다. http://www.acornpub.co.kr/book/jpa-programmig


JPA에 대해서는 블로그 만들기 강좌 중간에 계속 설명하게 될 것이다.



이제 글을 작성하면. 카테고리 정보를 포함하고 글이 써지게 된다.

카테고리 별로 글을 가지고 오려면 어떻게 해야 할까?


지금까지 비어있던 PostDao 에 드디어 추가해야 할 것이 생겼다.


package com.millky.blog.infrastructure.dao;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import com.millky.blog.domain.model.entity.Post;

public interface PostDao extends JpaRepository<post>, integer=""> {

	public Page<post> findByCategoryId(int categoryId, Pageable pageable);
}
</post></post,>


이 메서드를 하나 추가했는데...

public Page<Post> findByCategoryId(int categoryId, Pageable pageable);


잘 읽어보면... 카테고리 아이디 로 찾아오겠다는 이야기 이다.

구현체도 필요 없다. 인터페이스만 정의하면 알아서 쿼리를 만들어준다.


실제로 

post0_.id as id1_2_, post0_._csrf as _csrf2_2_, post0_.category_id as category3_2_, post0_.content as content4_2_, post0_.name as name5_2_, post0_.reg_date as reg_date6_2_, post0_.subtitle as subtitle7_2_, post0_.title as title8_2_, post0_.user_id as user_id9_2_
from post post0_ 
where post0_.category_id=? 
order by post0_.id desc 
limit ?

이런 쿼리를 생성해 준다.


DAO가 준비 되었으면 컨트롤러를 수정해 보자.

@RequestMapping("/list")
public String list(Model model,
		@RequestParam(value = "category", required = false, defaultValue = "0") int categoryId,
		@PageableDefault(sort = { "id" }, direction = Direction.DESC, size = 5) Pageable pageable) {

	Page<post> postPage;

	if (categoryId > 0) {
		postPage = postDao.findByCategoryId(categoryId, pageable);

	} else {
		postPage = postDao.findAll(pageable);
	}

	model.addAttribute("categoryId", categoryId);
	model.addAttribute("postPage", postPage);

	return "post/list";
}
</post>


뭐 일단은 간단하게 만들었다.
(URL부분은 나중에 다시 정의하게 될 것 같다. - Fancy URL 과 RESTful URL 의 차이도 알아보자.)

category 라는 파라미터로 카테고리 아이디가 들어오면 해당 카테고리만 찾아오기. 아니면 모두 가지고 오기를 사용한다.


그럼 /post/list?category=1 이런식으로 질의를 날리면 1번 카테고리에 해당하는 글만 가지고 오게 된다.



음 여기까지는 되었는데..

이게 블로그 테마가.. 원래 카테고리 부분이 없다보니 ㅠㅠ 

해당 부분을 만들기 위해 몇가지 추가 작업을 진행하였다.


우선 같은 분이 만드신 http://ironsummitmedia.github.io/startbootstrap-stylish-portfolio/ 를 보니 슬라이드 네비게이션이 있어 해당 부분을 차용했다.

그러다 보니 카테고리 정보가 항상 필요해서. CategoryInterceptor 도 만들었다. (Interceptor에 대한 부분은 지난시간에 설명했다. UI에 대한 부분도. 이 글에서는 생략한다.)




이렇게 카테고리 기능을 추가 하였다.

카테고리 추가로 페이징 부분도 조금 수정되었다. 아마 코드를 보면 금방 확인 가능 할 것이다.


카테고리 수정 및 삭제에 대한 부분은... 나중에 관리 페이지 만들면서 진행해 보도록 하겠다.

다음시간은 중간 정리하는 시간을 가져볼까 한다..


여기까지 수고하셨습니다.



추가적으로

아래 디펜던시 추가된 부분은.. 나중에 사용하기 위해 잠시 주석처리 해두었다.

<!-- <dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
</dependency> -->
<!-- <dependency>
	<groupId>com.zaxxer</groupId>
	<artifactId>HikariCP</artifactId>
</dependency> -->
<!-- <dependency>
	<groupId>net.bull.javamelody</groupId>
	<artifactId>javamelody-core</artifactId>
	<version>1.56.0</version>
</dependency> -->

(사실 push전 삭제 했어야 하는데 ㅠㅠ 이제서야 발견 ㅠㅠ)



오늘 작업한 코드는 https://github.com/origoni/Spring-Blog/tree/v0.0.15 여기를 참고 부탁드린다.


라이브 데모 : http://blog.millky.com/post/list

변경사항은 : https://github.com/origoni/Spring-Blog/commit/

깃헙 : https://github.com/origoni/Spring-Blog

프로젝트 다운받아 시작하는 방법은(for Windows User)? http://millky.com/@origoni/post/1145

블로그개발 시리즈 - 다른글 : http://millky.com/@origoni/folder/30/post/list

역순 정렬도 있어요.. : http://millky.com/@origoni/folder/30/post/list?sort=regDate,asc



godseop 2015-11-20 14:58:15

감사히 잘보고있습니다 다음글도 기대하고있습니다^^
back to top