2020-11-10 TIL

7 minute read

mybatis

프로젝트에 마이바티스 적용하기

  • 보드매퍼.xml에 sql문을 옮겨넣는다.

오토박싱

public class BoardDaoImpl implements com.eomcs.pms.dao.BoardDao{
.
.
.
  @Override
  public Board findByNo(int no) throws Exception {

    //    김밥공장 sqlSessionFactory =
    //        new 김밥공장건설사().build(설계도);
    //      try (김밥 stmt = 김밥공장.깁밥만들어()) {

    SqlSessionFactory sqlSessionFactory =
        new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("com/eomcs/pms/conf/mybatis-config.xml"));
    try (SqlSession sqlSession = sqlSessionFactory.openSession(true)) {
      //      sqlSession.selectOne("BoardDao.findByNo", Integer.valueOf(no)); 
      Board board = sqlSession.selectOne("BoardDao.findByNo", no);

      sqlSession.update("BoardDao.updateViewCount", no); // no가 오토박싱된다.
    
      return board;

    }
  }
.
.
.
}

앱에서 사용하는 객체를 앱이니리스너에서 준비한다.

  • findByNo를 보드매퍼로 옮긴다.
  <select id="findByNo" 
  parameterType="java.lang.Integer"
  resultType="com.eomcs.pms.domain.Board">
  select
            b.no,
            b.title,
            b.content,
            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
            where b.no = ?
  </select>
  • 보드 타입을 리턴
  • 컬럼 명이 같아야 넣을 수 있다.
    • 컬럼 명과 같도록 뒤에 이름을 알려주거나 맨 위에 보드맵으로 알려준다.
  <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"/>
  • 이 경우, resultType으로 하지말고 resultMap으로 바꿔준다.
  • 프리머티브 타입과 그 매핑 클래스, 스트링 클래스에 대해서는 값을 지정할 때 파라미터 이름을 마음대로 줄 수 있다.
  <select id="findByNo" 
  parameterType="java.lang.Integer"
  resultMap="BoardMap">
  select
            b.no,
            b.title,
            b.content,
            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
            where b.no = #{ohora} // 아무거나 적어도 된다.
            // 하지만 프라퍼티 이름은 의미가 통하도록 준다.
  </select>
  • 업데이트는 리절트 타입이 필요 없다.
  • 뷰카운트는 업데이트 하는 것이다.(인서트나 셀렉트가 아님)
    <update id="updateViewCount" parameterType="java.lang.Integer">
      update pms_board set 
        vw_cnt = vw_cnt + 1
      where no = #{no}
    </update>
    
  • 업데이트는 리절트 타입이 필요 없다.
  • 프라퍼티는 게터, 세터를 호출하는 것이다.
  <update id="update" parameterType="com.eomcs.pms.domain.Board">
    update pms_board set
      title = #{title},
      content = #{content}
     where no = #{no}
  </update>
  • delete
  <delete id="delete" parameterType="java.lang.Integer">
  delete from pms_board
   where no=#{no}
  </delete>
public class BoardDaoImpl implements com.eomcs.pms.dao.BoardDao{
.
.
.
  @Override
  public int delete(int no) throws Exception {
    SqlSessionFactory sqlSessionFactory =
        new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream(
            "com/eomcs/pms/conf/mybatis-config.xml"));

    // 커밋과 관련된 것이기 때문에 공유하면 안된다.
    try (SqlSession sqlSession = sqlSessionFactory.openSession(true)) {

      return sqlSession.delete("BoardDao.delete", no);
    }
  }
  • App에서 사용하는 객체를 준비한다.
  • sqlsessionFactory 객체를 생성하여 context에 담는다.
  • BoardDaoImpl(BoardDao구현체) 객체를 준비한다.
  • Command 구현체 생성 코드도 이동시켜 준비한다.
