스프링 레거시(Spring legacy) - Service, Controller 작성
서비스(Service) 작성
서비스에서는 DAO의 메소드를 호출하여 비즈니스 로직을 수행합니다.
서비스 스캔 등록
우선 서비스를 스캔할 수 있도록 스프링의 root-context.xml
파일에 서비스 컴포넌트 스캔 경로를 추가해줍시다. 아래 예시의 경우 kro.rubisco.service
패키지를 컴포넌트 스캔 경로로 등록했습니다.
src/main/webapp/WEB-INF/spring/root-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- Root Context: defines shared resources visible to all other web components -->
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" name="dataSource">
<property value="oracle.jdbc.driver.OracleDriver" name="driverClassName"/>
<property value="[DB 주소]" name="url"/>
<property value="[DB 아이디]" name="username"/>
<property value="[DB 암호]" name="password"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:/mybatis-config.xml"></property>
<property name="mapperLocations" value="classpath:mappers/**/*Mapper.xml"></property>
</bean>
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate" destroy-method="clearCache">
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"></constructor-arg>
</bean>
<context:component-scan base-package="kro.rubisco.service"></context:component-scan>
</beans>
서비스 인터페이스 작성
이제 서비스의 인터페이스를 작성하겠습니다. Board, Category, Comment, Group, Member에 대하여 CRUD 기능을 넣었습니다. DAO와 유사합니다.
/kro/rubisco/service/BoardService.java
package kro.rubisco.service;
import java.util.List;
import kro.rubisco.dto.BoardDTO;
public interface BoardService {
public void regist(BoardDTO board) throws Exception;
public BoardDTO read(Long documentId) throws Exception;
public void modify(BoardDTO board) throws Exception;
public void remove(Long documentId) throws Exception;
public List<BoardDTO> listAll() throws Exception;
}
/kro/rubisco/service/CategoryService.java
package kro.rubisco.service;
import java.util.List;
import kro.rubisco.dto.CategoryDTO;
public interface CategoryService {
public void regist(CategoryDTO category) throws Exception;
public CategoryDTO read(Long categoryId) throws Exception;
public void modify(CategoryDTO category) throws Exception;
public void remove(Long categoryId) throws Exception;
public List<CategoryDTO> listAll() throws Exception;
}
/kro/rubisco/service/GroupService.java
package kro.rubisco.service;
import java.util.List;
import kro.rubisco.dto.GroupDTO;
public interface GroupService {
public void regist(GroupDTO group) throws Exception;
public GroupDTO read(Long groupId) throws Exception;
public void modify(GroupDTO group) throws Exception;
public void remove(Long groupId) throws Exception;
public List<GroupDTO> listAll() throws Exception;
}
/kro/rubisco/service/MemberService.java
package kro.rubisco.service;
import java.util.List;
import kro.rubisco.dto.MemberDTO;
public interface MemberService {
public void regist(MemberDTO member) throws Exception;
public MemberDTO read(Long memberId) throws Exception;
public void modify(MemberDTO member) throws Exception;
public void remove(Long memberId) throws Exception;
public List<MemberDTO> listAll() throws Exception;
}
서비스 구현
이제 서비스 인터페이스의 구현체를 작성합니다. @Service
어노테이션을 통해 서비스 컴포넌트임을 선언하고, sqlSession에 DAO 인터페이스를 연결하여 DAO 구현체를 생성자 주입합니다.
DAO 구현체를 통해 CRUD 쿼리를 실행시킬 수 있으며, @Transactional
어노테이션을 통해 트랜잭션 처리를 할 수 있습니다.
/kro/rubisco/service/impl/BoardServiceImpl.java
package kro.rubisco.service.impl;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import kro.rubisco.dao.BoardDAO;
import kro.rubisco.dto.BoardDTO;
import kro.rubisco.service.BoardService;
@Service
@Transactional(readOnly = true)
public class BoardServiceImpl implements BoardService {
private final BoardDAO boardDAO;
@Autowired
public BoardServiceImpl(SqlSession sqlSession) {
this.boardDAO = sqlSession.getMapper(BoardDAO.class);
}
@Override
public void regist(BoardDTO board) throws Exception {
boardDAO.create(board);
}
@Override
public BoardDTO read(Long documentId) throws Exception {
return boardDAO.read(documentId);
}
@Override
public void modify(BoardDTO board) throws Exception {
boardDAO.update(board);
}
@Override
public void remove(Long documentId) throws Exception {
boardDAO.delete(documentId);
}
@Override
public List<BoardDTO> listAll() throws Exception {
return boardDAO.listAll();
}
}
/kro/rubisco/service/impl/CategoryServiceImpl.java
package kro.rubisco.service.impl;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import kro.rubisco.dao.CategoryDAO;
import kro.rubisco.dto.CategoryDTO;
import kro.rubisco.service.CategoryService;
@Service
@Transactional(readOnly = true)
public class CategoryServiceImpl implements CategoryService {
private final CategoryDAO categoryDAO;
@Autowired
public CategoryServiceImpl(SqlSession sqlSession) {
this.categoryDAO = sqlSession.getMapper(CategoryDAO.class);
}
@Override
public void regist(CategoryDTO category) throws Exception {
categoryDAO.create(category);
}
@Override
public CategoryDTO read(Long categoryId) throws Exception {
return categoryDAO.read(categoryId);
}
@Override
public void modify(CategoryDTO category) throws Exception {
categoryDAO.update(category);
}
@Override
public void remove(Long categoryId) throws Exception {
categoryDAO.delete(categoryId);
}
@Override
public List<CategoryDTO> listAll() throws Exception {
return categoryDAO.listAll();
}
}
/kro/rubisco/service/impl/CommentServiceImpl.java
package kro.rubisco.service.impl;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import kro.rubisco.dao.CommentDAO;
import kro.rubisco.dto.CommentDTO;
import kro.rubisco.service.CommentService;
@Service
@Transactional(readOnly = true)
public class CommentServiceImpl implements CommentService {
private final CommentDAO commentDAO;
@Autowired
public CommentServiceImpl(SqlSession sqlSession) {
this.commentDAO = sqlSession.getMapper(CommentDAO.class);
}
@Override
public void regist(CommentDTO comment) throws Exception {
commentDAO.create(comment);
}
@Override
public CommentDTO read(Long commentId) throws Exception {
return commentDAO.read(commentId);
}
@Override
public void modify(CommentDTO comment) throws Exception {
commentDAO.update(comment);
}
@Override
public void remove(Long commentId) throws Exception {
commentDAO.delete(commentId);
}
@Override
public List<CommentDTO> listAll() throws Exception {
return commentDAO.listAll();
}
}
/kro/rubisco/service/impl/GroupServiceImpl.java
package kro.rubisco.service.impl;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import kro.rubisco.dao.GroupDAO;
import kro.rubisco.dto.GroupDTO;
import kro.rubisco.service.GroupService;
@Service
@Transactional(readOnly = true)
public class GroupServiceImpl implements GroupService {
private final GroupDAO groupDAO;
@Autowired
public GroupServiceImpl(SqlSession sqlSession) {
this.groupDAO = sqlSession.getMapper(GroupDAO.class);
}
@Override
public void regist(GroupDTO group) throws Exception {
groupDAO.create(group);
}
@Override
public GroupDTO read(Long groupId) throws Exception {
return groupDAO.read(groupId);
}
@Override
public void modify(GroupDTO group) throws Exception {
groupDAO.update(group);
}
@Override
public void remove(Long groupId) throws Exception {
groupDAO.delete(groupId);
}
@Override
public List<GroupDTO> listAll() throws Exception {
return groupDAO.listAll();
}
}
/kro/rubisco/service/impl/MemberServiceImpl.java
package kro.rubisco.service.impl;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import kro.rubisco.dao.MemberDAO;
import kro.rubisco.dto.MemberDTO;
import kro.rubisco.service.MemberService;
@Service
@Transactional(readOnly = true)
public class MemberServiceImpl implements MemberService {
private final MemberDAO memberDAO;
@Autowired
public MemberServiceImpl(SqlSession sqlSession) {
this.memberDAO = sqlSession.getMapper(MemberDAO.class);
}
@Override
public void regist(MemberDTO member) throws Exception {
memberDAO.create(member);
}
@Override
public MemberDTO read(Long memberId) throws Exception {
return memberDAO.read(memberId);
}
@Override
public void modify(MemberDTO member) throws Exception {
memberDAO.update(member);
}
@Override
public void remove(Long memberId) throws Exception {
memberDAO.delete(memberId);
}
@Override
public List<MemberDTO> listAll() throws Exception {
return memberDAO.listAll();
}
}
컨트롤러(Controller) 작성
컨트롤러에서는 사용자의 요청을 받아 서비스를 호출하고 DispatcherServlet에 ModelAndView를 반환합니다.
루트 경로 변경
우선 톰캣의 루트 경로를 수정합니다. 왼쪽 하단에 톰캣 서버를 더블클릭하세요.
Modules
탭을 선택하고 Edit...
버튼을 클릭합니다.
Path
를 /
로 설정하고 OK
버튼을 클릭 후 서버를 재시작합니다.
컨트롤러 스캔 등록
컨트롤러를 스캔할 수 있도록 스프링의 servlet-context.xml
파일에 컨트롤러 컴포넌트 스캔 경로를 추가해줍시다. 아래 예시의 경우 kro.rubisco.controller
패키지를 컴포넌트 스캔 경로로 등록했습니다.
src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
<!-- Enables the Spring MVC @Controller programming model -->
<annotation-driven />
<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
<resources mapping="/resources/**" location="/resources/" />
<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
<context:component-scan base-package="kro.rubisco.controller" />
</beans:beans>
필터 추가
한글 깨짐 문제를 해결하고 get, post 이외의 전송방식을 구현하기위해 web.xml
파일에 encodingFilter와 httpMethodFilter를 추가합니다.
src/main/webapp/WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/root-context.xml</param-value>
</context-param>
<!-- Creates the Spring Container shared by all Servlets and Filters -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Processes application requests -->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter>
<filter-name>httpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>httpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
매퍼 수정
게시글이 입력되면 id값이 BoardDTO에 반환될 수 있도록 boardMapper
의 insert
태그 아래에 selectKey
태그를 추가해줍니다.
boardMapper.xml
...
<insert id="create">
insert into board (category_id, is_notice, title, content, member_id)
values (#{categoryId}, #{isNotice, typeHandler=kro.rubisco.config.YNTypeHandler}, #{title}, #{content}, #{memberId})
<selectKey keyProperty="documentId" resultType="Long" order="AFTER">
select max(document_id) as document_id from board
</selectKey>
</insert>
...
order
속성이 AFTER
이므로, insert 쿼리가 수행된 후에 selectKey
쿼리를 수행하여 BoardDTO의 documentId
에 결과값을 반환합니다.
컨트롤러 구현
BoardController, CommentController, MemberController, AdminController를 작성하도록 하겠습니다.
/kro/rubisco/controller/BoardController.java
package kro.rubisco.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import kro.rubisco.dto.BoardDTO;
import kro.rubisco.service.BoardService;
import kro.rubisco.service.CategoryService;
import lombok.RequiredArgsConstructor;
@Controller
@RequiredArgsConstructor
@RequestMapping("/board")
public class BoardController {
private final BoardService boardService;
private final CategoryService categoryService;
@GetMapping()
public String getBoardListView(Model model) throws Exception {
model.addAttribute("boardList", boardService.listAll());
return "board/getBoardList";
}
@GetMapping("/{documentId}")
public String getBoardView(
@PathVariable("documentId") Long documentId,
Model model
) throws Exception {
model.addAttribute("board", boardService.read(documentId));
return "board/getBoard";
}
@GetMapping(params = "act=write")
public String getInsertBoardView(
@RequestParam(value="documentId", required=false) Long documentId,
Model model
) throws Exception {
model.addAttribute("board", documentId == null ? new BoardDTO() : boardService.read(documentId));
model.addAttribute("categoryList", categoryService.listAll());
return "board/insertBoard";
}
@PostMapping()
public String insertBoard(BoardDTO board) throws Exception {
boardService.regist(board);
return "redirect:/board/" + board.getDocumentId();
}
@PatchMapping(params = "documentId")
public String updateBoard(BoardDTO board) throws Exception {
boardService.modify(board);
return "redirect:/board/" + board.getDocumentId();
}
@DeleteMapping(params = "documentId")
public String deleteBoard(
@RequestParam("documentId") Long documentId
)throws Exception {
boardService.remove(documentId);
return "redirect:/board";
}
}
/kro/rubisco/controller/CommentController.java
package kro.rubisco.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import kro.rubisco.dto.CommentDTO;
import kro.rubisco.service.CommentService;
import lombok.RequiredArgsConstructor;
@Controller
@RequiredArgsConstructor
@RequestMapping("/comment")
public class CommentController {
private CommentService commentService;
@PostMapping(params = "commentId")
public String insertComment(CommentDTO comment) throws Exception {
commentService.regist(comment);
return "redirect:/board/" + comment.getDocumentId();
}
@PatchMapping(params = "commentId")
public String updateComment(CommentDTO comment) throws Exception {
commentService.modify(comment);
return "redirect:/board/" + comment.getDocumentId();
}
@DeleteMapping(params = "commentId")
public String deleteComment(CommentDTO comment) throws Exception {
commentService.remove(comment.getCommentId());
return "redirect:/board/" + comment.getDocumentId();
}
}
/kro/rubisco/controller/MemberController.java
package kro.rubisco.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import kro.rubisco.dto.MemberDTO;
import kro.rubisco.service.MemberService;
import lombok.RequiredArgsConstructor;
@Controller
@RequiredArgsConstructor
@RequestMapping("/member")
public class MemberController {
private final MemberService memberService;
@GetMapping()
public String getMemberInfo() {
return "member/getMemberInfo";
}
@GetMapping(params="act=signUp")
public String getSignUpView() throws Exception {
return "member/signUpForm";
}
@PostMapping()
public String insertMember(MemberDTO member) throws Exception {
memberService.regist(member);
return "redirect:/";
}
@PatchMapping()
public String updateMember(MemberDTO member) throws Exception {
memberService.modify(member);
return "redirect:/member";
}
@DeleteMapping()
public String deleteMember() throws Exception {
return "redirect:/";
}
}
/kro/rubisco/controller/AdminController.java
package kro.rubisco.controller;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import kro.rubisco.dto.CategoryDTO;
import kro.rubisco.dto.GroupDTO;
import kro.rubisco.service.CategoryService;
import kro.rubisco.service.GroupService;
import lombok.RequiredArgsConstructor;
@Controller
@RequiredArgsConstructor
@RequestMapping("/admin")
public class AdminController {
private final CategoryService categoryService;
private final GroupService groupService;
@GetMapping("/category")
public String getCategoryListView(Model model) throws Exception {
List<CategoryDTO> categoryList = categoryService.listAll();
model.addAttribute("categoryList", categoryList);
return "category/getCategoryList";
}
@PostMapping("/category")
public String insertCategory(CategoryDTO category) throws Exception {
categoryService.regist(category);
return "redirect:/admin/category";
}
@PatchMapping("/category")
public String updateCategory(CategoryDTO category) throws Exception {
categoryService.modify(category);
return "redirect:/admin/category";
}
@GetMapping("/group")
public String getGroupListView(Model model) throws Exception {
List<GroupDTO> groupList = groupService.listAll();
model.addAttribute("groupList", groupList);
return "group/getGroupList";
}
@PostMapping("/group")
public String insertGroup(GroupDTO group) throws Exception {
groupService.regist(group);
return "redirect:/admin/group";
}
@PatchMapping("/group")
public String updateCategory(GroupDTO group) throws Exception {
groupService.modify(group);
return "redirect:/admin/group";
}
}