MS SQL Server에서 JPA NativeQuery 사용시 유니코드(Unicode) 문제
SQL Server에서 유니코드 데이터 유형과 JPA(구현체 Hibernate) NativeQuery를 함께 사용하는 경우 MappingException이 발생 한다. 이를 해결하는 방법을 소개 한다.
참고로 이 문제는 JPQL(Java Persistence Query Language)을 사용하는 경우에는 발생하지 않는다.
먼저 코드를 보면
MEMBER 테이블은 유니코드 데이터 유형 중 하나인 NVARCHAR인 MEMBER_ID와 MEMBER_NAME 컬럼을 가지고 있다.

@Entity
@Table(name = "MEMBER")
public class Member {
@Id
@Column(name="MEMBER_ID", columnDefinition = "NVARCHAR(20)")
private String memberId;
@Column(name="MEMBER_NAME", columnDefinition = "NVARCHAR(20)")
private String memberName;
@Column(name="AGE")
private Integer age;
// ..
}그리고 MEMBER 테이블에는 3건 데이터를 가지고 있다.

org.hibernate.MappingException: No Dialect mapping for JDBC type: -9
아래의 NativeQuery 테스트 코드를 실행해 보면 MappingException을 만나게 된다.
public class UnicodeTest {
final String PERSISTENCE_UNIT_NAME = "article";
EntityManagerFactory emf =
Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME);
@Test
public void test_native_sql() {
// given
final int age = 30;
// when
EntityManager em = this.emf.createEntityManager();
Query nativeQuery =
em.createNativeQuery("select member_id, member_name, age " +
"from member " +
"where age=:age")
.setParameter("age", age);
List<Member> members = nativeQuery.getResultList();
// then
Assert.assertEquals(2, members.size());
}
}
No Dialect mapping for JDBC type: -9?
예외가 발생한 부분을 추적해 보자. 예외는 org.hibernate.dialect.TypeNames 클래스의 get()을 수행하면서 발생한 것이다.

그리고 여기서 JDBC type -9 가 의미하는 것은 바로 com.microsoft.sqlserver.jdbc.JDBCType의 NVARCHAR 이다.

Hibernate Dialect
No Dialect mapping for JDBC type 을 알아보기 전에 Hibernate Dialect(방언)에 대해 알아보자.
Hibernate 문서는 Dialect를 아래와 같이 정의 하고 있다.
Represents a dialect of SQL implemented by a particular RDBMS. Subclasses implement Hibernate compatibility with different systems. - https://docs.jboss.org/hibernate/orm/3.5/api/org/hibernate/dialect/Dialect.html
RDBMS 마다 SQL을 상이하게 사용하는 부분이 있다. Hibernate는 각각의 RDBMS 마다 특수화된 Dialect를 사용하여 호환성을 확보하는 것이다.

필자의 경우 Hibernate Dialect를 org.hibernate.dialect.SQLServerDialect를 사용 하였다.
<persistence-unit name="article" transaction-type="RESOURCE_LOCAL">
//..
<property name="hibernate.dialect" value="org.hibernate.dialect.SQLServerDialect" />
</properties>
</persistence-unit>No Dialect mapping for JDBC type
앞서 언급한 MappingException 예외를 상기해 보면, 예외는 TypeNames에서 발생한 것이다.
TypeNames는 Dialect의 registerColumnType을 통해 등록된다.


결국 문제는 필자가 사용하는 SQLServerDialect가 JDBC Type 중 하나인 NVARCHAR를 지원하지 않아서 발생하는 것이었다.
그래서 해결책은?
StackOverFlow에서 답을 찾을 수 있다.

즉, 기존의 Dialect(방언) 상속하여 유니코드 관련 데이터 타입을 추가로 등록해 주라는 것이다.
이와 관련되서 Hibernate Dialect 문서에 언급이 있다.
Subclasses should provide a public default constructor that register() a set of type mappings and default Hibernate properties. - https://docs.jboss.org/hibernate/orm/3.5/api/org/hibernate/dialect/Dialect.html
SQLServerUnicodeDialect
SQLServerDialect를 상속하여 SQLServerUnicodeDialect 라는 이름으로 클래스를 만들고 hibernate.dialect 에 설정해 주었다.
그리고 위의 테스트 케이스를 다시 실행해 보면 테스트를 통과 한다.
public class SQLServerUnicodeDialect extends SQLServerDialect {
public SQLServerUnicodeDialect() {
registerHibernateType(Types.NCHAR, StandardBasicTypes.CHARACTER.getName());
registerHibernateType(Types.NCHAR, 1, StandardBasicTypes.CHARACTER.getName());
registerHibernateType(Types.NCHAR, 255, StandardBasicTypes.STRING.getName());
registerHibernateType(Types.NVARCHAR, StandardBasicTypes.STRING.getName());
registerHibernateType(Types.LONGNVARCHAR, StandardBasicTypes.TEXT.getName());
registerHibernateType(Types.NCLOB, StandardBasicTypes.CLOB.getName());
}
}
<persistence-unit name="article" transaction-type="RESOURCE_LOCAL">
//..
<property name="hibernate.dialect" value="ymyoo.article.sqlserver.unicode.SQLServerUnicodeDialect" />
</properties>
</persistence-unit>GitHub
전체 코드는 필자의 GitHub에서 확인 가능 하다.