Posts 학원 #75일차: Chain of Responsibility 패턴, Persistance 프레임워크 MyBatis
Post
Cancel

학원 #75일차: Chain of Responsibility 패턴, Persistance 프레임워크 MyBatis

CommandFilterManager 변경

이전 수업에서는 Chain of Responsibility 패턴을 적용해 CommandFilter, CommandFilterManager, FilterChain을 만들었다. 이번 수업에는 CommandFilter 구현체를 관리하고 실행하는 역할을 하는 CommandFilterManager 를 LinkedList 구조로 바꾸었다.

Chain of Responsibility 패턴

  • 작업 요청을 받은 객체(sender)작업자(receiver)에게 그 책임을 위임하는 구조에서 사용하는 설계 기법이다.
  • 작업자 간에 연결 고리를 구축하여 작업을 나누어 처리할 수 있다.
  • 체인 방식이기 때문에 작업에 참여하는 모든 객체가 서로 알 필요가 없다.
  • 오직 자신과 연결된 다음 작업자만 알면 되기 때문에 객체 간에 결합도를 낮추는 효과가 있다.
  • 보통 필터링을 구현할 때 이 설계 기법을 많이 사용한다.

CommandFilterManager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package com.eomcs.pms.filter;

import java.util.Map;
import com.eomcs.pms.handler.Request;

// 역할:
// - CommandFilter 구현체를 관리하고 실행시킨다.
// -
public class CommandFilterManager {
  Chain firstChain;
  Chain lastChain;

  public void add(CommandFilter filter) {
    Chain chain = new Chain(filter);
    if (lastChain == null) {
      firstChain = lastChain = chain;
      return;
    }
    lastChain.nextChain = chain;
    lastChain = chain;
  }

  public FilterChain getFilterChains() {
    return firstChain;
  }

  // 각각의 필터에게 준비하라고 요청한다.
  public void init(Map<String, Object> context) throws Exception {
    Chain chain = firstChain; // 첫번째 체인을 가져온다.
    while (chain != null) { // 그 체인이 있다면 
      chain.filter.init(context); // 그 체인의 필터에 대해 초기화시킨다.
      chain = chain.nextChain; // 그 체인의 다음 체인을 가져온다.
    } // 더이상 체인이 없다면 나간다.
  }

  //각각의 필터에게 마무리하라고 요청한다.
  public void destroy() {
    Chain chain = firstChain;
    while (chain != null) {
      chain.filter.destroy();
      chain = chain.nextChain;
    }
  }

  private static class Chain implements FilterChain {
    CommandFilter filter;
    Chain nextChain;

    public Chain(CommandFilter filter) {
      this.filter = filter;
    }

    // 커맨드 필터에게 위임 (다음 필터체인을 넘겨줌)
    @Override
    public void doFilter(Request request) throws Exception {
      filter.doFilter(request, nextChain);
    }
  }
}

CommandFilter

1
2
3
4
5
6
7
8
public interface CommandFilter {
  default void init(Map<String,Object> context) throws Exception {
  }
  
  void doFilter(Request request, FilterChain next) throws Exception;
  
  default void destroy(){}
}

App.service()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
    
//........
	  // commandMap 객체를 context 맵에 보관한다.
    // => 필터나 커맨드 객체가 사용할 수 있기 때문이다.
    context.put("commandMap", commandMap);

    // 필터 관리자 준비
    CommandFilterManager filterManager = new CommandFilterManager();

    // 필터를 등록한다.
    filterManager.add(new LogCommandFilter());
    filterManager.add(new AuthCommandFilter());
    FilterManager.add(new DefaultCommandFilter());

    // 필터가 사용할 값을 context 맵에 담는다.
    File logFile = new File("command.log");
    context.put("logFile", logfile);

    // 필터들을 준비시킨다.
    filterManager.init(context);

    // 사용자가 입력한 명령을 처리할 필터 체인을 얻는다.
    FilterChain filterChain = filterManager.getFilterChains();

    loop:
      while (true) {
        String inputStr = Prompt.inputString("명령> ");

        if (inputStr.length() == 0) {
          continue;
        }

        commandStack.push(inputStr);
        commandQueue.offer(inputStr);

        switch (inputStr) {
          case "history": printCommandHistory(commandStack.iterator()); break;
          case "history2": printCommandHistory(commandQueue.iterator()); break;
          case "quit":
          case "exit":
            System.out.println("안녕!");
            break loop;
          default:
            // 커맨드나 필터가 사용할 객체를 준비한다.
            Request request = new Request(inputStr, context);

            // 필터들의 체인을 실행한다.
            if (filterChain != null) {
              filterChain.doFilter(request);
            }
        }
        System.out.println();
      }
    Prompt.close();

    // 필터들을 마무리시킨다.
    filterManager.destroy();

    notifyApplicationContextListenerOnServiceStopped();

