BackEnd/WEB

mybatis_국비Day73

Leo.K 2022. 6. 15. 17:00

지난 시간에는 JNDI를 사용한 mapping방법을 정리했다. 이 경우에는 DBCP에 대한 정보를 가지고 있는 JNDI방식이 context.xml파일에 정의된 내용을 기반으로 찾는 방식이므로 context.xml파일이 반드시 필요하다고 말을 했는데, 이 xml파일 없이도 mapping이 가능하다는 점을 오늘 배웠다. 후에 스프링을 배울때는 JNDI기법도 사용하긴 하지만 대부분 필자가 앞으로 사용하고자 하는 방식을 사용한다고 한다. 

기본적으로 JNDI는 톰캣이 구동되면서 context.xml파일을 파싱하여 해당 환경 설정을 셋팅해주는 것인데 오늘 배운 POOLED방식을 사용하면 DBCP에 대한 정보를 입력해줌으로써 Mybatis가 직접 처리하도록 위임시키는 방법이다.

공유하는 소스코드에는 gogek.xml로 되어 있을텐데, 이전 시간에 테스트 해본 사원테이블이 아니라 고객테이블로 테스트를 진행해본 결과이다. 테이블과 그에 맞는 컬럼이 다름으로 인해 Vo만 다를 뿐 소스코드의 로직은 동일하기 때문에 환경설정에 대한 코드만 첨부하도록 하겠다.

[ config.mybatis.sqlMapConfig_POOL ]

더보기
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
<?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 resource="config/mybatis/db.properties">
          <property name="username" value="test"/>
          <property name="password" value="test"/>
    </properties>
 
    <environments default="">
        <environment id="">        <!-- DB에 연결하는 작업 -->
            <transactionManager type="JDBC" />
            
            <dataSource type="POOLED">
                <property name="driver"   value="${driver}" />        
                <property name="url"      value="${url}" />        
                <property name="username" value="${username}" />        
                <property name="password" value="${password}" />        
            </dataSource>
 
        </environment>
    </environments>
    
    <!-- Mapping하는 작업 -->
    <mappers>
        <mapper resource="config/mybatis/mapper/gogek.xml" />
    </mappers>
</configuration>
cs

[ config.mybatis.db.properties ] - 확장자가 properties인 파일이다.

더보기
1
2
driver=oracle.jdbc.driver.OracleDriver
url=jdbc:oracle:thin:@localhost:1521:xe
cs

 

[ Mybatis에서 파라미터 사용하기 ]

1. 파라미터(자료형 지정)에 의한 결과 추출.

형식 : #{파라미터 이름}은 임의로 지정해도 된다. 파라미터의 정보에서 자료형은 parameterType으로 지정해주고, 변수명(파라미터명)은 변수명 명명규칙을 위반하지 않는 경우에 대해서 아무거나 사용해도 된다. 그래서 변수명이 헷갈리는 것을 방지하지 위해 컬럼명과 동일하게 사용하는 것을 권장한다.

1
2
3
<select id="SawonDeptno" parameterType="int" resultType="vo.SawonVo">
   SELECT * FROM sawon where deptno=#{deptno}
</select>
cs

 

2. 파라미터(클래스형 지정)에 의한 결과 추출

형식 : #{파라미터이름}, 1번 예시와는 다르게, 파라미터가 클래스 형인 경우, 즉 변수가 2개 이상인 경우에는 인자명을 아무렇게나 사용하면 안되고, 반드시 위에서 필자가 권장한 방식대로 클래스 멤버의 변수명과 동일시 해야 한다. 이렇게 하면 해당 클래스의 Getter메서드가 호출된다. 그러므로 당연히 클래스에는 Getter메서드가 정의되어 있어야 한다. 

1
2
3
<select id="SawonDeptnoName" parameterType="vo.Sawon"  resultType="vo.Sawon">
    SELECT * FROM sawon where deptno=#{deptno} and saname like #{saname} || '%'
</select>
cs

 

3. 파라미터(java.util.map 지정)에 의한 결과 추출

형식 : #{파라미터이름} => 여기서 #{인자명}은 Map의 key의 이름과 동일해야 한다. 방식은 2번과 동일하다. 자료구조만 map이 된 것이다. 반드시 맵의 키 이름과 인자명을 동일시 해야한다. 그렇게 하면 map.get(key)가 호출되어 value값을 가져오게 된다. 아래와 같은 데이터가 실제 map에 있다고 가정하고 생각해보면 이해가 쉬울 것이다.

Map map = new HashMap();

map.put("deptno", 10);

map.put("saname", "김");  

1
2
3
<select id="SawonDeptnoNameMap" parameterType="java.util.Map"  resultType="vo.Sawon">
    SELECT * FROM sawon where deptno=#{deptno} and saname like '%' || #{saname} || '%'
</select>
cs

 

[ Q1. 사원테이블에서 입사년도별 사원 조회 ] - 파라미터로 map을 전송해보자.

데이터를 전송하기 전에 정규식을 통해 입력값이 1900~2000년대의 값만 넘어가도록 유효성 검사를 했으므로, 서블릿에서는 null값이 들어올 수가 없다. 따라서 방어코딩 없이 바로 파라미터를 수신하여 그 값을 map에 포장을해서 dao의 정의해놓은 메소드를 호출하면 된다. mybatis자체적으로 selectList()메소드가 오버로드 되어있는데, 필자는 애써 map을 포장해두고, 함수의 인자로 map을 넣어주지 않아서 계속 오류를 잡는데 애먹었다,, 주의하도록 하자.

고려사항

  • dao에서 파라미터로 포장한 map을 전달했는지
  • dao에서 namespace.mapper_id를 인자로 넘기는 부분에서 오타나 일치하지 않은 이름을 적진 않았는지
  • sawon.xml파일에서 parameterType으로 java.util.Map을 지정했는지
  • SQL문이 잘못되지는 않았는지
  • sqlMapConfig.xml에서 mappers태그에 설정이 잘 되어 있는지

[ sawon_sahire_year_input.html ]

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
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<!-- BootStrap 3.x -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
 
<style type="text/css">
    #disp{
        width: 1000px;
    }
</style>
 
 
<script type="text/javascript">
    var regex = /^(1[9][0-9]{2}|2[0][0-9]{2})$/;
    function find(){
        
        var start = $("#start").val().trim();
        var end   = $("#end").val().trim();
        
        
        if(start=='' || regex.test(start) == false){
            alert('연도형식으로 4자리 숫자만 입력하세요.');
            $("#start").val('');
            $("#start").focus();
            return;
        }
        
        if(end=='' || regex.test(end) == false){
            alert('연도형식으로 4자리 숫자만 입력하세요.');
            $("#end").val('');
            $("#end").focus();
            return;
        }
        
        //Ajax요청
        $.ajax({    
            url        : 'sawon/list_sahire.do',                //SawonSahireListAction
            data       : {'start':start, 'end':end},
            success    : function(res_data){
                $("#disp").html(res_data);
            },
            error    : function(err){
                alert(err.responseText);
            }
        });
    }
</script>
</head>
<body>
<hr>
    연도구간 : <input id="start" type="text"   placeholder="시작년도"> ~
               <input id="end"   type="text"   placeholder="끝 년도">
               <input type="button" value="조회" onclick="find();">
<hr>
    
<div id="disp"></div>           
</body>
</html>
cs

[ SawonSahireListAction ]

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
package action;
 
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import dao.SawonDao;
import vo.SawonVo;
 
/**
 * Servlet implementation class SawonListAction
 */
@WebServlet("/sawon/list_sahire.do")
public class SawonSahireListAction extends HttpServlet {
    private static final long serialVersionUID = 1L;
 
    /**
     * @see HttpServlet#service(HttpServletRequest request, HttpServletResponse response)
     */
    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // TODO Auto-generated method stub
        // 수신하는 url의 종류 
        // /sawon/list_sahire.do?start=1988&end=1993
        
        
        int    start = Integer.parseInt(request.getParameter("start"));
        int    end   = Integer.parseInt(request.getParameter("end"));
        
        
        
