2.5 通用 Mapper @KeySql 注解 genId 方法详解
为了方便使用全局主键(例如: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 {@Overridepublic 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> {@Overridepublic 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. 测试
@Testpublic 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: 194ee80ac-d156-412d-b63e-b75b49026874DEBUG [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: 10f21aab9-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;@Overridepublic 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. 测试
@Testpublic 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: 11598437075106922497DEBUG [main] - ==> Preparing: INSERT INTO country ( id,countryname,countrycode ) VALUES( ?,?,? )DEBUG [main] - ==> Parameters: 1598437075176128513(Long), Afghanistan(String), AF(String)DEBUG [main] - <== Updates: 11598437075176128513DEBUG [main] - ==> Preparing: INSERT INTO country ( id,countryname,countrycode ) VALUES( ?,?,? )DEBUG [main] - ==> Parameters: 1598437075178225665(Long), Albania(String), AL(String)DEBUG [main] - <== Updates: 11598437075178225665
示例三,基于 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 注解,这些注解可以指定不同的实现方法。
还没完,InsertListMapper 特殊支持
通用 Mapper 提供了好几个 InsertListMapper 接口,最早提供的都是和数据库相关的方法,所以在 Id 策略上都有和数据库相关的限制。
最新增加的 tk.mybatis.mapper.additional.insert.InsertListMapper 接口是一个和数据库无关的方法,他不支持任何的主键策略。但是有了 genId 方式后,这个接口增加了对 @KeySql 注解 genId 方法的支持。
下面是一个示例。
使用前面 示例一 中的 User 实体和表。
接口定义:
import tk.mybatis.mapper.additional.insert.InsertListMapper;public interface UserMapper extends InsertListMapper<User> {}
测试代码:
@Testpublic void testInsertList() {SqlSession sqlSession = getSqlSession();try {UserMapper mapper = sqlSession.getMapper(UserMapper.class);List<User> userList = new ArrayList<User>(countries.length);for (int i = 0; i < countries.length; i++) {userList.add(new User(countries[i][0], countries[i][1]));}Assert.assertEquals(countries.length, mapper.insertList(userList));for (User user : userList) {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@778d1062]DEBUG [main] - ==> Preparing: INSERT INTO user ( id,name,role ) VALUES ( ?,?,? ) ,( ?,?,? ) , ( ?,?,? ) , ( ?,?,? ) , ( ?,?,? ) ,( ?,?,? ) , ( ?,?,? ) , ( ?,?,? ) , ( ?,?,? ) ,( ?,?,? ) , ( ?,?,? ) , ( ?,?,? ) , ( ?,?,? )DEBUG [main] - ==> Parameters:1b8511b7-304a-47a7-abec-c2f38e498e69(String), Angola(String), AO(String),2d31db7e-5d4c-4997-9db3-44163018a3c7(String), Afghanistan(String), AF(String),31ac04d7-cb61-45b2-9b7d-d75e82a65529(String), Albania(String), AL(String),39d77534-bf9a-47d4-aaa0-63d32c2d4562(String), Algeria(String), DZ(String),5186f2e5-57b1-4837-8e06-0392f70e6dbe(String), Andorra(String), AD(String),4ccab2fb-db59-4588-ae90-c33e9acc5a84(String), Anguilla(String), AI(String),
通过 genId 方式可以批量插入,并且可以回写 ID。