Mybatis 퍼시스턴스 프레임워크

bitcamp-java-project-client-41-a

오늘 수업에서는 퍼시스턴스 프레임워크 중 하나인 mybatis 프레임워크의 사용법을 배워보았다. 이번 수업의 목표는 JDBC 코드를 제거하는 것을 목표로 한다. Persistance Framework가 JDBC API를 대신 호출해주기 때문이다. 그러나 JDBC API를 제대로 알지 못하면 안된다.

Persistance Framework

데이터의 저장, 조회, 변경, 삭제를 다루는 클래스 및 설정 파일들의 집합이다. Persistance Framework를 사용하면 JDBC 프로그래밍의 번거로움 없이 간결하게 데이터베이스와 연동할 수 있다. 소스코드에서 SQL문을 분리하여 관리한다.

image

프로젝트의 이전 버전에서는 자바 코드에 JDBC API를 호출하는 코드가 있었다. 반면 Persistance Framework는 JDBC API를 호출하는 자바 코드를 클래스로 만들어 제공한다. 즉 JDBC 프레임워크를 캡슐화한다. 따라서 자바 코드는 이 Persistance Framework를 호출하면 DBMS에 접근하는 것은 Framework가 가 JDBC API를 사용해서 해준다.

image

프레임워크란? 관련 클래스와 설정 파일

Persistance Framework에는 크게 2가지 종류가 있다. SQL Mapper와 OR(Object Relational) Mapper이다. SQL Mapper는 테이블에 대해 질의하지만, OR Mapper는 객체를 질의한다는 점에서 차이가 있다.

SQL Mapper는 각 DBMS에 맞춰서 작성해야 한다. 따라서 DBMS에 종속된다는 단점이 있다. 그러나 그런 만큼 DBMS에 최적화를 시킬 수 있고, 성능을 극한으로 끌어올릴 수 있다는 장점이 있다. 물론 이것은 SQL을 잘 다룬다는 가정 하의 이야기다.

반면 OR 매퍼는 HSQL과 같이 프레임워크 전용 질의문법을 통해 객체를 질의한다. 즉 DBMS에 상관없이 사용할 수 있는 질의문법으로, DBMS에 종속되지 않는다는 장점이 있다. 그러나 DBMS에 최적화를 시킬 수 없고, DBMS의 성능을 극한으로 끌어올릴 수 없다는 단점이 있다.

SQL 매퍼와 OR 매퍼가 기존 코드를 어떻게 변경하는지 살펴보자.

image

SQL 매퍼는 자바 코드와 SQL 코드로 합쳐져 있는 소스코드를 자바 코드와 SQL문으로 분리하여 관리할 수 있게 해준다. 자바 코드는 SQL Mapper + JDBC API를 호출하고, SQL Mapper는 SQL을 사용한다.

image

OR Mapper는 전용 질의어를 DBMS에서 인식할 수 있는 SQL로 변환한다. 개발자는 DBMS를 고려할 필요가 없어 프로그래밍이 편하다. 그러나 사용하는 DBMS의 Adapter가 없다면 실행할 수 없다. 즉 DBMS용 Adapter가 있어야 하기 때문에 코드가 특정 OR Mapper에 종속된다는 문제점이 있다.

