Spring

Spring boot에서 maven을 통해 mybatis MySQL 사용하기 - select

趙河晶 2019. 8. 1. 15:38

- 환경
   IntelliJ IDEA 2018.3.6 (Ultimate Edition)
   Mac OS

Spring boot을 사용하기 전에 Spring boot이 무엇인지 알아보자. 일단, Spring이 IoC/DI 기반으로 된 자바 프레임 워크다. IoC는 객체 생성 및 소멸이 알아서 대신 작동되는 것이고 DI는 interface의 method를 사용하면 나중에 주입하는 클래스에 따라 다른 기능을 하는 것이다.
그럼 Spring boot은 단순하게 스프링 프로젝트라고 생각하면 된다. 스프링이 점점 지원하는 라이브러리가 많아지니까 자주 사용하는 프로젝트 조합을 미리 만들어 빌드하는게 Spring boot이다. tomcat 서버를 내장하고 있기에 Spring에서 하던 연동 작업을 할 필요가 없다.

또, 제목에 있는 Maven은 무엇인지 알아보자. 내가 자바 프로젝트에서 필요한 라이브러리 jar 파일을 가지고 수행하다가 협업을 한다고 생각해보자. 그럼 내가 사용하는 jar 파일들을 수동으로 넘겨줘야 한다. 하지만 Maven을 사용하면 필요한 라이브러리를 pom.xml 파일에 정의함으로써 자동으로 프로젝트에 받아오게 한다. 

그럼 이제 Spring boot 프로젝트를 빌드해보자.

1. Spring boot 프로젝트 생성

 Intellij에서 'Spring Initializr'이 Spring boot을 말한다. 얘를 클릭하고 다음으로 넘어간다.

 그리고 group과 프로젝트명을 적고 넘어간다. 나는 롤 프로게이머 선수단 목록을 보여주는 웹 페이지를 만들 것이므로 lol_pro라고 했다.

 그리고 앞서 말한 프로젝트에 필요한 라이브러리를 프로젝트 빌드 시 같이 구축할 수 있다는 부분이 위 사진으로 설명된다. 여기서 나는 제목에 언급된 것 처럼 MyBatis로 MySQL를 사용할 것이므로 두 개를 선택해주고 Lombok도 선택해주었다.

 Next를 계속 누르고 오른쪽 하단에 해당 알림창이 뜨면 Enable Auto-Import를 선택해준다.

 그리고 프로젝트에 필요한 plugin을 설치하자. 우선 나는 Lombok을 사용하므로 Preference의(단축키는 'Command' + ',') Plugins 에서 lombok을 검색하면 나오는 'Lombok'을 설치했다. 

 그리고 MyBatis plugin도 검색해서 설치해준다.

2. 프로젝트 MVC 나누기
 MVC를 나누기 전에 Spring MVC에 대해 알고가면 프로젝트 흐름을 이해하는 데 도움이 된다. (당연히 알고 있어야 하는 것이고)
 웹 브라우저가 URL을 요청하면 DispatcherServlet으로 간다. 먼저 HandlerMapping에서 URL과 매핑되는 Controller를 검색해서 return해준다. Dispatcher Servlet에서 해당 controller에게 처리 요청을 하고 model, view를 return 받는다. 그리고 View에게 출력을 요청하면 View가 브라우저로 JSP를 생성한다.

다시 프로젝트로 돌아와서 일단은 해당 단계에서 View는 제외하고 Model, Controller, DAO, Service, Config 부분을 나누어 줄 것이다. 빌드된 프로젝트에 아래와 같이 패키지 및 클래스를 생성했다.

 2-1. config
  DataAccessConfig 파일은 Mapper에서 매칭된 쿼리문이 적힌 xml 파일을 가져와 데이터베이스의 데이터에 접근할 수 있도록 설정해주는 클래스이다.
  DataSourceConfig 파일은 application.properties 파일에 적힌 설정값을 토대로 데이터에 접근하기 위한 기본 작업을 하는 클래스이다.
 2-2. controller
  MemberController 파일은 브라우저가 요청한 URL에 맞는 함수를 실행해서 JSP에 나타낼 정보를 설정해준다.
 2-3. dao
 MemberDao 파일은 말 그대로 데이터를 가져오기 위한 인터페이스이다.
 2-4. model
 가져온 데이터를 저장하기 위한 클래스이다.
 2-5. service
 데이터를 저장한 MemberModel 객체를 dao 객체로부터 받아 특정 작업을 수행하기 위한 파일인데 선언부로 인터페이스를 두고 구현부로 Impl를 따로 두었다. 현재는 dao로 부터 받아온 모델 객체를 Controller에게 넘겨주기만 하면 된다.

3. MySQL 디비에 스키마 구축
나는 로컬에다가 MySQL를 설치하여 사용하였다. 
아래는 스키마 생성에 사용된 명령어이다.

CREATE SCHEMA lol DEAFAULT CHARACTER SET utf8;

아래는 테이블 생성에 사용된 명령어이다.

CREATE TABLE lol.t1 (id INT NOT NULL, name VARCHAR(100) NOT NULL,
			nickname VARCHAR(128) NOT NULL, position VARCHAR(100) NOT NULL,
                        PRIMARY KEY(id));

그리고 INSERT 문을 통해 데이터를 t1 테이블에 아래와 같이 삽입하였다.

4. 코드 작성 전 annotation 허용
 Annotation이란 @을 이용해 주석으로 자바 코드에 주석을 달아 특별한 의미를 부여한다. 컴파일러를 위한 정보를 제공하기 위한 용도이다. 
 Intellij에서 이를 처리하는게 프로젝트 빌드 초기 설정에는 허용되어 있지 않다. 그래서 Preference(다시 'Command' + ',') > Build, Execution, Deployment > Compiler > Annoation Processors 에서 Enable annotation processing 부분을 체크해준다.

5. application.properties에 접근할 MySQL DB 설정값 작성

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/lol?serverTimezone=UTC&allowPublicKeyRetrieval=true&useSSL=false
spring.datasource.username=root
spring.datasource.password=password

 위와 같이 작성하면 되는데 나는 root 계정으로 접근해서 allowPublicKeyRetrieval 속성을 true로 주었다.
 그럼 이제 여기에 적힌 설정을 내용을 토대로 DataSourceConfig 클래스에 configure해주면 된다.

6. DataSourceConfig

package com.chojpsh1.lol_pro.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
public class DataSourceConfig {

    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource() {
        return DataSourceBuilder.create().build();
    }
}

application.properties에 작성했던 datasource의 공통된 부분만 ConfigurationProperties annotation로 넘겨주면 DataSourceBuilder가 그에 맞게 수행할 것이다.

7. MemberModel
 데이터를 받아와 저장할 객체를 정의한다.

package com.chojpsh1.lol_pro.model;

import lombok.Getter;

@Getter
public class MemberModel {

    private int id;
    private String name;
    private String nickname;
    private String position;
}

 나는 Lombok을 이용해서 @Getter annotation을 통해 getter가 생성되도록 했다. Lombok을 사용하지 않는다면 public int getId() { return id; } 이렇게 getter를 구현해주면 된다.

8. MemberDao
 이제 데이터가 저장된 객체에 접근하는 객체를 정의해준다.

package com.chojpsh1.lol_pro.dao;

import com.chojpsh1.lol_pro.model.MemberModel;

import java.util.List;

public interface MemberDao {
    List<MemberModel> getMember();
}

 앞에 테이블에 삽입된 모습을 보면 알 수 있듯이 한 record가 아니라 여러 record가 있다. 한개만 있다면 그냥 MemberModel getMember();로 선언해도 되지만 여러 record를 뽑아오므로 List로 가져온다.

 이제 MySQL DB에서 수행할 쿼리문을 작성해보자. 앞에 말한 MVC 동작 과정을 다시 보면 Servlet에서 해당 URL에 대한 수행 작업을 알기 위해 Mapper에게 처리 요청을 한다. 그래서 어디에 있는 mapper에 가서 데이터를 접근할 지 설정하는 것이 DataAccessConfig이고, 데이터에 접근해서 무슨 동작을 할지 mapper 안에 정의 해준다. 
 그러기위해 resources 폴더 내에 mapper 폴더를 하나 만들어 t1 테이블에 접근하는 쿼리문을 작성할 xml 파일을 생성한다.
7. Mapper 쿼리문 작성

 내가 생성한 mapper 위치는 위와 같다. 

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.chojpsh1.lol_pro.dao.MemberDao">

    <select id="getMember" resultType="com.chojpsh1.lol_pro.model.MemberModel">
		select * from t1
	</select>

</mapper>

8. DataAccessConfig

package com.chojpsh1.lol_pro.config;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;

