Получение Hibernate и SQL Server для приятной игры с VARCHAR и NVARCHAR - PullRequest
13 голосов
/ 08 марта 2011

В настоящее время я включаю символы UTF-8 в некоторых таблицах большой базы данных. Эти таблицы уже имеют тип MS-SQL NVARCHAR. Кроме того, у меня есть несколько полей, использующих VARCHAR.

Существует хорошо известная проблема взаимодействия Hibernate с драйвером JDBC (см., Например, Отображение в varchar и nvarchar в hibernate ). Короче говоря, Hibernate / JDBC генерирует SQL, который передает все строки как Unicode, независимо от базового типа SQL. Когда поле не-Unicode (varchar) в базе данных сравнивается с входной строкой Unicode, индикаторы для этого столбца не соответствуют кодировке, поэтому выполняется полное сканирование таблицы. В драйвере JDBC (версии JTDS и MS) есть параметр для передачи строк Unicode как ASCII, но это предложение типа «все или ничего», запрещающее ввод международных символов в базу данных.

Большинство сообщений, которые я видел по этой проблеме, содержат одно из двух решений: 1) изменить все в базе данных на NVARCHAR или 2) установить sendStringParametersAsUnicode = false, У меня такой вопрос: есть ли какое-нибудь известное решение для того, чтобы VARCHAR и NVARCHAR хорошо играли вместе? Это огромная проблема для моей среды, чтобы изменить все на NVARCHAR из-за нисходящих зависимостей и других внешних проблем.

Ответы [ 5 ]

4 голосов
/ 12 марта 2013
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 голосов
/ 08 марта 2011

Одна мысль ..

Скрыть ваши столбцы varchar за индексированными представлениями. Взгляды, брошенные на nvarchar. Это позволяет поддерживать 2 интерфейса для одних и тех же данных.

То же самое относится и к другому ... используйте представления для нижестоящих вещей, но они приводятся к varchar (все ваши таблицы теперь nvarchar). В этом случае не было бы необходимости индексировать их. Предложение WHERE со значением varchar (по сравнению со столбцом nvarchar) будет расширено до nvarchar и будет использоваться индекс

2 голосов
/ 09 марта 2011

Я решил попробовать как хак, что может работать, не касаясь базы данных. Для этого я создал пользовательский тип для полей NVARCHAR. Для этого требуются драйверы JDBC 4 (используя драйверы от Microsoft) и Hibernate 3.6.0. SendStringParametersAsUnicode имеет значение false.

Вот подход, я все еще проверяю его правильность - любые комментарии от людей с большим опытом, чем я приветствую

Добавление нового диалекта для поддержки нового типа данных

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 AccountId=@P35    

и после:

 exec sp_prepexec @p1 output,N'@P0 varchar(8000),@P1  .... @P6 nvarchar(4000),@P7 ... ,N'update Account set ... Validated=@P4, prefix=@P5, firstName=@P6 ... where AccountId=@P35    

Кажется, работает аналогично для "SELECT .."

1 голос
/ 29 января 2014
  1. Копировать классы StringNVarcharType.java и NVarcharTypeDescriptor.java из hibernate-core 4.3.0.Final.

  2. StringNVarcharType.hbm.xml содержимое

  3. Используйте следующие зависимости в 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>
    
  4. Информируйте hibernate о сопоставлении:

    <!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>
    
  5. Используйте тип свойства nstring в файлах сопоставлений * .hbm.xml, в которых есть типы столбцов базы данных nvarchar2.

Ссылки:

  1. http://alenovarini.wikidot.com/mapping-a-custom-type-in-hibernate
  2. http://blog.xebia.com/2009/11/09/understanding-and-writing-hibernate-user-types/
1 голос
/ 08 марта 2011

Это менее серьезная проблема Hibernate, чем то, как работают драйверы JDBC.На практике я думаю, что единственная проблема, которая возникнет (кроме очевидного повреждения данных, если вы записываете данные Unicode в столбец varchar), - это когда вы пытаетесь сопоставить строку.

SQL Server будет неявноПреобразуйте nvarchar в varchar в SQL-выражении, но при запуске запроса со строкой в ​​предложении where он не найдет существующие индексы, если типы не совпадают точно.

Так, например,

SELECT * FROM Person WHERE last_name = N'Smith'

приведет к сканированию таблицы, если поле last_name определено как varchar и есть индекс для него.

Еще один обходной путь для этой проблемы производительности - использование хранимых процедур для преобразования типов.перед выполнением запроса.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...