0%

spring-data-jpa自定义id生成器笔记

前言

在分布式环境中 一般数据id 都是全局唯一 拥有特定的生成规则
一般都是从专门的取号中心 取的
所以jpa中为了全局统一处理 id生成 也提供了扩展方案

此处取jpa hibernate实现处理

示例

hibernate 6.5版本之前的方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
package com.ming.core.orm;

import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.IdentifierGenerator;

import java.io.Serializable;
import java.util.concurrent.ThreadLocalRandom;

/**
* @author ming
*/
public class SnowflakeIdGenerator implements IdentifierGenerator {

// 2010-11-14 02:43:00 UTC
private static final long EPOCH = 1288834974657L;
private static final int WORKER_ID_BITS = 5;
private static final int DATACENTER_ID_BITS = 5;
private static final int SEQUENCE_BITS = 12;

private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);
private static final long MAX_DATACENTER_ID = ~(-1L << DATACENTER_ID_BITS);
private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);

private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;
private static final long DATACENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
private static final long TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS;

private final long workerId;
private final long datacenterId;
private long sequence = 0L;

private long lastTimestamp = -1L;

public SnowflakeIdGenerator() {
this(workerId(), datacenterId());
}

public SnowflakeIdGenerator(long workerId, long datacenterId) {
if (workerId > MAX_WORKER_ID || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", MAX_WORKER_ID));
}
if (datacenterId > MAX_DATACENTER_ID || datacenterId < 0) {
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", MAX_DATACENTER_ID));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}

private static long workerId() {
return ThreadLocalRandom.current().nextLong(0, MAX_WORKER_ID + 1);
}

private static long datacenterId() {
return ThreadLocalRandom.current().nextLong(0, MAX_DATACENTER_ID + 1);
}

private long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}

private long timeGen() {
return System.currentTimeMillis() - EPOCH;
}

@Override
public Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException {
long timestamp = timeGen();

if (timestamp < lastTimestamp) {
throw new HibernateException(String.format(
"Clock moved backwards. Refusing to generate id for %d milliseconds",
lastTimestamp - timestamp));
}

if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & SEQUENCE_MASK;
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0;
}

lastTimestamp = timestamp;

return ((timestamp << TIMESTAMP_LEFT_SHIFT) |
(datacenterId << DATACENTER_ID_SHIFT) |
(workerId << WORKER_ID_SHIFT) |
sequence);
}
}

1
2
3
4
@Id
@GeneratedValue(generator = "snowflake")
@GenericGenerator(name = "snowflake", type = SnowflakeIdGenerator.class)
protected Long id;
hibernate 6.5版本之后的方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
package com.ming.core.orm;

import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.generator.BeforeExecutionGenerator;
import org.hibernate.generator.EventType;

import java.io.Serializable;
import java.util.EnumSet;
import java.util.concurrent.ThreadLocalRandom;

import static org.hibernate.generator.EventTypeSets.INSERT_ONLY;

/**
* @author ming
*/
public class SnowflakeSequenceGenerator implements BeforeExecutionGenerator {

// 2010-11-14 02:43:00 UTC
private static final long EPOCH = 1288834974657L;
private static final int WORKER_ID_BITS = 5;
private static final int DATACENTER_ID_BITS = 5;
private static final int SEQUENCE_BITS = 12;

private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);
private static final long MAX_DATACENTER_ID = ~(-1L << DATACENTER_ID_BITS);
private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);

private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;
private static final long DATACENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
private static final long TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS;

private final long workerId;
private final long datacenterId;
private long sequence = 0L;

private long lastTimestamp = -1L;

public SnowflakeSequenceGenerator() {
this(workerId(), datacenterId());
}

public SnowflakeSequenceGenerator(long workerId, long datacenterId) {
if (workerId > MAX_WORKER_ID || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", MAX_WORKER_ID));
}
if (datacenterId > MAX_DATACENTER_ID || datacenterId < 0) {
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", MAX_DATACENTER_ID));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}

private static long workerId() {
return ThreadLocalRandom.current().nextLong(0, MAX_WORKER_ID + 1);
}

private static long datacenterId() {
return ThreadLocalRandom.current().nextLong(0, MAX_DATACENTER_ID + 1);
}

private long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}

private long timeGen() {
return System.currentTimeMillis() - EPOCH;
}

private Serializable nextId() {
long timestamp = timeGen();

if (timestamp < lastTimestamp) {
throw new HibernateException(String.format(
"Clock moved backwards. Refusing to generate id for %d milliseconds",
lastTimestamp - timestamp));
}

if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & SEQUENCE_MASK;
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0;
}

lastTimestamp = timestamp;

return ((timestamp << TIMESTAMP_LEFT_SHIFT) |
(datacenterId << DATACENTER_ID_SHIFT) |
(workerId << WORKER_ID_SHIFT) |
sequence);
}


@Override
public Object generate(SharedSessionContractImplementor session, Object owner, Object currentValue, EventType eventType) {
return nextId();
}

@Override
public EnumSet<EventType> getEventTypes() {
return INSERT_ONLY;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.ming.core.orm;

import org.hibernate.annotations.IdGeneratorType;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* @author ming
*/
@IdGeneratorType(SnowflakeSequenceGenerator.class)
@Retention(RUNTIME)
@Target({METHOD, FIELD})
public @interface SnowflakeSequence {
}

1
2
3
@Id
@SnowflakeSequence
protected Long id;

hibernate 在高版本之后提供了更加简洁的方式 实现自定义id生成
自定义id生成器配合@MappedSuperclass 可以做到继承映射+自定义生成id 只要引用了InId id都是配置的生成方案

总结

jpa 我个人很喜欢用 虽然很多人说jpa会比直接写sql慢
不过我认为jpa如果配置的好 在大多数情况下 并不会慢多少 反而可以利用orm的特性 进行一些优化调整 充分利用硬件资源
大多数用mybatis的 都是觉得直接写sql简单 但是当项目越来越大的时候mybatis 写的sql调整起来反而更加考验开发的sql功底 而且不同的db 对sql的要求也有所不同 对开发要求就更高了