A상품의 PK를 이용하여 복사하는 API를 개발 중입니다.
클라가 pathVariable로 A상품의 PK를 보내주면 select-insert 문을 이용해서 같은 정보를 가진, 그러나 다른 PK인 A_copy 상품을 생성합니다.
그런데 단순히 아래처럼 select한 결과를 insert하는 게 아니라 서비스단에서 넘겨준 파라미터로 새로운 데이터도 넣어주어야 합니다.
INSERT INTO product_image (key, filename, size)
(SELECT
pi.key, pi,filename, pi.size
FROM
product_image pi
INNER JOIN
product p ON pi.product_id = p.id
WHERE
p.id = #{productId}
)
그래서 mybatis에서 제공하는 <selectKey>
를 이용하여 서비스단에서 넘겨준 파라미터 + select한 값으로 A_copy 상품을 생성해 보도록 하겠습니다!
우선 multi selectKey를 하는 방법은 어렵지 않습니다.
selectKey에 있는 속성 중 keyProperty
와 keyColumn
에 여러 필드와 컬럼을 명시하면 됩니다.
<insert id="copyOne" parameterType="com.~.vo.ProductCopyRequestVo" useGeneratedKeys="true" keyProperty="resultId">
<selectKey keyProperty="name,companyName,supplierId,manufacturerId,currentStock,statusId,
shippingCompanyId,returnInfoId,managerId,thumbnailId"
keyColumn="name, company_name, supplier_id, manufacturer_id, current_stock, status_id,
shipping_company_id, return_info_id, manager_id, thumbnail_id"
resultType="com.vo.ProductCopyRequestVo" order="BEFORE">
SELECT
name, company_name, supplier_id, manufacturer_id, current_stock, status_id, shipping_company_id, return_info_id, manager_id, thumbnail_id
FROM
product
WHERE
product_id = #{productId}
</selectKey>
INSERT INTO product
(name, company_name, supplier_id, manufacturer_id, current_stock, status_id, shipping_company_id, return_info_id, manager_id, thumbnail_id, weight_id, volume_id)
VALUES
(CONCAT(#{name}, '_copy'), #{companyName}, #{supplierId}, #{manufacturerId}, #{currentStock}, #{statusId}, #{weightId}, #{volumeId})
</insert>
첫번째 에러. A상품이 갖고있는 이름(name)에 대한 타입이 불분명하다.
java.sql.SQLDataException: Cannot determine value type from string '제품이름'
...
Caused by: org.apache.ibatis.executor.result.ResultMapException: Error attempting to get column 'name' from result set. Cause: java.sql.SQLDataException: Cannot determine value type from string '제품이름'
...
Caused by: com.mysql.cj.exceptions.DataConversionException: Cannot determine value type from string '제품이름'
원인: ProductCopyRequeseVo가 갖고있는 @RequiredArgsConstructor 와 @NonNull의 조합때문이었습니다.
@Getter
@Setter
@RequiredArgsConstructor
public class ProductCopyRequestVo extends CreateVo {
@NonNull
private BigInteger productId;
...
}
@RequiredArgsConstructor 생성자는 @NonNull이 명시된 필드들로 기본 생성자를 생성하는데
생성자 파라미터의 위치에 영향을 받습니다.
첫 파라미터인 productId가 BigInteger였고, 심지어 vo에는 selectKey로 찾은 필드가 명시되어 있지 않았습니다.
해결방법: vo에 selectKey에 적어준 keyProperty에 해당하는 모든 필드를 추가해 주었습니다.
Caused by: java.sql.SQLDataException: Cannot determine value type from string '제품이름'
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:115)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:98)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:90)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:64)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:74)
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:96)
at com.mysql.cj.jdbc.result.ResultSetImpl.getBigDecimal(ResultSetImpl.java:648)
at com.mysql.cj.jdbc.result.ResultSetImpl.getBigDecimal(ResultSetImpl.java:663)
at net.sf.log4jdbc.sql.jdbcapi.ResultSetSpy.getBigDecimal(ResultSetSpy.java:3042)
at com.zaxxer.hikari.pool.HikariProxyResultSet.getBigDecimal(HikariProxyResultSet.java)
at org.apache.ibatis.type.BigIntegerTypeHandler.getNullableResult(BigIntegerTypeHandler.java:38)
at org.apache.ibatis.type.BigIntegerTypeHandler.getNullableResult(BigIntegerTypeHandler.java:28)
at org.apache.ibatis.type.BaseTypeHandler.getResult(BaseTypeHandler.java:86)
... 158 more
이번에도 똑같은 에러가 발생했습니다.. 흠... 그런데 콘솔창을 쭈우웅ㄱ 보니 아래에서 두번째 BigIntegerTypeHandler
!!!
아! @NoArgsConstrucotr
가 없어서 여전히 @NonNull 이 붙은 필드들을 이용해서 생성자를 만들고 있었습니다.
@NonNull이 붙은 필드들은 이미 서비스단에서 셋해주고 그 다음에 copyOne의 parameterType으로 넘기고 있었기 때문에 selectKey를 사용하는 시점에서는 productId와 나머지 NonNull 필드들도 생성자를 만들 필요가 없습니다.
원인,해결방법: vo에 @NoArgsConstructor
를 사용합니다.
company_name
을 위한 getter가 없다.Caused by: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named ' company_name' in 'class com.vo.ProductCopyRequestVo'
company_name 앞에 공백은 내가 오타를 낸 게 아닙니다. 찐으로 저렇게 나옵니다.
왜지 뭐지 뭘까
아 어떤 블로그에서 multi selectKey를 사용하기 위해서 keyProperty
에 여러 필드를 명시해도 상관없지만 ,
뒤에 공백은 없애야한다고 보았습니다.
설마 keyColumn도 공백이 허용되지 않는건가? 싶어서 문서를 확인해보니...
아 그런 말은 없잖아요
원인,해결방법: keyColumn
에도 공백을 없앱니다.
그럼 이번에는 select문에 alias를 모두 명시해 줍니다.
우왕! INSERT문을 통해 드디어 생성이 되었습니다!
해결!
인 줄 알았으나, 생성 후 PK를 클라에 response 해주어야 하는데 null이 들어갔습니다.
┌────── RESPONSE LOG ─────────────────────────────────────────────────────────────────────────────
| [ REQUEST ID ] uuid...
| [ REQUEST URI ] POST | /v1/product/productPK/copy
| [ FAILED REQUEST BODY ] ▼▼▼▼▼
| null
| [ RESPONSE BODY ] ▼▼▼▼▼
| {
"id" : "uuid...",
"data" : {
"id" : null
},
"errors" : null
}
└────────────────────────────────────────────────────────────────────────────────────────────────
...
결국 selectKeys와... useGeneratedKeys는 공존할 수 없다는 슬픈 소식을 접했습니다..
왜 공존할 수 없는지는 다음에 더 깊게 알아보겠습니다.........