mybatis-mapper / mapper

MyBatis Mapper
https://mapper.mybatis.io
Apache License 2.0
325 stars 47 forks source link

genId 属性不生效 #106

Closed DragonZru closed 2 months ago

DragonZru commented 2 months ago

版本 2.2.1

model `import com.ylli.api.common.uid.SnowFlakeGenerator; import io.mybatis.provider.Entity; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.data.annotation.Version;

import java.sql.Timestamp; import java.util.Map;

@Entity.Table("t_example") @Data @NoArgsConstructor public class Example {

@Entity.Column(id = true, updatable = false, genId = SnowFlakeGenerator.class)
public Long id;

public String username;

public String password;

@Version
public Long version;

public Boolean status;

// 排除列
@Entity.Transient
public Map<String, Object> map;

public Timestamp createTime;

public Timestamp updateTime;

public Example(String username, String password) {
    this.username = username;
    this.password = password;
}

/*
CREATE TABLE `example`.`t_example`  (

id bigint UNSIGNED NOT NULL, username varchar(255) NOT NULL, password varchar(255) NOT NULL, version bigint NOT NULL DEFAULT 0, status tinyint(1) NOT NULL DEFAULT 1, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NULL ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id) ); */ }`

mapper

`import com.ylli.api.example.model.Example; import io.mybatis.mapper.Mapper;

@org.apache.ibatis.annotations.Mapper public interface ExampleMapper extends Mapper<Example, Long> { }`

SnowFlakeGenerator

`public class SnowFlakeGenerator implements IDGenerator, GenId {

/*
 * 开始时间截 (2020-01-01)
 */
private final long epoch = 1577836800000L;
/*
 * 机器id所占的位数
 */
private final long workerIdBits = 5L;
/*
 * 数据标识id所占的位数
 */
private final long datacenterIdBits = 5L;
/*
 * 序列在id中占的位数
 */
private final long sequenceBits = 12L;
/*
 * 生成序列的掩码,这里为4095
 * 二进制最高位0表示正数,1表示负数,故取值范围为 0111111...
 */
private final long sequenceMask = (1 << sequenceBits) - 1;
/*
数据中心最大值与工作机最大值
 */
private final long maxWorkerId = (1 << workerIdBits) - 1;
private final long maxDatacenterId = (1 << datacenterIdBits) - 1;
/*
 * 工作机器ID(0~31) 默认0
 */
private long workerId = 0L;
/*
 * 数据中心ID(0~31) 默认0
 */
private long datacenterId = 0L;
/*
 * 毫秒内序列(0~4095)
 */
private long sequence = 0L;
/*
 * 上次生成ID的时间截
 */
private long lastTimestamp = -1L;

/*
 * 构造函数
 *
 * @param workerId     工作ID (0~31)
 * @param datacenterId 数据中心ID (0~31)
 */
public SnowFlakeGenerator(long workerId, long datacenterId) {
    if (workerId > maxWorkerId || workerId < 0) {
        throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
    }
    if (datacenterId > maxDatacenterId || datacenterId < 0) {
        throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
    }
    this.workerId = workerId;
    this.datacenterId = datacenterId;
}

public SnowFlakeGenerator() {
}

/*
 * 获得下一个ID (该方法是线程安全的)
 * @return SnowflakeId
 */
private synchronized long invoke() {
    long timestamp = System.currentTimeMillis();

    //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
    if (timestamp < lastTimestamp) {
        throw new RuntimeException(
                String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
    }

    //如果是同一时间生成的,则进行毫秒内序列
    if (lastTimestamp == timestamp) {
        sequence = (sequence + 1) & sequenceMask;
        //毫秒内序列溢出
        if (sequence == 0) {
            //阻塞到下一个毫秒,获得新的时间戳
            timestamp = tilNextMillis(lastTimestamp);
        }
    } else {
        //时间戳改变,毫秒内序列重置
        sequence = 0L;
    }

    //上次生成ID的时间截
    lastTimestamp = timestamp;

    //移位并通过或运算拼到一起组成64位的ID
    return ((timestamp - epoch) << (sequenceBits + workerIdBits + datacenterIdBits)) // 时间戳 (左移)
            | (datacenterId << (sequenceBits + workerIdBits)) // 数据中心id (左移 sequence+ workerId 位)
            | (workerId << sequenceBits) // 工作机器id (左移 sequence 位)
            | sequence;  // 序列号
}

/**
 * 阻塞到下一个毫秒,直到获得新的时间戳
 *
 * @param lastTimestamp 上次生成ID的时间截
 * @return 当前时间戳
 */
protected long tilNextMillis(long lastTimestamp) {
    long timestamp = System.currentTimeMillis();
    while (timestamp <= lastTimestamp) {
        tilNextMillis(lastTimestamp);
        Thread.onSpinWait();
    }
    return timestamp;
}

@Override
public Long nextId() {
    return invoke();
}

@Override
public Long genId(EntityTable table, EntityColumn column) {
    return invoke();
}

}`

测试 exampleMapper.insert(new Example("name", "description"));

错误提示:

Error updating database. Cause: java.sql.SQLIntegrityConstraintViolationException: Column 'id' cannot be null

The error may exist in com/ylli/api/example/mapper/ExampleMapper.java (best guess)

The error may involve com.ylli.api.example.mapper.ExampleMapper.insert-Inline

The error occurred while setting parameters

SQL: INSERT INTO t_example(id,username,password,version,status,create_time,update_time) VALUES (?,?,?,?,?,?,?)

Cause: java.sql.SQLIntegrityConstraintViolationException: Column 'id' cannot be null

请问有什么问题,当我切换 mapper4中 @KeySql(genId = SnowFlakeGenerator.class) 是可以的。

DragonZru commented 2 months ago

已解决: 覆盖实现 ` @org.apache.ibatis.annotations.Mapper public interface ExampleMapper extends Mapper<Example, Long> {

@Lang(Caching.class)
//@SelectKey(statement = "SELECT SEQ.NEXTVAL FROM DUAL", keyProperty = "id", before = true, resultType = long.class)
@Options(useGeneratedKeys = false, keyProperty = "id")
@InsertProvider(type = EntityProvider.class, method = "insert")
int insert(Example entity);

} ` mapper5 默认实现 useGeneratedKeys = true ,导致genid 失效

abel533 commented 2 months ago

BaseMapper是没有特殊配置的,Mapper是有特殊配置的。可以考虑继承 BaseMapper.