/ #JAVA#SPRING

스프링 레거시(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를 반환합니다.

루트 경로 변경

우선 톰캣의 루트 경로를 수정합니다. 왼쪽 하단에 톰캣 서버를 더블클릭하세요.

img22

Modules 탭을 선택하고 Edit... 버튼을 클릭합니다.

img23

Path/로 설정하고 OK버튼을 클릭 후 서버를 재시작합니다.

img24

컨트롤러 스캔 등록

컨트롤러를 스캔할 수 있도록 스프링의 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에 반환될 수 있도록 boardMapperinsert 태그 아래에 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";
    }
}

소스코드

service_controller.zip