@Configuration
@MapperScan(basePackages = "com.chojpsh1.lol_pro.dao")
public class DataAccessConfig {

    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
        sessionFactoryBean.setDataSource(dataSource);
        sessionFactoryBean.setMapperLocations(
                new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml")
        );
        return sessionFactoryBean.getObject();
    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

mapper에서 매칭될 파일의 경로가 getResources 함수로 들어간다.

이제 데이터를 저장하는 과정은 완료되었으니 이를 가져와 작업을 수행하는 코드를 작성해보자.
9. MemberService

package com.chojpsh1.lol_pro.service;

import com.chojpsh1.lol_pro.model.MemberModel;

import java.util.List;

public interface MemberService {
    List<MemberModel> printMember();
}

 일단은 그냥 데이터를 가져와 출력하는 게 목표이므로 데이터를 출력해주는 서비스를 할 함수를 인터페이스에 선언한다.

10. MemberServiceImpl

package com.chojpsh1.lol_pro.service.Impl;

import com.chojpsh1.lol_pro.dao.MemberDao;
import com.chojpsh1.lol_pro.model.MemberModel;
import com.chojpsh1.lol_pro.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class MemberServiceImpl implements MemberService {

    @Autowired
    private MemberDao dao;

    @Override
    public List<MemberModel> printMember() {
        List<MemberModel> member = dao.getMember();
        return member;
    }
}

 데이터를 받아 넘겨준다.(controller가 넘겨받을 것이다.)

11. MemberController

package com.chojpsh1.lol_pro.controller;

import com.chojpsh1.lol_pro.model.MemberModel;
import com.chojpsh1.lol_pro.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.List;

@Controller
public class MemberController {

    @Autowired
    MemberService memberService;

    @RequestMapping("/list")
    public String list(Model model) {
        List<MemberModel> member = memberService.printMember();

        model.addAttribute("memberList", member);

        return "list";
    }
}

 RequestMapping annotation을 통해 URL에 /list 가 요청되면 list 함수를 실행할 것인데 인자값에 있는 spring 프레임워크에서 ui, 즉 View 로 값을 넘겨줄 수 있는 Model 객체로 데이터를 준다. 즉, 내 코드에서는 Model에 addAttribute 함수를 통해 'memberList'라는 id에데이터들의 집합 list를 매칭시켜주었다. 그럼 View 부분인 jsp에서 해당 id로 값을 사용할 수 있다.

근데 듣기론 Spring boot에서는 View로 jsp보단 다른 걸 사용한다고 들었는데..일단 jsp가 제일 기본이니까 Jsp로 했다. (Velocity도 많이 들었는데 일단 나는 초짜니까 jsp로 했다.)

12. pom.xml에 JSP View 사용 관련 dependency 추가
 Spring boot에서는 JSP를 View로 사용하는게 기본 설정이 아니여서 pom.xml에 이에 맞는 dependency를 추가해줘야한다. 

	<dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
	</dependency>
	<dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
	</dependency>

위 두개 dependency만 추가해주면 된다.

13. application.properties에 view 설정값 추가
 그리고 View를 담당할 jsp가 있을 파일 위치를 application.properties에 spring의 view로 설정해주면 된다.

spring.mvc.view.prefix=/WEB-INF/view/
spring.mvc.view.suffix=.jsp

 이렇게 하면 spring에서 main/webapp에 있을 WEB-INF/view/ 폴더 내에 확장자가 jsp파일을 view로 사용한다. 

 따라서 main폴더 내에 webapp 폴더를 만들고 application.properties에 작성한 경로대로 jsp 파일을 만들어준다.

 아까 11번 MemberController를 보면 "list"를 반환한다. 이는 Controller가 View 파일 이름을 반환하면 Servlet에서 반환받아 이에 해당하는 jsp 파일을 찾아 브라우저에 띄운다. 그래서 파일 이름을 list.jsp로 했다.

14. JSP View

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page language="java" contentType="text/html; charset=EUC-KR"
         pageEncoding="EUC-KR"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=EUC-KR">
    <title>롤 프로게이머</title>
</head>

<body>
    <table border="1">
        <thead>
            <tr>
                <th>번호</th>
                <th>이름</th>
                <th>닉네임</th>
                <th>포지션</th>
            </tr>
        </thead>
        <tbody>
            <c:forEach items="${memberList}" var="member">
                <tr>
                    <td>${member.id}</td>
                    <td>${member.name}</td>
                    <td>${member.nickname}</td>
                    <td>${member.position}</td>
                </tr>
            </c:forEach>
        </tbody>
    </table>
</body>

</html>

 jsp 코드를 보면 아까 11번 MemberController에서 attribute로 준 memberList를 사용하고 있는 것을 볼 수 있다.

이렇게 하고 프로젝트를 실행한 후 localhot:8080/list로 접속하면 데이터베이스에 저장된 정보가 표로 출력되는 걸 볼 수 있다.