Получение Hibernate и SQL Server для работы с VARCHAR и NVARCHAR
В настоящее время я запускаю символы UTF-8 в некоторых таблицах большой базы данных. Эти таблицы уже имеют тип NVARCHAR MS-SQL. Кроме того, у меня есть несколько полей, в которых используется VARCHAR.
Существует известная проблема с взаимодействием Hibernate с драйвером JDBC (см., например, Сопоставление с varchar и nvarchar в спящем режиме). Короче говоря, Hibernate/JDBC генерирует SQL, который передает все строки как Unicode, независимо от типа SQL. Когда поле не-unicode (varchar) в базе данных сравнивается с входной строкой Unicode, знаки для этого столбца не соответствуют кодировке, поэтому выполняется полное сканирование таблицы. В драйвере JDBC (как версии JTDS, так и MS) есть параметр для передачи строк Unicode в виде ASCII, но это все или ничего, что запрещает ввод международных символов в базу данных.
Большинство сообщений, которые я видел по этой проблеме, придумали одно из двух решений: 1) изменить все в базе данных на NVARCHAR или 2) установить sendStringParametersAsUnicode = false,
Тогда мой вопрос заключается в том, есть ли какое-нибудь известное решение для того, чтобы VARCHAR и NVARCHAR играли хорошо вместе? Это проблема огромная для моей среды, чтобы изменить все на NVARCHAR из-за зависимостей нисходящего потока и других внешних проблем.
Ответы
Ответ 1
Я решил попробовать это как хак, который может работать, не касаясь базы данных. Для этого я создал пользовательский тип для полей NVARCHAR. Для этого требуются драйверы JDBC 4 (с использованием Microsoft) и Hibernate 3.6.0. Значение параметра sendStringParametersAsUnicode является ложным.
Здесь подход, я все еще проверяю его правильность - любые комментарии от людей с большим опытом, чем я рад
Добавить новый диалог для поддержки нового типа данных
public class SQLAddNVarCharDialect extends SQLServerDialect {
public SQLAddNVarCharDialect(){
super();
registerColumnType( Types.NVARCHAR, 8000, "nvarchar($1)" );
registerColumnType( Types.NVARCHAR, "nvarchar(255)" );
}
}
Добавьте новый тип. Обратите внимание на setNString
в nullSafeSet
public class NStringUserType implements UserType {
@Override
public Object assemble(Serializable arg0, Object owner)
throws HibernateException {
return deepCopy(arg0);
}
@Override
public Object deepCopy(Object arg0) throws HibernateException {
if(arg0==null) return null;
return arg0.toString();
}
@Override
public Serializable disassemble(Object arg0) throws HibernateException {
return (Serializable)deepCopy(arg0);
}
@Override
public boolean equals(Object arg0, Object arg1) throws HibernateException {
if(arg0 == null )
return arg1 == null;
return arg0.equals(arg1);
}
@Override
public int hashCode(Object arg0) throws HibernateException {
return arg0.hashCode();
}
@Override
public boolean isMutable() {
return false;
}
@Override
public void nullSafeSet(PreparedStatement st, Object value, int index)
throws HibernateException, SQLException {
if(value == null)
st.setNull(index,Types.NVARCHAR);
else
st.setNString(index, value.toString());
}
@Override
public Object replace(Object arg0, Object target, Object owner)
throws HibernateException {
return deepCopy(arg0);
}
@Override
public Class returnedClass() {
return String.class;
}
@Override
public int[] sqlTypes() {
return new int[]{Types.NVARCHAR};
}
@Override
public Object nullSafeGet(ResultSet resultSet, String[] names, Object owner)
throws HibernateException, SQLException {
String result = resultSet.getString(names[0]);
return result == null || result.trim().length() == 0
? null : result;
}
}
Обновить сопоставления для всех полей NVARCHAR
<property name="firstName" type="NStringUserType">
<column name="firstName" length="40" not-null="false" />
</property>
Необработанный SQL до (с помощью sendUnicode.. = true):
exec sp_prepexec @p1 output,N'@P0 nvarchar(4000),@P1 datetime,@P2 varchar(8000),@P3 nvarchar(4000),@P4 nvarchar(4000),@P5 nvarchar(4000),@P6 nvarchar(4000)... ,N'update Account set ... where [email protected]
И после:
exec sp_prepexec @p1 output,N'@P0 varchar(8000),@P1 .... @P6 nvarchar(4000),@P7 ... ,N'update Account set ... [email protected], [email protected], [email protected] ... where [email protected]
Кажется, работает аналогично для "SELECT.."
Ответ 2
public class SQLServerUnicodeDialect extends org.hibernate.dialect.SQLServerDialect {
public SQLServerUnicodeDialect() {
super();
registerColumnType(Types.CHAR, "nchar(1)");
registerColumnType(Types.LONGVARCHAR, "nvarchar(max)" );
registerColumnType(Types.VARCHAR, 4000, "nvarchar($l)");
registerColumnType(Types.VARCHAR, "nvarchar(max)");
registerColumnType(Types.CLOB, "nvarchar(max)" );
registerColumnType(Types.NCHAR, "nchar(1)");
registerColumnType(Types.LONGNVARCHAR, "nvarchar(max)");
registerColumnType(Types.NVARCHAR, 4000, "nvarchar($l)");
registerColumnType(Types.NVARCHAR, "nvarchar(max)");
registerColumnType(Types.NCLOB, "nvarchar(max)");
registerHibernateType(Types.NCHAR, StandardBasicTypes.CHARACTER.getName());
registerHibernateType(Types.LONGNVARCHAR, StandardBasicTypes.TEXT.getName());
registerHibernateType(Types.NVARCHAR, StandardBasicTypes.STRING.getName());
registerHibernateType(Types.NCLOB, StandardBasicTypes.CLOB.getName() );
}
}
Ответ 3
Одна мысль..
Скрыть ваши столбцы varchar за индексированными представлениями. Представления, наложенные на nvarchar. Это позволяет вам поддерживать 2 интерфейса по тем же данным.
То же самое применяется другим способом... использовать представления для вашего нисходящего потока, но они передаются в varchar (все ваши таблицы теперь nvarchar). В этом случае нет необходимости индексировать их. Предложение WHERE с значением varchar (по сравнению с столбцом nvarchar) будет расширено до nvarchar, и индекс будет использоваться
Ответ 4
Это меньше проблем с гибернацией, чем то, как работают драйверы JDBC. На практике я думаю, что единственная проблема, которая возникнет (кроме очевидного повреждения данных, если вы пишете данные Unicode в столбце varchar) - это когда вы запрашиваете попытку сопоставления строки.
SQL Server будет неявно преобразовывать nvarchar в varchar в SQL-запросе ok, но когда вы запускаете запрос со строкой в предложении where, он не найдет существующие индексы, если типы не соответствуют точно.
Итак, например
SELECT * FROM Person WHERE last_name = N'Smith'
приведет к сканированию таблицы, если поле last_name определено как varchar, и там есть индекс.
Другим обходным решением для этой проблемы производительности является использование хранимых процедур для преобразования типов перед выполнением запроса.
Ответ 5
-
Копировать классы StringNVarcharType.java и NVarcharTypeDescriptor.java из hibernate-core 4.3.0.Final.
-
Содержимое StringNVarcharType.hbm.xml
-
Используйте следующие зависимости в Maven:
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5-pre6</version> <!-- Make sure you don't use the default dependency version found in hibernate-c3p0! -->
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-c3p0</artifactId>
<version>3.6.10.Final</version>
<exclusions>
<exclusion>
<artifactId>c3p0</artifactId>
<groupId>c3p0</groupId>
</exclusion>
</exclusions>
</dependency>
-
Сделайте спящий режим информацией о отображении:
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<mapping resource="StringNVarcharType.hbm.xml" />
<!-- Continue with your other mappings here -->
</session-factory>
</hibernate-configuration>
-
Используйте тип свойства nstring в ваших файлах сопоставления *.hbm.xml, где у вас есть столбцы базы данных nvarchar2.
Литература: