让Hibernate和SQL Server与VARCHAR和NVARCHAR一起玩得很好

2022-09-04 03:05:03

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

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

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


答案 1
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() );
    }
}

答案 2

我决定尝试将其作为一种可以在不接触数据库的情况下起作用的黑客。为此,我为 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)" );     
    }
}

添加新类型。注意setNStringnullSafeSet

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>    

Raw SQL before (with sendUnicode..=真):

 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..”也类似。


推荐