        Map<String, Integer> map = new HashMap<String, Integer>();
        map.put("start", start);
        map.put("end", end);
        
        List<SawonVo> list = SawonDao.getInstance().selectListSahireYear(map);
        
        request.setAttribute("list", list);
 
        //forward
        String forward_page = "sawon_list.jsp";
        RequestDispatcher disp = request.getRequestDispatcher(forward_page);
        disp.forward(request, response);
 
    }
 
}
 
cs

 

[ TypeAliases 지정하는 방법 ]

실무에서는 필자가 정리하던 실습과는 다르게 폴더(package)의 구조가 더 명확히 세분화되어 구분되어 있다고 한다. 예를 들어 필자가 지금껏 설정했던 resultType은 vo패키지에 있는 SawonVo였기 때문에 vo.SawonVo로 작성할 수 있었다.         하지만 만일 Myapp패키지 안에 com패키지 안에 dir패키지 아래 vo패키지 아래 실제 Vo파일이 있었다고 해보자.(필자가 아무렇게 지어낸 구조이다.) 그렇다면 CRUD명령을 수행하고 값을 반환받을 타입을 지정하기 위해서 다음과 같은 형식으로 매번 작성해야 한다. -> resultType = Myapp.com.dir.vo.SawonVo

매번 위처럼 작성해야 하는 번거로움을 줄이기 위해서 type속성에 별칭을 설정할 수 있는 typeAliases 태그가 존재한다. 이 태그 내부에 별칭을 지정하면 이후에는 resultType에는 개발자가 지정한 별칭을 적용할 수 있다. 실 사용 예시를 보자.

1
2
3
4
5
6
7
8
9
<!-- 이 태그는 sqlMapConfig.xml에 작성합니다. -->
<typeAliases>
    <typeAlias type="vo.SawonVo" alias="sawon"/>
</typeAliases>
 
<!-- 이 태그는 sawon.xml에 작성합니다. -->
<select id="sawon_list_sahire_year" parameterType="java.util.Map" resultType="sawon">
    select * from sawon where to_number(to_char(sahire, 'YYYY')) between #{start} and #{end}
</select>
cs

 

[ 기존 작업한 파일의 DB작업을 Mybatis를 사용하여 변경하기 ]

  1. 프로젝트에 mybatis 라이브러리를 추가한다. jar파일
  2. 환경 설정 파일을 추가한다. mappers설정에 오류가 없는지 확인한다.
  3. 함수를 호출하는 dao에서 코드를 수정하다.

 

[ MyBatis -> insert, delete, update ]

  • CRUD 명령 중에서 select를 제외한 나머지 3개의 명령어는 DML인데, 오직 이 DML명령어에 대해서만 트랜잭션 로그가 생성된다. 
  • 트랜잭션은 커넥션에서 관리한다. 따라서 커넥션을 반납하기(sqlSession.close()) 전에 트랜잭션 로그에 있는 데이터를 처리해야만 실제 DB에 저장된다. 
  • mybatis는 기본적으로 트랜잭션이 걸려있어서, auto-commit을 설정하지 않으면 트랜잭션 로그에 일시적으로 저장되고, 실제 db에 저장되지 않는다. 이 트랜잭션 로그에서 commit을 하면 실제 DB에 적용이 되고, rollback을 하면 명령을 취소할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public int insert(VisitVo vo) {//버튼을 누름으로써 자바에게 전달받은 vo
    // TODO Auto-generated method stub
    int res = 0;
    
    
    SqlSession    sqlSession = factory.openSession();
    
    res = sqlSession.insert("visit.visit_insert", vo);
    
    //Transaction 적용
    sqlSession.commit();
    
    sqlSession.close();
    
    
    
    return res;
}
cs

 

CRUD명령을 Mybatis로 수정하기 

[ VisitDao ]

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package dao;
 
import java.util.List;
 
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
 
import service.MyBatisConnector;
import vo.VisitVo;
 
 
public class VisitDao {
 
    SqlSessionFactory factory;    
    
    //single-ton : 객체 1개만 생성해서 사용하자
    static VisitDao single = null;
 