ORM이란? ORM은 객체 관계 매핑, 객체와 RDB를 별개로 설계하고 ORM이 중간에서 매핑해주는 역할을 한다. 죽 ORM은 SQL문이 아닌 RDB에 데이터 그 자체와 매핑하기 때문에 SQL을 직접 작성할 필요가 없다. 해당 객체와의 매핑에 필요한 SQL문을 만들어준다. 이로인해 어떤 RDB를 사용하던 상관 없다. SQL문이 아닌 ORM에서 제공하는 메서드, 코드 등을 이용하기에 직관적이다.

실제로는 Hibernate(OR Mapper)보다 MyBatis(SQL Mapper)를 더 많이 사용한다. 레거시 시스템의 데이터를 다루기 쉽게 하기 때문이다.

Mybatis

mybatis는 persistance framework 중 하나로, JDBC 프로그래밍을 캡슐화하여 데이터베이스 연동을 쉽게 하도록 도와준다. 자바 소스 파일에서 SQL을 분리하여 별도의 파일로 관리하기 때문에 자바 소스 코드를 간결하게 유지할 수 있다.

JDBC 프로그래밍을 할 때와 마찬가지로 직접 SQL을 다루기 때문에 레거시(legacy) 시스템에서 사용하는 데이터베이스와 연동할 때 유리하다. SQL을 통해 데이터베이스와 연동한다고 해서 보통 SQL 매퍼(mapper)라 부른다.

캡슐화? 클래스로 정의하는 것을 캡슐화라고 한다. 클래스에서 우리는 무엇을 공개할지, 공개하지 않을 지 접근제어자를 통해서 제어한다. 이렇게 포장해버리고, 우리는 포장된 것을 이용한다.

실습

1단계: 프로젝트에 MyBatis 라이브러리를 추가한다.

  • mybatis 라이브러리 정보를 찾아 의존 라이브러리 블록에 mybatis 라이브러리를 등록한다.
  • gradle을 이용해 eclipse 설정 파일을 갱신한다. -> $ gradle eclipse

2단계: MyBatis 설정 파일을 준비한다.

1
2
3
4
5
6
# jdbc.properties
# key=value for application
jdbc.driver=org.mariadb.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/studydb
jdbc.username=study
jdbc.password=1111

키를 구분하기 쉽기 위해 접두어(jdbc)를 붙였고, 접두어와 이름을 구분하기 위해 .을 붙인다. 그러나 접두어+이름 형태가 규칙은 아니다. 접두어.이름은 하나의 키(문자열)일 뿐이다. 단지 구분하기 쉽게 하기 위해 위와 같은 키를 설정했다.

  • Mybatis 설정 파일: src/main/resources/com/eomcs/pms/conf/mybatis-config.xml
    • DBMS 서버의 접속 정보를 갖고 있는 jdbc.properties 파일의 경로를 등록한다.
    • DBMS 서버 정보를 설정한다.
    • DB 커넥션 풀을 설정한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?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>

  <!-- 설정 파일에서 사용할 .properties 파일 정보 -->
  <properties resource="com/eomcs/pms/conf/jdbc.properties"></properties>

  <!-- DBMS 접속 정보들 -->
  <environments default="development">
  
    <!-- 한 개의 DBMS 접속 정보 -->
    <environment id="development">
    
      <!-- mybatis 가 트랜잭션을 다룰 때 사용할 방법을 지정 => JDBC API 사용 -->
      <transactionManager type="JDBC"/>
      
      <!-- 데이터베이스의 연결 정보 => jdbc.properties 파일에 설정된 key-value -->
      <dataSource type="POOLED">
        <property name="driver" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
      </dataSource>
    </environment>
  </environments>
  
  <!-- SQL 문이 들어 있는 파일들 -->
  <mappers>
    <mapper resource="com/eomcs/pms/mapper/BoardMapper.xml"/>
  </mappers>
</configuration>

컴파일되면 자바 클래스 파일과 설정 파일이 합쳐진다. 단, 일반 파일이기 때문에 .이 아니라 /를 사용하여 경로를 작성한다.

3단계: BoardDaoImplMybatis를 적용한다.

