Programming/Spring

mybatis selectKey 사용 방법 및 주의할 점 (selectKey 다중 컬럼)

Jan92 2024. 3. 20. 22:11

mybatis selectKey 사용 방법 및 주의할 점

mybatis selectKey 사용 방법

mybatis에서는 insert, update, delete 쿼리에 대한 결과로 해당 쿼리에 영향을 받은 행의 수를 반환합니다.

그중에서 insert의 경우 성공 시 int 타입의 1이라는 결과를 반환하며, 실패 시 에러가 발생하게 되는데요.

(update, delete의 경우 쿼리에 영향을 받은 행의 수가 여러 행일 경우 그 값만큼 결과를 반환하는 반면, insert의 경우 여러 건에 대해 쿼리가 성공하더라도 1을 반환한다는 특징이 있습니다.)

 

해당 포스팅에서는 selectKey 요소를 통해 mybatis에서 insert 결과로 해당 데이터의 key 값을 받아오는 방법 및 selectKey 사용법, 그리고 주의할 점에 대해서 정리하였습니다.

 


MySQL을 사용할 때 selectKey 사용 방법

CREATE TABLE `user` (
  `idx` int NOT NULL AUTO_INCREMENT,
  `id` varchar(45) CHARACTER SET utf8mb4_bin COLLATE utf8mb4_bin DEFAULT NULL,
  `name` varchar(45) COLLATE utf8mb4_bin DEFAULT NULL,
  `created_at` datetime DEFAULT CURRENT_TIMESTAMP,
  `updated_at` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`idx`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4_bin COLLATE=utf8mb4_bin

(예시에서 사용될 간단한 테이블)

 

1. selectKey

<!-- 1. order="BEFORE" + MAX() -->
<insert id="insert" parameterType="hashMap">
    <selectKey keyProperty="idx" resultType="int" order="BEFORE">
        SELECT IFNULL(MAX(idx), 0) + 1 FROM user
    </selectKey>
    INSERT INTO user
        (idx, id, name) 
    VALUES
        (#{idx}, #{id}, #{name})	
</insert>

<!-- 2. order="AFTER" + LAST_INSERT_ID() -->
<insert id="insert" parameterType="hashMap">
    <selectKey keyProperty="idx" resultType="int" order="AFTER">
        SELECT LAST_INSERT_ID()
    </selectKey>
    INSERT INTO user
        (id, name) 
    VALUES
        (#{id}, #{name})	
</insert>

mysql에서는 다음과 같이 insert문 안에서 selectKey 요소를 사용하여 insert 쿼리의 결과 데이터에 대한 key 값을 가져올 수 있는데요.

selectKey에서 사용되는 각 속성의 내용은 아래와 같습니다.

 

  • keyProperty: 생성된 키 값을 저장할 매개변수의 이름을 지정합니다.
  • resultType: 생성된 키 값의 데이터 유형을 지정합니다.
  • order: selectKey 요소가 실행되는 시점을 지정합니다. BEFORE 또는 AFTER로 설정할 수 있으며, BEFORE의 경우 INSERT 문이 실행되기 전에 selectKey 부분이 실행되고, AFTER의 경우 INSERT 문이 실행된 후 selectKey 부분이 실행됩니다.

 

예시로 사용된 코드에 대해 살펴보면, 1번 예시의 경우 insert 문이 실행되기 전에 selectKey 부분이 실행되어 user 테이블에 저장된 '가장 큰 idx 값'에 1을 더하여 key 값으로 사용하는 방법의 예시입니다.

2번 예시의 경우 insert 문이 실행된 다음 selectKey 부분이 실행되며, 여기서 'SELECT LAST_INSERT_ID()' 함수는 현재 세션에서 마지막으로 삽입된 데이터의 AUTO_INCREMENT 값을 반환하는 함수입니다.

 

 

*** 주의할 점

위 1번 예시 코드에서 사용된 selectKey 방식에서는 동시성 문제가 발생하여 중복된 키가 생성될 수 있으며, 2번 예시의 경우에도 bulk insert가 수행되었을 때 key 값에 대한 결과 등 주의해야 할 부분이 있는데요.

 

때문에 키의 생성 같은 경우 데이터베이스가 제공하는 자체의 시퀀스 기능(mysql의 auto-increment, orecle의 sequence)을 사용하는 것이 더 좋으며, 생성된 key 값을 가져오는 방식 또한 아래 'useGeneratedKeys' 방식이 더 안전합니다.

 

 

 

2. useGeneratedKeys

<!-- mysql useGeneratedKeys -->
<insert id="insert" parameterType="hashMap" useGeneratedKeys="true" keyProperty="idx">
    INSERT INTO user
        (id, name) 
    VALUES
        (#{id}, #{name})	
</insert>

'useGeneratedKeys'는 JDBC에서 제공하는 기능으로 INSERT 문이 실행된 후 자동으로 생성된 key 값을 가져오는 기능이며, mybatis에서도 다음 예시와 같이 해당 속성을 true로 설정하여 사용할 수 있습니다.

 

selectKey에서와 마찬가지로 'keyProperty' 속성에는 생성된 키 값을 저장할 매개변수의 이름을 지정합니다.

 

 

 

3. 다중 컬럼에 대한 selectKey 사용

<!-- 다중 컬럼에 대한 selectKey -->
<insert id="insert" parameterType="hashMap">
    <selectKey keyProperty="created_at,updated_at" resultType="java.util.Map" order="AFTER">
        SELECT created_at, updated_at from user where name = #{name}
    </selectKey>
    INSERT INTO user
        (id, name) 
    VALUES
        (#{id}, #{name})	
</insert>

selectKey를 사용하면 해당 데이터의 key 값뿐만 아니라 다른 컬럼의 값도 가져올 수 있는데요.

위 예시와 같이 다중 컬럼에 대한 값을 가져올 수도 있으며, 이때 주의할 점으로는 'keyProperty' 속성에 대한 값을 지정할 때 컬럼 사이에 ','를 제외한 공백이 있어서는 안 된다는 것입니다.

(keyProeprty="created_at, update_at" keyProperty를 다음과 같이 입력하는 경우 오류가 발생합니다.)

 

 

*** selectKey로 조회한 값 사용 방법

public void insertTest(Map<String, Object> inputData) {
	int result = userDao.insert(inputData);	
    
	logger.info("result: " + result);	         // result: 1
	logger.info("idx: " + inputData.get("idx"));	 // idx: [생성된 key 값]
	logger.info("created_at: " + inputData.get("created_at"));
	logger.info("updated_at: " + inputData.get("updated_at"));
}

selectKey를 통해 조회한 값을 반환받아 사용하려고 할 때, insert method에서 파라미터 타입으로 map을 사용하는 경우 insert 후 map 인스턴스 내부에서 keyProperty로 지정된 매개변수를 가져오는 방식으로 조회한 값을 사용할 수 있습니다.

 

파라미터 타입으로 dto를 사용하는 경우 해당 dto에 keyProperty로 지정할 매개변수를 생성해 놓고 get 메서드를 통해 조회한 해당 매개변수의 값을 사용할 수 있습니다.

 

 


Oracle을 사용할 때 selectKey 사용법

/* 테이블 생성 */
CREATE TABLE USERS 
(
  IDX NUMBER NOT NULL 
, ID VARCHAR2(20 BYTE) NOT NULL 
, NAME VARCHAR2(20 BYTE) NOT NULL 
, CONSTRAINT USER_IDX PRIMARY KEY 
) ;

/* 시퀀스 생성 */
CREATE SEQUENCE USER_SEQUENCE 
	INCREMENT BY 1        // 1씩 증가
	START WITH 1          // 시작값
	MINVALUE 1            // 최소값
	MAXVALUE 999          // 최대값
	NOCACHE;              // 캐시 사용 여부

(예시에 사용될 간단한 테이블 및 시퀀스)

 

MySQL에서 키 값 생성을 위해 auto-increment를 사용한다면 Oracle에서는 Sequence를 사용하는데요.

 

<!-- oracle sequence 사용 -->
<insert id="insert" parameterType="hashMap">
    <!-- 시퀀스를 호출하여 키 값을 할당 받고 해당 값을 insert 문에서 사용합니다. --> 
    <selectKey keyProperty="idx" resultType="int" order="BEFORE">
        SELECT USER_SEQUENCE.nextval FROM dual
    </selectKey>
    INSERT INTO users
        (idx, id, name)
    VALUES
        (#{idx}, #{id}, #{name})
</insert>

예시 코드에서는 insert 문이 실행되기 전에 selectKey 부분이 동작하며, 동작 과정에서 시퀀스의 nextval를 호출하여 해당 시퀀스의 다음 값을 가져오고 그 값을 insert 문에서 키 값으로 사용하는 방식입니다.

 

 

 

< 공식 문서 >
https://mybatis.org/mybatis-3/sqlmap-xml.html

< 참고 자료 >
https://velog.io/@jiu_lee/MyBatis-InsertUpdate-%ED%9B%84-PK-%EA%B0%92-return