mybatis selectKey 사용 방법 및 주의할 점 (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