Skip to content

fix: primitive float 필드가 sqlTypes 표기 불일치로 JDBC 배치 쓰기에서 SQLException 이 나는 문제 수정 - #362

Open
wantaekchoi wants to merge 1 commit into
eGovFramework:mainfrom
wantaekchoi:fix/methodmap-float-sqltype
Open

fix: primitive float 필드가 sqlTypes 표기 불일치로 JDBC 배치 쓰기에서 SQLException 이 나는 문제 수정#362
wantaekchoi wants to merge 1 commit into
eGovFramework:mainfrom
wantaekchoi:fix/methodmap-float-sqltype

Conversation

@wantaekchoi

Copy link
Copy Markdown
Contributor

수정 사유 Reason for modification

  • 버그수정 Bug fixes
  • 기능개선 Enhancements
  • 기능추가 Adding features
  • 기타 Others

수정된 소스 내용 Modified source

EgovJdbcBatchItemWriter 로 primitive float 필드가 있는 VO 를 쓰면 java.sql.SQLException 이 납니다.

sqlTypes 를 만드는 쪽은 Class.getSimpleName() 을 씁니다. primitive 필드는 "int"·"double"·"long" 처럼 소문자 이름이 나오고, primitive float"float" 이 나옵니다.

// EgovReflectionSupport.java:247
sqlTypes[i] = item.getClass().getDeclaredField(params[i]).getType().getSimpleName().toString();

받는 쪽 분기는 이렇습니다.

$ git grep -n 'sqlTypes\[i\].equals' .../item/database/support/EgovMethodMapItemPreparedStatementSetter.java
57:  if (sqlTypes[i].equals("String")) {
59:  } else if (sqlTypes[i].equals("int")) {
61:  } else if (sqlTypes[i].equals("double")) {
63:  } else if (sqlTypes[i].equals("Date")) {
65:  } else if (sqlTypes[i].equals("byte")) {
67:  } else if (sqlTypes[i].equals("short")) {
69:  } else if (sqlTypes[i].equals("boolean")) {
71:  } else if (sqlTypes[i].equals("long")) {
73:  } else if (sqlTypes[i].equals("Float")) {
75:  } else if (sqlTypes[i].equals("BigDecimal")) {
77:  } else if (sqlTypes[i].equals("byte[]")) {

primitive 를 받는 분기 여섯 개(int·double·byte·short·boolean·long)는 모두 소문자 이름으로 비교하는데, 73줄만 wrapper 이름인 "Float" 로 비교합니다. 그래서 산출값 "float" 은 어느 분기에도 걸리지 않고 80줄의 throw new SQLException() 으로 떨어집니다. 메시지도 원인도 없는 예외라 로그만 보면 무엇이 문제인지 알 수 없습니다.

죽은 경로가 아닙니다. params 를 설정한 정상 쓰기 흐름에서 산출과 소비가 한 메서드 안에 이어져 있고, getSqlTypeArray 를 부르는 운영 코드는 이 한 곳뿐입니다.

// EgovJdbcBatchItemWriter.java:176
local = new SqlTypeCache(itemClass, reflector.getSqlTypeArray(params, firstItem));
// EgovJdbcBatchItemWriter.java:184
itemPreparedStatementSetter.setValues(item, ps, params, sqlTypes, methodMap);

같은 EgovReflectionSupport 안의 읽기 경로는 primitive float 을 지원 대상으로 봅니다.

// EgovReflectionSupport.java:270  (parsingFromString)
} else if (type == float.class) {
    parsingValue = Float.parseFloat(tokenValue);

AS-IS / TO-BE

                 } else if (sqlTypes[i].equals("long")) {
                     ps.setLong(i + 1, (Long) reflector.invokeGettterMethod(item, params[i], methodMap));
-                } else if (sqlTypes[i].equals("Float")) {
+                } else if (sqlTypes[i].equals("float") || sqlTypes[i].equals("Float")) {
                     ps.setFloat(i + 1, (Float) reflector.invokeGettterMethod(item, params[i], methodMap));

"Float""float" 으로 바꾸지 않고 "float" 을 OR 로 더했습니다. setValues 는 공개 메서드라 sqlTypes 를 직접 넘기는 호출자가 있을 수 있고, wrapper Float 필드는 수정 전에도 정상 동작하기 때문입니다. 치환하면 그 동작이 사라집니다.

영향 범위

운영 코드는 이 한 줄입니다. else if 조건을 넓히기만 해서 기존에 매치되던 입력의 동작은 바뀌지 않고, 그동안 SQLException 으로 떨어지던 "float"ps.setFloat 으로 들어갑니다. 나머지 분기와 산출부는 손대지 않았습니다.

JUnit 테스트 JUnit tests

  • JUnit 테스트 JUnit tests
  • 수동 테스트 Manual testing

EgovMethodMapFloatSqlTypeTest 를 3건 추가했습니다. sqlTypes 를 손으로 만들지 않고 실제 getSqlTypeArray 산출값을 그대로 소비부에 넘깁니다.

  • primitiveFloatIsSetLikeSiblingPrimitives — 128·129줄에서 assertEquals("double", sqlTypes[0])·assertEquals("float", sqlTypes[1]) 로 산출 표기를 먼저 확정하고, 형제 double 과 같은 자리에서 ps.setFloat 이 호출되는지 봅니다.
  • wrapperFloatIsStillSet — 161줄 assertEquals("Float", sqlTypes[0]). wrapper 경로가 그대로인지 확인합니다.
  • primitiveFloatIsSetThroughWriter (181줄) — setValues 를 직접 부르지 않고 EgovJdbcBatchItemWriter.write() 를 경유합니다. PreparedStatement 대역은 형제 테스트 EgovJdbcBatchWriteReflectionTest:155FakePsHandler 를 그대로 씁니다.

수정한 한 줄을 sqlTypes[i].equals("Float") 로 되돌리면 두 건이 실패합니다.

$ mvn -o test -pl Batch/org.egovframe.rte.bat.core -Dtest=EgovMethodMapFloatSqlTypeTest -DfailIfNoTests=false
[INFO] Running org.egovframe.rte.bat.core.item.database.EgovMethodMapFloatSqlTypeTest
[ERROR] Tests run: 3, Failures: 0, Errors: 2, Skipped: 0, Time elapsed: 0.178 s <<< FAILURE!
[ERROR]   EgovMethodMapFloatSqlTypeTest.primitiveFloatIsSetLikeSiblingPrimitives:135 » SQL
[ERROR]   EgovMethodMapFloatSqlTypeTest.primitiveFloatIsSetThroughWriter:203 » Runtime java.sql.SQLException
[ERROR] Tests run: 3, Failures: 0, Errors: 2, Skipped: 0
[INFO] BUILD FAILURE

두 건 모두 같은 자리에서 던져집니다.

java.sql.SQLException
	at org.egovframe.rte.bat.core.item.database.support.EgovMethodMapItemPreparedStatementSetter.setValues(EgovMethodMapItemPreparedStatementSetter.java:80)

수정본으로 되돌린 뒤 모듈 전체입니다.

$ mvn -o test -pl Batch/org.egovframe.rte.bat.core
[INFO] Tests run: 89, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS

테스트 브라우저 Test Browser

  • Chrome
  • Firefox
  • Edge
  • Safari
  • Opera
  • Internet Explorer
  • 기타 Others

테스트 스크린샷 또는 캡처 영상 Test screenshots or captured video

화면이 없는 실행환경 모듈이라 첨부하지 않았습니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant