Development/Java

[DB] JTDS Mybatis NVarchar 관련 이슈

반응형

 

DB와의 커넥션을 맺기 위해 주로 사용하는 풀링 라이브러리가 여러개 있다.

dbcp, jtds, hikari, c3p0 등...

 

그런데, 한글이나 유니코드 문자열을 저장하려면 MS-SQL의 경우 NVARCHAR() 형으로 저장을 해줘야 하는데

 

JTDS를 사용한다면 곤욕을 치룰 수 있는 부분이 존재한다.

 

NVARCHAR를 사용할 수 없다는 것인데, mybatis의 xml 매퍼에서 다음과 같이 insert or update를 진행했다고 하면

 

 


    
        insert into SOME_TABLE
        (
            [GROUP_NAME]
            , [REG_ID]
        )
        values
            (
                #{groupName},
                #{regId}
            )
    

groupName을 nvarchar로 지정했다고 하자.

 

jtds의 커넥션 정보를 아래와 같이 설정한 상태이다.

 

jdbc.some_db.driverClass=com.microsoft.sqlserver.jdbc.SQLServerDriver
jdbc.some_db.url=jdbc:sqlserver://SOME_IP:PORT;databaseName=SOME_DB;sendStringParametersAsUnicode=false;

 

유니코드 관련 파라미터 깨짐을 방지하기 위해 sendStringParametersAsUnicode=false; 도 설정한 상태이다.

 

근데 이상태로 insert문을 호출하면 다음과 같은 Exception이 발생한다.

 


java.lang.AbstractMethodError
    at net.sourceforge.jtds.jdbc.JtdsPreparedStatement.setNString(JtdsPreparedStatement.java:1062)
    at org.apache.commons.dbcp.DelegatingPreparedStatement.setNString(DelegatingPreparedStatement.java:238)
    at org.apache.commons.dbcp.DelegatingPreparedStatement.setNString(DelegatingPreparedStatement.java:238)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.jdbcdslog.PreparedStatementLoggingProxy.invoke(PreparedStatementLoggingProxy.java:49)
    at $Proxy202.setNString(Unknown Source)
    at org.apache.ibatis.type.BaseTypeHandler.setParameter(BaseTypeHandler.java:23)
    at org.apache.ibatis.executor.parameter.DefaultParameterHandler.setParameters(DefaultParameterHandler.java:73)
    at org.apache.ibatis.executor.statement.PreparedStatementHandler.parameterize(PreparedStatementHandler.java:61)
    at org.apache.ibatis.executor.statement.RoutingStatementHandler.parameterize(RoutingStatementHandler.java:43)
    at org.apache.ibatis.executor.SimpleExecutor.prepareStatement(SimpleExecutor.java:56)
    at org.apache.ibatis.executor.SimpleExecutor.doUpdate(SimpleExecutor.java:28)
    at org.apache.ibatis.executor.BaseExecutor.update(BaseExecutor.java:88)
    at org.apache.ibatis.executor.CachingExecutor.update(CachingExecutor.java:43)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:122)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.insert(DefaultSqlSession.java:111)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:350)
    at $Proxy12.insert(Unknown Source)
    at org.mybatis.spring.SqlSessionTemplate.insert(SqlSessionTemplate.java:232)
    at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:59)
    at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:25)
    at $Proxy109.insertCoppaGroup(Unknown Source)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:212)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:126)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:96)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:617)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:578)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:900)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:827)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:882)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:789)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:637)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at net.netmarble.web.filter.ErrorHandleFilter.doFilter(ErrorHandleFilter.java:40)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at net.netmarble.web.filter.PrefixDomainFilter.doFilter(PrefixDomainFilter.java:43)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293)
    at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:859)
    at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:602)
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:396)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
    at java.lang.Thread.run(Thread.java:662)

내용을 잘 살펴보면, AbstractMethodException이 발생했다는걸 알 수 있다.

 

이 AbstractionMethodException이 어디서 발생했는지 추적을 해보면, net.sourceforge.jtds.jdbc.JtdsPreparedStatement.setNString 에서 발생을 했다는 걸 알 수 있다.

 

그래서 그 부분을 찾아 들어가보면..

 

public void setNString(int parameterIndex, String value) throws SQLException {
    throw new AbstractMethodError();
}

 

뭔개소리야에 대한 이미지 검색결과

 

JTDS에는 setNString이 구현이 안되어 있다(?)

 

호출하면 그냥 AbstractMethodError를 내뿜는 것 밖엔 하는 일이 없다는 뜻이다.

 

진짜 뭔 개소린가

 

사실, 최근에는 JTDS를 많이 사용은 안하는 추세인데..

 

혹시 JTDS를 사용한다면 이런 경우를 조심하도록 하자.

.

여튼 이 문제를 해결할 방법은? TypeHandler를 지정한 다음, JTDS를 사용하지 않는 방법이 있다 ^^;

 

일단.. TypeHandler를 구현을 하도록 하자.

 

NVarchar의 경우 setNString을 명시적으로 호출해주는? 부분을 구현해줘야 하는데

 

지금 수정작업을 마친 프로젝트의 경우 이렇게 명시적으로 Nvarchar를 set해주는 부분을 구현을 해줘야 했었는데,

 

사실.. 그냥 이런 부분 없이도 driver만 바꿔준다면 문제없이 진행될 수도 있다.

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.NStringTypeHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

@MappedJdbcTypes(JdbcType.NVARCHAR)
public class NVarcharTypeHandler extends BaseTypeHandler {

    private static final Logger logger = LoggerFactory.getLogger(NVarcharTypeHandler.class);

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
        ps.setNString(i, parameter);
    }

    @Override
    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return rs.getNString(columnName);
    }

    @Override
    public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return cs.getNString(columnIndex);
    }
}

 

이렇게 구현을 해놓은 뒤에, mybatis xml 매퍼 안에서 적용할 파라미터에 구현한 typehandler를 적용해주면 되겠다.


    
        insert into TB_SOME_TABLE
        (
            [GROUP_NAME]
            , [REG_ID]
        )
        values
            (
                #{groupName,jdbcType=NVARCHAR,typeHandler=com.gompang.NVarcharTypeHandler},
                #{regId}
            )
    

적용을 끝낸 뒤에는 커넥션 스트링을 바꿔주도록 하자

 

기존

jdbc.some_db.driverClass=net.sourceforge.jtds.jdbc.Driver
jdbc.some_db.url=jdbc:jtds:sqlserver://SOME_IP:PORT;sendStringParametersAsUnicode=false;

수정 후

jdbc.some_db.driverClass=com.microsoft.sqlserver.jdbc.SQLServerDriver
jdbc.some_db.url=jdbc:sqlserver://IP:PORT;databaseName=SOME_DB;sendStringParametersAsUnicode=false;

 

 

 

반응형