SQL Server 自 2008 版起引入了 uniqueidentifier
字段,它存储的是一个 UUID, 或者叫 GUID,内部存储为 16 个字节。SQL Server 可用两个函数来生成 uniqueidentifier
, 分别是 NEWID()
和 NEWSEQUENTIALID()
, 后者只能用作字段的默认值。Java 也有一个 UUID 工具类 java.uti.UUID
, UUID.randomUUID().toString()
生成一个随机的 UUID 字符串,在 java.util.UUID
也是用两个 long
字段表示内部状态。
SQL Server 的 uniqueidentifier
类型字段表明了内部如何存储,在我们操作它时,它的外在表现形式都是一个固定格式 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
的字符串,不区分大小写的。
本文所使用的 SQL Server 是 2017 版,通过 Docker 来启动的
docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=yourStrong(!)Password' -p 1433:1433 -d microsoft/mssql-server-linux:2017-latest
然后我们创建一个带有 uniqueidentifier
类型字段的表
1 2 3 4 |
CREATE TABLE customers ( id UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(), name VARCHAR(36) ) |
默认值也可以指定为 NEWSEQUENTIALID()
, uniqueidentifier
字段不指定默认值也没问题。上面把 id 设置为主键,接下为了验证大小写的问题。
关于 NEWID()
与 NEWSEQUENTIALID()
的一个显著区别是
SELECT NEWID(); --这句是合法的
SELECT NEWSEQUENTIALID(); -- 这条语句出错,提示该函数只能用作创建表时 uniquidentifier 类型字段的默认值
用 SQL 语句插入
1 2 3 4 5 6 |
insert into customers(id, name) values(newid(), 'Hello'); insert into customers(id, name) values('A972C577-DFB0-064E-1189-0154C99310DAAC12', 'World'); --以下两条语句均会失败 insert into customers(id, name) values('a972c577-dfb0-064e-1189-0154c99310daac12', 'loser case'); insert into customers(id, name) values('X972C577-DFB0-064E-1189-0154C99310DAAC12', 'Not a GUID'); |
第三条语句在第二条 id 的小写形式,破坏了唯一性约束,因为大小写字符串只是一种外面表现形式,第四条语句把第一个字母改成了 X
后,它实际不是一个有效的 GUID。
所以往数据库表中插入或更新 uniqueidentifier
字段时,相当于把字符用 java.uti.UUID.fromString("your-input")
转换输入为 UUID
对象
- 如果不能转换为 UUID 对象,操作失败
- 转换后的 UUID 对象再看是否违反了唯一性约束,因此输入字符的大小写不敏感
查询 uniqueidentifier
字段
展示为字符串,因为大小写不敏感,所以下面的条件查询
select * from customers where id='a972c577-dfb0-064e-1189-0154c99310daac12'
也可以查出 name 为 "World" 的记录,由于 SQL Server 是把该字符串转换为 UUID 来比较的,因此与输入的外在形式无关。
JDBC 操作 uniqueidentifier
字段
也是把它当作字符串来看待,rs.getString(..)
, pstmt.setString(index, "<UUID-STRING>")
,只是这个字符串必须是一个符合规格的 UUID。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
DriverManager.registerDriver(new SQLServerDriver()); Connection conn = DriverManager.getConnection( "jdbc:sqlserver://localhost;databaseName=master", "sa", "yourStrong(!)Password"); PreparedStatement pstmt = conn.prepareStatement("insert into customers(id, name) values(?, ?)"); pstmt.setString(1, UUID.randomUUID().toString()); //下面三行也能用来设置 uniqueidentifier 字段的值 //pstmt.setString(1, "98F99731-9AB0-4CF7-A861-051771E481F9"); //pstmt.setObject(1, UUID.randomUUID().toString()); //pstmt.setObject(1, UUID.randomUUID()); //JDBC 驱动还是会调用 UUID 的 toString() 方法 pstmt.setString(2, "From JDBC"); pstmt.execute(); ResultSet rs = conn.createStatement().executeQuery("select * from customers"); while (rs.next()) { System.out.println(rs.getString(1) + "=>" + rs.getString(2)); //getObject(1) 也是 String 类型 } |
执行后插入一条新记录,并查询显示出所有的记录
A972C577-DFB0-064E-1189-0154C99310DA=>World
4A69AA7E-2136-48B2-951C-3EC52942534D=>From JDBC
2F7EB3C8-F4E3-4ECF-9727-B2898CC70F75=>Hello
还有一个小发现就是,查询显示出来的结果与插入的顺序是不一致的。
pstmt.setObject(1, UUID.randomUUID())
也没问题,上面最终也是会调用 UUID.randomUUID().toString() 方法,说到底 SQL Server 在插入该字段数据之前收到的还是一个字符串。
SQL Server JDBC 驱动的 PreparedStatement 实现类是 SQLServerPreparedStatement
, 它有 setUniqueIdentifier(...)
方法,不过仍然是接收字符串,与 setString(...)
没什么不同。
1 2 |
SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) conn.prepareStatement("insert into customers(id) values(?)"); pstmt.setUniqueIdentifier(1, UUID.randomUUID().toString()); |
总之 SQL Server 的 uniqueidentifier
字段是内部存储 16 字节,外部表现为字符串的类型,操作是接受的字符串必须是一个合法的 GUID(UUID) 串就行。
链接: