Java 让Hibernate和SQL Server更好地使用VARCHAR和NVARCHAR

Java 让Hibernate和SQL Server更好地使用VARCHAR和NVARCHAR,java,sql-server,hibernate,jdbc,Java,Sql Server,Hibernate,Jdbc,我目前正在一个大型数据库的一些表中启用UTF-8字符。这些表已经是MS-SQL类型的NVARCHAR。此外,我还有几个使用VARCHAR的字段 Hibernate与JDBC驱动程序的交互有一个众所周知的问题(参见示例)。简而言之,Hibernate/JDBC生成的SQL将所有字符串作为Unicode传递,而不管底层SQL类型如何。将数据库中的非unicode(varchar)字段与unicode输入字符串进行比较时,该列的标记与编码不匹配,因此将执行完整的表扫描。在JDBC驱动程序(JTDS和M

我目前正在一个大型数据库的一些表中启用UTF-8字符。这些表已经是MS-SQL类型的NVARCHAR。此外,我还有几个使用VARCHAR的字段

Hibernate与JDBC驱动程序的交互有一个众所周知的问题(参见示例)。简而言之,Hibernate/JDBC生成的SQL将所有字符串作为Unicode传递,而不管底层SQL类型如何。将数据库中的非unicode(varchar)字段与unicode输入字符串进行比较时,该列的标记与编码不匹配,因此将执行完整的表扫描。在JDBC驱动程序(JTDS和MS版本)中,有一个参数将Unicode字符串作为ASCII传递,但这是一个全有或全无的命题,不允许将国际字符输入数据库

我在这个问题上看到的大多数帖子都提出了两种解决方案之一——1)将数据库中的所有内容都更改为NVARCHAR或2)将sendStringParametersAsUnicode设置为false, 那么我的问题是——有没有已知的解决方案让VARCHAR和NVARCHAR配合得很好?由于下游依赖关系和其他外部问题,将所有内容更改为NVARCHAR对我的环境来说是一个巨大的问题。

一个想法

将varchar列隐藏在索引视图后面。投给nvarchar的视图。这允许您在同一数据上维护两个接口


这同样适用于另一种情况。。。对下游内容使用视图,但这些视图转换为varchar(所有表现在都是nvarchar)。在这种情况下,不需要为它们编制索引。带有varchar值的WHERE子句(与nvarchar列相比)将扩展为nvarchar,并且将使用索引。与JDBC驱动程序的工作方式相比,这不是一个Hibernate问题。在实践中,我认为唯一会出现的问题(除了将Unicode数据写入varchar列时明显的数据损坏之外)是在查询字符串时尝试匹配

SQL Server将在SQL语句ok中将nvarchar隐式转换为varchar,但当您在where子句中使用字符串运行查询时,如果类型不完全匹配,它将找不到现有索引

比如说

SELECT * FROM Person WHERE last_name = N'Smith'
如果last_name字段被定义为varchar并且有索引,则将导致表扫描


解决此性能问题的另一个解决方法是在执行查询之前使用存储过程进行类型转换。

我决定尝试使用此方法作为一种不接触数据库的黑客攻击。为此,我为NVARCHAR字段创建了一个自定义类型。这需要JDBC4驱动程序(使用微软的驱动程序)和Hibernate3.6.0。sendStringParametersAsUnicode为false

这里是方法,我仍然在验证它的正确性——欢迎来自经验比我丰富的人的任何评论

添加新方言以支持新数据类型

public class SQLAddNVarCharDialect extends SQLServerDialect {

    public SQLAddNVarCharDialect(){
        super();

        registerColumnType( Types.NVARCHAR, 8000, "nvarchar($1)" );     
        registerColumnType( Types.NVARCHAR,  "nvarchar(255)" );     
    }
}
添加新类型。注意
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>    
及之后:

 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    
似乎对“选择…”也有类似的作用

公共类SQLServerUnicodeDialect扩展了org.hibernate.dial.SQLServerDialect{ 公共SQLServerUnicodeDialect(){ 超级(); 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()); } }
  • 从hibernate core 4.3.0.Final复制StringNVarcharType.java和NVarcharTypeDescriptor.java类

  • 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>
    
    
    com.mchange
    

  • 我遇到了这个问题,这是解决这个问题的最简单方法。只需将以下参数添加到连接字符串中: sendStringParametersAsUnicode=false


    实际上,“com.microsoft.sqlserver.jdbc.Parameter#getSSPAUJDBCType“负责将每个字符串转换为NVARCHAR,您可以忽略此情况。

    这可能可行,但使用存储过程在很大程度上破坏了使用Hibernate的许多好处:)我完全同意。我怀疑如果您想在混合的varchar/nvarchar环境中使用Hibernate,它将带来很多困难。使用视图中的数据类型转换来转换架构和解决下游依赖关系可能更简单。此外,还应选择特定的SQLServerDialogue类(例如SQLServer2008的SQLServerDialog2008类) 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() ); } }
    <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>