我如何在 Java 中复制 PostgreSQL 的 uuid_generate_v3()?
How do I replicate PostgreSQL's uuid_generate_v3() in Java?
PostgreSQL:
create extension if not exists "uuid-ossp";
select uuid_generate_v3(uuid_nil(), 'this is a test');
uuid_generate_v3
--------------------------------------
e1e27115-9f5b-366d-90e8-e07b1b36b99c
(1 row)
Java:
java> java.util.UUID.nameUUIDFromBytes("this is a test".getBytes());
java.util.UUID res9 = 54b0c58c-7ce9-32a8-b551-351102ee0938
如何在 Java 中生成与 PostgreSQL 相同的 UUID?
据我所知,没有直接的方法,您必须深入研究 PostgreSQL(及其库)的源代码并自己在 Java 中复制算法,或本机调用相同的库函数。一个更简单的选择是让 PostgreSQL 数据库为您生成它们,但我认为由于某种原因这是不可能的。
正如您从 the documentation, PostgreSQL takes an MD5 hash first to protect against reverse-engineering. They then use a UUID generator from the OSSP UUID 库中看到的那样。
我试过了:
java.util.UUID.nameUUIDFromBytes(Md5Utils.getMd5Digest("this is a test"));
但是产生了:
d69495fb-d538-3991-b96b-aa262ab6dce5
虽然这不是原始问题的答案,但我不得不以另一种方式进行此转换,即在迁移期间模仿 PostgreSQL 中的 UUID.nameUUIDFromBytes
行为:
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
CREATE EXTENSION IF NOT EXISTS "plpgsql";
CREATE OR REPLACE FUNCTION uuidv3(bytes bytea)
RETURNS uuid
RETURNS null on null input
STABLE
PARALLEL SAFE
AS $$
DECLARE
md5bytes bytea;
BEGIN
md5bytes := digest(bytes, 'md5');
-- md5Bytes[6] &= 0x0f; /* clear version */
-- md5Bytes[6] |= 0x30; /* set to version 3 */
md5bytes := set_byte(md5bytes, 6, (get_byte(md5bytes, 6) & x'0F'::int) | x'30'::int);
-- md5Bytes[8] &= 0x3f; /* clear variant */
-- md5Bytes[8] |= 0x80; /* set to IETF variant */
md5bytes := set_byte(md5bytes, 8, (get_byte(md5bytes, 8) & x'3F'::int)| x'80'::int);
RETURN (encode(md5bytes, 'hex'))::uuid;
END
$$
LANGUAGE plpgsql;
并且您应该使用内部 textsend
将文本列转换为 bytea
:
SELECT uuidv3(textsend('this is a test'));
虽然我必须承认我从未尝试过另一种方式。
此处描述了生成版本 3 UUID 的算法https://www.rfc-editor.org/rfc/rfc4122#section-4.3
但关键步骤是:
- 分配一个 UUID 用作所有 UUID 的“名称 space ID”
从该名称中的名称生成 space.
- 选择 MD5 或 SHA-1 作为哈希算法
- 将名称转换为规范的八位字节序列
- 计算名称 space ID 与名称连接的哈希值。
- 将 的某些字节更改为预定义值(参见上面的 link)
- 将生成的 UUID 转换为本地字节顺序。
postgres 函数签名是 uuid_generate_v3(namespace uuid, name text)
所以它以名称 space UUID 和 name
作为参数。
Java 方法 nameUUIDFromBytes(byte[] name)
仅采用 name
并使用 MD5 对其进行哈希处理以创建 UUID。要获得与 PostgreSQL 相同的输出,您必须自己将 namespace 字节和 name
字节连接在一起。
对于名称space,您使用了 uuid_nil()
(全为零),即 Java.
中的 new UUID(0L, 0L)
将它们放在一起看起来像这样:
byte[] bytes = Arrays.concatenate(toByteArray(new UUID(0L, 0L)), "this is a test".getBytes(StandardCharsets.UTF_8));
System.out.println(UUID.nameUUIDFromBytes(bytes)); // prints out e1e27115-9f5b-366d-90e8-e07b1b36b99c
并且您可以将名称space UUID 转换为字节数组,如下所示:
private static byte[] toByteArray(UUID uuid) {
ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
bb.putLong(uuid.getMostSignificantBits());
bb.putLong(uuid.getLeastSignificantBits());
return bb.array();
}
这是一个使用 System.arraycopy()
连接命名空间和名称的示例。
package com.example;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
public class Example {
/**
* Returns a name-based UUID using NIL UUID as namespace.
*
* @param name a string
* @return a UUID
*/
public static UUID getNameBasedUsingNil(String name) {
// 1. Get NIL and NAME bytes
final byte[] nil = new byte[16]; // NIL namespace
final byte[] nam = name.getBytes(StandardCharsets.UTF_8);
// 2. Concatenate NIL and NAME bytes
final byte[] bytes = new byte[nil.length + nam.length];
System.arraycopy(nil, 0, bytes, 0, 16);
System.arraycopy(nam, 0, bytes, 16, nam.length);
// 3. Generate a name-based UUID
return UUID.nameUUIDFromBytes(bytes);
}
public static void main(String[] args) {
String name = "this is a test";
UUID uuid = getNameBasedUsingNil(name);
System.out.println(uuid);
}
}
输出:
e1e27115-9f5b-366d-90e8-e07b1b36b99c
PostgreSQL:
create extension if not exists "uuid-ossp";
select uuid_generate_v3(uuid_nil(), 'this is a test');
uuid_generate_v3
--------------------------------------
e1e27115-9f5b-366d-90e8-e07b1b36b99c
(1 row)
Java:
java> java.util.UUID.nameUUIDFromBytes("this is a test".getBytes());
java.util.UUID res9 = 54b0c58c-7ce9-32a8-b551-351102ee0938
如何在 Java 中生成与 PostgreSQL 相同的 UUID?
据我所知,没有直接的方法,您必须深入研究 PostgreSQL(及其库)的源代码并自己在 Java 中复制算法,或本机调用相同的库函数。一个更简单的选择是让 PostgreSQL 数据库为您生成它们,但我认为由于某种原因这是不可能的。
正如您从 the documentation, PostgreSQL takes an MD5 hash first to protect against reverse-engineering. They then use a UUID generator from the OSSP UUID 库中看到的那样。
我试过了:
java.util.UUID.nameUUIDFromBytes(Md5Utils.getMd5Digest("this is a test"));
但是产生了:
d69495fb-d538-3991-b96b-aa262ab6dce5
虽然这不是原始问题的答案,但我不得不以另一种方式进行此转换,即在迁移期间模仿 PostgreSQL 中的 UUID.nameUUIDFromBytes
行为:
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
CREATE EXTENSION IF NOT EXISTS "plpgsql";
CREATE OR REPLACE FUNCTION uuidv3(bytes bytea)
RETURNS uuid
RETURNS null on null input
STABLE
PARALLEL SAFE
AS $$
DECLARE
md5bytes bytea;
BEGIN
md5bytes := digest(bytes, 'md5');
-- md5Bytes[6] &= 0x0f; /* clear version */
-- md5Bytes[6] |= 0x30; /* set to version 3 */
md5bytes := set_byte(md5bytes, 6, (get_byte(md5bytes, 6) & x'0F'::int) | x'30'::int);
-- md5Bytes[8] &= 0x3f; /* clear variant */
-- md5Bytes[8] |= 0x80; /* set to IETF variant */
md5bytes := set_byte(md5bytes, 8, (get_byte(md5bytes, 8) & x'3F'::int)| x'80'::int);
RETURN (encode(md5bytes, 'hex'))::uuid;
END
$$
LANGUAGE plpgsql;
并且您应该使用内部 textsend
将文本列转换为 bytea
:
SELECT uuidv3(textsend('this is a test'));
虽然我必须承认我从未尝试过另一种方式。
此处描述了生成版本 3 UUID 的算法https://www.rfc-editor.org/rfc/rfc4122#section-4.3
但关键步骤是:
- 分配一个 UUID 用作所有 UUID 的“名称 space ID” 从该名称中的名称生成 space.
- 选择 MD5 或 SHA-1 作为哈希算法
- 将名称转换为规范的八位字节序列
- 计算名称 space ID 与名称连接的哈希值。
- 将 的某些字节更改为预定义值(参见上面的 link)
- 将生成的 UUID 转换为本地字节顺序。
postgres 函数签名是 uuid_generate_v3(namespace uuid, name text)
所以它以名称 space UUID 和 name
作为参数。
Java 方法 nameUUIDFromBytes(byte[] name)
仅采用 name
并使用 MD5 对其进行哈希处理以创建 UUID。要获得与 PostgreSQL 相同的输出,您必须自己将 namespace 字节和 name
字节连接在一起。
对于名称space,您使用了 uuid_nil()
(全为零),即 Java.
new UUID(0L, 0L)
将它们放在一起看起来像这样:
byte[] bytes = Arrays.concatenate(toByteArray(new UUID(0L, 0L)), "this is a test".getBytes(StandardCharsets.UTF_8));
System.out.println(UUID.nameUUIDFromBytes(bytes)); // prints out e1e27115-9f5b-366d-90e8-e07b1b36b99c
并且您可以将名称space UUID 转换为字节数组,如下所示:
private static byte[] toByteArray(UUID uuid) {
ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
bb.putLong(uuid.getMostSignificantBits());
bb.putLong(uuid.getLeastSignificantBits());
return bb.array();
}
这是一个使用 System.arraycopy()
连接命名空间和名称的示例。
package com.example;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
public class Example {
/**
* Returns a name-based UUID using NIL UUID as namespace.
*
* @param name a string
* @return a UUID
*/
public static UUID getNameBasedUsingNil(String name) {
// 1. Get NIL and NAME bytes
final byte[] nil = new byte[16]; // NIL namespace
final byte[] nam = name.getBytes(StandardCharsets.UTF_8);
// 2. Concatenate NIL and NAME bytes
final byte[] bytes = new byte[nil.length + nam.length];
System.arraycopy(nil, 0, bytes, 0, 16);
System.arraycopy(nam, 0, bytes, 16, nam.length);
// 3. Generate a name-based UUID
return UUID.nameUUIDFromBytes(bytes);
}
public static void main(String[] args) {
String name = "this is a test";
UUID uuid = getNameBasedUsingNil(name);
System.out.println(uuid);
}
}
输出:
e1e27115-9f5b-366d-90e8-e07b1b36b99c