public class AppInitListener implements ApplicationContextListener {
  @Override
  public void contextInitialized(Map<String,Object> context) {
    System.out.println("프로젝트 관리 시스템(PMS)에 오신 걸 환영합니다!");

    try {
      Connection con = DriverManager.getConnection(
          "jdbc:mysql://localhost:3306/studydb?user=study&password=1111");
      //context.put("con", con); // 앱에서 사용하려고 담아둔 것이기 때문에 필요없다.

      //MyBatis 구현체 생성
      SqlSessionFactory sqlSessionFactory =
          new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream(
              "com/eomcs/pms/conf/mybatis-config.xml"));
      context.put("sqlSessionFactory", sqlSessionFactory);

      //Dao 구현체 생성
      BoardDao boardDao = new BoardDaoImpl(sqlSessionFactory);
      MemberDao memberDao = new MemberDaoImpl(con);
      ProjectDao projectDao = new ProjectDaoImpl(con);
      TaskDao taskDao = new TaskDaoImpl(con);

      //Command 구현체 생성 및 CommandMap 객체 준비
      Map<String,Command> commandMap = new HashMap<>();

      commandMap.put("/board/add", new BoardAddCommand(boardDao, memberDao));
      commandMap.put("/board/list", new BoardListCommand(boardDao));
      commandMap.put("/board/detail", new BoardDetailCommand(boardDao));
      commandMap.put("/board/update", new BoardUpdateCommand(boardDao));
      commandMap.put("/board/delete", new BoardDeleteCommand(boardDao));

      commandMap.put("/member/add", new MemberAddCommand(memberDao));
      commandMap.put("/member/list", new MemberListCommand(memberDao));
      commandMap.put("/member/detail", new MemberDetailCommand(memberDao));
      commandMap.put("/member/update", new MemberUpdateCommand(memberDao));
      commandMap.put("/member/delete", new MemberDeleteCommand(memberDao));

      commandMap.put("/project/add", new ProjectAddCommand(projectDao, memberDao));
      commandMap.put("/project/list", new ProjectListCommand(projectDao));
      commandMap.put("/project/detail", new ProjectDetailCommand(projectDao));
      commandMap.put("/project/update", new ProjectUpdateCommand(projectDao, memberDao));
      commandMap.put("/project/delete", new ProjectDeleteCommand(projectDao));

      commandMap.put("/task/add", new TaskAddCommand(taskDao, projectDao, memberDao));
      commandMap.put("/task/list", new TaskListCommand(taskDao));
      commandMap.put("/task/detail", new TaskDetailCommand(taskDao));
      commandMap.put("/task/update", new TaskUpdateCommand(taskDao, projectDao, memberDao));
      commandMap.put("/task/delete", new TaskDeleteCommand(taskDao));

      commandMap.put("/hello", new HelloCommand());
      commandMap.put("/login", new LoginCommand(memberDao));
      commandMap.put("/whoami", new WhoamiCommand());
      commandMap.put("/logout", new LogoutCommand());

      context.put("Commandmap", commandMap);

    } catch (Exception e) {
      System.out.println("시스템이 사용할 객체를 준비하는 중에 오류 발생");
      e.printStackTrace();
    }
  }
  • BoardDaoImpl 수정
// 마이바티스 적용
// sqlSessionFactory를 자체적으로 생성하지 않고
// 생성자를 통해 외부에서 주입 받는다(DI)
public class BoardDaoImpl implements com.eomcs.pms.dao.BoardDao{

  //  Connection con;

  SqlSessionFactory   sqlSessionFactory;

  public BoardDaoImpl(SqlSessionFactory   sqlSessionFactory) {
    this.sqlSessionFactory = sqlSessionFactory;
  }
    @Override
  public int insert(Board board) throws Exception {

    // 커밋과 관련된 것이기 때문에 공유하면 안된다.
    try (SqlSession sqlSession = sqlSessionFactory.openSession(true)) {

      return sqlSession.insert("BoardDao.insert", board);
    }
  }
  • App의 service()에서 필요없는 것들을 삭제한다.
public void service() throws Exception {

    notifyApplicationContextListenerOnServiceStarted();

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

    // 필터를 등록한다.
    filterManager.add(new LogCommandFilter(new File("command.log")));
    //filterManager.add(new AuthCommandFilter());
    filterManager.add(new DefaultCommandFilter());

    filterManager.init(context);

멤버에도 마이바티스를 적용한다.

  • MemberMapper.xml 을 생성한다.
<?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="MemberDao">

  <resultMap type="com.eomcs.pms.domain.Member" id="MemberMap">
    <!-- 자바 코드
          Member member = new Member();
          member.setNo(rs.getInt("no"));
          member.setName(rs.getString("name"));
          member.setEmail(rs.getString("email"));
          member.setPhoto(rs.getString("photo"));
          member.setTel(rs.getString("tel"));
          member.setRegisteredDate(rs.getDate("cdt"));
    -->
    <id column="no"           property="no"/>
    <result column="name"     property="name"/>
    <result column="email"    property="email"/>
    <result column="photo"    property="photo"/>
    <result column="tel"      property="tel"/>
    <result column="cdt"      property="registeredDate"/>
    </resultMap>

  <select id="findAll" resultMap="MemberMap">
    select
     no,
     name,
     email,
     tel,
     cdt
   from
     pms_member
    order by
     no desc
  </select>
  
  <insert id="insert" parameterType="com.eomcs.pms.domain.Member">
   insert into pms_member(name,email,password,photo,tel)
   values(#{name}, #{email}, #{password}, #{photo}, #{tel})
  </insert>
  
  <select id="findByNo" 
    parameterType="java.lang.Integer" 
    resultMap="MemberMap">
     select
      no,
      name,
      email,
      photo,
      tel,
      cdt
     from 
     pms_member
      where 
      no =#{no}
  </select>
  
  <select id="findByName" 
  parameterType="java.lang.String" 
  resultMap="MemberMap">
   select
    no,
    name,
    email,
    photo,
    tel,
    cdt
   from 
   pms_member
    where 
    name =#{name}
  </select>

  <update id="update" parameterType="com.eomcs.pms.domain.Board">
    update pms_member set
     name =#{name},
     email =#{email},
     password =password(#{password}),
     photo =#{photo},
     tel =#{tel}
    where no =#{no}
  </update>
  
  <delete id="delete" parameterType="java.lang.Integer">
    delete from pms_member 
    where no=#{no}
  </delete>
  
  <select id="findByProjectNo" parameterType="java.lang.Integer" resultMap="MemberMap">
  select 
    m.no, 
    m.name
  from 
  pms_member_project mp inner join pms_member m on
   mp.member_no=m.no
    where 
    mp.project_no=#{No}
  order by 
  m.name asc
  </select>
  
  <select id="findByEmailPassword" parameterType="java.lang.String" resultMap="MemberMap">
  select
    no,
    name,
    email,
    photo,
    tel,
    cdt
  from pms_member
  where 
  email =#{email}
  and password = password(#{password})
  </select>
  
</mapper>
  • mybatis-config 에서 SQL 문이 들어있는 파일을 추가한다. ```java

</configuration>


- MemberDaoImpl 를 수정한다.

```java
package com.eomcs.pms.dao.mariadb;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import com.eomcs.pms.domain.Member;

public class MemberDaoImpl implements com.eomcs.pms.dao.MemberDao {

  SqlSessionFactory sqlSessionFactory;

  public MemberDaoImpl(SqlSessionFactory sqlSessionFactory) {
    this.sqlSessionFactory = sqlSessionFactory;
  }

  @Override
  public int insert(Member member) throws Exception {
    try (SqlSession sqlSession = sqlSessionFactory.openSession(true)) {
      return sqlSession.insert("MemberDao.insert", member);
    }
  }

  @Override
  public int delete(int no) throws Exception {
    try (SqlSession sqlSession = sqlSessionFactory.openSession(true)) {
      return sqlSession.delete("MemberDao.delete", no);
    }
  }

  @Override
  public Member findByNo(int no) throws Exception {
    try (SqlSession sqlSession = sqlSessionFactory.openSession(true)) {
      return (Member) sqlSession.selectOne("MemberDao.findByNo", no);
    }
  }

  @Override
  public Member findByName(String name) throws Exception {
    try (SqlSession sqlSession = sqlSessionFactory.openSession(true)) {
      // select의 결과가 하나가 아닐 경우에는 List를 쓴다.
      List<Member> members = sqlSession.selectList("MemberDao.findByName", name);
      if (members.size() > 0) {
        return members.get(0);
      } else {
        return null;
      }
    }
  }

  @Override
  public List<Member> findAll() throws Exception {
    try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
      // select의 결과가 하나가 아닐 경우에는 List를 쓴다.
      return sqlSession.selectList("MemberDao.findAll");
    }
  }

  @Override
  public int update(Member member) throws Exception {
    try (SqlSession sqlSession = sqlSessionFactory.openSession(true)) {
      return sqlSession.update("MemberDao.update", member);
    }
  }

  @Override
  public List<Member> findByProjectNo(int projectNo) throws Exception {
    try (SqlSession sqlSession = sqlSessionFactory.openSession(true)) {
      return sqlSession.selectOne("MemberDao.findByProjectNo", projectNo);
    }
  }

  @Override
  public Member findByEmailPassword(String email, String password) throws Exception {
    Map<String,String> map = new HashMap<>();
    map.put("email", email);
    map.put("password", password);
    try (SqlSession sqlSession = sqlSessionFactory.openSession(true)) {
      // 파라미터를 하나만 줄 수 있기 때문에 map에 여러개의 값을 담아서 map을 넘긴다.
      return sqlSession.selectOne("MemberDao.findByEmailPassword", map);
    }
  }
}
  • AppInitListener 에서 Dao 구현체를 수정한다.
      MemberDao memberDao = new MemberDaoImpl(sqlSessionFactory);

프로젝트에 마이바티스 적용

  • proejctVO = project value object (도메인 객체)

  • findAll

    // 위에 나온 조인 결과가 left에 존재
    // 기준이 되는 데이터는 반드시 출력한다.
    left outer pms_member_project mp on p.no=mp.project_no    order by p.no desc </select>
    
  • 프로젝트 맵퍼

// 멤버 객체를 setOwner 하라는 것

<result column="owner_no"     property="no"/>
<result column="owner_name"   property="name"/>
</association>

// 멤버 객체를 어레이리스트에 담아서 그 리스트를 members에 담으라는 것

  • 프로젝트 오너, 프로젝트 리스트의 멤버 하나하나마다 객체가 된다.
  • 메모리 낭비가 심하다.
  • 같은 이름이어도 해시가 다 다르다.

  • 매퍼에서 설정한다.
    • 일반 컬럼은 result로 지정했을 때

    프로젝트 관리 시스템(PMS)에 오신 걸 환영합니다! 로그 파일을 열었습니다. 명령> /project/list [프로젝트 목록] 번호, 프로젝트명, 시작일 ~ 종료일, 관리자, 팀원 11, test101, 2020-01-01 ~ 2020-02-02, x1(70807318), [] 10, test100, 2020-01-01 ~ 2020-02-02, x1(910091170), [] 9, test2, 2020-01-01 ~ 2020-02-02, x1(1183888521), [] 8, test1, 2020-01-01 ~ 2020-02-02, x1(2041605291), [] 7, p4, 2020-04-04 ~ 2020-05-05, f(1008925772), [c(1052245076),e(2136288211)] 6, p3, 2020-03-03 ~ 2020-04-04, c(293907205), [d(1175259735),e(1205406622)] 4, p1p1, 2020-01-02 ~ 2020-01-03, a(1794717576), [b(988800485),c(345902941),d(454325163),e(796667727)]

  • 실무에 가면 primary key 값은 id 를 준다.
    • 객체를 중복시키기 않기 위해서
      • 메모리 낭비가 덜하다.
    • mapper 에서 id를 사용하면 id 에서 같은 값을 가지는 객체들은 모두 하나로 묶여진다.
      • 만약 title 을 id로 지정할 경우 타이틀이 같은 프로젝트는 전부 하나의 객체로 간주된다.(오너나 멤버가 다르더라도)
      • 오너나 멤버가 다른 같은 타이틀의 프로젝트가 있다면 나머지 프로젝트는 버려진다.
    • 이러한 일이 발생하지 않도록 id는 같은 값이 없는 no로 지정해야 한다.
  • 조인한 테이블의 프라이머리 키 값은 반드시 셀렉트해라
  • 그리고 그 프라이머리 키는 id로 지정하라

  • insert

    insert into pms_project(title,content,sdt,edt,owner) values(#{title},#{content},#{startDate},#{endDate},#{owner.no})) // getOwner.getNo

    <insert id=”insert” parameterType=”com.eomcs.pms.domain.Project” // 파라미터로 받은 프로젝트 객체에 setNo를 호출해서 insert 한 후에 그 no 값을 넣어줘 // //insert 후의 no //setNo useGeneratedKeys=”true” keyColumn=”no” keyProperty=”no”> insert into pms_project(title,content,sdt,edt,owner) values(#{title},#{content},#{startDate},#{endDate},#{owner.no})) </insert>

    • 프로젝트를 삭제할 때 task가 키로 되어 있어서 삭제하려고 하면 오류가 발생한다.
      • 이 경우, 비활성화 컬럼을 하나 추가해서 비활성화된 사람은 팀 목록에서 출력되지 않게 한다.
      • 하지만 그 회원이 했던 task는 그대로 남아있게 된다.

마이그레이션

  • 기존의 것에 새 구조, 새 기술을 적용하는 것

Categories:

Updated: