[Err] MyBatis - multi selectKey 알아가는 과정

0

Exception

목록 보기
16/16

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에 있는 속성 중 keyPropertykeyColumn 에 여러 필드와 컬럼을 명시하면 됩니다.

  1. no getter for property
  <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에 해당하는 모든 필드를 추가해 주었습니다.

  1. 1번과 동일
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 를 사용합니다.

  1. vo에 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 에도 공백을 없앱니다.

  1. no getter for name
    흠.. 이래도 저래도 selectKey가 작동하지 않습니다.
    그런데 아무리 생각해봐도 resultType을 명시해줬으면 KeyColumn은 필요없지 않을까요? 과감히 버리고 다시 시도해 보았지만 여전히 no getter for name 입니다.

그럼 이번에는 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
}
└────────────────────────────────────────────────────────────────────────────────────────────────
  1. responseBody 에 생성된 PK가 들어가지 않는다.

...
결국 selectKeys와... useGeneratedKeys는 공존할 수 없다는 슬픈 소식을 접했습니다..
왜 공존할 수 없는지는 다음에 더 깊게 알아보겠습니다.........

profile
야호 약간 헌 개발자....

0개의 댓글