    public static VisitDao getInstance() {
 
        //객체가 생성되어 있지 않으면 만들어라.
        if (single == null) {
            single = new VisitDao();
        }
        //이전에 만들어 놨던 객체를 그대로 반환한다.
        return single;
    }
 
    //외부에서 생성하지 못하도록 접근제한. 객체는 getInstance메소드를 통해서만 생성가능.
    private VisitDao() {
        // TODO Auto-generated constructor stub
        factory = MyBatisConnector.getInstance().getSqlSessionFactory();
    }
    
    
    //방명록 조회
    public List<VisitVo> selectList() {
 
        List<VisitVo> list = null;
        
        SqlSession    sqlSession = factory.openSession();
                
        list = sqlSession.selectList("visit.visit_list");
        
        sqlSession.close();
 
        return list;
    }
    
    
    public int insert(VisitVo vo) {//버튼을 누름으로써 자바에게 전달받은 vo
        // TODO Auto-generated method stub
        int res = 0;
        
        //Transaction 적용방법(2): openSession(boolean) :  <- Auto Commit을 하도록 설정 
        SqlSession    sqlSession = factory.openSession(true);
        
        res = sqlSession.insert("visit.visit_insert", vo);
        
        //Transaction 적용방법(1)
        //sqlSession.commit();
        
        sqlSession.close();
        
        
        
        return res;
    }
    
    public int delete(int idx) {//버튼을 누름으로써 자바에게 전달받은 vo
        // TODO Auto-generated method stub
        int res = 0;
        
        //1. 생성
        SqlSession sqlSession = factory.openSession(true);
        
        //2. 작업
        res = sqlSession.delete("visit_delete", idx);
        
        //3. 반납
        sqlSession.close();
        
        
        return res;
    }
    
    //idx에 대한 객체 1건 구하기
    public VisitVo selectOne(int idx) {
 
        VisitVo vo = null;
        
        SqlSession sqlSession = factory.openSession();
        
        vo = sqlSession.selectOne("visit.visit_one", idx);
        
        sqlSession.close();
 
        return vo;
    }
    
    public int update(VisitVo vo) {//버튼을 누름으로써 자바에게 전달받은 vo
        // TODO Auto-generated method stub
        int res = 0;
        
        //1. 생성
        SqlSession sqlSession = factory.openSession();
        
        //2. 작업
        res = sqlSession.update("visit_update", vo);
        sqlSession.commit();
        
        //3. 반납
        sqlSession.close();
        
        return res;
    }
}
 
cs

[ visit.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
<?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="visit">
 
    <!-- 전체조회 -->
    <select id="visit_list" resultType="vo.VisitVo">
        select * from visit order by idx desc
    </select>
    
    <!-- idx에 해당되는 1건 데이터 조회 -->
    <select id="visit_one" parameterType="int" resultType="visit">
        select * from visit where idx=#{idx}
    </select>
    
    
    <!-- insert, update, delete는 resultType이 필요없다. 리턴 값이 결과 처리행 int로 정해져 있다. -->
    <!-- insert 추가 -->
    <insert id="visit_insert" parameterType="vo.VisitVo">
        insert into visit values(seq_visit_idx.nextVal, #{name}, #{content}, #{pwd}, #{ip}, sysdate)
    </insert>
    
    <!-- delete 삭제 -->
    <delete id="visit_delete" parameterType="int">
        delete from visit where idx = #{idx}
    </delete>
    
    
    <!-- update 수정 -->
    <update id="visit_update" parameterType="visit">
        update visit set name = #{name}, content = #{content}, pwd = #{pwd} , ip = #{ip}, regdate = sysdate where idx=#{idx}
    </update>
</mapper>
cs

'BackEnd > WEB' 카테고리의 다른 글

MyBatis_SubQuery_국비Day75  (0) 2022.06.17
mybatis_검색기능_국비Day74  (0) 2022.06.16
ORM_DB프레임워크_국비_DAY72  (0) 2022.06.14
XML_Naver검색API_국비DAY71  (0) 2022.06.13
XML_국비_DAY70  (0) 2022.06.10