使用 PostgreSQL 和 SQL Server 休眠 UUID

Bre*_*man 5 java sql-server postgresql uuid hibernate

我有一个应用程序,我想在 PostgreSQL 和 SQL Server 上运行。我想使用 java.util.UUID 作为 ID。

我已将 SQL Server 中的列定义为

id  UNIQUEIDENTIFIER ROWGUIDCOL NOT NULL UNIQUE
Run Code Online (Sandbox Code Playgroud)

我已将 PostgreSQL 中的列定义为

id  UUID NOT NULL
Run Code Online (Sandbox Code Playgroud)

这些列在我的 JPA 实体中定义为

@Id
@Column(name = "id")
public UUID getId() {
    return id;
}
Run Code Online (Sandbox Code Playgroud)

这适用于 PostgreSQL,因为它将 UUID 传递给 PostgreSQL JDBC 驱动程序。这种方式适用于 SQL Server,因为 Hibernate 在将 UUID 发送到 SQL Server 之前将其转换为其二进制形式。不幸的是,二进制格式略有不同,导致 GUID 的字符串表示(例如,使用 SSMS 查看它们时)不同,这至少是令人困惑的。

这可以在 SQL Server 中通过将列的类型更改为 uuid-char 来解决

@Id
@Type(type = "uuid-char")
@Column(name = "id")
public UUID getId() {
    return id;
}
Run Code Online (Sandbox Code Playgroud)

然而,它在 PostgreSQL 中不再起作用,因为在 Postgres 中没有从 varchar 到 uuid 的隐式映射。

有些人建议更改生成器以生成 guid。这在 Postgres 中不起作用,因为 PostgreSQL94Dialect 不支持它。

为这两个数据库制作这个词的最优雅的解决方案是什么?我正在考虑使用从 UUID 到二进制的自定义转换为 SQLServer 创建我自己的方言,但我不确定这是要走的路。

Jon*_*n N 6

我有类似的需求,但我也想使用 Hibernate DDL 生成并确保 SQL Server 生成 uniqueidentifier 类型,并且还想支持 hsqldb 进行单元测试。要在 SQL Server 中使用 UUID,您肯定需要使用字符串而不是二进制,因为 SQL Server 中的二进制表示形式用于与 UUID 不同的 GUID。例如,请参阅Java Hibernate 和 SQL Server 中 UUID 的不同表示形式

您可以使用以下命令创建正确的映射并为 SQL Server 生成正确的 sql:

@Type(type = "uuid-char")
@Column(columnDefinition="uniqueidentifier")
public UUID getUuid()
Run Code Online (Sandbox Code Playgroud)

这只是按原样工作,但不幸的是,Hibernate 中无法为不同的数据库提供不同的列定义,因此这在除 SQL Server 之外的任何其他数据库上都会失败。

所以,你必须走很远的路。这是 Hibernate 4.3 的全部内容:

  1. 在重写的 SQLServerDialect 中注册新的 GUID sql 类型,并使用此方言而不是基本方言

    public class SQLServer2008UnicodeDialect extends SQLServer2008Dialect
    {
        public SQLServer2008UnicodeDialect() 
        {
            // the const is from the MS JDBC driver, the value is -145
            registerColumnType( microsoft.sql.Types.GUID, "uniqueidentifier" ); 

            // etc. Bonus hint: I also remap all the varchar types to nvarchar while I'm at it, like so:
            registerColumnType( Types.CLOB, "nvarchar(MAX)" );
            registerColumnType( Types.LONGVARCHAR, "nvarchar(MAX)" );
            registerColumnType( Types.LONGNVARCHAR, "nvarchar(MAX)" );
            registerColumnType( Types.VARCHAR, "nvarchar(MAX)" );
            registerColumnType( Types.VARCHAR, 8000, "nvarchar($l)" );
        }
     }
Run Code Online (Sandbox Code Playgroud)
  1. 创建一个包装器 UUIDCustomType,其行为类似于内置的 UUIDCharType,但根据数据库类型进行委托。您需要在Hibernate 配置之前调用 init(databaseType) 。也许自定义方言可以做到这一点,但我在 Spring 应用程序启动中调用它。

DatabaseType 是我已经根据系统配置设置的枚举,可以使用方言类或字符串或其他内容对其进行修改以适应口味。

这是https://zorq.net/b/2012/04/21/switching-hibernates-uuid-type-mapping-per-database/中描述的内容的变体

public enum DatabaseType
{
    hsqldb,
    sqlserver,
    mysql,
    postgres
}

public class UUIDCustomType extends AbstractSingleColumnStandardBasicType<UUID> implements LiteralType<UUID>
{
    private static final long serialVersionUID = 1L;

    private static SqlTypeDescriptor SQL_DESCRIPTOR;
    private static JavaTypeDescriptor<UUID> TYPE_DESCRIPTOR;

    public static void init( DatabaseType databaseType )
    {
        if ( databaseType == DatabaseType.sqlserver )
        {
            SQL_DESCRIPTOR = SqlServerUUIDTypeDescriptor.INSTANCE;
        }
        else if ( databaseType == DatabaseType.postgres  )
        {
            SQL_DESCRIPTOR = PostgresUUIDType.PostgresUUIDSqlTypeDescriptor.INSTANCE;
        }
        else
        {
            SQL_DESCRIPTOR = VarcharTypeDescriptor.INSTANCE;
        }

        TYPE_DESCRIPTOR = UUIDTypeDescriptor.INSTANCE;
    }

    public UUIDCustomType()
    {
        super( SQL_DESCRIPTOR, TYPE_DESCRIPTOR );
    }

    @Override
    public String getName()
    {
        return "uuid-custom";
    }

    @Override
    public String objectToSQLString( UUID value, Dialect dialect ) throws Exception
    {
        return StringType.INSTANCE.objectToSQLString( value.toString(), dialect );
    }

    public static class SqlServerUUIDTypeDescriptor extends VarcharTypeDescriptor
    {
        private static final long serialVersionUID = 1L;

        public static final SqlServerUUIDTypeDescriptor INSTANCE = new SqlServerUUIDTypeDescriptor();

        public SqlServerUUIDTypeDescriptor()
        {
        }

        @Override
        public int getSqlType()
        {
            return microsoft.sql.Types.GUID;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)
  1. 在 Hibernate 将拾取的位置注册自定义类型(我对所有实体都有一个公共基类)。我使用 defaultForType = UUID.class 注册它,以便所有 UUID 都使用它,这意味着我根本不需要注释 UUID 属性。

    @TypeDefs( {
            @TypeDef( name = "uuid-custom", typeClass = UUIDCustomType.class, defaultForType = UUID.class )
    } )
    public class BaseEntityWithId { 
Run Code Online (Sandbox Code Playgroud)

警告:实际上没有使用 postgres 进行测试,但在 Hibernate 4.3 上的 hsqldb 和 sql server 上工作得很好。


Bre*_*man 1

我最终编写了自己的 Dialect 和 BasicType,默认将 Java 类型 java.util.UUID 链接到 uuid-char。

此类的灵感来自 PostgreSQLDialect

public class MySQLServerDialect extends SQLServer2012Dialect {

    public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
        super.contributeTypes( typeContributions, serviceRegistry );

        typeContributions.contributeType( SQLServerUUIDType.INSTANCE );
    }
}
Run Code Online (Sandbox Code Playgroud)

该类的灵感来自于 UUIDCharType

public class SQLServerUUIDType extends AbstractSingleColumnStandardBasicType<UUID> implements LiteralType<UUID> {
    public static final SQLServerUUIDType INSTANCE = new SQLServerUUIDType();

    public SQLServerUUIDType() {
        super( VarcharTypeDescriptor.INSTANCE, UUIDTypeDescriptor.INSTANCE );
    }

    @Override
    protected boolean registerUnderJavaType() {
        return true;
    }

    public String getName() {
        return "my-uuid";
    }

    public String objectToSQLString(UUID value, Dialect dialect) throws Exception {
        return StringType.INSTANCE.objectToSQLString( value.toString(), dialect );
    }
}
Run Code Online (Sandbox Code Playgroud)

我不认为这是最优雅的解决方案,但它目前有效。