Подтвердить что ты не робот

Получение 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 из-за зависимостей нисходящего потока и других внешних проблем.

4b9b3361

Ответ 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.

Литература: