MyBatis - 활용을 통해 이해하기

제훈·2024년 8월 16일
0

SW공학, DB

목록 보기
20/21

MyBatis

MyBatis 개요

MyBatis 개요 이후로 활용을 해보려고 한다.

xml로 할 것이며, TransactionFactory, DataSource 와 같은 것들도 사용해보자.

Mapper 인터페이스로 활용하는 경우

쿼리를 꺼내는 작업으로 사용하기 위해 활용할 Mapper 인터페이스

import org.apache.ibatis.annotations.Select;

import java.util.Date;

public interface Mapper {

    @Select("SELECT NOW()")
    Date selectNow();
}

Application

public class Application {
    private static String driver = "com.mysql.cj.jdbc.Driver";
    private static String url = "jdbc:mysql://localhost:3306/menudb";
    private static String user = "swcamp";
    private static String password = "swcamp";

    public static void main(String[] args) {
        Environment environment = new Environment("dev", new JdbcTransactionFactory(), new PooledDataSource(driver, url, user, password));

        // configuration은 설계도
        Configuration configuration = new Configuration(environment);
        configuration.addMapper(Mapper.class);

        // 설계도를 줄테니 공장을 지어달라는 의미로 사용
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

        // 이제 수동 커밋을 해야 한다. false를 해야지 수동 커밋이 된다.
        SqlSession sqlSession = sqlSessionFactory.openSession(false);

        // 쿼리를 꺼내는 작업
        Mapper mapper = sqlSession.getMapper(Mapper.class);
        Date date = mapper.selectNow();

        System.out.println("date = " + date);

        sqlSession.close();
    }
}

이전부터 사용했던 menudb를 그대로 사용해보았고

Mapper 인터페이스와 Application은 같은 패키지에 넣어뒀다.
같은 이름이 있긴 한데 import 할 필요 없다.

  • JdbcTransactionFactory : 수동 커밋
  • ManagedTransactionFactory : 자동 커밋

  • PooledDataSource : ConnectionPool 사용
  • UnPooledDataSource : ConnectionPool 미사용

출력결과


xml파일 사용하는 경우

설정 정보를 작성하기 위해서 mybatis-config.xml 파일을 생성할 것이다.

설정 내용 정의

  1. Mybatis 공식 사이트를 가면 xml 파일로 했을 때 설정 내용을 Mybatis 설정이라고 선언해줘야 하는데, 아래 코드를 최상단에 적어주면 된다.
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
		PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
		"http://mybatis.org/dtd/mybatis-3-config.dtd">
  • DOCTYPE으로 설정하는 내용은 하위에 사용 가능한 태그, 속성들이 정의되어 있는 파일이다.
    → 따라서 해당 파일에 작성하는 태그 혹은 속성 등이 맞게 작성되었는지 확인한다.
  1. Mybatis 설정이라는 것을 작성했기 때문에 설정 내용 아래 최상위 태그인 <configuration>을 작성해주면 된다.
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
. . .
</configuration>

Environment

  1. MyBatis에서 연동할 DB 정보를 등록하는 것으로, <environments> 태그는 여러 개의 <environment> 태그를 포함할 수 있으며, default 속성으로 기본 사용할 환경 설정을 지정할 수 있다.

  2. <environment> 내부에는 <transactionManager>, <dataSource>, <property> 와 같이 여러 가지 태그가 들어갈 수 있다.

  • <transactionManager> : 트랜잭션 매니저를 JDBC 혹은 MANAGED로 설정할 수 있다.

    • JDBC : MyBatis API에서 제공하는 commit, rollback 메소드 등을 사용해서 트랜젝션을 관리하는 방식 (수동 commit)
    • MANAGED : MyBatis API 보다는 컨테이너가 직접 트랜젝션을 관리하는 방식 (자동 commit)
  • <dataSource> : 속성으로 커넥션풀 사용 여부를 POOLEDUNPOOLED로 설정할 수 있다.

    • 구분POOLEDUNPOOLED
      특징최초 Connection 객체 생성 시 해당 정보를 pool 영역에 저장해 두고 이후 Connection 객체를 생성할 때 이를 재사용한다.Connection 객체를 별도로 저장하지 않고, 호출 시마다 매번 생성하여 사용한다.
      장점Connection 객체를 생성하여 데이터베이스와 연결을 구축하는데 걸리는 시간이 단축된다.Connection 연결이 많지 않은 코드를 작성할 때 간단하게 구현할 수 있다.
      단점단순한 로직을 수행하는 객체를 만들기에는 설정해야 할 정보가 많다.매번 새로운 Connection 객체를 생성하므로 속도가 상대적으로 느리다.

properties

  1. 설정 파일에서 공통적인 속성을 정의하거나 외부 파일에서 값을 가져오는 태그이다.
  2. 외부 프로퍼티 파일은 resource 하위의 경로를 기술하면 된다.
    • 클래스패스 경로가 아닌 경우 url속성에 "file:d:\"로 시작하는 경로를 기술하면 된다.
  3. <properties> 태그 안쪽에 <property> 태그를 작성하여 properties 파일에 값을 설정할 수 있다.

위에 작성한 몇 가지 XML Tag 들을 사용해서 예제를 만들 것이다.

이전에 사용했던 menudb 로 할 것이고, MyBatis를 사용하기 위해서는 mapper, config 파일이 필요하다.

resources 폴더에 추가

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="dev">
        <environment id="dev">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/menudb"/>
                <property name="username" value="swcamp"/>
                <property name="password" value="swcamp"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper.xml"/>
    </mappers>
</configuration>

mapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mapper">
    <select id="selectNow" resultType="java.util.Date">
        SELECT NOW()
    </select>
</mapper>

Application 클래스

public class Application {
    public static void main(String[] args) {
        String resource = "mybatis-config.xml";

        try {
            InputStream inputStream = Resources.getResourceAsStream(resource);

            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

            // 커넥션 객체를 session이라고 할 뿐이다. + 수동 커밋
            SqlSession session = sqlSessionFactory.openSession(false);

            // 여기서 꼭 소속과 id가 필요하다. 그래서 mapper.xml에 네임스페이스를 적어준 것이다. 소속이 필요해서.
            Date date = session.selectOne("mapper.selectNow");
            System.out.println("date = " + date);

            session.close();

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

주석에도 적혀있지만 mapper.xml에 <select> 부분을 보면 name도 같이 적혀 있다.
소속과 id가 있어야 selectOne()을 사용할 수 있는 것이다.

출력 결과


심화 예제 활용

메뉴 관리 프로그램 만들기

xml 파일에 DB에 적어둔 것들을 resultMap에 컬럼들을 정의해두고 <select> 를 통해서 원하는 쿼리 처리를 하는 방식으로 사용해보려고 한다.

새로운 프로젝트로 한다.

MenuDTO

package com.ohgiraffers.section01.xmlconfig;

public class MenuDTO {
    private int menuCode;
    private String menuName;
    private int menuPrice;
    private int categoryCode;
    private String orderableStatus;

    public MenuDTO() {
    }

    public MenuDTO(int menuCode, String menuNmae, int menuPrice, int categoryCode, String orderableStatus) {
        this.menuCode = menuCode;
        this.menuName = menuNmae;
        this.menuPrice = menuPrice;
        this.categoryCode = categoryCode;
        this.orderableStatus = orderableStatus;
    }
    
    (getter, setter 코드 길어서 생략)

    @Override
    public String toString() {
        return "MenuDTO{" +
                "menuCode=" + menuCode +
                ", menuName='" + menuName + '\'' +
                ", menuPrice=" + menuPrice +
                ", categoryCode=" + categoryCode +
                ", orderableStatus='" + orderableStatus + '\'' +
                '}';
    }
}

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="dev">
        <environment id="dev">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/menudb"/>
                <property name="username" value="swcamp"/>
                <property name="password" value="swcamp"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="com/jehun/section01/xmlconfig/menu-mapper.xml"/>
    </mappers>
</configuration>

menu-mapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="MenuMapper">
    <resultMap id="menuResultMap" type="com.jehun.section01.xmlconfig.MenuDTO">
        <id property="menuCode" column="MENU_CODE"/>
        <result property="menuName" column="MENU_NAME"/>
        <result property="menuPrice" column="MENU_PRICE"/>
        <result property="categoryCode" column="CATEGORY_CODE"/>
        <result property="orderableStatus" column="ORDERABLE_STATUS"/>
    </resultMap>
</mapper>

기본 세팅의 Application

public class Application {
    public static void main(String[] args) {

        MenuController menuController = new MenuController();
        Scanner sc = new Scanner(System.in);

        do{
            System.out.println("===== 메뉴 관리 =====");
            System.out.println("1. 메뉴 전체 조회");
            System.out.println("2. 메뉴 코드로 메뉴 조회");
            System.out.println("3. 신규 메뉴 추가");
            System.out.println("4. 메뉴 수정");
            System.out.println("5. 메뉴 삭제");
            System.out.println("9. 프로그램 종료");

            System.out.print("메뉴 관리 번호를 입력하세요 : ");
            int num = sc.nextInt();

            switch(num) {
                case 1: break;
                case 9:
                    System.out.println("프로그램을 종료하겠습니다."); return;
                default:
                    System.out.println("번호를 잘못 입력하셨습니다.");

            }
        } while (true);
    }
}

이제 case문에 1,2,3,4,5번을 다 추가할 것이다.

필요한 클래스들은 Controller, Service, DAO에 값을 사용하려면 DTO도 필요하기에 위에 작성해뒀다.

그리고 xml을 읽을 Template 클래스, 결과를 반환해줄 PrintResult 클래스가 필요하다.

6개의 클래스가 필요하다는 뜻이다.

xml을 읽을 Template 클래스

public class Template {

    private static SqlSessionFactory sqlSessionFactory;

    public static SqlSession getSqlSession() {
        if (sqlSessionFactory == null) {
            String resource = "com/jehun/section01/xmlconfig/mybatis-config.xml";

            try {
                InputStream inputStream = Resources.getResourceAsStream(resource);
                sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return sqlSessionFactory.openSession(false);
    }
}

1번부터 한 번 해보자.


1번. 메뉴 전체 조회

일단 필요한건 Controller에서 알맞은 메소드를 호출하는 것이다.

그럼 Controller는 Service를 의존성 주입하면서 해당하는 메소드를 또 호출할 것이고,

Service 또한 DAO를 의존성 주입한 뒤 호출할 것이다.

코드를 보자.

Application의 switch 문 case 1에 추가

	기존 코드
			switch(num) {
                case 1:
                    menuController.findAllMenus();
                    break;
                case 9:
    기존 코드

MenuController 코드

public class MenuController {
    private final MenuService menuService;
    private final PrintResult printResult;

    public MenuController() {
        menuService = new MenuService();
        printResult = new PrintResult();
    }

    public void findAllMenus() {
        List<MenuDTO> menusList = menuService.findAllMenus();

        if (!menusList.isEmpty()) {
            printResult.printMenus(menusList);
        } else {
            printResult.printErrorMessage("조회할 메뉴가 없습니다.");
        }
    }
}

기본 생성자에 Service와 printResult를 의존성 주입한다.

printResult는 반환(메시지 출력) 해주는 클래스이다.

이제 2,3,4,5 번이 추가되면서는 findAllMenus() 메소드 아래에 계속 추가될 예정이다.


MenuService 코드

import static com.jehun.section01.xmlconfig.Template.getSqlSession;

public class MenuService {

    private final MenuDAO menuDAO;

    public MenuService() {
        menuDAO = new MenuDAO();
    }

    public List<MenuDTO> findAllMenus() {
        SqlSession sqlSession = getSqlSession();

        List<MenuDTO> menuList = menuDAO.selectAllMenus(sqlSession);

        return menuList;
    }
}

Service는 비즈니스 로직이기도 하고, 해당 xml 파일을 읽어오는 Template 클래스의 getSqlSession()을 불러온다.


MenuDAO

public class MenuDAO {
    public List<MenuDTO> selectAllMenus(SqlSession sqlSession) {
        return sqlSession.selectList("MenuMapper.selectAllMenus");
    }
}

sqlSession을 비즈니스 로직으로부터 매개변수로 받아서 MenuMapper.xmlselectAllMenus 쿼리를 리스트로 받아오는 것이 MenuDAO가 한 일이며

그것을 Service가 받아와서 반환해주면 컨트롤러는 출력을 해준다.

만약 비어있으면 조회할 메뉴가 없다면서 에러 메세지라고 출력해주게 된다.


최종 출력 결과


2번. 메뉴코드로 1개 조회

기존 Application 코드에 2번 case 문을 추가해주자.

Application

                case 2:
                    System.out.print("조회하고 싶은 메뉴 코드를 입력하세요 : ");
                    int code = sc.nextInt();
                    menuController.findByMenuCode(code);
                    break;

Controller의 findByMenuCode()에 메뉴코드를 입력받는 메소드를 활용해서 넣어주려고 한다.

뭐 입력받는 부분을 메소드로 따로 빼도 괜찮다.

Controller 도 위의 전체 메뉴 조회와 크게 다르지 않다. 그냥 DTO 자체로 받아오느냐, List로 받느냐의 차이일 뿐이다.

Controller

기존 코드에 추가

    public void findByMenuCode(int code) {
        MenuDTO menuDTO = menuService.findByMenuCode(code);

        if (menuDTO != null) {
            printResult.printMenu(menuDTO);
        } else {
            printResult.printErrorMessage("해당 코드로 조회된 메뉴가 없습니다.");
        }
    }

Service 의존성 주입한 뒤 원하는 메뉴가 없다면 조회되는게 없다는 식으로 했다.

있다면 printMenu가 실행되게 했다.

printResult 에 추가

    public void printMenu(MenuDTO menuDTO) {
        System.out.println("해당 코드의 메뉴");
        System.out.println(menuDTO);
    }

MenuService에 추가

    public MenuDTO findByMenuCode(int menuCode) {
        SqlSession sqlSession = getSqlSession();

        MenuDTO menuDTO = menuDAO.selectMenuByMenuCode(sqlSession, menuCode);

        sqlSession.close();

        return menuDTO;
    }

여기서 사용할 SqlSession은 이제 menu-mapper.xml 속에 selectMenuByMenuCode 쿼리를 추가해서 사용할 것이다.

MenuDAO에 추가

    public MenuDTO selectMenuByMenuCode(SqlSession sqlSession, int menuCode) {
        return sqlSession.selectOne("MenuMapper.selectMenuByMenuCode", menuCode);
    }

menu-mapper.xml 에 추가

    <select id="selectMenuByMenuCode" resultMap="menuResultMap" parameterType="_int">
        SELECT
               MENU_CODE
             , MENU_NAME
             , MENU_PRICE
             , CATEGORY_CODE
             , ORDERABLE_STATUS
          FROM TBL_MENU
         WHERE MENU_CODE = #{ menuCode }
    </select>

MyBatis를 사용할 때는 정수와 같은 것들을 입력받아서 사용할 때 parameterType="_int" 해당 부분과 #{ menuCode } 이런 형태로 작성해야 한다고 한다..


최종 출력 결과


3번. 신규 메뉴 추가하기

Application에 case문 추가

                case 3:
                    menuController.registerMenu(inputMenu());

inputMenu 메소드도 Application에 작성

    private static Map<String, String> inputMenu() {
        Scanner sc = new Scanner(System.in);

        System.out.print("신규 메뉴의 이름을 입력해 주세요 : ");
        String menuName = sc.nextLine();
        System.out.print("신규 메뉴의 가격을 입력해 주세요 : ");
        String menuPrice = sc.nextLine();
        System.out.print("신규 메뉴의 카테고리 코드를 입력해 주세요 : ");
        String categoryCode = sc.nextLine();

        Map<String, String> parameter = new HashMap<>();
        parameter.put("menuName", menuName);
        parameter.put("menuPrice", menuPrice);
        parameter.put("categoryCode", categoryCode);

        return parameter;
    }

각각 이름과 숫자여서 String, int 형이라 다르게 받는 것보다 map에 key, value로 나눠서 일단 String으로 저장한 뒤 바꿔줄 생각으로 작성했다.

Controller의 registerMenu 메소드

기존 코드에 추가

    public void registerMenu(Map<String, String> parameter) {
        // 가공처리는 컨트롤러의 역할 (이름, 코드, 카테고리 코드같은 다양한 자료형의 값들을 파싱해 하나의 타입으로)
        String menuName = parameter.get("menuName");
        int menuPrice = Integer.parseInt(parameter.get("menuPrice"));
        int categoryCode = Integer.parseInt(parameter.get("categoryCode"));

        MenuDTO menuDTO = new MenuDTO();
        menuDTO.setMenuName(menuName);
        menuDTO.setMenuPrice(menuPrice);
        menuDTO.setCategoryCode(categoryCode);

        if (menuService.registerMenu(menuDTO)) {
            printResult.printSuccessMessage("register");
        } else {
            printResult.printErrorMessage("메뉴 추가 실패..");
        }

여기서 printResult의 printSuccessMessage 는 각 메시지에 따라 신규 메뉴 추가, 메뉴 수정, 메뉴 삭제 이 중에서 선택해서 그 때마다 다른 메시지가 나오게 하려고 한다.

일단은 메뉴 추가를 하고 있기 때문에 등록만 작성하고 수정, 삭제는 세팅만 해두려고 한다.

printResult의 printSuccesssMessage

    /* DML 작업 메소드 : insert, update, delete 작업 성공 시 해당 성공 메시지 출력용 메소드 */
    public void printSuccessMessage(String message) {
        String successMessage = "";

        switch (message) {
            case "register" :
                successMessage = "신규 메뉴 등록에 성공하였습니다.";
                break;
            case "modify" :
                break;
            case "remove" :
                break;
        }

        System.out.println("successMessage = " + successMessage);
    }

다음은 서비스이다.
서비스의 registerMenu를 보자.

MenuService에 추가

    public boolean registerMenu(MenuDTO menuDTO) {
        SqlSession sqlSession = getSqlSession();

        int result = menuDAO.insertMenu(sqlSession, menuDTO);

        if (result > 0) sqlSession.commit();
        else sqlSession.rollback();

        sqlSession.close();

        return result > 0 ? true : false;
    }

여기서 result를 boolean 값을 통해서 하려고 했으나, 기존 MyBatis의 SqlSession 속에 있는 insert 메소드가 int형으로 반환을 해준다.

그래서 int형으로 받아서 크기에 따라 커지면 등록이 된 것이고, 아니면 안 된 것이기 때문에 삼항 연산자를 통해서 나타내봤다.

MenuDAO에도 insertMenu 메소드를 추가해줬다.

    public int insertMenu(SqlSession sqlSession, MenuDTO menuDTO) {
        return sqlSession.insert("MenuMapper.insertMenu", menuDTO);
    }

menu-mapper.xml 에도 insert문 추가

    <insert id="insertMenu" parameterType="com.jehun.section01.xmlconfig.MenuDTO">
        INSERT
          INTO TBL_MENU
        (
          MENU_NAME
        , MENU_PRICE
        , CATEGORY_CODE
        , ORDERABLE_STATUS
        )
        VALUES
        (
          #{ menuName }
        , #{ menuPrice }
        , #{ categoryCode }
        , 'Y'
        )
    </insert>

이런 식으로 해서 최종 출력까지 하면 등록도 잘 된다.

하지만 auto-increment로 되어 있는 menu_code로 인해 메뉴 번호는 지정하기 위해선 다른 조치가 필요하다. (일단은 생략할 것이다.)


4번. 메뉴 수정 및 삭제

원래는 4번, 5번으로 나눠야 하지만 그냥 같이 알아보려고 한다. 위의 흐름과 크게 다를건 없기 때문이다.

Application case문

                case 4:
                    menuController.modifyMenu(inputModifyMenu());
                    break;
                case 5:
                    System.out.print("삭제하고 싶은 메뉴 코드를 입력하세요 : ");
                    int deleteCode = sc.nextInt();
                    menuController.deleteMenu(deleteCode);
                    break;

메뉴 추가해주는 것처럼 inputModifyMenu() 메소드도 아래와 같이 Application에 추가해줬다.

    private static Map<String, String> inputModifyMenu() {
        Scanner sc = new Scanner(System.in);

        System.out.print("변경할 메뉴의 메뉴 번호를 입력해 주세요 : ");
        String menuCode = sc.nextLine();
        System.out.print("변경할 메뉴의 이름을 입력해 주세요 : ");
        String menuName = sc.nextLine();
        System.out.print("변경할 메뉴의 가격 입력해 주세요 : ");
        String menuPrice = sc.nextLine();

        Map<String, String> parameter = new HashMap<>();
        parameter.put("menuCode", menuCode);
        parameter.put("menuName", menuName);
        parameter.put("menuPrice", menuPrice);

        return parameter;
    }

Controller 에도 modifyMenu, deleteMenu 추가

    public void modifyMenu(Map<String, String> parameter) {
        int menuCode = Integer.parseInt(parameter.get("menuCode"));
        String menuName = parameter.get("menuName");
        int menuPrice = Integer.parseInt(parameter.get("menuPrice"));

        MenuDTO menuDTO = new MenuDTO();
        menuDTO.setMenuCode(menuCode);
        menuDTO.setMenuName(menuName);
        menuDTO.setMenuPrice(menuPrice);

        if (menuService.modifyMenu(menuDTO)) {
            printResult.printSuccessMessage("modify");
        } else {
            printResult.printErrorMessage("메뉴 추가 실패..");
        }
    }

    public void deleteMenu(int deleteCode) {
        if (menuService.deleteMenu(deleteCode)) {
            printResult.printSuccessMessage("remove");
        } else {
            printResult.printErrorMessage("해당 코드로 조회된 메뉴가 없습니다.");
        }
    }

printResult의 성공했을 때의 메세지를 출력해주는 SuccessMessage case 문

            case "modify" :
                successMessage = "메뉴 수정에 성공하였습니다.";
                break;
            case "remove" :
                successMessage = "메뉴 삭제에 성공하였습니다.";
                break;

MenuDAO

    public int modifyMenu(SqlSession sqlSession, MenuDTO menuDTO) {
        return sqlSession.update("MenuMapper.modifyMenu", menuDTO);
    }

    public int deleteMenuByMenuCode(SqlSession sqlSession, int deleteCode) {
        return sqlSession.delete("MenuMapper.deleteMenu", deleteCode);
    }

menu-mapper.xml

    <update id="modifyMenu" parameterType="com.ohgiraffers.section01.xmlconfig.MenuDTO">
        UPDATE
               TBL_MENU
           SET
               MENU_NAME = #{ menuName }
             , MENU_PRICE = #{ menuPrice }
         WHERE MENU_CODE = #{ menuCode }
    </update>

    <delete id="deleteMenu" parameterType="com.ohgiraffers.section01.xmlconfig.MenuDTO">
        DELETE
          FROM TBL_MENU
         WHERE MENU_CODE = #{ menuCode }
    </delete>

Mapper를 DAO + Mapper 역할을 주기

전부 다 잘 되는 것을 알 수 있었다.

이번에는 MenuMapper 라는 인터페이스에 어노테이션을 활용하여 menu-mapper.xml 의 역할과 DAO의 역할을 해주게끔 하는 2번째 예제를 다뤄볼 생각이다.

위의 코드들과 비교하면 DAO를 쓰지 않고, Service에서 MenuMapper를 불러와서 사용하는 것밖에 없다고 보면 된다.

MenuMapper 인터페이스는 DAO + Mapper 의 역할을 할 수도 있다.

/* DAO + Mapper 의 의미*/
public interface MenuMapper {
    @Results(id="menuResultMap", value = {
            @Result(id = true, property = "menuCode", column = "MENU_CODE"),
            @Result(property = "menuName", column = "MENU_NAME"),
            @Result(property = "menuPrice", column = "MENU_PRICE"),
            @Result(property = "categoryCode", column = "CATEGORY_CODE"),
            @Result(property = "orderableStatus", column = "ORDERABLE_STATUS")
    })
    @Select("SELECT\n" +
            "       MENU_CODE\n" +
            "     , MENU_NAME\n" +
            "     , MENU_PRICE\n" +
            "     , CATEGORY_CODE\n" +
            "     , ORDERABLE_STATUS\n" +
            "  FROM TBL_MENU")
    List<MenuDTO> selectAllMenus();

    @Select("SELECT\n" +
            "               MENU_CODE\n" +
            "             , MENU_NAME\n" +
            "             , MENU_PRICE\n" +
            "             , CATEGORY_CODE\n" +
            "             , ORDERABLE_STATUS\n" +
            "          FROM TBL_MENU\n" +
            "         WHERE MENU_CODE = #{menuCode}")
    @ResultMap("menuResultMap")
    MenuDTO selectMenu(int menuCode);

    @Insert("""
            INSERT
                      INTO TBL_MENU
                    (
                      MENU_NAME
                    , MENU_PRICE
                    , CATEGORY_CODE
                    , ORDERABLE_STATUS
                    )
                    VALUES
                    (
                      #{menuName}
                    , #{menuPrice}
                    , #{categoryCode}
                    , 'Y'
                    )
            """)
    int insertMenu(MenuDTO menu);

    @Update("""
            UPDATE
                           TBL_MENU
                       SET MENU_NAME = #{menuName}
                         , MENU_PRICE = #{menuPrice}
                     WHERE MENU_CODE = #{menuCode}
            """)
    int updateMenu(MenuDTO menu);

    @Delete("""
            DELETE
                      FROM TBL_MENU
                     WHERE MENU_CODE = #{menuCode}
            """)
    int deleteMenu(int menuCode);
}

이는 곧 xml에서 다뤘을 때의 쿼리들이 인텔리제이의 DML과 관련있는 어노테이션과 함께 사용한 것이다.

이 인터페이스를 Service에서 사용하는것과 마찬가지다 아래 코드를 같이 보자.


MenuService

public class MenuService {

    private MenuMapper menuMapper;

	// 메뉴 전체 조회
    public List<MenuDTO> findAllMenus() {
        SqlSession sqlSession = getSqlSession();

        menuMapper = sqlSession.getMapper(MenuMapper.class);
        List<MenuDTO> menuList = menuMapper.selectAllMenus();

        sqlSession.close();
        return menuList;
    }

	// 메뉴 단 건 조회
    public MenuDTO findByMenuCode(int menuCode) {
        SqlSession sqlSession = getSqlSession();

        menuMapper = sqlSession.getMapper(MenuMapper.class);
        MenuDTO menuDTO = menuMapper.selectMenu(menuCode);

        sqlSession.close();
        return menuDTO;
    }

	// 신규 메뉴 등록
    public boolean registerMenu(MenuDTO menuDTO) {
        SqlSession sqlSession = getSqlSession();

        menuMapper = sqlSession.getMapper(MenuMapper.class);
        int result = menuMapper.insertMenu(menuDTO);

        if (result > 0) sqlSession.commit();
        else sqlSession.rollback();

        sqlSession.close();

        return result > 0 ? true : false;
    }

	// 메뉴 수정
    public boolean modifyMenu(MenuDTO menuDTO) {
        SqlSession sqlSession = getSqlSession();

        menuMapper = sqlSession.getMapper(MenuMapper.class);
        int result = menuMapper.updateMenu(menuDTO);

        if (result > 0) sqlSession.commit();
        else sqlSession.rollback();

        sqlSession.close();

        return result > 0 ? true : false;
    }

	// 메뉴 삭제
    public boolean deleteMenu(int deleteCode) {
        SqlSession sqlSession = getSqlSession();

        menuMapper = sqlSession.getMapper(MenuMapper.class);
        int result = menuMapper.deleteMenu(deleteCode);

        if (result > 0) sqlSession.commit();
        else sqlSession.rollback();

        sqlSession.close();

        return result > 0 ? true : false;
    }
}

잘 보면 그냥 쿼리만 불러올 뿐 똑같은 코드인거나 마찬가지이다..

인터페이스를 잘 보면 SELECT 를 제외한 DML은 사실 int형을 반환해준다.

그래서 result에서 int형으로 삼항 연산을 통해 잘 되는지 아닌지 확인할 수 있다.


Mapper 인터페이스와 xml 매핑하기

한 가지 방법이 더 있다.

mapper 인터페이스를 DAO의 역할로만 사용하면서

위에서 처럼 xml에는 쿼리를 담아서 mapper 인터페이스의 메소드와 같은 이름의 id로 둔 다음 사용하는 방식이 있다.

Menumapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jehun.section03.remix.MenuMapper">
    <resultMap id="menuResultMap" type="com.jehun.section03.remix.MenuDTO">
        <id property="menuCode" column="MENU_CODE"/>
        <result property="menuName" column="MENU_NAME"/>
        <result property="menuPrice" column="MENU_PRICE"/>
        <result property="categoryCode" column="CATEGORY_CODE"/>
        <result property="orderableStatus" column="ORDERABLE_STATUS"/>
    </resultMap>

    <select id="selectAllMenus" resultMap="menuResultMap">
        SELECT
        MENU_CODE
        , MENU_NAME
        , MENU_PRICE
        , CATEGORY_CODE
        , ORDERABLE_STATUS
        FROM TBL_MENU
    </select>

    (... 중략)

이 xml 파일에 쿼리가 들어가는데 다 같은 내용이라서 중략했다.

그럼 인터페이스는 어떻게 이뤄질까?


MenuMapper 인터페이스

public interface MenuMapper {
    List<MenuDTO> selectAllMenus();

    MenuDTO selectMenu(int menuCode);

    int insertMenu(MenuDTO menu);

    int updateMenu(MenuDTO menu);

    int deleteMenu(int menuCode);
}

잘 보면 xml에 있는 <mapper><select> 태그에 인터페이스에 관해 매핑되어 있고, id 값도 인터페이스의 메소드와 같은 것을 알 수 있다.

그것으로 구분해서 사용할 수도 있다.

profile
백엔드 개발자 꿈나무

0개의 댓글