通用 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 {
        @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 的类型需要保证一致。

这里提供一个能应用于生产环境的思路,由于和具体环境有关,因此这里没有直接可用的代码。

Vesta 地址:https://gitee.com/robertleepeak/vesta-id-generator

示例代码:

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 注解,这些注解可以指定不同的实现方法。

猜你喜欢

转载自blog.csdn.net/isea533/article/details/80040379