MyBatis) Insert/Update 후 PK 값 return : useGeneratedKeys 속성 / SelectKey 태그

이지우·2022년 11월 3일
0

https://hyunalee.tistory.com/65
https://taetaetae.github.io/2017/04/04/mybatis-use-generated-keys/
https://sowon-dev.github.io/2021/07/26/210727MyBatis-keyProperty/
https://m.blog.naver.com/l1523/221706040458

  • useGeneratedKeys 속성
    mapper insert 클래스 호출 시 MyBatis의 userGenerateKeys="true"를 통해 keyColumn 속성값과 같은 컬럼명의 값이 keyProperty의 속성값과 같은 VO 객체의 변수에 자동으로 담기게 됨
    -> getter 이용
    / , 이용해 하나 이상의 값을 가져올 수 있음

    * Mapper.xml
    <insert id="insert" useGeneratedKeys="true" keyColumn="replyCd" keyProperty="replyCd">
    
        INSERT INTO tbl_reply (replyCd, commentCd, movieCd, userid, reply)
        VALUES (seq_reply.nextval, #{commentCd}, #{movieCd}, #{userid}, #{reply})
    
    </insert>
    * ServiceImpl
    @Transactional 
    @Override
    public Long register(ReplyVO vo) {
    
        log.info("register...: " + vo); // > register...: ReplyVO(replyCd=null, commentCd=489, movieCd=20000006, userid=mem8, reply=ㅇㄴ, replyDate=null, updateDate=null)
    
        // 댓글 개수 업데이트
        commentMapper.updateReplyCnt(vo.getCommentCd(), 1);
    
        mapper.insert(vo);
    
        log.info(vo); // > - ReplyVO(replyCd=291, commentCd=489, movieCd=20000006, userid=mem8, reply=ㅇㄴ, replyDate=null, updateDate=null)
        log.info(vo.getReplyCd()); // > 291
    
        return vo.getReplyCd();
    }

  • SelectKey 태그
    : 직접 Select 쿼리를 이전/이후에 출력 가능
    -> 똑같이 getter 이용해 반환 가능
    이때 오직 Select절만, 하나의 컬럼만 가능. 두 개 이상은 vo 등의 객체를 resultType으로 지정할 것

    • order="AFTER"

      <!-- useGeneratedKeys="true" keyColumn="contents" keyProperty="contents" -->
          <update id="update" >
      
      <!-- 		keyProperty="contents" resultType="java.lang.String" -->
              <selectKey keyProperty="commentDate,contents" resultType="com.moviepedia.domain.CommentVO"  order="AFTER">
                  SELECT contents, commentDate FROM tbl_comment WHERE commentCd = #{commentCd}
              </selectKey>
      
              UPDATE tbl_comment SET contents = #{contents}, updateDate = SYSDATE
              WHERE commentCd = #{commentCd}
      
          </update>
      @Override
          public String modify(CommentVO vo) {
      
              log.info("modify.... : " + vo);
      
              mapper.update(vo);
              String modifiedContents = vo.getContents();
              log.info("modifiedContents: " + modifiedContents);
              log.info(vo.getCommentDate());
      
              return modifiedContents;
          }
    • 중요) order="AFTER" 지정한 경우,
      mapper.xml에서 insert/update 구문에서 지정한 ParameterType으로 값이 반환됨
      -> 그러므로 VO, DTO를 사용한 경우, 해당 객체에 반환되는 값이 들어갈 변수가 객체에 있어야함

    • order="BEFORE"

      	public int updateActorRepMovieList(PeopleVO peopleVO);
      //	public int updateActorRepMovieList(@Param("peopleCd")  String peopleCd);
      		// 객체가 아닌 String peopleCd 하나만 파라미터로 지정하면 keyProperty 변수 setter 찾을수없다는 에러 발생 -> @Param 지정 시 정상 실행
      <update id="updateActorRepMovieList">
      
              <selectKey keyProperty="repMovieList" resultType="java.lang.String" order="BEFORE">
      <!-- 	<selectKey keyProperty="repMovieList" resultType="com.moviepedia.domain.PeopleVO" order="BEFORE"> -->
      <!-- 	<selectKey keyProperty="repMovieList,repMovieListDate" resultType="hashmap" order="BEFORE"> -->
              <!-- resultType 모두 가능 -->
      
              SELECT LISTAGG(ft.movieCd ||'|'|| ft.movieNm, ',') WITHIN GROUP (ORDER BY ft.movieCd) AS repMovieList
      <!-- 			, SYSDATE AS repMovieListDate -->
              FROM (
                  SELECT ROWNUM, iv4.* 
                  FROM (
                      SELECT iv3.peopleNm, iv3.updateDate, iv3.movieCd, iv3.movieNm
                          ,iv3.commaCnt - REGEXP_COUNT(iv3.actorDetail, ',', iv3.peopleIdx) +1 AS actorOrder
                          ,iv3.cntStarRating, iv3.avgStarRating
                          ,iv3.actorDetail
                      FROM (
                          SELECT iv2.peopleNm, iv2.updateDate
                              , m.movieCd, m.movieNm, m.actorDetail
                              , REGEXP_INSTR(m.actorDetail, iv2.peopleNm) AS peopleIdx
                              , REGEXP_COUNT(m.actorDetail, ',') AS commaCnt  
                              , COUNT(s.starRating)  AS cntStarRating
                              , ROUND(AVG(s.starRating) , 1) AS avgStarRating
                          FROM (
                              SELECT iv1.peopleCd, iv1.peopleNm, iv1.updateDate, REGEXP_SUBSTR(iv1.actorFilmos, '[^,]+', 1, level) actorFilmo
                              FROM (
                                  SELECT p.peopleCd, p.peopleNm, p.actorFilmos, p.updateDate 
                                  FROM tbl_people p 
                                  WHERE p.peopleCd= #{peopleCd}
                              ) iv1
                              CONNECT BY REGEXP_SUBSTR(iv1.actorFilmos, '[^,]+', 1, level) IS NOT NULL
                              /* CONNECT BY는 WHERE 이전에 실행되므로 FROM절이 단일행이 아니면 전체 FULL SCAN하여 연삭속도 극히느려짐 */
                          ) iv2
                          INNER JOIN tbl_movie m
                          ON (iv2.actorFilmo = m.movieCd)
                          LEFT OUTER JOIN tbl_starRating s
                          ON (m.movieCd = s.movieCd)
                          GROUP BY iv2.peopleNm, iv2.updateDate, m.movieCd, m.movieNm, m.actorDetail
                      ) iv3
                      WHERE iv3.peopleIdx != 0 /* INSTR()은 찾고자 하는 문자열 없으면 0 반환 */
                      ORDER BY actorOrder ASC
                  ) iv4	    
                  WHERE <![CDATA[ROWNUM <= 10]]>
                  ORDER BY iv4.cntStarRating DESC, NVL(iv4.avgStarRating,0) DESC, iv4.updateDate ASC
              ) ft
              WHERE ROWNUM BETWEEN 1 AND 2
      
          </selectKey>
      
          UPDATE tbl_people 
      		SET 
      		    repMovieList = #{repMovieList}, 
      		    repMovieListDate = NVL2(#{repMovieList}, SYSDATE, NULL)
      <!-- 	    	<if test="repMovieList != null">문법에러 발생 -->
      <!-- 	    	<if test="#{repMovieList} != null">에러발생하지 않으나 원하는결과x.문법오류같음 -->
      <!-- 	    	<if test="#{repMovieList != null} ">에러발생하지 않으나 원하는결과x.문법오류같음 -->
      <!-- 	    		repMovieListDate = SYSDATE -->
      <!-- 	    	</if> -->
      		    
      	<!-- 	    repMovieListDate = #{repMovieListDate} -->
      		WHERE peopleCd = #{peopleCd}        
      
      </update>
    • 중요) order="BEFORE" 지정한 경우,
      mapper.xml에서 insert/update 구문에서 명시한 ParameterType으로 값이 반환되지 않음!!

      -> ParameterType이 객체이고 resultType 또한 동일한 객체로 명시해줬다고 그 객체의 변수에 저장되지 않음
      그래서 INSERT/UPDATE된 값을 포함하는 객체를 얻고 싶다면 service단에서 다시 SELECT해야함
      => 그러므로 상기 예제에서 ParameterType을 객체로 받을 필요가 없다. 그냥 String peopleCd만 받으면됨.

      하지만 이때 mapper 메서드의 파라미터엔 @Param 지정 필수(이유는 모름..)
      그렇지 않으면

      org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.executor.ExecutorException: No setter found for the keyProperty 'repMovieList' in java.lang.String.

      와 같이 keyProperty 변수의 setter 찾을 수 없다는 에러 발생

    • 만약 order="BEFORE" 지정해 SELECT문 실행 후 얻은 값을 INSERT/UPDATE에 사용하고자 할때 그 변수를 keyProperty 속성값에 지정하지 않았다면, parameterType를 객체로 지정했을 경우 변수가 객체의 변수임을 정확히 명시해야함.
      -> 만약 WHERE peopleCd = #{peopleCd} 로 변수명으로만 지정하면,

      org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.binding.BindingException: Parameter 'peopleCd' not found. Available parameters are [peopleVO, param1, repMovieList]

      와 같이 해당 파라미터 찾을 수 없다는 에러 발생함.
      -> WHERE peopleCd = #{peopleVO.peopleCd} 와 같이 peopleVO의 변수 peopleCd임을 명시해 줘야함.


    • selectKey 쿼리는 하나의 row만 반환받을 수 있는 것 같다.. 확실친 않음

    • 하나의 <INSERT/UPDATE> 태그 안엔 하나의 만 가능


    • 시행착오) selecctKey의 keyProperty값 이용해 조건문 수행 불가..한 것같다.
      -> NVL, NVL2 함수를 이용해 NULL 체크 가능함을 이용할 것

      http://www.gurubee.net/article/66445

      UPDATE tbl_people 
      		SET 
      		    repMovieList = #{repMovieList}, 
      		    repMovieListDate = NVL2(#{repMovieList}, SYSDATE, NULL)
      <!-- 	    	<if test="repMovieList != null">문법에러 발생 -->
      <!-- 	    	<if test="#{repMovieList} != null">에러발생하지 않으나 원하는결과x.문법오류같음 -->
      <!-- 	    	<if test="#{repMovieList != null} ">에러발생하지 않으나 원하는결과x.문법오류같음 -->
      <!-- 	    		repMovieListDate = SYSDATE -->
      <!-- 	    	</if> -->
      		    
      	<!-- 	    repMovieListDate = #{repMovieListDate} -->
      		WHERE peopleCd = #{peopleCd}    

  • 시행착오) 여러 변수 반환 받기
    -. useGeneratedKeys
    insert, update의 파라미터 vo 객체에 값이 없는 변수는 값이 반환되지 않음
    ex) commentVO. 수정할 내용 contents 는 담았으나 updateDate는 담지않음
    -> keyProperty="updateDate, contents" 하였으나 updateDate는 null값 반환했음

    -. SelectKey 태그
    왜인지는 모르겠지만 keyProperty 속성의 값 순서에 따라 에러가 발생하거나 정상적으로 반환하거나 했다.
    ex) keyProperty="commentDate,contents" : 정상 실행 / keyProperty="contents, commentDate" : 에러 발생(CommentVO에 commentDate 찾을 수 없다고)
    ->, 다음 공백 때문에 발생한 에러였다!! 변수명을 공백까지 포함해서 인식함. 공백 없이 작성할 것

org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.executor.ExecutorException: Error selecting key or setting result to parameter object. Cause: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named ' repMovieList' in 'class com.moviepedia.domain.PeopleVO'
profile
IT개발 입문합니다.

0개의 댓글