MS SQL Server에서 JPA NativeQuery 사용시 유니코드(Unicode) 문제
SQL Server에서 유니코드 데이터 유형과 JPA(구현체 Hibernate) NativeQuery를 함께 사용하는 경우 MappingException이 발생 한다. 이를 해결하는 방법을 소개 한다.
참고로 이 문제는 JPQL(Java Persistence Query Language)을 사용하는 경우에는 발생하지 않는다.
먼저 코드를 보면
MEMBER 테이블은 유니코드 데이터 유형 중 하나인 NVARCHAR인 MEMBER_ID와 MEMBER_NAME 컬럼을 가지고 있다.
1 2 3 4 5 6 7 8 9 10 11 12
@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을 만나게 된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
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를 사용 하였다.
1 2 3 4 5
<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 에 설정해 주었다.
그리고 위의 테스트 케이스를 다시 실행해 보면 테스트를 통과 한다.
1 2 3 4 5 6 7 8 9 10
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()); } }
1 2 3 4 5
<persistence-unit name="article" transaction-type="RESOURCE_LOCAL"> //.. <property name="hibernate.dialect" value="ymyoo.article.sqlserver.unicode.SQLServerUnicodeDialect" /> </properties> </persistence-unit>
GitHub
전체 코드는 필자의 GitHub에서 확인 가능 하다.