为了方便使用全局主键(例如:Vesta是一款通用的ID产生器,互联网俗称统一发号器),通用 Mapper 4.0.2 版本增加了新的控制主键生成的策略。
@KeySql
注解增加了下面的方法:
/**
* Java 方式生成主键,可以和发号器一类的服务配合使用
*
* @return
*/
Class<? extends GenId> genId() default GenId.NULL.class;
使用该功能的时候,需要配置 genId
属性。
由于生成主键的方式通常和使用环境有关,因此通用 Mapper 没有提供默认的实现。
GenId
接口如下:
public interface GenId<T> {
class NULL implements GenId {
@Override
public Object genId(String table, String column) {
throw new UnsupportedOperationException();
}
}
T genId(String table, String column);
}
通过接口方法可以看出,在生成 Id 时,可以得到当前的表名和列名,我们可以使用这两个信息生成和表字段相关的 Id 信息。也可以完全不使用这两个信息,生成全局的 Id。
使用 genId 方式时,字段的值可以回写!
示例一,使用 UUID
完整测试代码:
https://github.com/abel533/Mapper/tree/master/base/src/test/java/tk/mybatis/mapper/base/genid
1. 实现 GenId
使用 UUID 方式时,首先我们需要提供一个能产生 UUID 的实现类:
public class UUIdGenId implements GenId<String> {
@Override
public String genId(String table, String column) {
return UUID.randomUUID().toString();
}
}
这个实现类就是简单的返回了一个 String
类型的值,并且没有处理 UUID 中的 -
。
2. 配置 genId
例如有如下表(hsqldb 数据库):
create table user (
id varchar(64) NOT NULL PRIMARY KEY,
name varchar(32),
code VARCHAR(2)
);
对应如下实体:
public class User {
@Id
@KeySql(genId = UUIdGenId.class)
private String id;
private String name;
private String code;
public User() {
}
public User(String name, String code) {
this.name = name;
this.code = code;
}
//省略 setter 和 getter
}
我们只需要在注解中配置 @KeySql(genId = UUIdGenId.class)
即可,需要注意的是,如果你使用了 @KeySql
提供的其他方式,genId
就不会生效,genId
是所有方式中优先级最低的。
3. 测试
@Test
public void testUUID(){
SqlSession sqlSession = getSqlSession();
try {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
for (int i = 0; i < countries.length; i++) {
User user = new User(countries[i][0], countries[i][1]);
Assert.assertEquals(1, mapper.insert(user));
Assert.assertNotNull(user.getId());
System.out.println(user.getId());
}
} finally {
sqlSession.close();
}
}
输出的部分日志如下:
DEBUG [main] - Setting autocommit to false on JDBC Connection [org.hsqldb.jdbc.JDBCConnection@3eb7fc54]
DEBUG [main] - ==> Preparing: INSERT INTO user ( id,name,code ) VALUES( ?,?,? )
DEBUG [main] - ==> Parameters: 94ee80ac-d156-412d-b63e-b75b49026874(String), Angola(String), AO(String)
DEBUG [main] - <== Updates: 1
94ee80ac-d156-412d-b63e-b75b49026874
DEBUG [main] - ==> Preparing: INSERT INTO user ( id,name,code ) VALUES( ?,?,? )
DEBUG [main] - ==> Parameters: 0f21aab9-0637-4b7f-ade9-fe5bc4cf1693(String), Afghanistan(String), AF(String)
DEBUG [main] - <== Updates: 1
0f21aab9-0637-4b7f-ade9-fe5bc4cf1693
示例二,简单的全局时序ID
完整测试代码:
https://github.com/abel533/Mapper/tree/master/base/src/test/java/tk/mybatis/mapper/base/genid
1. 实现 GenId
使用 UUID 方式时,首先我们需要提供一个能产生 UUID 的实现类:
public class SimpleGenId implements GenId<Long> {
private Long time;
private Integer seq;
@Override
public synchronized Long genId(String table, String column) {
long current = System.currentTimeMillis();
if (time == null || time != current) {
time = current;
seq = 1;
} else if (current == time) {
seq++;
}
return (time << 20) | seq;
}
}
这是一个简单的实现,通过同步方法保证唯一,不考虑任何特殊情况,不要用于生产环境。
2. 配置 genId
例如有如下表(hsqldb 数据库):
create table country (
id bigint NOT NULL PRIMARY KEY,
countryname varchar(32),
countrycode VARCHAR(2)
);
对应如下实体:
public class Country {
@Id
@KeySql(genId = SimpleGenId.class)
private Long id;
private String countryname;
private String countrycode;
public Country() {
}
public Country(String countryname, String countrycode) {
this.countryname = countryname;
this.countrycode = countrycode;
}
//省略 setter 和 getter
}
我们只需要在注解中配置 @KeySql(genId = SimpleGenId.class)
即可,需要注意的是,如果你使用了 @KeySql
提供的其他方式,genId
就不会生效,genId
是所有方式中优先级最低的。
3. 测试
@Test
public void testGenId(){
SqlSession sqlSession = getSqlSession();
try {
CountryMapper mapper = sqlSession.getMapper(CountryMapper.class);
for (int i = 0; i < countries.length; i++) {
Country country = new Country(countries[i][0], countries[i][1]);
Assert.assertEquals(1, mapper.insert(country));
Assert.assertNotNull(country.getId());
System.out.println(country.getId());
}
} finally {
sqlSession.close();
}
}
输出的部分日志如下:
DEBUG [main] - Setting autocommit to false on JDBC Connection [org.hsqldb.jdbc.JDBCConnection@96def03]
DEBUG [main] - ==> Preparing: INSERT INTO country ( id,countryname,countrycode ) VALUES( ?,?,? )
DEBUG [main] - ==> Parameters: 1598437075106922497(Long), Angola(String), AO(String)
DEBUG [main] - <== Updates: 1
1598437075106922497
DEBUG [main] - ==> Preparing: INSERT INTO country ( id,countryname,countrycode ) VALUES( ?,?,? )
DEBUG [main] - ==> Parameters: 1598437075176128513(Long), Afghanistan(String), AF(String)
DEBUG [main] - <== Updates: 1
1598437075176128513
DEBUG [main] - ==> Preparing: INSERT INTO country ( id,countryname,countrycode ) VALUES( ?,?,? )
DEBUG [main] - ==> Parameters: 1598437075178225665(Long), Albania(String), AL(String)
DEBUG [main] - <== Updates: 1
1598437075178225665
示例三,基于 Vesta 的实现
通过前面两个例子应该能看出,通过不同的 GenId
实现就能切换不同的 ID 生成方式,ID 的类型需要保证一致。
这里提供一个能应用于生产环境的思路,由于和具体环境有关,因此这里没有直接可用的代码。
示例代码:
public class VestaGenId implement GenId<Long> {
public Long genId(String table, String column){
//ApplicationUtil.getBean 需要自己实现
IdService idService = ApplicationUtil.getBean(IdService.class);
return idService.genId();
}
}
这个代码也很简单,由于注解只能配置类,不能注入,因此 VestaGenId
实现中不能直接注入 IdService
,需要通过静态方法从 Spring Context 中获取,获取后就可以正常使用了。
对 Spring 熟悉的人可以很容易理解
ApplicationUtil.getBean
方法。
如果对 Spring 不了解,可以看看 http://sb33060418.iteye.com/blog/2372874 这里的SpringContextHolder。
特殊的地方
使用 genId
时,在和数据库交互前,ID 值就已经生成了,由于这个 ID 和数据库的机制无关,因此一个实体中可以出现任意个 使用 genId
方式的 @KeySql
注解,这些注解可以指定不同的实现方法。