关于java:使用PostgreSQL和SQL Server的Hibernate UUID

Hibernate UUID with PostgreSQL and SQL Server

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

我在SQL Server中将列定义为

1
id  UNIQUEIDENTIFIER ROWGUIDCOL NOT NULL UNIQUE

我在PostgreSQL中将我的列定义为

1
id  UUID NOT NULL

列在我的JPA实体中定义为

1
2
3
4
5
@Id
@Column(name ="id")
public UUID getId() {
    return id;
}

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

通过将列的类型更改为uuid char,可以在SQL Server中纠正此问题。

1
2
3
4
5
6
@Id
@Type(type ="uuid-char")
@Column(name ="id")
public UUID getId() {
    return id;
}

然而,由于postgres中没有从varchar到uuid的隐式映射,所以它不再在postgresql中工作。

有些人建议更改生成器以生成guid。这在Postgres中不起作用,因为Postgresql94方言中不支持这一点。

对于这两个数据库来说,最优雅的解决方案是什么?我在考虑为sqlserver创建自己的方言,用一个从uuid到binary的自定义转换,但我不确定这是怎么做的。


我有类似的要求,但我也希望使用HibernateDDL生成,并确保SQL Server生成了唯一标识符类型,还希望支持hsqldb进行单元测试。要在SQL Server中使用UUID,您肯定希望遍历字符串而不是二进制字符串,因为SQL Server中的二进制表示是针对不同于UUID的GUID的。例如,在Java Hibernate和SQL Server中UUID的不同表示

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

1
2
3
@Type(type ="uuid-char")
@Column(columnDefinition="uniqueidentifier")
public UUID getUuid()

但不幸的是,在Hibernate中,无法为不同的数据库使用不同的列定义,所以这在除SQL Server之外的任何数据库上都将失败。

所以,你必须走很远的路。这一切都是为了休眠4.3:

  • 在重写的SQLServerDialect中注册新的GUID SQL类型,并使用此方言而不是基方言
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
        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)" );
            }
         }
  • 创建一个包装UuidCustomType,其行为类似于内置UuidCharType,但根据数据库类型委托。您需要在休眠配置之前调用init(databasetype)。也许自定义方言可以做到这一点,但我在我的Spring应用程序启动中称之为它。
  • databasetype是一个枚举,我已经根据系统配置设置了这个枚举,使用方言类或字符串或其他任何东西修改它来品尝它。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    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;
            }
        }
    }
  • 在Hibernate将接收的位置注册自定义类型(我有一个所有实体的公共基类)。我使用defaultfortype=uuid.class注册它,以便所有uuid都使用它,这意味着我根本不需要注释uuid属性。
  • 1
    2
    3
    4
        @TypeDefs( {
                @TypeDef( name ="uuid-custom", typeClass = UUIDCustomType.class, defaultForType = UUID.class )
        } )
        public class BaseEntityWithId {

    警告:实际上不是用Postgres测试的,但是在Hibernate4.3上对hsqldb和SQL Server非常有用。


    最后,我编写了自己的方言和Basic类型,将Java类型java. UTIL uuID默认链接到uuID char。

    这门课的灵感来源于PostgresqlDialect

    1
    2
    3
    4
    5
    6
    7
    8
    public class MySQLServerDialect extends SQLServer2012Dialect {

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

            typeContributions.contributeType( SQLServerUUIDType.INSTANCE );
        }
    }

    这个类的灵感来自于UuidCharType

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    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 );
        }
    }

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