com.eomcs.pms.dao.mariadb.BoardImpl 클래스 변경

  • SQL을 뜯어내어 BoardMapper.xml로 옮긴다.
  • JDBC 코드를 뜯어내고 그 자리에 Mybatis 클래스로 대체한다.

변경 전

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class BoardDaoImpl implements com.eomcs.pms.dao.BoardDao{

  Connection con;

  public BoardDaoImpl(Connection con) {
    this.con = con;
  }

  @Override
  public int insert(Board board) throws Exception {
    try (PreparedStatement stmt = con.prepareStatement(
        "insert into pms_board(title,content,writer) values(?,?,?)")) {

      stmt.setString(1, board.getTitle());
      stmt.setString(2, board.getContent());
      stmt.setInt(3, board.getWriter().getNo());
      return stmt.executeUpdate();
    }
  }
  
	//..
  
  @Override
  public List<Board> findAll() throws Exception {
    try (PreparedStatement stmt = con.prepareStatement(
        "select b.no, b.title, b.cdt, b.vw_cnt, m.no writer_no, m.name"
            + " from pms_board b inner join pms_member m on b.writer=m.no"
            + " order by b.no desc")) {

      try (ResultSet rs = stmt.executeQuery()) {

        ArrayList<Board> list = new ArrayList<>();

        while (rs.next()) {
          Board board = new Board();
          board.setNo(rs.getInt("no"));
          board.setTitle(rs.getString("title"));

          Member member = new Member();
          member.setNo(rs.getInt("writer_no"));
          member.setName(rs.getString("name"));
          board.setWriter(member);

          board.setRegisteredDate(rs.getDate("cdt"));
          board.setViewCount(rs.getInt("vw_cnt"));

          list.add(board);
        }
        return list;
      }
    }
  }
}

변경 후

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class BoardDaoImpl implements com.eomcs.pms.dao.BoardDao{
	//....
  @Override
  public int insert(Board board) throws Exception {
    InputStream inputStream = Resources.getResourceAsStream("com/eomcs/pms/conf/mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputstream);
    // mybatis의 커밋 상태
    // - sqlSession.openSession(): 수동 커밋
    // - sqlSession.openSession(true): 오토 커밋
    try (SqlSession sqlSession = sqlSessionFactory.openSession(true)) {
      
      // => SqlSession 객체에 insert SQL문을 실행하라고 명령한다.
      return sqlSession.insert("Board.insert", board);
    }
  }

  //....
  @Override
  public List<Board> findAll() throws Exception {
    // mybatis 객체 준비
    
    // => mybatis 설정 파일을 읽어 들일 입력 스트림을 준비한다.
    InputStream inputStream = Resources.getResourceAsstream("com/eomcs/pms/conf/mybatis-config.xml");
    
    // => 입력 스트림에서 mybatis 설정 값을 읽어 SqlSessionFactory를 만든다.
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    
    // => SqlSessionFactory에서 SqlSession 객체를 얻는다.
    //    SqlSession 객체는 SQL문을 실행하는 객체다.
    try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
      // => SqlSession 객체에게 별도 파일에 분리한 SQL을 찾아 실행하라고 명령한다.
      return sqlSession.selectList("BoardDao.findAll");
    }
  }
}

SqlSession 객체는 메서드끼리 공유할 수 없다.

BoardMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<?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">
  
<!-- namespace 속성: SQL 문을 가리킬 때 사용할 그룹명이다. -->
<mapper namespace="BoardDao">
  
  <!-- 컬럼의 값을 어떤 자바 객체에 담을 것인지 
       다음과 같이 따로 정의할 수 있다. 
      - type 속성 : 자바 클래스명 
      - id 속성 : 컬럼-프로퍼티의 연결 정보를 가리킬 이름. 
                  SQL 문을 정의할 때 사용.
  -->
  <resultMap type="com.eomcs.pms.domain.Board" id="BoardMap">
    <!-- 자바 코드
         Board b = new Board();
         b.setNo(rs.getInt("no"));
         b.setTitle(rs.getString("title"));
         b.setRegisteredDate(rs.getDate("cdt"));
         b.setViewCount(rs.getInt("vw_cnt"));
    -->
    <id column="no"     property="no"/>
    <result column="title"  property="title"/>
    <result column="cdt"    property="registeredDate"/>
    <result column="vw_cnt" property="viewCount"/>
    
    <!-- 
      Member m = new Member();
      m.setNo(rs.getInt("no"));
      m.setName(rs.getString("name"));
      b.setWriter(m);
    -->
    <association property="writer" javaType="com.eomcs.pms.domain.Member">
      <id column="writer_no"    property="no"/>
      <result column="name"     property="name"/>
    </association>
  </resultMap>


  <!-- select 태그
    - select SQL 문을 정의하는 태그다.
    - id 속성
      - mybatis에서 SQL을 찾을 때 사용할 이름이다.
      - 보통 이 SQL을 사용하는 메서드 이름으로 설정한다.
      - 이렇게 하면 다른 개발자가 이해하기 쉬울 것이다.
    - resultType 속성
      - select 결과물에서 각 레코드 값을 담을 도메인 객체 이름이다.
      - select의 결과가 여러 개라면, 해당 도메인 객체도 여러 개가 생성될 것이다.
      - 이렇게 여러 개 생성된 도메인 객체는 List 컬렉션 객체에 저장된다.
      - 그리고 List 컬렉션 객체가 최종적으로 리턴된다.
   -->
  <select id="findAll" resultMap="BoardMap">
    select 
      b.no, 
      b.title, 
      b.cdt, 
      b.vw_cnt, 
      m.no writer_no, 
      m.name
    from 
      pms_board b 
      inner join pms_member m on b.writer=m.no
    order by 
      b.no desc
  </select>
  
  <insert id="insert" parameterType="com.eomcs.pms.domain.Board">
    insert into pms_board(title,content,writer) 
    values(#{title}, #{content}, #{writer.no})
  </insert>
</mapper>
  • namespace 속성

    • <mapper namespace="BoardDao">
    • SQL 문을 가리킬 때 사용할 그룹명이다.
    • 보통 SQL을 사용하는 객체 이름으로 설정한다.
    • 즉 DAO 인터페이스 또는 클래스명으로 설정한다.
    • 그래야만 유지보수할 때 DAO와 연결된 SQL을 바로 찾을 수 있다.
  • 컬럼의 값을 어떤 자바 객체에 담을 것인지 다음과 같이 따로 정의할 수 있다.

    • <resultMap type="com.eomcs.pms.domain.Board id="BoardMap">
      • type 속성: 자바 클래스 명
      • id 속성: 컬럼-프로퍼티의 연결 정보를 가리킬 이름 SQL문을 정의할 때 사용
    • <association/>: Member라는 클래스의 객체를 만들어서 setNo를 호출해서 값을 담고, setName을 호출해서 값을 담하라. 그리고 Member 클래스를 setWriter를 호출해서 값을 담아라.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    <resultMap type="com.eomcs.pms.domain.Board" id="BoardMap">
      <id column="no" property="no"/>
      <result column="title" property="title"/>
      <result column="cdt" property="registeredDate"/>
      <result column="vw_cnt" property="viewCount"/>
      <association property="writer" javaType="com.eomcs.pms.domain.Member">
        <id column="writer_no" property="no"/>
        <result column="name" property="name"/>
      </association>
    </resultMap>
    
  • <select> SQL문을 정의하는 태그이다.

  • id 속성

    • mybatis에서 SQL을 찾을 때 사용할 이다.
    • 보통 이 SQL을 사용하는 메서드 이름으로 설정한다.
    • 이렇게 하면 다른 개발자가 이해하기 쉬울 것이다.
  • resultType 속성

    • select 결과물에서 각 도메인이다.
    • select의 결과가 여러 개라면, 하나의 도메인 객체도 여러 개가 생성될 것이다.
    • 이렇게 여러 개 생성된 도메인 객체는 List 컬렉션 객체에 저장될 것이다.
    • 그리고 List 컬렉션 객체가 최종적으로 리턴된다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    <select id="findAll" resultMap="BoardMap">
      select 
      	b.no, 
      	b.title, 
      	b.cdt, 
      	b.vw_cnt, 
      	m.no writer_no, 
      	m.name 
      from 
      	pms_board b 
      	inner join pms_member m on b.writer=m.no 
      order by 
      	b.no desc
    </select>
    

mybatis select 실행 과정

image

select 결과를 자바 객체에 저장하기

image

  • 컬럼 이름과 같은 자바 필드에 값을 저장한다.
  • 필드는 프로퍼티가 아니다.
  • get프로퍼티명(), set프로퍼티명()가 둘다 있다면 read/writer 프로퍼티, get프로퍼티()만 있다면 read only 프로퍼티이다.

mybatis는 select 결과를 자바 객체에 저장할 때 우선 프로퍼티를 찾는다. mybatis는 컬럼명이랑 일치하는 프로퍼티(setter)를 찾아서 저장한다. 이때 setter가 없다면 필드를 찾는다. 보통 특별한 경우가 아니면 프로퍼티 이름은 필드명과 같다. 그러나 이것은 setter에서 set을 빼고 뒤의 글자를 소문자화해서 같아진 것이지, 프로퍼티와 필드는 같은 뜻이 아니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<mapper namespace="BoardDao">

<!-- 컬럼의 값을 어떤 자바 객체에 담을 것인지
다음과 같이 따로 정의할 수 있다. 
 - type 속성: 컬럼-프로퍼티 연결 
 - id 속성: 컬럼-프로퍼티의 연결 정보를 가리킬 이름. 
 						SQL문을 정의할 때 사용
 -->

<resultMap type="com.eomcs.pms.domain.Board" id="BoardMap">
	<result column="no" 		property="no"/>
	<result column="title" 	property="title"/>
	<result column="cdt" 		property="registeredDate"/>
	<result column="vw_cnt" property="viewCount"/>
</resultMap>

<!-- select 태그
	-  -->
	<select id="findAll" resultMap="BoardMap">
	select 
	b.no, 
	b.title, 
	b.cdt, 
	b.vw_cdt, 
	m.no writer_no, 
	m.name
	from pms_board b inner join pms_member m on b.writer=m.no
	order by b.no desc
	</select>

	<!-- select 태그 -->
</mapper>

resultMap 속성의 값을 BoardMap으로 하게 되면 이제 select 문 안에서 별명을 지정하지 않아도 된다.

1
2
3
4
5
6
7
<resultMap type="com.eomcs.pms.domain.Board" id="BoardMap">
  <!-- primary 값: id태그 지정-->
	<id column="no" 		property="no"/>
	<result column="title" 	property="title"/>
	<result column="cdt" 		property="registeredDate"/>
	<result column="vw_cnt" property="viewCount"/>
</resultMap>

Primary Key에 대해 id 태그를 지정해주면 같은 select를 했을 때 기존에 보관되었던 select 결과를 리턴한다.

=> 내일 질문: select 결과는 어디에 어떤 형식으로 저장되는가?

OGNL: Object Graph Navigration Language

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Board {
  Member writer;
}

class Member {
  int no;
  String name;
  Address addr;
  
}

class Address {
  String postNo;
  String baseAddr;
}


Address a = new Address();
Member m = new Member();
m.setAddr(a);
Board b = new Board();
b.setWriter(m);

주소 알아내기 => b.getWriter().getAddr().getBaseAddr() 이걸 OGNL 표기법으로 표시하면 b.writer.addr.baseAddr 이렇게 된다. mybatis는 OGNL 표기법을 사용한다.

1
2
3
4
5
<insert id="insert" parameterType="com.eomcs.pms.domain.board">
  insert into pms_board(title,content,writer) 
  values(#{title}, #{content}, #{writer.no})")
</insert>

This post is licensed under CC BY 4.0 by the author.

모두의 네트워크 #4장: 데이터 링크 계층

학원 #76일차: 프로젝트에 MyBatis 적용: MyBatis가 join 결과를 다루는 방법, 프로그램에서 사용하는 객체를 listener에서 준비시키기, sqlSessionFactory 공유,

Loading comments from Disqus ...