nextStrOpt() {
57 | return nextOpt().map(Object::toString);
58 | }
59 |
60 | /**
61 | * 返回经过格式化后字符串
62 | * @return
63 | * @throws SeqException 无法获得序号抛出异常
64 | */
65 | default String nextStr() throws SeqException {
66 | return nextStrOpt().orElseThrow(() -> new SeqException("Nothing to offer"));
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/sequence-core/src/test/java/com/power4j/kit/seq/persistent/provider/H2SynchronizerTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.kit.seq.persistent.provider;
18 |
19 | import com.power4j.kit.seq.persistent.SeqSynchronizer;
20 | import org.junit.After;
21 | import org.junit.Before;
22 |
23 | /**
24 | * H2 测试
25 | *
26 | *
27 | * @author lishangbu
28 | * @date 2021/8/28
29 | * @since 1.5.0
30 | */
31 | public class H2SynchronizerTest extends SynchronizerTestCase {
32 |
33 | public final static String SEQ_TABLE = "tb_seq";
34 |
35 | private H2Synchronizer h2Synchronizer;
36 |
37 | private H2Synchronizer createSeqSynchronizer() {
38 | H2Synchronizer sqlSynchronizer = new H2Synchronizer(SEQ_TABLE, TestServices.getH2DataSource());
39 | sqlSynchronizer.init();
40 | return sqlSynchronizer;
41 | }
42 |
43 | @Before
44 | public void setUp() {
45 | h2Synchronizer = createSeqSynchronizer();
46 | }
47 |
48 | @After
49 | public void tearDown() {
50 | if (h2Synchronizer != null) {
51 | h2Synchronizer.dropTable();
52 | }
53 | }
54 |
55 | @Override
56 | protected SeqSynchronizer getSeqSynchronizer() {
57 | return h2Synchronizer;
58 | }
59 |
60 | }
--------------------------------------------------------------------------------
/sequence-core/src/test/java/com/power4j/kit/seq/persistent/provider/MySqlSynchronizerTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.kit.seq.persistent.provider;
18 |
19 | import com.power4j.kit.seq.persistent.SeqSynchronizer;
20 | import org.junit.After;
21 | import org.junit.Before;
22 |
23 | /**
24 | * Mysql 测试
25 | *
26 | *
27 | * @author CJ (power4j@outlook.com)
28 | * @date 2020/7/3
29 | * @since 1.0
30 | */
31 | public class MySqlSynchronizerTest extends SynchronizerTestCase {
32 |
33 | public final static String SEQ_TABLE = "tb_seq";
34 |
35 | private MySqlSynchronizer mySqlSynchronizer;
36 |
37 | private MySqlSynchronizer createSeqSynchronizer() {
38 | MySqlSynchronizer sqlSynchronizer = new MySqlSynchronizer(SEQ_TABLE, TestServices.getMySqlDataSource());
39 | sqlSynchronizer.init();
40 | return sqlSynchronizer;
41 | }
42 |
43 | @Before
44 | public void setUp() {
45 | mySqlSynchronizer = createSeqSynchronizer();
46 | }
47 |
48 | @After
49 | public void tearDown() {
50 | if (mySqlSynchronizer != null) {
51 | mySqlSynchronizer.dropTable();
52 | }
53 | }
54 |
55 | @Override
56 | protected SeqSynchronizer getSeqSynchronizer() {
57 | return mySqlSynchronizer;
58 | }
59 |
60 | }
--------------------------------------------------------------------------------
/sequence-core/src/test/java/com/power4j/kit/seq/persistent/MySqlSeqHolderTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.kit.seq.persistent;
18 |
19 | import com.power4j.kit.seq.TestUtil;
20 | import com.power4j.kit.seq.core.SeqFormatter;
21 | import com.power4j.kit.seq.persistent.provider.MySqlSynchronizer;
22 | import com.power4j.kit.seq.persistent.provider.TestServices;
23 | import org.junit.After;
24 |
25 | public class MySqlSeqHolderTest extends SeqHolderTestCase {
26 |
27 | public final static String SEQ_TABLE = "tb_seq";
28 |
29 | public final String seqName = "power4j_" + MySqlSeqHolderTest.class.getSimpleName();
30 |
31 | public final String partition = TestUtil.strNow();
32 |
33 | private MySqlSynchronizer seqSynchronizer;
34 |
35 | public SeqHolder createSeqHolder() {
36 | seqSynchronizer = new MySqlSynchronizer(SEQ_TABLE, TestServices.getMySqlDataSource());
37 | seqSynchronizer.init();
38 | SeqHolder holder = new SeqHolder(seqSynchronizer, seqName, partition, 1L, 1000, SeqFormatter.DEFAULT_FORMAT);
39 | holder.prepare();
40 | return holder;
41 | }
42 |
43 | @After
44 | public void tearDown() {
45 | if (seqSynchronizer != null) {
46 | seqSynchronizer.dropTable();
47 | }
48 | }
49 |
50 | @Override
51 | protected SeqHolder getSeqHolder() {
52 | return createSeqHolder();
53 | }
54 |
55 | }
--------------------------------------------------------------------------------
/sequence-core/src/test/java/com/power4j/kit/seq/persistent/LettuceSeqHolderTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.kit.seq.persistent;
18 |
19 | import com.power4j.kit.seq.TestUtil;
20 | import com.power4j.kit.seq.core.SeqFormatter;
21 | import com.power4j.kit.seq.persistent.provider.SimpleLettuceSynchronizer;
22 | import com.power4j.kit.seq.persistent.provider.TestServices;
23 | import io.lettuce.core.RedisClient;
24 | import org.junit.After;
25 |
26 | public class LettuceSeqHolderTest extends SeqHolderTestCase {
27 |
28 | public final static String SEQ_CACHE_NAME = "power4j:" + LettuceSeqHolderTest.class.getSimpleName();
29 |
30 | public final String seqName = "seq_holder_test";
31 |
32 | public final String partition = TestUtil.strNow();
33 |
34 | private RedisClient redisClient;
35 |
36 | public SeqHolder createSeqHolder() {
37 | redisClient = TestServices.getRedisClient();
38 | SimpleLettuceSynchronizer seqSynchronizer = new SimpleLettuceSynchronizer(SEQ_CACHE_NAME, redisClient);
39 | seqSynchronizer.init();
40 | SeqHolder holder = new SeqHolder(seqSynchronizer, seqName, partition, 1L, 1000, SeqFormatter.DEFAULT_FORMAT);
41 | holder.prepare();
42 | return holder;
43 | }
44 |
45 | @After
46 | public void tearDown() {
47 | if (redisClient != null) {
48 | redisClient.shutdown();
49 | }
50 | }
51 |
52 | @Override
53 | protected SeqHolder getSeqHolder() {
54 | return createSeqHolder();
55 | }
56 |
57 | }
--------------------------------------------------------------------------------
/examples/actuator-example/src/main/java/com/power4j/sequence/example/SequenceActuatorExampleApplication.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.sequence.example;
18 |
19 | import com.power4j.kit.seq.core.Sequence;
20 | import org.springframework.beans.factory.annotation.Autowired;
21 | import org.springframework.boot.SpringApplication;
22 | import org.springframework.boot.autoconfigure.SpringBootApplication;
23 | import org.springframework.web.bind.annotation.GetMapping;
24 | import org.springframework.web.bind.annotation.RequestParam;
25 | import org.springframework.web.bind.annotation.RestController;
26 |
27 | import java.util.ArrayList;
28 | import java.util.HashMap;
29 | import java.util.List;
30 | import java.util.Map;
31 |
32 | @RestController
33 | @SpringBootApplication
34 | public class SequenceActuatorExampleApplication {
35 |
36 | @Autowired
37 | private Sequence sequence;
38 |
39 | public static void main(String[] args) {
40 | SpringApplication.run(SequenceActuatorExampleApplication.class, args);
41 | }
42 |
43 | @GetMapping("/")
44 | public Map getSequence(@RequestParam(required = false) Integer size) {
45 | size = (size == null || size <= 0) ? 10 : size;
46 | List list = new ArrayList<>(size);
47 | for (int i = 0; i < size; ++i) {
48 | list.add(sequence.nextStr());
49 | }
50 | Map data = new HashMap<>();
51 | data.put("seq", list);
52 | return data;
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/sequence-core/src/test/java/com/power4j/kit/seq/persistent/PostgreSqlSeqHolderTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.kit.seq.persistent;
18 |
19 | import com.power4j.kit.seq.TestUtil;
20 | import com.power4j.kit.seq.core.SeqFormatter;
21 | import com.power4j.kit.seq.persistent.provider.PostgreSqlSynchronizer;
22 | import com.power4j.kit.seq.persistent.provider.TestServices;
23 | import org.junit.After;
24 |
25 | /**
26 | * @author CJ (power4j@outlook.com)
27 | * @date 2020/7/29
28 | * @since 1.3
29 | */
30 | public class PostgreSqlSeqHolderTest extends SeqHolderTestCase {
31 |
32 | public final static String SEQ_TABLE = "tb_seq";
33 |
34 | public final String seqName = "power4j_" + PostgreSqlSeqHolderTest.class.getSimpleName();
35 |
36 | public final String partition = TestUtil.strNow();
37 |
38 | private PostgreSqlSynchronizer seqSynchronizer;
39 |
40 | public SeqHolder createSeqHolder() {
41 | seqSynchronizer = new PostgreSqlSynchronizer(SEQ_TABLE, TestServices.getPostgreSqlDataSource());
42 | seqSynchronizer.init();
43 | SeqHolder holder = new SeqHolder(seqSynchronizer, seqName, partition, 1L, 1000, SeqFormatter.DEFAULT_FORMAT);
44 | holder.prepare();
45 | return holder;
46 | }
47 |
48 | @After
49 | public void tearDown() {
50 | if (seqSynchronizer != null) {
51 | seqSynchronizer.dropTable();
52 | }
53 | }
54 |
55 | @Override
56 | protected SeqHolder getSeqHolder() {
57 | return createSeqHolder();
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/sequence-core/src/test/java/com/power4j/kit/seq/persistent/H2SqlSeqHolderTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.kit.seq.persistent;
18 |
19 | import com.power4j.kit.seq.TestUtil;
20 | import com.power4j.kit.seq.core.SeqFormatter;
21 | import com.power4j.kit.seq.persistent.provider.H2Synchronizer;
22 | import com.power4j.kit.seq.persistent.provider.PostgreSqlSynchronizer;
23 | import com.power4j.kit.seq.persistent.provider.TestServices;
24 | import org.junit.After;
25 |
26 | /**
27 | * @author lishangbu
28 | * @date 2021/8/28
29 | * @since 1.5.0
30 | */
31 | public class H2SqlSeqHolderTest extends SeqHolderTestCase {
32 |
33 | public final static String SEQ_TABLE = "tb_seq";
34 |
35 | public final String seqName = "power4j_" + H2SqlSeqHolderTest.class.getSimpleName();
36 |
37 | public final String partition = TestUtil.strNow();
38 |
39 | private H2Synchronizer seqSynchronizer;
40 |
41 | public SeqHolder createSeqHolder() {
42 | seqSynchronizer = new H2Synchronizer(SEQ_TABLE, TestServices.getH2DataSource());
43 | seqSynchronizer.init();
44 | SeqHolder holder = new SeqHolder(seqSynchronizer, seqName, partition, 1L, 1000, SeqFormatter.DEFAULT_FORMAT);
45 | holder.prepare();
46 | return holder;
47 | }
48 |
49 | @After
50 | public void tearDown() {
51 | if (seqSynchronizer != null) {
52 | seqSynchronizer.dropTable();
53 | }
54 | }
55 |
56 | @Override
57 | protected SeqHolder getSeqHolder() {
58 | return createSeqHolder();
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/sequence-spring-boot-starter/src/main/java/com/power4j/kit/seq/spring/boot/autoconfigure/actuator/EndpointAutoConfiguration.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.kit.seq.spring.boot.autoconfigure.actuator;
18 |
19 | import org.springframework.beans.factory.annotation.Autowired;
20 | import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
21 | import org.springframework.boot.autoconfigure.AutoConfiguration;
22 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
23 | import org.springframework.context.ApplicationContext;
24 | import org.springframework.context.annotation.Bean;
25 |
26 | /**
27 | * 监控端点自动配置
28 | *
29 | * @author CJ (power4j@outlook.com)
30 | * @date 2020/9/26
31 | * @since 1.4
32 | */
33 | @AutoConfiguration
34 | public class EndpointAutoConfiguration {
35 |
36 | @Autowired
37 | private ApplicationContext applicationContext;
38 |
39 | @Bean
40 | @ConditionalOnMissingBean(SequenceEndpoint.class)
41 | @ConditionalOnAvailableEndpoint(endpoint = SequenceEndpoint.class)
42 | public SequenceEndpoint sequenceEndpoint() {
43 | return new SequenceEndpoint(applicationContext);
44 | }
45 |
46 | @Bean
47 | @ConditionalOnMissingBean(SeqSynchronizerEndpoint.class)
48 | @ConditionalOnAvailableEndpoint(endpoint = SeqSynchronizerEndpoint.class)
49 | public SeqSynchronizerEndpoint seqSynchronizerEndpoint() {
50 | return new SeqSynchronizerEndpoint(applicationContext);
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/bench-test/src/main/java/com/power4j/kit/seq/LongSeqPoolBench.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.kit.seq;
18 |
19 | import com.power4j.kit.seq.core.LongSeqPool;
20 | import org.openjdk.jmh.annotations.*;
21 | import org.openjdk.jmh.infra.Blackhole;
22 | import org.openjdk.jmh.runner.Runner;
23 | import org.openjdk.jmh.runner.options.Options;
24 | import org.openjdk.jmh.runner.options.OptionsBuilder;
25 |
26 | import java.util.concurrent.TimeUnit;
27 |
28 | /**
29 | * 单机序号池测试
30 | *
31 | * @author CJ (power4j@outlook.com)
32 | * @date 2020/7/3
33 | * @since 1.0
34 | */
35 | @Fork(1)
36 | @State(Scope.Benchmark)
37 | @BenchmarkMode(Mode.Throughput)
38 | @Warmup(iterations = 1, time = 3)
39 | @Measurement(iterations = 3, time = 10)
40 | @OutputTimeUnit(TimeUnit.SECONDS)
41 | public class LongSeqPoolBench {
42 |
43 | private LongSeqPool longSeqPool;
44 |
45 | @Setup
46 | public void setup() {
47 | longSeqPool = LongSeqPool.forRange("longSeqPool", BenchParam.SEQ_INIT_VAL, Long.MAX_VALUE, false);
48 | }
49 |
50 | @Benchmark
51 | @Threads(1)
52 | public void testSingleThread(Blackhole bh) {
53 | bh.consume(longSeqPool.next());
54 | }
55 |
56 | @Benchmark
57 | @Threads(4)
58 | public void test4Threads(Blackhole bh) {
59 | bh.consume(longSeqPool.next());
60 | }
61 |
62 | public static void main(String[] args) throws Exception {
63 | Options opt = new OptionsBuilder().include(LongSeqPoolBench.class.getSimpleName()).build();
64 | new Runner(opt).run();
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/sequence-core/src/test/java/com/power4j/kit/seq/persistent/MongoSeqHolderTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.kit.seq.persistent;
18 |
19 | import com.mongodb.client.MongoClient;
20 | import com.power4j.kit.seq.TestUtil;
21 | import com.power4j.kit.seq.core.SeqFormatter;
22 | import com.power4j.kit.seq.persistent.provider.SimpleMongoSynchronizer;
23 | import com.power4j.kit.seq.persistent.provider.TestServices;
24 | import org.junit.After;
25 |
26 | /**
27 | * @author CJ (power4j@outlook.com)
28 | * @date 2020/7/20
29 | * @since 1.0
30 | */
31 | public class MongoSeqHolderTest extends SeqHolderTestCase {
32 |
33 | public final static String DB_NAME = "seq_test";
34 |
35 | public final static String COL_NAME = "power4j_" + MongoSeqHolderTest.class.getSimpleName();
36 |
37 | public final String seqName = "holder_test";
38 |
39 | public final String partition = TestUtil.strNow();
40 |
41 | private MongoClient mongoClient;
42 |
43 | public SeqHolder createSeqHolder() {
44 | mongoClient = TestServices.getMongoClient();
45 | SimpleMongoSynchronizer seqSynchronizer = new SimpleMongoSynchronizer(DB_NAME, COL_NAME, mongoClient);
46 | seqSynchronizer.init();
47 | SeqHolder holder = new SeqHolder(seqSynchronizer, seqName, partition, 1L, 1000, SeqFormatter.DEFAULT_FORMAT);
48 | holder.prepare();
49 | return holder;
50 | }
51 |
52 | @After
53 | public void tearDown() {
54 | if (mongoClient != null) {
55 | mongoClient.close();
56 | }
57 | }
58 |
59 | @Override
60 | protected SeqHolder getSeqHolder() {
61 | return createSeqHolder();
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/sequence-core/src/main/java/com/power4j/kit/seq/ext/InMemorySequenceRegistry.java:
--------------------------------------------------------------------------------
1 | package com.power4j.kit.seq.ext;
2 |
3 | import com.power4j.kit.seq.core.Sequence;
4 |
5 | import java.util.HashMap;
6 | import java.util.Map;
7 | import java.util.Objects;
8 | import java.util.Optional;
9 | import java.util.concurrent.locks.Lock;
10 | import java.util.concurrent.locks.ReentrantReadWriteLock;
11 | import java.util.function.Function;
12 |
13 | /**
14 | * @author CJ (power4j@outlook.com)
15 | * @date 2021/9/2
16 | * @since 1.0
17 | */
18 | public class InMemorySequenceRegistry> implements SequenceRegistry {
19 |
20 | private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
21 |
22 | private final Lock rLock = rwLock.readLock();
23 |
24 | private final Lock wLock = rwLock.writeLock();
25 |
26 | /**
27 | * Guard with rwLock,So we don't need ConcurrentHashMap. TODO: GC
28 | */
29 | private final Map map = new HashMap<>(8);
30 |
31 | @Override
32 | public Optional register(String name, S seq) {
33 | assert Objects.nonNull(seq);
34 | wLock.lock();
35 | try {
36 | return Optional.ofNullable(map.put(name, seq));
37 | }
38 | finally {
39 | wLock.unlock();
40 | }
41 | }
42 |
43 | @Override
44 | public Optional get(String name) {
45 | rLock.lock();
46 | try {
47 | return Optional.ofNullable(map.get(name));
48 | }
49 | finally {
50 | rLock.unlock();
51 | }
52 | }
53 |
54 | @Override
55 | public Optional remove(String name) {
56 | wLock.lock();
57 | try {
58 | return Optional.ofNullable(map.remove(name));
59 | }
60 | finally {
61 | wLock.unlock();
62 | }
63 | }
64 |
65 | @Override
66 | public S getOrRegister(String name, Function func) {
67 | S seq = get(name).orElse(null);
68 | if (Objects.isNull(seq)) {
69 | wLock.lock();
70 | try {
71 | seq = map.get(name);
72 | if (Objects.isNull(seq)) {
73 | seq = func.apply(name);
74 | assert Objects.nonNull(seq);
75 | map.put(name, seq);
76 | }
77 | }
78 | finally {
79 | wLock.unlock();
80 | }
81 | }
82 | return seq;
83 | }
84 |
85 | @Override
86 | public int size() {
87 | rLock.lock();
88 | try {
89 | return map.size();
90 | }
91 | finally {
92 | rLock.unlock();
93 | }
94 | }
95 |
96 | }
97 |
--------------------------------------------------------------------------------
/examples/mongo-example/src/main/java/com/power4j/sequence/example/SequenceMongoExampleApplication.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.sequence.example;
18 |
19 | import com.power4j.kit.seq.core.Sequence;
20 | import com.power4j.kit.seq.persistent.SeqSynchronizer;
21 | import org.springframework.beans.factory.annotation.Autowired;
22 | import org.springframework.boot.SpringApplication;
23 | import org.springframework.boot.autoconfigure.SpringBootApplication;
24 | import org.springframework.web.bind.annotation.GetMapping;
25 | import org.springframework.web.bind.annotation.RequestParam;
26 | import org.springframework.web.bind.annotation.RestController;
27 |
28 | import java.util.ArrayList;
29 | import java.util.HashMap;
30 | import java.util.List;
31 | import java.util.Map;
32 |
33 | @RestController
34 | @SpringBootApplication
35 | public class SequenceMongoExampleApplication {
36 |
37 | @Autowired
38 | private Sequence sequence;
39 |
40 | @Autowired
41 | private SeqSynchronizer seqSynchronizer;
42 |
43 | public static void main(String[] args) {
44 | SpringApplication.run(SequenceMongoExampleApplication.class, args);
45 | }
46 |
47 | @GetMapping("/")
48 | public Map getSequence(@RequestParam(required = false) Integer size) {
49 | size = (size == null || size <= 0) ? 10 : size;
50 | List list = new ArrayList<>(size);
51 | for (int i = 0; i < size; ++i) {
52 | list.add(sequence.nextStr());
53 | }
54 | Map data = new HashMap<>();
55 | data.put("query_count", Long.toString(seqSynchronizer.getQueryCounter()));
56 | data.put("update_count", Long.toString(seqSynchronizer.getUpdateCounter()));
57 | data.put("seq", list);
58 | return data;
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/examples/redis-example/src/main/java/com/power4j/sequence/example/SequenceRedisExampleApplication.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.sequence.example;
18 |
19 | import com.power4j.kit.seq.core.Sequence;
20 | import com.power4j.kit.seq.persistent.SeqSynchronizer;
21 | import org.springframework.beans.factory.annotation.Autowired;
22 | import org.springframework.boot.SpringApplication;
23 | import org.springframework.boot.autoconfigure.SpringBootApplication;
24 | import org.springframework.web.bind.annotation.GetMapping;
25 | import org.springframework.web.bind.annotation.RequestParam;
26 | import org.springframework.web.bind.annotation.RestController;
27 |
28 | import java.util.ArrayList;
29 | import java.util.HashMap;
30 | import java.util.List;
31 | import java.util.Map;
32 |
33 | @RestController
34 | @SpringBootApplication
35 | public class SequenceRedisExampleApplication {
36 |
37 | @Autowired
38 | private Sequence sequence;
39 |
40 | @Autowired
41 | private SeqSynchronizer seqSynchronizer;
42 |
43 | public static void main(String[] args) {
44 | SpringApplication.run(SequenceRedisExampleApplication.class, args);
45 | }
46 |
47 | @GetMapping("/")
48 | public Map getSequence(@RequestParam(required = false) Integer size) {
49 | size = (size == null || size <= 0) ? 10 : size;
50 | List list = new ArrayList<>(size);
51 | for (int i = 0; i < size; ++i) {
52 | list.add(sequence.nextStr());
53 | }
54 | Map data = new HashMap<>();
55 | data.put("query_count", Long.toString(seqSynchronizer.getQueryCounter()));
56 | data.put("update_count", Long.toString(seqSynchronizer.getUpdateCounter()));
57 | data.put("seq", list);
58 | return data;
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/sequence-spring-boot-starter/src/main/java/com/power4j/kit/seq/spring/boot/autoconfigure/actuator/SequenceEndpoint.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.kit.seq.spring.boot.autoconfigure.actuator;
18 |
19 | import com.power4j.kit.seq.core.Sequence;
20 | import lombok.AllArgsConstructor;
21 | import lombok.Getter;
22 | import org.springframework.beans.factory.SmartInitializingSingleton;
23 | import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
24 | import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
25 | import org.springframework.context.ApplicationContext;
26 |
27 | import java.util.List;
28 | import java.util.stream.Collectors;
29 |
30 | /**
31 | * Endpoint for {@link Sequence}
32 | *
33 | * @author CJ (power4j@outlook.com)
34 | * @date 2020/9/26
35 | * @since 1.4
36 | */
37 | @Endpoint(id = "sequence")
38 | public class SequenceEndpoint implements SmartInitializingSingleton {
39 |
40 | private final ApplicationContext applicationContext;
41 |
42 | private List sequenceInfoList;
43 |
44 | public SequenceEndpoint(ApplicationContext applicationContext) {
45 | this.applicationContext = applicationContext;
46 | }
47 |
48 | @Override
49 | public void afterSingletonsInstantiated() {
50 | sequenceInfoList = applicationContext.getBeansOfType(Sequence.class)
51 | .entrySet()
52 | .stream()
53 | .map(kv -> new SequenceInfo(kv.getKey(), kv.getValue().getClass().getName(), kv.getValue().getName()))
54 | .collect(Collectors.toList());
55 | }
56 |
57 | @ReadOperation
58 | public List sequenceBeans() {
59 | return sequenceInfoList;
60 | }
61 |
62 | @Getter
63 | @AllArgsConstructor
64 | public static class SequenceInfo {
65 |
66 | private final String beanName;
67 |
68 | private final String className;
69 |
70 | private final String seqName;
71 |
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/sequence-spring-boot-starter/src/main/java/com/power4j/kit/seq/spring/boot/autoconfigure/MongoSynchronizerConfigure.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.kit.seq.spring.boot.autoconfigure;
18 |
19 | import com.mongodb.client.MongoClient;
20 | import com.mongodb.client.MongoClients;
21 | import com.power4j.kit.seq.persistent.SeqSynchronizer;
22 | import com.power4j.kit.seq.persistent.provider.SimpleMongoSynchronizer;
23 | import org.springframework.boot.autoconfigure.AutoConfiguration;
24 | import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
25 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
26 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
27 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
28 | import org.springframework.context.annotation.Bean;
29 |
30 | /**
31 | * @author CJ (power4j@outlook.com)
32 | * @date 2020/7/20
33 | * @since 1.0
34 | */
35 | @AutoConfiguration
36 | @ConditionalOnClass(MongoClient.class)
37 | public class MongoSynchronizerConfigure {
38 |
39 | @Bean(destroyMethod = "close")
40 | @ConditionalOnMissingBean
41 | @ConditionalOnProperty(prefix = SequenceProperties.PREFIX, name = "backend", havingValue = "mongo")
42 | public MongoClient mongoClient(SequenceProperties sequenceProperties) {
43 | return MongoClients.create(sequenceProperties.getMongoUri());
44 | }
45 |
46 | @Bean
47 | @ConditionalOnMissingBean
48 | @ConditionalOnBean(MongoClient.class)
49 | public SeqSynchronizer mongoSynchronizer(SequenceProperties sequenceProperties, MongoClient mongoClient) {
50 | SimpleMongoSynchronizer synchronizer = new SimpleMongoSynchronizer("sequence",
51 | sequenceProperties.getTableName(), mongoClient);
52 | if (!sequenceProperties.isLazyInit()) {
53 | synchronizer.init();
54 | }
55 | return synchronizer;
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/sequence-spring-boot-starter/src/main/java/com/power4j/kit/seq/spring/boot/autoconfigure/SequenceProperties.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.kit.seq.spring.boot.autoconfigure;
18 |
19 | import lombok.Data;
20 | import org.springframework.boot.context.properties.ConfigurationProperties;
21 |
22 | /**
23 | * @author CJ (power4j@outlook.com)
24 | * @date 2020/7/6
25 | * @since 1.0
26 | */
27 | @Data
28 | @ConfigurationProperties(prefix = SequenceProperties.PREFIX)
29 | public class SequenceProperties {
30 |
31 | public final static String PREFIX = "power4j.sequence";
32 |
33 | /**
34 | * 开关
35 | */
36 | private boolean enabled = true;
37 |
38 | /**
39 | * 后端类型
40 | */
41 | private BackendTypeEnum backend;
42 |
43 | /**
44 | * 懒加载
45 | */
46 | private boolean lazyInit = false;
47 |
48 | /**
49 | * 表名称(对于Redis则表示缓存名称,对于MongoDB则是集合名称,以此类推)
50 | */
51 | private String tableName = "seq_registry";
52 |
53 | /**
54 | * 每次从后端取值的步进,这个值需要权衡性能和序号丢失
55 | */
56 | private int fetchSize = 100;
57 |
58 | /**
59 | * 序号名称
60 | */
61 | private String name = "seq";
62 |
63 | /**
64 | * 起始值
65 | */
66 | private long startValue = 1L;
67 |
68 | /**
69 | * Lettuce URI
70 | * https://lettuce.io/core/release/reference/index.html#redisuri.uri-syntax
71 | */
72 | private String lettuceUri = "redis://localhost";
73 |
74 | /**
75 | * MongoDB URI https://docs.mongodb.com/manual/reference/connection-string/
76 | */
77 | private String mongoUri = "mongodb://localhost";
78 |
79 | public enum BackendTypeEnum {
80 |
81 | /**
82 | * MySQL
83 | */
84 | mysql,
85 | /**
86 | * Redis
87 | */
88 | redis,
89 | /**
90 | * Redis Cluster
91 | */
92 | redisCluster,
93 | /**
94 | * MongoDB
95 | */
96 | mongo,
97 | /**
98 | * PostgreSQL
99 | */
100 | postgresql
101 |
102 | }
103 |
104 | }
105 |
--------------------------------------------------------------------------------
/examples/mongo-example/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 | 4.0.0
20 |
21 | com.power4j.kit
22 | sequence-examples
23 | ${revision}
24 |
25 | mongo-example
26 | mongo-example
27 |
28 | Sequence example with mongodb backend
29 |
30 |
31 | true
32 | true
33 |
34 |
35 |
36 |
37 | com.power4j.kit
38 | sequence-spring-boot-starter
39 | ${project.parent.version}
40 |
41 |
42 | org.springframework.boot
43 | spring-boot-starter-web
44 |
45 |
46 | org.mongodb
47 | mongodb-driver-sync
48 |
49 |
50 | org.springframework.boot
51 | spring-boot-starter-test
52 | test
53 |
54 |
55 | org.junit.vintage
56 | junit-vintage-engine
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | org.springframework.boot
66 | spring-boot-maven-plugin
67 | ${spring-boot.version}
68 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/sequence-spring-boot-starter/src/main/java/com/power4j/kit/seq/spring/boot/autoconfigure/actuator/SeqSynchronizerEndpoint.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.kit.seq.spring.boot.autoconfigure.actuator;
18 |
19 | import com.power4j.kit.seq.persistent.SeqSynchronizer;
20 | import lombok.AllArgsConstructor;
21 | import lombok.Getter;
22 | import org.springframework.beans.factory.SmartInitializingSingleton;
23 | import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
24 | import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
25 | import org.springframework.context.ApplicationContext;
26 |
27 | import java.util.List;
28 | import java.util.Map;
29 | import java.util.stream.Collectors;
30 |
31 | /**
32 | * Endpoint for {@link SeqSynchronizer}
33 | *
34 | * @author CJ (power4j@outlook.com)
35 | * @date 2020/9/26
36 | * @since 1.4
37 | */
38 | @Endpoint(id = "sequence-synchronizer")
39 | public class SeqSynchronizerEndpoint implements SmartInitializingSingleton {
40 |
41 | private final ApplicationContext applicationContext;
42 |
43 | private Map synchronizerBeanMap;
44 |
45 | public SeqSynchronizerEndpoint(ApplicationContext applicationContext) {
46 | this.applicationContext = applicationContext;
47 | }
48 |
49 | @Override
50 | public void afterSingletonsInstantiated() {
51 | synchronizerBeanMap = applicationContext.getBeansOfType(SeqSynchronizer.class);
52 | }
53 |
54 | @ReadOperation
55 | public List synchronizerInfo() {
56 |
57 | // @formatter:off
58 |
59 | return synchronizerBeanMap.entrySet()
60 | .stream()
61 | .map(kv -> new SynchronizerInfo(
62 | kv.getKey(),
63 | kv.getValue().getClass().getName(),
64 | kv.getValue().getQueryCounter(),
65 | kv.getValue().getUpdateCounter()))
66 | .collect(Collectors.toList());
67 |
68 | // @formatter:on
69 | }
70 |
71 | @Getter
72 | @AllArgsConstructor
73 | public static class SynchronizerInfo {
74 |
75 | private final String beanName;
76 |
77 | private final String className;
78 |
79 | private final Long queryCount;
80 |
81 | private final Long updateCount;
82 |
83 | }
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/examples/redis-example/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 | 4.0.0
20 |
21 | com.power4j.kit
22 | sequence-examples
23 | ${revision}
24 |
25 | redis-example
26 | redis-example
27 |
28 | Sequence example with redis backend
29 |
30 |
31 | true
32 | true
33 |
34 |
35 |
36 |
37 | com.power4j.kit
38 | sequence-spring-boot-starter
39 | ${project.parent.version}
40 |
41 |
42 | org.springframework.boot
43 | spring-boot-starter-web
44 |
45 |
46 | io.lettuce
47 | lettuce-core
48 |
49 |
50 | org.apache.commons
51 | commons-pool2
52 |
53 |
54 | org.springframework.boot
55 | spring-boot-starter-test
56 | test
57 |
58 |
59 | org.junit.vintage
60 | junit-vintage-engine
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | org.springframework.boot
70 | spring-boot-maven-plugin
71 | ${spring-boot.version}
72 |
73 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/sequence-spring-boot-starter/src/main/java/com/power4j/kit/seq/spring/boot/autoconfigure/JdbcSynchronizerConfigure.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.kit.seq.spring.boot.autoconfigure;
18 |
19 | import com.power4j.kit.seq.persistent.SeqSynchronizer;
20 | import com.power4j.kit.seq.persistent.provider.MySqlSynchronizer;
21 | import com.power4j.kit.seq.persistent.provider.PostgreSqlSynchronizer;
22 | import org.springframework.boot.autoconfigure.AutoConfiguration;
23 | import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
24 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
25 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
26 | import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
27 | import org.springframework.context.annotation.Bean;
28 |
29 | import javax.sql.DataSource;
30 |
31 | /**
32 | * JDBC 序号同步器配置
33 | *
34 | * @author CJ (power4j@outlook.com)
35 | * @date 2020/7/7
36 | * @since 1.0
37 | */
38 | @AutoConfiguration(after = DataSourceAutoConfiguration.class)
39 | public class JdbcSynchronizerConfigure {
40 |
41 | @Bean
42 | @ConditionalOnMissingBean
43 | @ConditionalOnBean(DataSource.class)
44 | @ConditionalOnProperty(prefix = SequenceProperties.PREFIX, name = "backend", havingValue = "mysql")
45 | public SeqSynchronizer mysqlSynchronizer(SequenceProperties sequenceProperties, DataSource dataSource) {
46 | MySqlSynchronizer synchronizer = new MySqlSynchronizer(sequenceProperties.getTableName(), dataSource);
47 | if (!sequenceProperties.isLazyInit()) {
48 | synchronizer.init();
49 | }
50 | return synchronizer;
51 | }
52 |
53 | @Bean
54 | @ConditionalOnMissingBean
55 | @ConditionalOnBean(DataSource.class)
56 | @ConditionalOnProperty(prefix = SequenceProperties.PREFIX, name = "backend", havingValue = "postgresql")
57 | public SeqSynchronizer postgreSqlSynchronizer(SequenceProperties sequenceProperties, DataSource dataSource) {
58 | PostgreSqlSynchronizer synchronizer = new PostgreSqlSynchronizer(sequenceProperties.getTableName(), dataSource);
59 | if (!sequenceProperties.isLazyInit()) {
60 | synchronizer.init();
61 | }
62 | return synchronizer;
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/sequence-core/src/main/java/com/power4j/kit/seq/persistent/SeqSynchronizer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.kit.seq.persistent;
18 |
19 | import java.util.Optional;
20 |
21 | /**
22 | * Seq记录同步接口
23 | *
24 | * - Seq记录唯一性 = 名称 + 分区
25 | * - 此接口的所有方法除非特别说明,默认必须提供线程安全保障
26 | *
27 | *
28 | * @author CJ (power4j@outlook.com)
29 | * @date 2020/7/1
30 | * @since 1.0
31 | */
32 | public interface SeqSynchronizer {
33 |
34 | /**
35 | * 创建序号记录,如已经存在则忽略
36 | * @param name 名称
37 | * @param partition 分区
38 | * @param nextValue 初始值
39 | * @return true 表示创建成功,false 表示记录已经存在
40 | */
41 | boolean tryCreate(String name, String partition, long nextValue);
42 |
43 | /**
44 | * 尝试更新记录
45 | *
46 | * 此接口为可选实现接口
47 | *
48 | * @param name
49 | * @param partition
50 | * @param nextValueOld
51 | * @param nextValueNew
52 | * @return true 表示更新成功
53 | * @throws UnsupportedOperationException 不支持此方法
54 | */
55 | boolean tryUpdate(String name, String partition, long nextValueOld, long nextValueNew);
56 |
57 | /**
58 | * 尝试加法操作
59 | * @param name
60 | * @param partition
61 | * @param delta 加数
62 | * @param maxReTry 最大重试次数,小于0表示无限制. 0 表示重试零次(总共执行1次) 1 表示重试一次(总共执行2次)。此参数跟具体的实现层有关。
63 | * @return 返回执行加法操作执行结果
64 | */
65 | AddState tryAddAndGet(String name, String partition, int delta, int maxReTry);
66 |
67 | /**
68 | * 查询当前值
69 | * @param name
70 | * @param partition
71 | * @return 返回null表示记录不存在
72 | */
73 | Optional getNextValue(String name, String partition);
74 |
75 | /**
76 | * 执行初始化.
77 | *
78 | * 无线程线程安全保障,但是可以多次执行
79 | *
80 | * 。
81 | */
82 | void init();
83 |
84 | /**
85 | * 关闭,执行资源清理.
86 | *
87 | * 无线程线程安全保障,但是可以多次执行
88 | *
89 | * 。
90 | */
91 | default void shutdown() {
92 | // do nothing
93 | }
94 |
95 | /**
96 | * 查询语句总共执行的次数
97 | * @return
98 | */
99 | default long getQueryCounter() {
100 | return 0L;
101 | }
102 |
103 | /**
104 | * 更新语句总共执行的次数
105 | * @return
106 | */
107 | default long getUpdateCounter() {
108 | return 0L;
109 | }
110 |
111 | }
112 |
--------------------------------------------------------------------------------
/bench-test/src/main/java/com/power4j/kit/seq/LettuceSeqHolderBench.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.kit.seq;
18 |
19 | import com.power4j.kit.seq.persistent.SeqHolder;
20 | import com.power4j.kit.seq.persistent.SeqSynchronizer;
21 | import com.power4j.kit.seq.persistent.provider.SimpleLettuceSynchronizer;
22 | import com.power4j.kit.seq.utils.EnvUtil;
23 | import io.lettuce.core.RedisClient;
24 | import org.openjdk.jmh.annotations.*;
25 | import org.openjdk.jmh.infra.Blackhole;
26 | import org.openjdk.jmh.runner.Runner;
27 | import org.openjdk.jmh.runner.options.Options;
28 | import org.openjdk.jmh.runner.options.OptionsBuilder;
29 |
30 | import java.util.concurrent.TimeUnit;
31 |
32 | /**
33 | * Lettuce 后端性能测试
34 | *
35 | * @author CJ (power4j@outlook.com)
36 | * @date 2020/7/12
37 | * @since 1.1
38 | */
39 | @Fork(1)
40 | @State(Scope.Benchmark)
41 | @BenchmarkMode(Mode.Throughput)
42 | @Warmup(iterations = 1, time = 3)
43 | @Measurement(iterations = 3, time = 10)
44 | @OutputTimeUnit(TimeUnit.SECONDS)
45 | public class LettuceSeqHolderBench {
46 |
47 | /**
48 | * redis://[password@]host [: port][/database]
49 | */
50 | private final static String REDIS_URI = EnvUtil.getStr("TEST_REDIS_URI", "redis://127.0.0.1:6379");
51 |
52 | private static SeqSynchronizer synchronizer;
53 |
54 | private static SeqHolder seqHolder;
55 |
56 | @Setup
57 | public void setup() {
58 | final String partition = TestUtil.getPartitionName();
59 | RedisClient redisClient = RedisClient.create(REDIS_URI);
60 |
61 | synchronizer = new SimpleLettuceSynchronizer("lettuce_ben_test", redisClient);
62 | synchronizer.init();
63 | seqHolder = new SeqHolder(synchronizer, "lettuce_ben_test", partition, BenchParam.SEQ_INIT_VAL,
64 | BenchParam.SEQ_POOL_SIZE, null);
65 | seqHolder.prepare();
66 | }
67 |
68 | @Benchmark
69 | @Threads(1)
70 | public void testSingleThread(Blackhole bh) {
71 | bh.consume(seqHolder.next());
72 | }
73 |
74 | @Benchmark
75 | @Threads(4)
76 | public void test4Threads(Blackhole bh) {
77 | bh.consume(seqHolder.next());
78 | }
79 |
80 | public static void main(String[] args) throws Exception {
81 | Options opt = new OptionsBuilder().include(LettuceSeqHolderBench.class.getSimpleName()).build();
82 | new Runner(opt).run();
83 | }
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/bench-test/src/main/java/com/power4j/kit/seq/MongoSeqHolderBench.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.kit.seq;
18 |
19 | import com.mongodb.client.MongoClient;
20 | import com.mongodb.client.MongoClients;
21 | import com.power4j.kit.seq.persistent.SeqHolder;
22 | import com.power4j.kit.seq.persistent.SeqSynchronizer;
23 | import com.power4j.kit.seq.persistent.provider.SimpleMongoSynchronizer;
24 | import com.power4j.kit.seq.utils.EnvUtil;
25 | import org.openjdk.jmh.annotations.*;
26 | import org.openjdk.jmh.infra.Blackhole;
27 | import org.openjdk.jmh.runner.Runner;
28 | import org.openjdk.jmh.runner.options.Options;
29 | import org.openjdk.jmh.runner.options.OptionsBuilder;
30 |
31 | import java.util.concurrent.TimeUnit;
32 |
33 | /**
34 | * @author CJ (power4j@outlook.com)
35 | * @date 2020/7/20
36 | * @since 1.0
37 | */
38 | @Fork(1)
39 | @State(Scope.Benchmark)
40 | @BenchmarkMode(Mode.Throughput)
41 | @Warmup(iterations = 1, time = 3)
42 | @Measurement(iterations = 3, time = 10)
43 | @OutputTimeUnit(TimeUnit.SECONDS)
44 | public class MongoSeqHolderBench {
45 |
46 | /**
47 | * mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database.collection][?options]]
48 | */
49 | private final static String MONGO_URI = EnvUtil.getStr("TEST_MONGO_URI", "mongodb://127.0.0.1:27017");
50 |
51 | private static SeqSynchronizer synchronizer;
52 |
53 | private static SeqHolder seqHolder;
54 |
55 | @Setup
56 | public void setup() {
57 | MongoClient mongoClient = MongoClients.create(MONGO_URI);
58 | synchronizer = new SimpleMongoSynchronizer("seq_bench", "seq_col", mongoClient);
59 | synchronizer.init();
60 | seqHolder = new SeqHolder(synchronizer, "mongo-bench-test", TestUtil.getPartitionName(),
61 | BenchParam.SEQ_INIT_VAL, BenchParam.SEQ_POOL_SIZE, null);
62 | seqHolder.prepare();
63 | }
64 |
65 | @Benchmark
66 | @Threads(1)
67 | public void testSingleThread(Blackhole bh) {
68 | bh.consume(seqHolder.next());
69 | }
70 |
71 | @Benchmark
72 | @Threads(4)
73 | public void test4Threads(Blackhole bh) {
74 | bh.consume(seqHolder.next());
75 | }
76 |
77 | public static void main(String[] args) throws Exception {
78 | Options opt = new OptionsBuilder().include(MongoSeqHolderBench.class.getSimpleName()).build();
79 | new Runner(opt).run();
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/examples/jdbc-example/src/main/java/com/power4j/sequence/example/SequenceJdbcExampleApplication.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.sequence.example;
18 |
19 | import com.power4j.kit.seq.core.Sequence;
20 | import com.power4j.kit.seq.persistent.SeqSynchronizer;
21 | import lombok.RequiredArgsConstructor;
22 | import org.springframework.boot.SpringApplication;
23 | import org.springframework.boot.autoconfigure.SpringBootApplication;
24 | import org.springframework.web.bind.annotation.GetMapping;
25 | import org.springframework.web.bind.annotation.PathVariable;
26 | import org.springframework.web.bind.annotation.RequestParam;
27 | import org.springframework.web.bind.annotation.RestController;
28 |
29 | import java.util.ArrayList;
30 | import java.util.HashMap;
31 | import java.util.List;
32 | import java.util.Map;
33 |
34 | @RequiredArgsConstructor
35 | @RestController
36 | @SpringBootApplication
37 | public class SequenceJdbcExampleApplication {
38 |
39 | private final static int MAX_SEQ_NAME = 40;
40 |
41 | private final Sequence sequence;
42 |
43 | private final SeqSynchronizer seqSynchronizer;
44 |
45 | private final SeqService seqService;
46 |
47 | public static void main(String[] args) {
48 | SpringApplication.run(SequenceJdbcExampleApplication.class, args);
49 | }
50 |
51 | @GetMapping("/")
52 | public Map getSequence(@RequestParam(required = false) Integer size) {
53 | size = (size == null || size <= 0) ? 10 : size;
54 | List list = new ArrayList<>(size);
55 | for (int i = 0; i < size; ++i) {
56 | list.add(sequence.nextStr());
57 | }
58 | Map data = new HashMap<>();
59 | data.put("query_count", Long.toString(seqSynchronizer.getQueryCounter()));
60 | data.put("update_count", Long.toString(seqSynchronizer.getUpdateCounter()));
61 | data.put("seq", list);
62 | return data;
63 | }
64 |
65 | @GetMapping("/name/{name}")
66 | public List getSequence(@PathVariable String name, @RequestParam(required = false) Integer size) {
67 | size = (size == null || size <= 0) ? 10 : size;
68 | if (name.length() > MAX_SEQ_NAME) {
69 | name = name.substring(0, 40);
70 | }
71 | List data = new ArrayList<>(10);
72 | for (int i = 0; i < size; ++i) {
73 | data.add(seqService.getForName(name));
74 | }
75 | return data;
76 | }
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/bench-test/src/main/java/com/power4j/kit/seq/H2SeqHolderBench.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.kit.seq;
18 |
19 | import com.power4j.kit.seq.persistent.SeqHolder;
20 | import com.power4j.kit.seq.persistent.SeqSynchronizer;
21 | import com.power4j.kit.seq.persistent.provider.H2Synchronizer;
22 | import com.power4j.kit.seq.utils.EnvUtil;
23 | import com.zaxxer.hikari.HikariConfig;
24 | import com.zaxxer.hikari.HikariDataSource;
25 | import org.openjdk.jmh.annotations.*;
26 | import org.openjdk.jmh.infra.Blackhole;
27 | import org.openjdk.jmh.runner.Runner;
28 | import org.openjdk.jmh.runner.options.Options;
29 | import org.openjdk.jmh.runner.options.OptionsBuilder;
30 |
31 | import java.util.concurrent.TimeUnit;
32 |
33 | /**
34 | * 取号器,使用H2作为后端
35 | *
36 | * @author lishangbu
37 | * @date 2021/8/28
38 | * @since 1.5.0
39 | */
40 | @Fork(1)
41 | @State(Scope.Benchmark)
42 | @BenchmarkMode(Mode.Throughput)
43 | @Warmup(iterations = 1, time = 3)
44 | @Measurement(iterations = 3, time = 10)
45 | @OutputTimeUnit(TimeUnit.SECONDS)
46 | public class H2SeqHolderBench {
47 |
48 | private final static String DEFAULT_H2_JDBC_URL = "jdbc:h2:mem:test;MODE=MYSQL;DB_CLOSE_DELAY=-1";
49 |
50 | private final static String JDBC_URL = EnvUtil.getStr("TEST_H2_URL", DEFAULT_H2_JDBC_URL);
51 |
52 | private static SeqSynchronizer synchronizer;
53 |
54 | private static SeqHolder seqHolder;
55 |
56 | @Setup
57 | public void setup() {
58 | HikariConfig config = new HikariConfig();
59 | config.setJdbcUrl(JDBC_URL);
60 | config.setUsername(EnvUtil.getStr("TEST_H2_USER", "sa"));
61 | config.setPassword(EnvUtil.getStr("TEST_H2_PWD", ""));
62 | synchronizer = new H2Synchronizer("seq_bench", new HikariDataSource(config));
63 | synchronizer.init();
64 | seqHolder = new SeqHolder(synchronizer, "h2-bench-test", TestUtil.getPartitionName(), BenchParam.SEQ_INIT_VAL,
65 | BenchParam.SEQ_POOL_SIZE, null);
66 | seqHolder.prepare();
67 | }
68 |
69 | @Benchmark
70 | @Threads(1)
71 | public void testSingleThread(Blackhole bh) {
72 | bh.consume(seqHolder.next());
73 | }
74 |
75 | @Benchmark
76 | @Threads(4)
77 | public void test4Threads(Blackhole bh) {
78 | bh.consume(seqHolder.next());
79 | }
80 |
81 | public static void main(String[] args) throws Exception {
82 | Options opt = new OptionsBuilder().include(H2SeqHolderBench.class.getSimpleName()).build();
83 | new Runner(opt).run();
84 | }
85 |
86 | }
87 |
--------------------------------------------------------------------------------
/bench-test/src/main/java/com/power4j/kit/seq/PostgreSqlSeqHolderBench.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.kit.seq;
18 |
19 | import com.power4j.kit.seq.persistent.SeqHolder;
20 | import com.power4j.kit.seq.persistent.SeqSynchronizer;
21 | import com.power4j.kit.seq.persistent.provider.PostgreSqlSynchronizer;
22 | import com.power4j.kit.seq.utils.EnvUtil;
23 | import com.zaxxer.hikari.HikariConfig;
24 | import com.zaxxer.hikari.HikariDataSource;
25 | import org.openjdk.jmh.annotations.*;
26 | import org.openjdk.jmh.infra.Blackhole;
27 | import org.openjdk.jmh.runner.Runner;
28 | import org.openjdk.jmh.runner.options.Options;
29 | import org.openjdk.jmh.runner.options.OptionsBuilder;
30 |
31 | import java.util.concurrent.TimeUnit;
32 |
33 | /**
34 | * @author CJ (power4j@outlook.com)
35 | * @date 2020/7/29
36 | * @since 1.3
37 | */
38 | @Fork(1)
39 | @State(Scope.Benchmark)
40 | @BenchmarkMode(Mode.Throughput)
41 | @Warmup(iterations = 1, time = 3)
42 | @Measurement(iterations = 3, time = 10)
43 | @OutputTimeUnit(TimeUnit.SECONDS)
44 | public class PostgreSqlSeqHolderBench {
45 |
46 | private final static String DEFAULT_POSTGRESQL_JDBC_URL = "jdbc:postgresql://127.0.0.1:5432/test?ssl=false";
47 |
48 | private final static String JDBC_URL = EnvUtil.getStr("TEST_POSTGRESQL_URL", DEFAULT_POSTGRESQL_JDBC_URL);
49 |
50 | private static SeqSynchronizer synchronizer;
51 |
52 | private static SeqHolder seqHolder;
53 |
54 | @Setup
55 | public void setup() {
56 | HikariConfig config = new HikariConfig();
57 | config.setJdbcUrl(JDBC_URL);
58 | config.setUsername(EnvUtil.getStr("TEST_POSTGRESQL_USER", "root"));
59 | config.setPassword(EnvUtil.getStr("TEST_POSTGRESQL_PWD", "root"));
60 | synchronizer = new PostgreSqlSynchronizer("seq_bench", new HikariDataSource(config));
61 | synchronizer.init();
62 | seqHolder = new SeqHolder(synchronizer, "postgresql-bench-test", TestUtil.getPartitionName(),
63 | BenchParam.SEQ_INIT_VAL, BenchParam.SEQ_POOL_SIZE, null);
64 | seqHolder.prepare();
65 | }
66 |
67 | @Benchmark
68 | @Threads(1)
69 | public void testSingleThread(Blackhole bh) {
70 | bh.consume(seqHolder.next());
71 | }
72 |
73 | @Benchmark
74 | @Threads(4)
75 | public void test4Threads(Blackhole bh) {
76 | bh.consume(seqHolder.next());
77 | }
78 |
79 | public static void main(String[] args) throws Exception {
80 | Options opt = new OptionsBuilder().include(PostgreSqlSeqHolderBench.class.getSimpleName()).build();
81 | new Runner(opt).run();
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/examples/actuator-example/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 | 4.0.0
20 |
21 | com.power4j.kit
22 | sequence-examples
23 | ${revision}
24 |
25 | actuator-example
26 | actuator-example
27 |
28 |
29 | true
30 | true
31 |
32 |
33 |
34 |
35 | com.power4j.kit
36 | sequence-spring-boot-starter
37 | ${project.parent.version}
38 |
39 |
40 | org.springframework.boot
41 | spring-boot-starter-web
42 |
43 |
44 | org.springframework.boot
45 | spring-boot-starter-jdbc
46 |
47 |
48 | org.springframework.boot
49 | spring-boot-starter-actuator
50 |
51 |
52 | com.mysql
53 | mysql-connector-j
54 | runtime
55 |
56 |
57 | org.postgresql
58 | postgresql
59 | runtime
60 |
61 |
62 | org.springframework.boot
63 | spring-boot-starter-test
64 | test
65 |
66 |
67 | org.junit.vintage
68 | junit-vintage-engine
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | org.springframework.boot
78 | spring-boot-maven-plugin
79 | ${spring-boot.version}
80 |
81 |
82 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/bench-test/src/main/java/com/power4j/kit/seq/MySqlSeqHolderBench.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.kit.seq;
18 |
19 | import com.power4j.kit.seq.persistent.SeqHolder;
20 | import com.power4j.kit.seq.persistent.SeqSynchronizer;
21 | import com.power4j.kit.seq.persistent.provider.MySqlSynchronizer;
22 | import com.power4j.kit.seq.utils.EnvUtil;
23 | import com.zaxxer.hikari.HikariConfig;
24 | import com.zaxxer.hikari.HikariDataSource;
25 | import org.openjdk.jmh.annotations.*;
26 | import org.openjdk.jmh.infra.Blackhole;
27 | import org.openjdk.jmh.runner.Runner;
28 | import org.openjdk.jmh.runner.options.Options;
29 | import org.openjdk.jmh.runner.options.OptionsBuilder;
30 |
31 | import java.util.concurrent.TimeUnit;
32 |
33 | /**
34 | * 取号器,使用MysSql作为后端
35 | *
36 | * @author CJ (power4j@outlook.com)
37 | * @date 2020/7/4
38 | * @since 1.0
39 | */
40 | @Fork(1)
41 | @State(Scope.Benchmark)
42 | @BenchmarkMode(Mode.Throughput)
43 | @Warmup(iterations = 1, time = 3)
44 | @Measurement(iterations = 3, time = 10)
45 | @OutputTimeUnit(TimeUnit.SECONDS)
46 | public class MySqlSeqHolderBench {
47 |
48 | private final static String DEFAULT_MYSQL_JDBC_URL = "jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false";
49 |
50 | private final static String JDBC_URL = EnvUtil.getStr("TEST_MYSQL_URL", DEFAULT_MYSQL_JDBC_URL);
51 |
52 | private static SeqSynchronizer synchronizer;
53 |
54 | private static SeqHolder seqHolder;
55 |
56 | @Setup
57 | public void setup() {
58 | HikariConfig config = new HikariConfig();
59 | config.setJdbcUrl(JDBC_URL);
60 | config.setUsername(EnvUtil.getStr("TEST_MYSQL_USER", "root"));
61 | config.setPassword(EnvUtil.getStr("TEST_MYSQL_PWD", "root"));
62 | synchronizer = new MySqlSynchronizer("seq_bench", new HikariDataSource(config));
63 | synchronizer.init();
64 | seqHolder = new SeqHolder(synchronizer, "mysql-bench-test", TestUtil.getPartitionName(),
65 | BenchParam.SEQ_INIT_VAL, BenchParam.SEQ_POOL_SIZE, null);
66 | seqHolder.prepare();
67 | }
68 |
69 | @Benchmark
70 | @Threads(1)
71 | public void testSingleThread(Blackhole bh) {
72 | bh.consume(seqHolder.next());
73 | }
74 |
75 | @Benchmark
76 | @Threads(4)
77 | public void test4Threads(Blackhole bh) {
78 | bh.consume(seqHolder.next());
79 | }
80 |
81 | public static void main(String[] args) throws Exception {
82 | Options opt = new OptionsBuilder().include(MySqlSeqHolderBench.class.getSimpleName()).build();
83 | new Runner(opt).run();
84 | }
85 |
86 | }
87 |
--------------------------------------------------------------------------------
/examples/jdbc-example/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 | 4.0.0
20 |
21 | com.power4j.kit
22 | sequence-examples
23 | ${revision}
24 |
25 | jdbc-example
26 | jdbc-example
27 |
28 | Sequence example with JDBC backend
29 |
30 |
31 | true
32 | true
33 |
34 |
35 |
36 |
37 | com.power4j.kit
38 | sequence-spring-boot-starter
39 | ${project.parent.version}
40 |
41 |
42 | org.springframework.boot
43 | spring-boot-starter-web
44 |
45 |
46 | org.springframework.boot
47 | spring-boot-starter-jdbc
48 |
49 |
50 | com.mysql
51 | mysql-connector-j
52 | runtime
53 |
54 |
55 | org.postgresql
56 | postgresql
57 | runtime
58 |
59 |
60 | com.h2database
61 | h2
62 | runtime
63 |
64 |
65 | org.springframework.boot
66 | spring-boot-starter-test
67 | test
68 |
69 |
70 | org.junit.vintage
71 | junit-vintage-engine
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | org.springframework.boot
81 | spring-boot-maven-plugin
82 | ${spring-boot.version}
83 |
84 |
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/sequence-spring-boot-starter/src/main/java/com/power4j/kit/seq/spring/boot/autoconfigure/SequenceAutoConfigure.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.kit.seq.spring.boot.autoconfigure;
18 |
19 | import com.power4j.kit.seq.core.SeqFormatter;
20 | import com.power4j.kit.seq.core.Sequence;
21 | import com.power4j.kit.seq.ext.InMemorySequenceRegistry;
22 | import com.power4j.kit.seq.ext.SequenceRegistry;
23 | import com.power4j.kit.seq.persistent.Partitions;
24 | import com.power4j.kit.seq.persistent.SeqHolder;
25 | import com.power4j.kit.seq.persistent.SeqSynchronizer;
26 | import lombok.extern.slf4j.Slf4j;
27 | import org.springframework.boot.autoconfigure.AutoConfiguration;
28 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
29 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
30 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
31 | import org.springframework.context.annotation.Bean;
32 | import org.springframework.context.annotation.Import;
33 |
34 | /**
35 | * 自动配置
36 | *
37 | * @author CJ (power4j@outlook.com)
38 | * @date 2020/7/6
39 | * @since 1.0
40 | */
41 | @Slf4j
42 | @AutoConfiguration
43 | @EnableConfigurationProperties(SequenceProperties.class)
44 | @Import({ JdbcSynchronizerConfigure.class, RedisSynchronizerConfigure.class })
45 | public class SequenceAutoConfigure {
46 |
47 | @Bean
48 | @ConditionalOnMissingBean(value = Long.class, parameterizedContainer = Sequence.class)
49 | @ConditionalOnProperty(prefix = SequenceProperties.PREFIX, name = "enabled", havingValue = "true",
50 | matchIfMissing = true)
51 | public Sequence sequence(SequenceProperties sequenceProperties, SeqSynchronizer seqSynchronizer) {
52 | log.info("Sequence create,Using {}", seqSynchronizer.getClass().getSimpleName());
53 | // 按月分区:即每个月有 Long.MAX 个序号可用
54 |
55 | // @formatter:off
56 |
57 | return SeqHolder.builder()
58 | .name(sequenceProperties.getName())
59 | .synchronizer(seqSynchronizer)
60 | .partitionFunc(Partitions.MONTHLY)
61 | .initValue(sequenceProperties.getStartValue())
62 | .poolSize(sequenceProperties.getFetchSize())
63 | .seqFormatter(SeqFormatter.DEFAULT_FORMAT)
64 | .build();
65 |
66 | // @formatter:on
67 |
68 | }
69 |
70 | @Bean
71 | @ConditionalOnMissingBean(SequenceRegistry.class)
72 | @ConditionalOnProperty(prefix = SequenceProperties.PREFIX, name = "enabled", havingValue = "true",
73 | matchIfMissing = true)
74 | public SequenceRegistry> sequenceRegistry() {
75 | return new InMemorySequenceRegistry<>();
76 | }
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/sequence-core/src/test/java/com/power4j/kit/seq/persistent/SeqHolderTestCase.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.kit.seq.persistent;
18 |
19 | import com.power4j.kit.seq.TestUtil;
20 | import lombok.extern.slf4j.Slf4j;
21 | import org.junit.Assert;
22 | import org.junit.Test;
23 |
24 | import java.time.Duration;
25 | import java.time.Instant;
26 | import java.util.HashSet;
27 | import java.util.Set;
28 | import java.util.concurrent.CompletableFuture;
29 | import java.util.concurrent.CountDownLatch;
30 | import java.util.concurrent.ExecutorService;
31 | import java.util.concurrent.Executors;
32 | import java.util.concurrent.atomic.AtomicLong;
33 |
34 | /**
35 | * @author CJ (power4j@outlook.com)
36 | * @date 2020/7/14
37 | * @since 1.0
38 | */
39 | @Slf4j
40 | public abstract class SeqHolderTestCase {
41 |
42 | protected abstract SeqHolder getSeqHolder();
43 |
44 | @Test
45 | public void getValueTest() {
46 | final SeqHolder holder = getSeqHolder();
47 | final int threads = 4;
48 | final int loops = 20000;
49 | CountDownLatch threadReady = new CountDownLatch(threads);
50 | CountDownLatch threadDone = new CountDownLatch(threads);
51 | AtomicLong opCount = new AtomicLong();
52 | ExecutorService executorService = Executors.newFixedThreadPool(threads);
53 | log.info(String.format("start test loops = %d threads = %d", loops, threads));
54 | Instant startTime = Instant.now();
55 | for (int t = 0; t < threads; ++t) {
56 | CompletableFuture.runAsync(() -> {
57 | threadReady.countDown();
58 | Set dataSet = new HashSet<>(loops);
59 | TestUtil.wait(threadReady);
60 |
61 | for (int i = 0; i < loops; ++i) {
62 | if (i % 5000 == 0) {
63 | log.info(String.format("[thread %s] test running [%d / %d]", Thread.currentThread().getName(),
64 | i, loops));
65 | }
66 | long val = holder.next();
67 | dataSet.add(val);
68 | }
69 | int size = dataSet.size();
70 | dataSet.clear();
71 | opCount.addAndGet(size);
72 | threadDone.countDown();
73 | log.info(String.format("[thread %s] [done] dataset size = %08d", Thread.currentThread().getName(),
74 | size));
75 | Assert.assertEquals(size, loops);
76 | }, executorService).exceptionally(e -> {
77 | threadDone.countDown();
78 | e.printStackTrace();
79 | return null;
80 | });
81 | }
82 |
83 | TestUtil.wait(threadDone);
84 | long timeCost = Duration.between(startTime, Instant.now()).toMillis();
85 | log.info(String.format("test end,opCount = %d,time cost = %d ms", opCount.get(), timeCost));
86 | Assert.assertEquals(opCount.get(), loops * threads);
87 | }
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/sequence-spring-boot-starter/src/main/java/com/power4j/kit/seq/spring/boot/autoconfigure/RedisSynchronizerConfigure.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.kit.seq.spring.boot.autoconfigure;
18 |
19 | import com.power4j.kit.seq.persistent.SeqSynchronizer;
20 | import com.power4j.kit.seq.persistent.provider.LettuceClusterSynchronizer;
21 | import com.power4j.kit.seq.persistent.provider.SimpleLettuceSynchronizer;
22 | import io.lettuce.core.RedisClient;
23 | import io.lettuce.core.RedisURI;
24 | import io.lettuce.core.cluster.RedisClusterClient;
25 | import org.springframework.boot.autoconfigure.AutoConfiguration;
26 | import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
27 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
28 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
29 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
30 | import org.springframework.context.annotation.Bean;
31 |
32 | /**
33 | * @author CJ (power4j@outlook.com)
34 | * @date 2020/7/13
35 | * @since 1.0
36 | */
37 | @AutoConfiguration
38 | @ConditionalOnClass({ RedisClient.class, RedisClusterClient.class })
39 | public class RedisSynchronizerConfigure {
40 |
41 | @Bean(destroyMethod = "shutdown")
42 | @ConditionalOnMissingBean
43 | @ConditionalOnProperty(prefix = SequenceProperties.PREFIX, name = "backend", havingValue = "redis")
44 | public RedisClient redisClient(SequenceProperties sequenceProperties) {
45 | RedisURI redisUri = RedisURI.create(sequenceProperties.getLettuceUri());
46 | return RedisClient.create(redisUri);
47 | }
48 |
49 | @Bean(destroyMethod = "shutdown")
50 | @ConditionalOnMissingBean
51 | @ConditionalOnProperty(prefix = SequenceProperties.PREFIX, name = "backend", havingValue = "redis-cluster")
52 | public RedisClusterClient redisClusterClient(SequenceProperties sequenceProperties) {
53 | RedisURI redisUri = RedisURI.create(sequenceProperties.getLettuceUri());
54 | return RedisClusterClient.create(redisUri);
55 | }
56 |
57 | @Bean
58 | @ConditionalOnMissingBean
59 | @ConditionalOnBean(RedisClient.class)
60 | public SeqSynchronizer redisSynchronizer(SequenceProperties sequenceProperties, RedisClient redisClient) {
61 | SimpleLettuceSynchronizer synchronizer = new SimpleLettuceSynchronizer(sequenceProperties.getTableName(),
62 | redisClient);
63 | if (!sequenceProperties.isLazyInit()) {
64 | synchronizer.init();
65 | }
66 | return synchronizer;
67 | }
68 |
69 | @Bean
70 | @ConditionalOnMissingBean
71 | @ConditionalOnBean(RedisClusterClient.class)
72 | public SeqSynchronizer redisClusterSynchronizer(SequenceProperties sequenceProperties,
73 | RedisClusterClient redisClusterClient) {
74 | LettuceClusterSynchronizer synchronizer = new LettuceClusterSynchronizer(sequenceProperties.getTableName(),
75 | redisClusterClient);
76 | if (!sequenceProperties.isLazyInit()) {
77 | synchronizer.init();
78 | }
79 | return synchronizer;
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/sequence-core/src/main/java/com/power4j/kit/seq/persistent/provider/H2Synchronizer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.kit.seq.persistent.provider;
18 |
19 | import com.power4j.kit.seq.core.exceptions.SeqException;
20 | import com.power4j.kit.seq.persistent.SeqSynchronizer;
21 | import lombok.AllArgsConstructor;
22 | import lombok.extern.slf4j.Slf4j;
23 |
24 | import javax.sql.DataSource;
25 | import java.sql.Connection;
26 |
27 | /**
28 | * H2数据源支持
29 | *
30 | * @author lishangbu
31 | * @date 2021/8/28
32 | * @since 1.5.0
33 | */
34 | @Slf4j
35 | @AllArgsConstructor
36 | public class H2Synchronizer extends AbstractSqlStatementProvider implements SeqSynchronizer {
37 |
38 | // @formatter:off
39 |
40 | private final static String H2_CREATE_TABLE =
41 | "CREATE TABLE IF NOT EXISTS $TABLE_NAME (" +
42 | "seq_name VARCHAR ( 255 ) NOT NULL," +
43 | "seq_partition VARCHAR ( 255 ) NOT NULL," +
44 | "seq_next_value BIGINT NOT NULL," +
45 | "seq_create_time TIMESTAMP NOT NULL," +
46 | "seq_update_time TIMESTAMP NULL," +
47 | "PRIMARY KEY ( `seq_name`, `seq_partition` ) " +
48 | ")";
49 |
50 | private final static String H2_DROP_TABLE = "DROP TABLE IF EXISTS $TABLE_NAME";
51 |
52 | private final static String H2_INSERT_IGNORE =
53 | "INSERT IGNORE INTO $TABLE_NAME" +
54 | "(seq_name,seq_partition,seq_next_value,seq_create_time)" +
55 | " VALUES (?,?,?,?)";
56 |
57 | private final static String H2_UPDATE_VALUE =
58 | "UPDATE $TABLE_NAME SET seq_next_value=?,seq_update_time=? " +
59 | "WHERE seq_name=? AND seq_partition=? AND seq_next_value=?";
60 |
61 | private final static String H2_SELECT_VALUE =
62 | "SELECT seq_next_value FROM $TABLE_NAME WHERE seq_name=? AND seq_partition=?";
63 |
64 | // @formatter:on
65 |
66 | private final String tableName;
67 |
68 | private final DataSource dataSource;
69 |
70 | @Override
71 | protected Connection getConnection() {
72 | try {
73 | return dataSource.getConnection();
74 | }
75 | catch (Exception e) {
76 | log.warn(e.getMessage(), e);
77 | throw new SeqException(e.getMessage(), e);
78 | }
79 | }
80 |
81 | @Override
82 | protected String getCreateTableSql() {
83 | return H2_CREATE_TABLE.replace("$TABLE_NAME", tableName);
84 | }
85 |
86 | @Override
87 | protected String getDropTableSql() {
88 | return H2_DROP_TABLE.replace("$TABLE_NAME", tableName);
89 | }
90 |
91 | @Override
92 | protected String getCreateSeqSql() {
93 | return H2_INSERT_IGNORE.replace("$TABLE_NAME", tableName);
94 | }
95 |
96 | @Override
97 | protected String getSelectSeqSql() {
98 | return H2_SELECT_VALUE.replace("$TABLE_NAME", tableName);
99 | }
100 |
101 | @Override
102 | protected String getUpdateSeqSql() {
103 | return H2_UPDATE_VALUE.replace("$TABLE_NAME", tableName);
104 | }
105 |
106 | }
107 |
--------------------------------------------------------------------------------
/sequence-core/src/main/java/com/power4j/kit/seq/persistent/provider/MySqlSynchronizer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.kit.seq.persistent.provider;
18 |
19 | import com.power4j.kit.seq.core.exceptions.SeqException;
20 | import com.power4j.kit.seq.persistent.SeqSynchronizer;
21 | import lombok.AllArgsConstructor;
22 | import lombok.extern.slf4j.Slf4j;
23 |
24 | import javax.sql.DataSource;
25 | import java.sql.Connection;
26 |
27 | /**
28 | * MySql支持
29 | *
30 | * @author CJ (power4j@outlook.com)
31 | * @date 2020/7/1
32 | * @since 1.0
33 | */
34 | @Slf4j
35 | @AllArgsConstructor
36 | public class MySqlSynchronizer extends AbstractSqlStatementProvider implements SeqSynchronizer {
37 |
38 | // @formatter:off
39 |
40 | private final static String MYSQL_CREATE_TABLE =
41 | "CREATE TABLE IF NOT EXISTS $TABLE_NAME (" +
42 | "seq_name VARCHAR ( 255 ) NOT NULL," +
43 | "seq_partition VARCHAR ( 255 ) NOT NULL," +
44 | "seq_next_value BIGINT NOT NULL," +
45 | "seq_create_time TIMESTAMP NOT NULL," +
46 | "seq_update_time TIMESTAMP NULL," +
47 | "PRIMARY KEY ( `seq_name`, `seq_partition` ) " +
48 | ")";
49 |
50 | private final static String MYSQL_DROP_TABLE = "DROP TABLE IF EXISTS $TABLE_NAME";
51 |
52 | private final static String MYSQL_INSERT_IGNORE =
53 | "INSERT IGNORE INTO $TABLE_NAME" +
54 | "(seq_name,seq_partition,seq_next_value,seq_create_time)" +
55 | " VALUES (?,?,?,?)";
56 |
57 | private final static String MYSQL_UPDATE_VALUE =
58 | "UPDATE $TABLE_NAME SET seq_next_value=?,seq_update_time=? " +
59 | "WHERE seq_name=? AND seq_partition=? AND seq_next_value=?";
60 |
61 | private final static String MYSQL_SELECT_VALUE =
62 | "SELECT seq_next_value FROM $TABLE_NAME WHERE seq_name=? AND seq_partition=?";
63 |
64 | // @formatter:on
65 |
66 | private final String tableName;
67 |
68 | private final DataSource dataSource;
69 |
70 | @Override
71 | protected Connection getConnection() {
72 | try {
73 | return dataSource.getConnection();
74 | }
75 | catch (Exception e) {
76 | log.warn(e.getMessage(), e);
77 | throw new SeqException(e.getMessage(), e);
78 | }
79 | }
80 |
81 | @Override
82 | protected String getCreateTableSql() {
83 | return MYSQL_CREATE_TABLE.replace("$TABLE_NAME", tableName);
84 | }
85 |
86 | @Override
87 | protected String getDropTableSql() {
88 | return MYSQL_DROP_TABLE.replace("$TABLE_NAME", tableName);
89 | }
90 |
91 | @Override
92 | protected String getCreateSeqSql() {
93 | return MYSQL_INSERT_IGNORE.replace("$TABLE_NAME", tableName);
94 | }
95 |
96 | @Override
97 | protected String getSelectSeqSql() {
98 | return MYSQL_SELECT_VALUE.replace("$TABLE_NAME", tableName);
99 | }
100 |
101 | @Override
102 | protected String getUpdateSeqSql() {
103 | return MYSQL_UPDATE_VALUE.replace("$TABLE_NAME", tableName);
104 | }
105 |
106 | }
107 |
--------------------------------------------------------------------------------
/sequence-core/src/main/java/com/power4j/kit/seq/persistent/provider/SimpleLettuceSynchronizer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.kit.seq.persistent.provider;
18 |
19 | import com.power4j.kit.seq.core.exceptions.SeqException;
20 | import com.power4j.kit.seq.persistent.SeqSynchronizer;
21 | import io.lettuce.core.RedisClient;
22 | import io.lettuce.core.api.StatefulRedisConnection;
23 | import io.lettuce.core.api.sync.RedisCommands;
24 | import io.lettuce.core.api.sync.RedisKeyCommands;
25 | import io.lettuce.core.api.sync.RedisScriptingCommands;
26 | import io.lettuce.core.api.sync.RedisStringCommands;
27 | import io.lettuce.core.support.ConnectionPoolSupport;
28 | import org.apache.commons.pool2.impl.GenericObjectPool;
29 | import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
30 |
31 | import java.util.function.Function;
32 |
33 | /**
34 | * 普通Lettuce连接池同步
35 | *
36 | * @author CJ (power4j@outlook.com)
37 | * @date 2020/7/10
38 | * @since 1.1
39 | */
40 | public class SimpleLettuceSynchronizer extends AbstractLettuceSynchronizer implements SeqSynchronizer {
41 |
42 | private final GenericObjectPool> pool;
43 |
44 | public SimpleLettuceSynchronizer(String cacheName, RedisClient redisClient) {
45 | this(cacheName, redisClient, new GenericObjectPoolConfig());
46 | }
47 |
48 | public SimpleLettuceSynchronizer(String cacheName, RedisClient redisClient,
49 | GenericObjectPoolConfig> poolConfig) {
50 | super(cacheName);
51 | this.pool = ConnectionPoolSupport.createGenericObjectPool(() -> redisClient.connect(), poolConfig);
52 | }
53 |
54 | public SimpleLettuceSynchronizer(String cacheName,
55 | GenericObjectPool> pool) {
56 | super(cacheName);
57 | this.pool = pool;
58 | }
59 |
60 | public R execCommand(Function, R> func) {
61 | try (StatefulRedisConnection connection = getRedisConnection()) {
62 | return func.apply(connection.sync());
63 | }
64 | }
65 |
66 | protected StatefulRedisConnection getRedisConnection() {
67 | try {
68 | return pool.borrowObject();
69 | }
70 | catch (Exception e) {
71 | throw new SeqException(e.getMessage(), e);
72 | }
73 | }
74 |
75 | @Override
76 | public void shutdown() {
77 | pool.close();
78 | }
79 |
80 | @Override
81 | protected R execStringCommand(Function, R> func) {
82 | try (StatefulRedisConnection connection = getRedisConnection()) {
83 | return func.apply(connection.sync());
84 | }
85 | }
86 |
87 | @Override
88 | protected R execScriptingCommand(Function, R> func) {
89 | try (StatefulRedisConnection connection = getRedisConnection()) {
90 | return func.apply(connection.sync());
91 | }
92 | }
93 |
94 | @Override
95 | protected R execKeyCommand(Function, R> func) {
96 | try (StatefulRedisConnection connection = getRedisConnection()) {
97 | return func.apply(connection.sync());
98 | }
99 | }
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/sequence-core/src/test/java/com/power4j/kit/seq/persistent/provider/TestServices.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.kit.seq.persistent.provider;
18 |
19 | import com.mongodb.client.MongoClient;
20 | import com.mongodb.client.MongoClients;
21 | import com.power4j.kit.seq.utils.EnvUtil;
22 | import com.zaxxer.hikari.HikariConfig;
23 | import com.zaxxer.hikari.HikariDataSource;
24 | import io.lettuce.core.RedisClient;
25 |
26 | import javax.sql.DataSource;
27 |
28 | /**
29 | * @author CJ (power4j@outlook.com)
30 | * @date 2020/7/3
31 | * @since 1.0
32 | */
33 | public class TestServices {
34 |
35 | /**
36 | * protocol//[hosts][/database][?properties]
37 | */
38 | private final static String DEFAULT_MYSQL_JDBC_URL = "jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false";
39 |
40 | /**
41 | * jdbc:postgresql://host:port/database
42 | */
43 | private final static String DEFAULT_POSTGRESQL_JDBC_URL = "jdbc:postgresql://127.0.0.1:5432/test?ssl=false";
44 |
45 | /**
46 | * jdbc:h2://host:port:database;[properties];
47 | */
48 | private final static String DEFAULT_H2_JDBC_URL = "jdbc:h2:mem:test;MODE=MYSQL;DB_CLOSE_DELAY=-1";
49 |
50 | /**
51 | * redis://[password@]host [: port][/database]
52 | */
53 | public final static String DEFAULT_REDIS_URI = "redis://127.0.0.1:6379";
54 |
55 | /**
56 | * mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database.collection][?options]]
57 | */
58 | public final static String DEFAULT_MONGO_URI = "mongodb://127.0.0.1:27017";
59 |
60 | public static DataSource getMySqlDataSource() {
61 | String jdbcUrl = EnvUtil.getStr("TEST_MYSQL_URL", DEFAULT_MYSQL_JDBC_URL);
62 | HikariConfig config = new HikariConfig();
63 | config.setJdbcUrl(jdbcUrl);
64 | config.setUsername(EnvUtil.getStr("TEST_MYSQL_USER", "root"));
65 | config.setPassword(EnvUtil.getStr("TEST_MYSQL_PWD", ""));
66 | return new HikariDataSource(config);
67 | }
68 |
69 | public static DataSource getPostgreSqlDataSource() {
70 | String jdbcUrl = EnvUtil.getStr("TEST_POSTGRESQL_URL", DEFAULT_POSTGRESQL_JDBC_URL);
71 | HikariConfig config = new HikariConfig();
72 | config.setJdbcUrl(jdbcUrl);
73 | config.setUsername(EnvUtil.getStr("TEST_POSTGRESQL_USER", "postgres"));
74 | config.setPassword(EnvUtil.getStr("TEST_POSTGRESQL_PWD", ""));
75 | return new HikariDataSource(config);
76 | }
77 |
78 | public static DataSource getH2DataSource() {
79 | String jdbcUrl = EnvUtil.getStr("TEST_H2_URL", DEFAULT_H2_JDBC_URL);
80 | HikariConfig config = new HikariConfig();
81 | config.setJdbcUrl(jdbcUrl);
82 | config.setUsername(EnvUtil.getStr("TEST_H2_USER", "sa"));
83 | config.setPassword(EnvUtil.getStr("TEST_H2_PWD", ""));
84 | return new HikariDataSource(config);
85 | }
86 |
87 | public static RedisClient getRedisClient() {
88 | String redisUri = EnvUtil.getStr("TEST_REDIS_URI", DEFAULT_REDIS_URI);
89 | RedisClient redisClient = RedisClient.create(redisUri);
90 |
91 | return redisClient;
92 | }
93 |
94 | public static MongoClient getMongoClient() {
95 | String mongoUri = EnvUtil.getStr("TEST_MONGO_URI", DEFAULT_MONGO_URI);
96 | MongoClient mongoClient = MongoClients.create(mongoUri);
97 |
98 | return mongoClient;
99 | }
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/sequence-core/src/main/java/com/power4j/kit/seq/persistent/provider/LettuceClusterSynchronizer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.kit.seq.persistent.provider;
18 |
19 | import com.power4j.kit.seq.core.exceptions.SeqException;
20 | import com.power4j.kit.seq.persistent.SeqSynchronizer;
21 | import io.lettuce.core.api.sync.RedisKeyCommands;
22 | import io.lettuce.core.api.sync.RedisScriptingCommands;
23 | import io.lettuce.core.api.sync.RedisStringCommands;
24 | import io.lettuce.core.cluster.RedisClusterClient;
25 | import io.lettuce.core.cluster.api.StatefulRedisClusterConnection;
26 | import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands;
27 | import io.lettuce.core.support.ConnectionPoolSupport;
28 | import org.apache.commons.pool2.impl.GenericObjectPool;
29 | import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
30 |
31 | import java.util.function.Function;
32 |
33 | /**
34 | * Lettuce 集群支持
35 | *
36 | * @author CJ (power4j@outlook.com)
37 | * @date 2020/7/12
38 | * @since 1.1
39 | */
40 | public class LettuceClusterSynchronizer extends AbstractLettuceSynchronizer implements SeqSynchronizer {
41 |
42 | private final GenericObjectPool> pool;
43 |
44 | public LettuceClusterSynchronizer(String cacheName, RedisClusterClient redisClient) {
45 | this(cacheName, redisClient, new GenericObjectPoolConfig());
46 | }
47 |
48 | public LettuceClusterSynchronizer(String cacheName, RedisClusterClient redisClient,
49 | GenericObjectPoolConfig> poolConfig) {
50 | super(cacheName);
51 | this.pool = ConnectionPoolSupport.createGenericObjectPool(() -> redisClient.connect(), poolConfig);
52 | }
53 |
54 | public LettuceClusterSynchronizer(String cacheName,
55 | GenericObjectPool> pool) {
56 | super(cacheName);
57 | this.pool = pool;
58 | }
59 |
60 | public R execCommand(Function, R> func) {
61 | try (StatefulRedisClusterConnection connection = getRedisConnection()) {
62 | return func.apply(connection.sync());
63 | }
64 | }
65 |
66 | protected StatefulRedisClusterConnection getRedisConnection() {
67 | try {
68 | return pool.borrowObject();
69 | }
70 | catch (Exception e) {
71 | throw new SeqException(e.getMessage(), e);
72 | }
73 | }
74 |
75 | @Override
76 | public void shutdown() {
77 | pool.close();
78 | }
79 |
80 | @Override
81 | protected R execStringCommand(Function, R> func) {
82 | try (StatefulRedisClusterConnection connection = getRedisConnection()) {
83 | return func.apply(connection.sync());
84 | }
85 | }
86 |
87 | @Override
88 | protected R execScriptingCommand(Function, R> func) {
89 | try (StatefulRedisClusterConnection connection = getRedisConnection()) {
90 | return func.apply(connection.sync());
91 | }
92 | }
93 |
94 | @Override
95 | protected R execKeyCommand(Function, R> func) {
96 | try (StatefulRedisClusterConnection connection = getRedisConnection()) {
97 | return func.apply(connection.sync());
98 | }
99 | }
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/sequence-core/src/main/java/com/power4j/kit/seq/persistent/provider/InMemorySeqSynchronizer.java:
--------------------------------------------------------------------------------
1 | package com.power4j.kit.seq.persistent.provider;
2 |
3 | import com.power4j.kit.seq.core.exceptions.SeqException;
4 | import com.power4j.kit.seq.persistent.AddState;
5 | import com.power4j.kit.seq.persistent.SeqSynchronizer;
6 | import lombok.Getter;
7 | import lombok.Setter;
8 |
9 | import java.util.HashMap;
10 | import java.util.Map;
11 | import java.util.Objects;
12 | import java.util.Optional;
13 | import java.util.concurrent.atomic.AtomicLong;
14 | import java.util.concurrent.locks.Lock;
15 | import java.util.concurrent.locks.ReentrantReadWriteLock;
16 |
17 | /**
18 | * 这个类用于测试
19 | *
20 | * @author CJ (power4j@outlook.com)
21 | * @date 2021/9/2
22 | * @since 1.0
23 | */
24 | public class InMemorySeqSynchronizer implements SeqSynchronizer {
25 |
26 | protected final AtomicLong queryCount = new AtomicLong();
27 |
28 | protected final AtomicLong updateCount = new AtomicLong();
29 |
30 | private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
31 |
32 | private final Lock rLock = rwLock.readLock();
33 |
34 | private final Lock wLock = rwLock.writeLock();
35 |
36 | private final Map map = new HashMap<>(8);
37 |
38 | @Override
39 | public boolean tryCreate(String name, String partition, long nextValue) {
40 | final String key = makeKey(name, partition);
41 | Row pre;
42 | wLock.lock();
43 | try {
44 | pre = map.putIfAbsent(key, Row.of(key, nextValue));
45 | }
46 | finally {
47 | wLock.unlock();
48 | }
49 | return Objects.isNull(pre);
50 | }
51 |
52 | @Override
53 | public boolean tryUpdate(String name, String partition, long nextValueOld, long nextValueNew) {
54 | final String key = makeKey(name, partition);
55 | wLock.lock();
56 | try {
57 | Row pre = map.get(key);
58 | if (Objects.nonNull(pre)) {
59 | updateCount.incrementAndGet();
60 | if (nextValueOld == pre.getNextValue()) {
61 | map.put(key, Row.of(key, nextValueNew));
62 | return true;
63 | }
64 | }
65 | }
66 | finally {
67 | wLock.unlock();
68 | }
69 | return false;
70 | }
71 |
72 | @Override
73 | public AddState tryAddAndGet(String name, String partition, int delta, int maxReTry) {
74 | final String key = makeKey(name, partition);
75 | Row pre;
76 | wLock.lock();
77 | try {
78 | pre = map.get(key);
79 | if (Objects.nonNull(pre)) {
80 | queryCount.incrementAndGet();
81 | Row row = Row.of(key, pre.getNextValue() + delta);
82 | map.put(key, row);
83 | updateCount.incrementAndGet();
84 | return AddState.success(pre.getNextValue(), row.getNextValue(), 1);
85 | }
86 | throw new SeqException(key + " not exists");
87 | }
88 | finally {
89 | wLock.unlock();
90 | }
91 | }
92 |
93 | @Override
94 | public Optional getNextValue(String name, String partition) {
95 | final String key = makeKey(name, partition);
96 | Row row;
97 | rLock.lock();
98 | try {
99 | row = map.get(key);
100 | }
101 | finally {
102 | rLock.unlock();
103 | }
104 | if (Objects.nonNull(row)) {
105 | queryCount.incrementAndGet();
106 | }
107 | return Optional.ofNullable(row).map(Row::getNextValue);
108 | }
109 |
110 | @Override
111 | public void init() {
112 | // Nothing
113 | }
114 |
115 | @Override
116 | public long getQueryCounter() {
117 | return SeqSynchronizer.super.getQueryCounter();
118 | }
119 |
120 | @Override
121 | public long getUpdateCounter() {
122 | return SeqSynchronizer.super.getUpdateCounter();
123 | }
124 |
125 | protected final String makeKey(String name, String partition) {
126 | return name + "/" + partition;
127 | }
128 |
129 | @Getter
130 | @Setter
131 | static class Row {
132 |
133 | private final String id;
134 |
135 | private Long nextValue;
136 |
137 | public static Row of(String id, Long nextValue) {
138 | return new Row(id, nextValue);
139 | }
140 |
141 | Row(String id, Long nextValue) {
142 | this.id = id;
143 | this.nextValue = nextValue;
144 | }
145 |
146 | }
147 |
148 | }
149 |
--------------------------------------------------------------------------------
/sequence-core/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 | 4.0.0
20 |
21 | com.power4j.kit
22 | sequence
23 | ${revision}
24 |
25 |
26 | sequence-core
27 |
28 |
29 |
30 | org.mongodb
31 | mongodb-driver-sync
32 | true
33 |
34 |
35 | io.lettuce
36 | lettuce-core
37 | true
38 |
39 |
40 | org.apache.commons
41 | commons-pool2
42 | true
43 |
44 |
45 | ch.qos.logback
46 | logback-classic
47 | true
48 |
49 |
50 | com.zaxxer
51 | HikariCP
52 | true
53 |
54 |
55 |
56 | com.mysql
57 | mysql-connector-j
58 | test
59 |
60 |
61 |
62 | org.postgresql
63 | postgresql
64 | test
65 |
66 |
67 |
68 | com.h2database
69 | h2
70 | test
71 |
72 |
73 |
74 |
75 |
76 |
77 | org.apache.maven.plugins
78 | maven-deploy-plugin
79 |
80 | false
81 |
82 |
83 |
84 | org.codehaus.mojo
85 | flatten-maven-plugin
86 | true
87 |
88 |
89 |
90 | flatten
91 | process-resources
92 |
93 | flatten
94 |
95 |
96 | true
97 | oss
98 | true
99 |
100 | expand
101 | remove
102 | remove
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/sequence-spring-boot-starter/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 | 4.0.0
20 |
21 | com.power4j.kit
22 | sequence
23 | ${revision}
24 |
25 |
26 | sequence-spring-boot-starter
27 |
28 | https://github.com/power4j/sequence
29 | Sequence Spring Boot support
30 |
31 |
32 |
33 | com.power4j.kit
34 | sequence-core
35 | ${project.parent.version}
36 |
37 |
38 | org.springframework.boot
39 | spring-boot-configuration-processor
40 | true
41 |
42 |
43 | org.springframework.boot
44 | spring-boot-starter-web
45 | true
46 |
47 |
48 | org.springframework.boot
49 | spring-boot-actuator-autoconfigure
50 | true
51 |
52 |
53 | io.lettuce
54 | lettuce-core
55 | true
56 |
57 |
58 | org.apache.commons
59 | commons-pool2
60 | true
61 |
62 |
63 | org.mongodb
64 | mongodb-driver-sync
65 | true
66 |
67 |
68 |
69 |
70 |
71 |
72 | org.apache.maven.plugins
73 | maven-deploy-plugin
74 |
75 | false
76 |
77 |
78 |
79 | org.codehaus.mojo
80 | flatten-maven-plugin
81 | true
82 |
83 |
84 |
85 | flatten
86 | process-resources
87 |
88 | flatten
89 |
90 |
91 | true
92 | oss
93 | true
94 |
95 | expand
96 | remove
97 | remove
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/.github/workflows/maven.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a Java project with Maven
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
3 |
4 | name: Java CI with Maven
5 | on:
6 | push:
7 | branches:
8 | - master
9 | - dev
10 | pull_request:
11 | types: [opened, synchronize, reopened]
12 | jobs:
13 | build:
14 | name: Build
15 | runs-on: ubuntu-latest
16 |
17 | services:
18 | redis:
19 | image: redis
20 | ports:
21 | - 6379:6379
22 | options: >-
23 | --health-cmd "redis-cli ping"
24 | --health-interval 10s
25 | --health-timeout 5s
26 | --health-retries 5
27 | mongo:
28 | image: mongo
29 | env:
30 | MONGO_INITDB_ROOT_USERNAME: test
31 | MONGO_INITDB_ROOT_PASSWORD: test
32 | MONGO_INITDB_DATABASE: test
33 | ports:
34 | - 27017:27017
35 | options: >-
36 | --health-cmd "echo 'db.runCommand("ping").ok' | mongosh --quiet"
37 | --health-interval 10s
38 | --health-timeout 5s
39 | --health-retries 5
40 | mysql:
41 | image: mysql:8.0
42 | env:
43 | MYSQL_ROOT_PASSWORD: 'root'
44 | MYSQL_DATABASE: 'test'
45 | MYSQL_USER: 'test'
46 | MYSQL_PASSWORD: 'test'
47 | ports:
48 | - 3306:3306
49 | options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
50 | postgres:
51 | image: postgres:9.6
52 | env:
53 | POSTGRES_USER: test
54 | POSTGRES_PASSWORD: test
55 | POSTGRES_DB: test
56 | ports:
57 | - 5432:5432
58 | options: >-
59 | --health-cmd pg_isready
60 | --health-interval 10s
61 | --health-timeout 5s
62 | --health-retries 5
63 | steps:
64 | - uses: actions/checkout@v3
65 | with:
66 | fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
67 |
68 | - name: Set up JDK 17
69 | uses: actions/setup-java@v3
70 | with:
71 | distribution: 'adopt'
72 | java-version: '17'
73 |
74 | - name: Cache Maven packages
75 | uses: actions/cache@v3
76 | with:
77 | path: ~/.m2
78 | key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
79 | restore-keys: ${{ runner.os }}-m2
80 |
81 | - name: Build
82 | env:
83 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
84 | MAVEN_OPTS: "-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN -Dorg.slf4j.simpleLogger.showDateTime=true -Djava.awt.headless=true"
85 | TEST_MYSQL_URL: "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true"
86 | TEST_MYSQL_USER: test
87 | TEST_MYSQL_PWD: test
88 | TEST_POSTGRESQL_URL: "jdbc:postgresql://localhost:5432/test?ssl=false"
89 | TEST_POSTGRESQL_USER: test
90 | TEST_POSTGRESQL_PWD: test
91 | TEST_REDIS_URI: "redis://localhost:6379"
92 | TEST_MONGO_URI: "mongodb://test:test@localhost:27017"
93 | run: mvn -B package -DskipTests=false --file pom.xml -U
94 | # todo setup test service https://stackoverflow.com/questions/65914287/github-action-with-mysql-test-database
95 | # - name: Test with Maven
96 | # run: mvn -B test -DskipTests=false
97 | # - name: Upload to Codecov
98 | # uses: codecov/codecov-action@v1
99 | # with:
100 | # #token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
101 | # #files: ./coverage1.xml,./coverage2.xml # optional
102 | # flags: unittests # optional
103 | # name: codecov-umbrella # optional
104 | # fail_ci_if_error: true # optional (default = false)
105 | # verbose: true # optional (default = false)
106 |
--------------------------------------------------------------------------------
/sequence-core/src/main/java/com/power4j/kit/seq/persistent/provider/AbstractSqlStatementProvider.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.kit.seq.persistent.provider;
18 |
19 | import lombok.extern.slf4j.Slf4j;
20 |
21 | import java.sql.Connection;
22 | import java.sql.PreparedStatement;
23 | import java.sql.SQLException;
24 | import java.sql.Timestamp;
25 | import java.time.LocalDateTime;
26 |
27 | /**
28 | * 描述信息
29 | *
30 | * @author CJ (power4j@outlook.com)
31 | * @date 2020/7/29
32 | * @since 1.0
33 | */
34 | @Slf4j
35 | public abstract class AbstractSqlStatementProvider extends AbstractJdbcSynchronizer {
36 |
37 | /**
38 | * 建表SQL
39 | * @return
40 | */
41 | protected abstract String getCreateTableSql();
42 |
43 | /**
44 | * 删表SQL
45 | * @return
46 | */
47 | protected abstract String getDropTableSql();
48 |
49 | /**
50 | * 创建记录SQL
51 | * @return
52 | */
53 | protected abstract String getCreateSeqSql();
54 |
55 | /**
56 | * 查询SQL
57 | * @return
58 | */
59 | protected abstract String getSelectSeqSql();
60 |
61 | /**
62 | * 更新SQL
63 | * @return
64 | */
65 | protected abstract String getUpdateSeqSql();
66 |
67 | @Override
68 | protected PreparedStatement getCreateTableStatement(Connection connection) throws SQLException {
69 | final String sql = getCreateTableSql();
70 | if (log.isDebugEnabled()) {
71 | log.debug("Create Table Sql:[{}]", sql);
72 | }
73 | return connection.prepareStatement(sql);
74 | }
75 |
76 | @Override
77 | protected PreparedStatement getDropTableStatement(Connection connection) throws SQLException {
78 | final String sql = getDropTableSql();
79 | if (log.isDebugEnabled()) {
80 | log.debug("Drop Table Sql:[{}]", sql);
81 | }
82 | return connection.prepareStatement(sql);
83 | }
84 |
85 | @Override
86 | protected PreparedStatement getCreateSeqStatement(Connection connection, String name, String partition,
87 | long nextValue) throws SQLException {
88 | final Timestamp now = Timestamp.valueOf(LocalDateTime.now());
89 | final String sql = getCreateSeqSql();
90 | if (log.isDebugEnabled()) {
91 | log.debug("Create Seq Sql:[{}]", sql);
92 | }
93 | PreparedStatement statement = connection.prepareStatement(sql);
94 | statement.setString(1, name);
95 | statement.setString(2, partition);
96 | statement.setLong(3, nextValue);
97 | statement.setTimestamp(4, now);
98 | log.debug(String.format("param: [%s] [%s] [%d] [%s]", name, partition, nextValue, now));
99 | return statement;
100 | }
101 |
102 | @Override
103 | protected PreparedStatement getSelectSeqStatement(Connection connection, String name, String partition)
104 | throws SQLException {
105 | final String sql = getSelectSeqSql();
106 | if (log.isDebugEnabled()) {
107 | log.debug("Select Seq Sql:[{}]", sql);
108 | }
109 | PreparedStatement statement = connection.prepareStatement(sql);
110 | statement.setString(1, name);
111 | statement.setString(2, partition);
112 | log.debug(String.format("param: [%s] [%s]", name, partition));
113 | return statement;
114 | }
115 |
116 | @Override
117 | protected PreparedStatement getUpdateSeqStatement(Connection connection, String name, String partition,
118 | long nextValueOld, long nextValueNew) throws SQLException {
119 | final Timestamp now = Timestamp.valueOf(LocalDateTime.now());
120 | final String sql = getUpdateSeqSql();
121 | if (log.isDebugEnabled()) {
122 | log.debug("Update Seq Sql:[{}]", sql);
123 | }
124 | PreparedStatement statement = connection.prepareStatement(sql);
125 | statement.setLong(1, nextValueNew);
126 | statement.setTimestamp(2, now);
127 | statement.setString(3, name);
128 | statement.setString(4, partition);
129 | statement.setLong(5, nextValueOld);
130 | log.debug(String.format("param: [%d] [%s] [%s] [%s] [%d]", nextValueNew, now.toString(), name, partition,
131 | nextValueOld));
132 | return statement;
133 | }
134 |
135 | }
136 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JAVA 序号工具包 Sequence
2 | [](https://app.codacy.com/gh/power4j/sequence?utm_source=github.com&utm_medium=referral&utm_content=power4j/sequence&utm_campaign=Badge_Grade_Dashboard)
3 | [](https://codecov.io/gh/power4j/sequence)
4 | [](https://travis-ci.org/github/power4j/sequence)
5 | [](https://maven-badges.herokuapp.com/maven-central/com.power4j.kit/sequence)
6 |
7 | 使用场景
8 |
9 | - 业务需要根据一定的规则生成序号,如:
10 | - 连续性:序号连续自增地进行分配
11 | - 相关性:多租户隔离、按年、月、日分区
12 | - 格式化: 序号输出的格式灵活可控
13 | - 技术上需要满足
14 | - 高性能
15 | - 线程安全
16 | - 适用性好
17 | - 配置简单、容易集成
18 |
19 |
20 | ## 项目说明
21 |
22 | - ***JDK 版本要求: `JDK 11+`***
23 |
24 | - 支持的后端
25 | - MySQL
26 | - PostgreSQL (`9.6 +`)
27 | - Redis
28 | - MongoDB
29 | - H2(MySQL兼容模式)
30 |
31 |
32 | ## 核心概念
33 |
34 | - 号池([`SeqPool`](sequence-core/src/main/java/com/power4j/kit/seq/core/SeqPool.java)): 一种设施,可以提供有限或者无限的序号。
35 | - 同步器([`SeqSynchronizer`](sequence-core/src/main/java/com/power4j/kit/seq/persistent/SeqSynchronizer.java)): 负责与某种后端(如数据库)交互,更新序号的当前值。
36 | - 取号器([`SeqHolder`](sequence-core/src/main/java/com/power4j/kit/seq/persistent/SeqHolder.java)): 负责缓存从后端批量取出的序号,然后交给本地的号池来管理。
37 | - 分区(`Partition`): 后端存储在保存序号信息时,序号的名称+分区表示一条唯一的记录,分区可以是静态的(一个字符串常量),也可以是动态分区(一个返回分区名称的函数)。分区并不是将序号的取值进行划分,主要是用于多租户等需要二次分类的场景以及避免号池耗尽。
38 | - 格式化接口([`SeqFormatter`](sequence-core\src\main\java\com\power4j\kit\seq\core\SeqFormatter.java)): 自定义格式输出的扩展接口.
39 |
40 | 注意事项:
41 | - 如果使用动态分区,不宜变化过于频繁,比如用系统时间作为分区。比较合适的是按年份、月份进行分区。
42 | - 每一个分区的序号取值独立的,比如按年份进行分区,那么每年都有`Long.MAX`个序号可用。
43 |
44 | ## 使用方法
45 |
46 |
47 | 引入依赖
48 | ```xml
49 |
50 | com.power4j.kit
51 | sequence-spring-boot-starter
52 | 最新版本
53 |
54 | ```
55 |
56 | 开启配置(JDBC)
57 | ```yaml
58 | power4j:
59 | sequence:
60 | # 数据同步使用的后端支持(如: mysql,oracle,redis),跟你实际使用的数据源类型对应
61 | backend: mysql
62 | ```
63 |
64 | 也可以使用Redis,则配置方式为
65 |
66 | ```yaml
67 | power4j:
68 | sequence:
69 | backend: redis
70 | lettuce-uri: "redis://127.0.0.1"
71 | ```
72 |
73 | > `maven`依赖等详细配置请查看`examples`目录下的演示项目
74 |
75 | 使用
76 |
77 | ```java
78 | @RestController
79 | @SpringBootApplication
80 | public class SequenceExampleApplication {
81 | @Autowired
82 | private Sequence sequence;
83 | public static void main(String[] args) {
84 | SpringApplication.run(SequenceExampleApplication.class, args);
85 | }
86 |
87 | @GetMapping("/seq")
88 | public List getSequence(@RequestParam(required = false) Integer size) {
89 | size = (size == null || size <= 0) ? 10 : size;
90 | List list = new ArrayList<>(size);
91 | for (int i = 0; i < size; ++i) {
92 | list.add(sequence.nextStr());
93 | }
94 | return list;
95 | }
96 | }
97 | ```
98 |
99 | ## 自定义配置
100 |
101 | 例子: [SeqConfig.java](examples/jdbc-example/src/main/java/com/power4j/sequence/example/SeqConfig.java)
102 |
103 | ## 性能测试
104 |
105 |
106 |
107 | > 测试环境用的Redis,MySQL等外部服务都是默认安装,没有调整参数。
108 | >
109 | > 如果需要自己测试,已经为你写好测试脚本,见[run-bench.sh](bench-test/run-bench.sh)
110 |
111 |
112 | ## 效果演示
113 |
114 | ### 分配10个序号
115 | 
116 |
117 | ### 数据库中的记录
118 | 
119 |
120 | ### Redis中的记录
121 | 
122 |
123 | ## 开发计划
124 |
125 | - [X] 支持MySQL(`1.0`)
126 | - [X] 支持PostgreSQL(`1.3`)
127 | - [X] 支持H2(`1.5`MySQL兼容模式)
128 | - [ ] 支持Oracle(不一定会支持,这货没有开源版本)
129 | - [x] 支持Redis(`1.1`)
130 | - [x] 支持MongoDB(`1.2`)
131 | - [X] Spring Boot 集成(`1.0`)
132 |
133 | ## 贡献指南
134 |
135 | 代码要求:
136 | - 统一风格,包含注释、代码缩进等与本项目保持一致
137 | - 保持代码整洁,比如注释掉的代码块等垃圾代码应该删除
138 | - 严格控制外部依赖,如果没有必要,请不要引入外部依赖
139 | - 请在类注释中保留你的作者信息,请不要害羞
140 |
141 | ### 数据库支持实现
142 |
143 | 1. 参考[`MySqlSynchronizer`](sequence-core/src/main/java/com/power4j/kit/seq/persistent/provider/MySqlSynchronizer.java)的实现方式,实现某个特定数据库后端的支持
144 | 2. 参考[`MySqlSynchronizerTest`](sequence-core/src/test/java/com/power4j/kit/seq/persistent/provider/MySqlSynchronizerTest.java) 编写单元测试,完成自测。如果你的代码能跑通测试,基本上应该没有严重bug
145 |
146 |
147 |
148 | ## Special Thanks
149 |
150 | - [JetBrains Developer Toolbox](https://www.jetbrains.com/?from=sequence)
151 |
152 |
153 | ## 联系方式
154 |
155 |
156 | 
--------------------------------------------------------------------------------
/sequence-core/src/test/java/com/power4j/kit/seq/ext/InMemorySequenceRegistryTest.java:
--------------------------------------------------------------------------------
1 | package com.power4j.kit.seq.ext;
2 |
3 | import com.power4j.kit.seq.core.Sequence;
4 | import com.power4j.kit.seq.persistent.Partitions;
5 | import com.power4j.kit.seq.persistent.SeqHolder;
6 | import com.power4j.kit.seq.persistent.SeqSynchronizer;
7 | import com.power4j.kit.seq.persistent.provider.InMemorySeqSynchronizer;
8 | import org.junit.Assert;
9 | import org.junit.Before;
10 | import org.junit.Test;
11 |
12 | import java.util.ArrayList;
13 | import java.util.List;
14 | import java.util.concurrent.atomic.AtomicInteger;
15 | import java.util.function.Supplier;
16 | import java.util.stream.Collectors;
17 |
18 | /**
19 | * @author CJ (power4j@outlook.com)
20 | * @date 2021/9/2
21 | * @since 1.0
22 | */
23 | public class InMemorySequenceRegistryTest {
24 |
25 | private final long initVal = 1L;
26 |
27 | private final int poolSize = 100;
28 |
29 | private final Supplier partitionFunc = Partitions.DAILY;
30 |
31 | private SeqSynchronizer seqSynchronizer;
32 |
33 | protected Sequence createSeq(String name) {
34 | return createSeq(name, partitionFunc);
35 | }
36 |
37 | protected Sequence createSeq(String name, Supplier partitionFunc) {
38 | return SeqHolder.builder()
39 | .name(name)
40 | .synchronizer(seqSynchronizer)
41 | .partitionFunc(partitionFunc)
42 | .initValue(initVal)
43 | .poolSize(poolSize)
44 | .seqFormatter((seqName, partition, value) -> String.format("%s.%s.%04d", seqName, partition, value))
45 | .build();
46 | }
47 |
48 | @Before
49 | public void setup() {
50 | seqSynchronizer = new InMemorySeqSynchronizer();
51 | }
52 |
53 | @Test
54 | public void simpleTest() {
55 | final String nameA100 = "A100";
56 | final String nameA200 = "A200";
57 |
58 | SequenceRegistry> registry = new InMemorySequenceRegistry<>();
59 | Sequence a100 = registry.get(nameA100).orElse(null);
60 | Assert.assertNull(a100);
61 |
62 | a100 = registry.getOrRegister(nameA100, this::createSeq);
63 | Assert.assertEquals(initVal, a100.next().longValue());
64 |
65 | registry.register(nameA200, createSeq(nameA200));
66 | long val = registry.get(nameA200).map(Sequence::next).orElse(-1L);
67 | Assert.assertEquals(initVal, val);
68 |
69 | Assert.assertEquals(2, registry.size());
70 |
71 | String v1 = registry.get(nameA100).map(Sequence::nextStr).orElse("");
72 | System.out.println(v1);
73 | Assert.assertTrue(v1.startsWith(nameA100));
74 |
75 | String v2 = registry.get(nameA200).map(Sequence::nextStr).orElse("");
76 | System.out.println(v2);
77 | Assert.assertTrue(v2.startsWith(nameA200));
78 |
79 | Assert.assertTrue(registry.remove(nameA100).isPresent());
80 | }
81 |
82 | @Test
83 | public void getOrRegisterTest() {
84 | SequenceRegistry> registry = new InMemorySequenceRegistry<>();
85 | String nameTemplate = "Biz_%03d";
86 |
87 | // 每个Sequence 使用一次
88 | for (int i = 0; i < 10; i++) {
89 | String name = String.format(nameTemplate, i);
90 | String val = registry.getOrRegister(name, this::createSeq).nextStr();
91 | System.out.println(val);
92 | String[] parts = val.split("\\.");
93 | Assert.assertEquals(1, Integer.parseInt(parts[2]));
94 | }
95 |
96 | // 每个Sequence 再使用一次
97 | for (int i = 0; i < 10; i++) {
98 | String name = String.format(nameTemplate, i);
99 | String val = registry.getOrRegister(name, this::createSeq).nextStr();
100 | System.out.println(val);
101 | String[] parts = val.split("\\.");
102 | Assert.assertEquals(2, Integer.parseInt(parts[2]));
103 | }
104 | }
105 |
106 | /**
107 | * 分区计算函数按3取模,因此 :
108 | * 取号器每一次取号,区段都和上一次不同
109 | * 每3次又回到之前的号段,但因为重新批量取号的缘故,会出现跳号现象
110 | */
111 | @Test
112 | public void partitionRoundRobinTest() {
113 | final int mod = 3;
114 | AtomicInteger count = new AtomicInteger();
115 | Supplier rolling = () -> Integer.toString(count.getAndIncrement() % mod);
116 | SequenceRegistry> registry = new InMemorySequenceRegistry<>();
117 | String nameTemplate = "R%02d";
118 |
119 | List results = new ArrayList<>(32);
120 | for (int i = 0; i < 3; i++) {
121 | String name = String.format(nameTemplate, i);
122 | for (int round = 0; round < 20; round++) {
123 | String val = registry.getOrRegister(name, o -> createSeq(o, rolling)).nextStr();
124 | results.add(val);
125 | String[] parts = val.split("\\.");
126 | long except = (round / mod) * poolSize + initVal;
127 | Assert.assertEquals(except, Integer.parseInt(parts[2]));
128 | }
129 | }
130 | // @formatter:off
131 | results.stream()
132 | .collect(Collectors.groupingBy(o -> o.substring(0, o.lastIndexOf('.'))))
133 | .entrySet()
134 | .stream()
135 | .sorted(java.util.Map.Entry.comparingByKey())
136 | .forEach(kv -> System.out.printf("%s : %s%n", kv.getKey(), String.join(" -> ", kv.getValue())));
137 | // @formatter:on
138 |
139 | }
140 |
141 | }
--------------------------------------------------------------------------------
/bench-test/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 | 4.0.0
20 |
21 | com.power4j.kit
22 | sequence
23 | ${revision}
24 |
25 |
26 | bench-test
27 | ${parent.artifactId}
28 |
29 | https://github.com/power4j/sequence
30 | performance test
31 |
32 |
33 | true
34 | true
35 | 1.37
36 |
37 | benchmarks
38 |
39 |
40 |
41 |
42 | com.power4j.kit
43 | sequence-core
44 | ${project.parent.version}
45 |
46 |
47 | ch.qos.logback
48 | logback-classic
49 | true
50 |
51 |
52 | org.openjdk.jmh
53 | jmh-core
54 | ${jmh.version}
55 |
56 |
57 | org.openjdk.jmh
58 | jmh-generator-annprocess
59 | ${jmh.version}
60 | provided
61 |
62 |
63 | com.zaxxer
64 | HikariCP
65 |
66 |
67 | com.mysql
68 | mysql-connector-j
69 |
70 |
71 | io.lettuce
72 | lettuce-core
73 |
74 |
75 | org.apache.commons
76 | commons-pool2
77 |
78 |
79 | org.mongodb
80 | mongodb-driver-sync
81 |
82 |
83 | org.postgresql
84 | postgresql
85 |
86 |
87 | com.h2database
88 | h2
89 |
90 |
91 |
92 |
93 |
94 |
95 | org.apache.maven.plugins
96 | maven-shade-plugin
97 | 3.5.0
98 |
99 |
100 | package
101 |
102 | shade
103 |
104 |
105 | ${uberjar.name}
106 |
107 |
108 | org.openjdk.jmh.Main
109 |
110 |
111 |
112 |
113 |
114 |
118 | *:*
119 |
120 | META-INF/*.SF
121 | META-INF/*.DSA
122 | META-INF/*.RSA
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
--------------------------------------------------------------------------------
/.mvn/wrapper/MavenWrapperDownloader.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2007-present the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | import java.net.*;
17 | import java.io.*;
18 | import java.nio.channels.*;
19 | import java.util.Properties;
20 |
21 | public class MavenWrapperDownloader {
22 |
23 | private static final String WRAPPER_VERSION = "0.5.6";
24 | /**
25 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
26 | */
27 | private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
28 | + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
29 |
30 | /**
31 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
32 | * use instead of the default one.
33 | */
34 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
35 | ".mvn/wrapper/maven-wrapper.properties";
36 |
37 | /**
38 | * Path where the maven-wrapper.jar will be saved to.
39 | */
40 | private static final String MAVEN_WRAPPER_JAR_PATH =
41 | ".mvn/wrapper/maven-wrapper.jar";
42 |
43 | /**
44 | * Name of the property which should be used to override the default download url for the wrapper.
45 | */
46 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
47 |
48 | public static void main(String args[]) {
49 | System.out.println("- Downloader started");
50 | File baseDirectory = new File(args[0]);
51 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
52 |
53 | // If the maven-wrapper.properties exists, read it and check if it contains a custom
54 | // wrapperUrl parameter.
55 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
56 | String url = DEFAULT_DOWNLOAD_URL;
57 | if(mavenWrapperPropertyFile.exists()) {
58 | FileInputStream mavenWrapperPropertyFileInputStream = null;
59 | try {
60 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
61 | Properties mavenWrapperProperties = new Properties();
62 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
63 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
64 | } catch (IOException e) {
65 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
66 | } finally {
67 | try {
68 | if(mavenWrapperPropertyFileInputStream != null) {
69 | mavenWrapperPropertyFileInputStream.close();
70 | }
71 | } catch (IOException e) {
72 | // Ignore ...
73 | }
74 | }
75 | }
76 | System.out.println("- Downloading from: " + url);
77 |
78 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
79 | if(!outputFile.getParentFile().exists()) {
80 | if(!outputFile.getParentFile().mkdirs()) {
81 | System.out.println(
82 | "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
83 | }
84 | }
85 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
86 | try {
87 | downloadFileFromURL(url, outputFile);
88 | System.out.println("Done");
89 | System.exit(0);
90 | } catch (Throwable e) {
91 | System.out.println("- Error downloading");
92 | e.printStackTrace();
93 | System.exit(1);
94 | }
95 | }
96 |
97 | private static void downloadFileFromURL(String urlString, File destination) throws Exception {
98 | if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
99 | String username = System.getenv("MVNW_USERNAME");
100 | char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
101 | Authenticator.setDefault(new Authenticator() {
102 | @Override
103 | protected PasswordAuthentication getPasswordAuthentication() {
104 | return new PasswordAuthentication(username, password);
105 | }
106 | });
107 | }
108 | URL website = new URL(urlString);
109 | ReadableByteChannel rbc;
110 | rbc = Channels.newChannel(website.openStream());
111 | FileOutputStream fos = new FileOutputStream(destination);
112 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
113 | fos.close();
114 | rbc.close();
115 | }
116 |
117 | }
118 |
--------------------------------------------------------------------------------
/sequence-core/src/main/java/com/power4j/kit/seq/persistent/provider/PostgreSqlSynchronizer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.kit.seq.persistent.provider;
18 |
19 | import com.power4j.kit.seq.core.exceptions.SeqException;
20 | import com.power4j.kit.seq.persistent.AddState;
21 | import com.power4j.kit.seq.persistent.SeqSynchronizer;
22 | import lombok.AllArgsConstructor;
23 | import lombok.extern.slf4j.Slf4j;
24 |
25 | import javax.sql.DataSource;
26 | import java.sql.Connection;
27 | import java.sql.PreparedStatement;
28 | import java.sql.ResultSet;
29 | import java.sql.SQLException;
30 | import java.sql.Timestamp;
31 | import java.time.LocalDateTime;
32 | import java.util.Optional;
33 |
34 | /**
35 | * PostgreSQL 支持
36 | *
37 | * @author CJ (power4j@outlook.com)
38 | * @date 2020/7/29
39 | * @since 1.3
40 | */
41 | @Slf4j
42 | @AllArgsConstructor
43 | public class PostgreSqlSynchronizer extends AbstractSqlStatementProvider implements SeqSynchronizer {
44 |
45 | // @formatter:off
46 |
47 | private final static String POSTGRESQL_CREATE_TABLE =
48 | "CREATE TABLE IF NOT EXISTS $TABLE_NAME (" +
49 | "seq_name VARCHAR ( 255 ) NOT NULL," +
50 | "seq_partition VARCHAR ( 255 ) NOT NULL," +
51 | "seq_next_value BIGINT NOT NULL," +
52 | "seq_create_time TIMESTAMP NOT NULL," +
53 | "seq_update_time TIMESTAMP NULL," +
54 | "PRIMARY KEY ( seq_name, seq_partition ) " +
55 | ")";
56 |
57 | private final static String POSTGRESQL_DROP_TABLE = "DROP TABLE IF EXISTS $TABLE_NAME";
58 |
59 | private final static String POSTGRESQL_INSERT_IGNORE =
60 | "INSERT INTO $TABLE_NAME" +
61 | "(seq_name,seq_partition,seq_next_value,seq_create_time)" +
62 | " VALUES (?,?,?,?) ON CONFLICT(seq_name, seq_partition) DO NOTHING";
63 |
64 | private final static String POSTGRESQL_UPDATE_VALUE =
65 | "UPDATE $TABLE_NAME SET seq_next_value=?,seq_update_time=? " +
66 | "WHERE seq_name=? AND seq_partition=? AND seq_next_value=?";
67 |
68 | private final static String POSTGRESQL_ADD_VALUE =
69 | "UPDATE $TABLE_NAME SET seq_next_value=seq_next_value + ?,seq_update_time=? " +
70 | "WHERE seq_name=? AND seq_partition=? RETURNING seq_next_value";
71 |
72 | private final static String POSTGRESQL_SELECT_VALUE =
73 | "SELECT seq_next_value FROM $TABLE_NAME WHERE seq_name=? AND seq_partition=?";
74 |
75 | // @formatter:on
76 |
77 | private final String tableName;
78 |
79 | private final DataSource dataSource;
80 |
81 | @Override
82 | protected Connection getConnection() {
83 | try {
84 | return dataSource.getConnection();
85 | }
86 | catch (Exception e) {
87 | log.warn(e.getMessage(), e);
88 | throw new SeqException(e.getMessage(), e);
89 | }
90 | }
91 |
92 | @Override
93 | public AddState tryAddAndGet(String name, String partition, int delta, int maxReTry) {
94 | try (Connection connection = getConnection()) {
95 | return addAndGet(connection, name, partition, delta).map(val -> AddState.success(val - delta, val, 1))
96 | .orElseThrow(() -> new SeqException(String.format("Not exist: %s %s", name, partition)));
97 | }
98 | catch (SQLException e) {
99 | log.warn(e.getMessage(), e);
100 | throw new SeqException(e.getMessage(), e);
101 | }
102 | }
103 |
104 | /**
105 | * 加法操作
106 | * @param connection
107 | * @param name
108 | * @param partition
109 | * @param delta
110 | * @return 返回加法操作后的结果,如果记录不存在返回null
111 | * @throws SQLException
112 | */
113 | private Optional addAndGet(Connection connection, String name, String partition, int delta)
114 | throws SQLException {
115 | final Timestamp now = Timestamp.valueOf(LocalDateTime.now());
116 | final String sql = POSTGRESQL_ADD_VALUE.replace("$TABLE_NAME", tableName);
117 | if (log.isDebugEnabled()) {
118 | log.debug("Add Value Sql:[{}]", sql);
119 | }
120 | try (PreparedStatement statement = connection.prepareStatement(sql)) {
121 | statement.setInt(1, delta);
122 | statement.setTimestamp(2, now);
123 | statement.setString(3, name);
124 | statement.setString(4, partition);
125 | log.debug(String.format("param: [%d] [%s] [%s] [%s]", delta, now.toString(), name, partition));
126 | updateCount.incrementAndGet();
127 | try (ResultSet resultSet = statement.executeQuery()) {
128 | if (resultSet.next() && resultSet.getObject(1) != null) {
129 | return Optional.of(resultSet.getLong(1));
130 | }
131 | }
132 | }
133 | return Optional.empty();
134 | }
135 |
136 | @Override
137 | protected String getCreateTableSql() {
138 | return POSTGRESQL_CREATE_TABLE.replace("$TABLE_NAME", tableName);
139 | }
140 |
141 | @Override
142 | protected String getDropTableSql() {
143 | return POSTGRESQL_DROP_TABLE.replace("$TABLE_NAME", tableName);
144 | }
145 |
146 | @Override
147 | protected String getCreateSeqSql() {
148 | return POSTGRESQL_INSERT_IGNORE.replace("$TABLE_NAME", tableName);
149 | }
150 |
151 | @Override
152 | protected String getSelectSeqSql() {
153 | return POSTGRESQL_SELECT_VALUE.replace("$TABLE_NAME", tableName);
154 | }
155 |
156 | @Override
157 | protected String getUpdateSeqSql() {
158 | return POSTGRESQL_UPDATE_VALUE.replace("$TABLE_NAME", tableName);
159 | }
160 |
161 | }
162 |
--------------------------------------------------------------------------------
/sequence-core/src/main/java/com/power4j/kit/seq/core/LongSeqPool.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.kit.seq.core;
18 |
19 | import com.power4j.kit.seq.core.exceptions.SeqException;
20 | import lombok.Getter;
21 |
22 | import java.util.Optional;
23 | import java.util.concurrent.atomic.AtomicLong;
24 |
25 | /**
26 | * 序号池(Long型)
27 | *
28 | * - 取值范围{@code [0,Long.MAX_VALUE]}
29 | * - 支持一次性取号和循环取号
30 | * - 线程安全,lock free
31 | *
32 | *
33 | * @author CJ (power4j@outlook.com)
34 | * @date 2020/6/30
35 | * @since 1.0
36 | */
37 | public class LongSeqPool implements SeqPool {
38 |
39 | private final static long ZERO = 0L;
40 |
41 | private final static long ONE = 1L;
42 |
43 | public final static long MAX_VALUE = Long.MAX_VALUE;
44 |
45 | public final static long MIN_VALUE = ZERO;
46 |
47 | /**
48 | * 表示一个号池外的值
49 | */
50 | public final static long OUT_OF_POOL = Long.MIN_VALUE;
51 |
52 | private final String name;
53 |
54 | @Getter
55 | private final long start;
56 |
57 | @Getter
58 | private final long end;
59 |
60 | @Getter
61 | private final boolean reRoll;
62 |
63 | private final AtomicLong current;
64 |
65 | /**
66 | * 根据数量创建
67 | * @param name 名称
68 | * @param start 起始值,包含
69 | * @param size 数量
70 | * @param reRoll 是否允许滚动
71 | * @return LongSeqPool
72 | */
73 | public static LongSeqPool forSize(String name, long start, int size, boolean reRoll) {
74 | return new LongSeqPool(name, start, start + size - ONE, reRoll);
75 | }
76 |
77 | /**
78 | * 根据区间创建
79 | * @param name 名称
80 | * @param min 起始值,包含
81 | * @param max end 结束值,包含
82 | * @param reRoll 是否允许滚动
83 | * @return LongSeqPool
84 | */
85 | public static LongSeqPool forRange(String name, long min, long max, boolean reRoll) {
86 | return new LongSeqPool(name, min, max, reRoll);
87 | }
88 |
89 | /**
90 | * 根据起始值创建,最大值为 Long.MAX_VALUE
91 | * @param name 名称
92 | * @param start 起始值,包含
93 | * @param reRoll 是否允许滚动
94 | * @return LongSeqPool
95 | */
96 | public static LongSeqPool startFrom(String name, long start, boolean reRoll) {
97 | return new LongSeqPool(name, start, Long.MAX_VALUE, reRoll);
98 | }
99 |
100 | /**
101 | * constructor
102 | * @param name 名称
103 | * @param start 起始值,包含
104 | * @param end 结束值,包含
105 | * @param reRoll 是否允许滚动,可以滚动的号池永远不会耗尽
106 | */
107 | private LongSeqPool(String name, long start, long end, boolean reRoll) {
108 | assertMinValue(start, "Invalid start value: " + start);
109 | this.name = name;
110 | this.start = start;
111 | this.end = end;
112 | this.reRoll = reRoll;
113 | this.current = new AtomicLong(start);
114 | if (end < start) {
115 | throw new IllegalArgumentException("Nothing to offer");
116 | }
117 | }
118 |
119 | @Override
120 | public String getName() {
121 | return name;
122 | }
123 |
124 | @Override
125 | public Long next() throws SeqException {
126 | return take();
127 | }
128 |
129 | @Override
130 | public Optional nextOpt() {
131 | final long val = take(OUT_OF_POOL);
132 | return Optional.ofNullable(OUT_OF_POOL == val ? null : val);
133 | }
134 |
135 | @Override
136 | public Long peek() {
137 | return current.get();
138 | }
139 |
140 | /**
141 | * 取出序号
142 | * @return 返回下一个序号
143 | * @throws SeqException 号池耗尽抛出异常,可以通过 {@code hasMore} 方法提前检查
144 | * @see SeqPool#hasMore
145 | */
146 | public long take() {
147 | final long val = take(OUT_OF_POOL);
148 | if (val == OUT_OF_POOL) {
149 | long current = peek();
150 | throw new SeqException(String.format("No more value,current = %08d(%d/%d)", current, start, end));
151 | }
152 | return val;
153 | }
154 |
155 | /**
156 | * 取出序号
157 | * @param defVal 号池耗尽时返回的默认值,必须是号池范围外的值,推荐使用 {@code OUT_OF_POOL}
158 | * @return 返回下一个序号,号池耗尽返回默认值
159 | * @throws IllegalArgumentException defVal 无效
160 | * @see LongSeqPool#OUT_OF_POOL
161 | */
162 | public long take(long defVal) {
163 | if (defVal >= start && defVal <= end) {
164 | throw new IllegalArgumentException("Bad defVal");
165 | }
166 | long val = current.getAndUpdate(this::updateFunc);
167 | return (val > end || val < start) ? defVal : val;
168 | }
169 |
170 | private long updateFunc(final long pre) {
171 | if (reRoll && pre >= end) {
172 | return minValue();
173 | }
174 | return pre + ONE;
175 | }
176 |
177 | @Override
178 | public boolean hasMore() {
179 | return remaining() > ZERO;
180 | }
181 |
182 | @Override
183 | public LongSeqPool fork(String name) {
184 | LongSeqPool seqPool = new LongSeqPool(name, start, end, reRoll);
185 | seqPool.setCurrent(peek());
186 | return seqPool;
187 | }
188 |
189 | @Override
190 | public long remaining() {
191 | return reRoll ? capacity() : end - peek() + ONE;
192 | }
193 |
194 | @Override
195 | public long capacity() {
196 | return end - start + ONE;
197 | }
198 |
199 | @Override
200 | public Long minValue() {
201 | return start;
202 | }
203 |
204 | @Override
205 | public Long maxValue() {
206 | return end;
207 | }
208 |
209 | private void setCurrent(long val) {
210 | current.set(val);
211 | }
212 |
213 | @Override
214 | public String toString() {
215 | return peek() + " -> [" + start + "," + end + "],reRoll = " + reRoll;
216 | }
217 |
218 | protected void assertMinValue(long val, String msg) {
219 | if (val < MIN_VALUE) {
220 | throw new SeqException(msg);
221 | }
222 | }
223 |
224 | }
225 |
--------------------------------------------------------------------------------
/sequence-core/src/main/java/com/power4j/kit/seq/persistent/provider/AbstractLettuceSynchronizer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.kit.seq.persistent.provider;
18 |
19 | import com.power4j.kit.seq.core.exceptions.SeqException;
20 | import com.power4j.kit.seq.persistent.AddState;
21 | import com.power4j.kit.seq.persistent.SeqSynchronizer;
22 | import io.lettuce.core.KeyScanCursor;
23 | import io.lettuce.core.ScanArgs;
24 | import io.lettuce.core.ScanCursor;
25 | import io.lettuce.core.ScriptOutputType;
26 | import io.lettuce.core.api.sync.RedisKeyCommands;
27 | import io.lettuce.core.api.sync.RedisScriptingCommands;
28 | import io.lettuce.core.api.sync.RedisStringCommands;
29 | import lombok.extern.slf4j.Slf4j;
30 |
31 | import java.util.Optional;
32 | import java.util.concurrent.atomic.AtomicBoolean;
33 | import java.util.concurrent.atomic.AtomicLong;
34 | import java.util.concurrent.atomic.AtomicReference;
35 | import java.util.function.Function;
36 |
37 | /**
38 | * @author CJ (power4j@outlook.com)
39 | * @date 2020/7/10
40 | * @since 1.1
41 | */
42 | @Slf4j
43 | public abstract class AbstractLettuceSynchronizer implements SeqSynchronizer {
44 |
45 | private final AtomicBoolean checkKey = new AtomicBoolean(true);
46 |
47 | private final AtomicLong queryCounter = new AtomicLong();
48 |
49 | private final AtomicLong updateCounter = new AtomicLong();
50 |
51 | private final AtomicReference serverScript = new AtomicReference<>();
52 |
53 | private final String cacheName;
54 |
55 | public AbstractLettuceSynchronizer(String cacheName) {
56 | this.cacheName = cacheName;
57 | }
58 |
59 | protected String makeKey(String seqName, String partition) {
60 | return cacheName + RedisConstants.KEY_DELIMITER + seqName + RedisConstants.KEY_DELIMITER + partition;
61 | }
62 |
63 | protected void validateKeyExists(RedisStringCommands redisCommands, String key, String msg) {
64 | if (null == redisCommands.get(key)) {
65 | throw new SeqException(msg);
66 | }
67 | }
68 |
69 | protected String loadScript(RedisScriptingCommands redisCommands, String script) {
70 | String id = redisCommands.scriptLoad(script);
71 | log.info("Script loaded,id = {}", id);
72 | return id;
73 | }
74 |
75 | protected boolean doUpdate(RedisScriptingCommands redisCommands, String name, String partition,
76 | long nextValueOld, long nextValueNew) {
77 | String scriptId = serverScript
78 | .updateAndGet((s -> s != null ? s : loadScript(redisCommands, RedisConstants.UPDATE_SCRIPT)));
79 | String[] keys = { makeKey(name, partition) };
80 | boolean ret = redisCommands.evalsha(scriptId, ScriptOutputType.BOOLEAN, keys, Long.toString(nextValueOld),
81 | Long.toString(nextValueNew));
82 | updateCounter.incrementAndGet();
83 | return ret;
84 | }
85 |
86 | protected Optional doGet(RedisStringCommands redisCommands, String name, String partition) {
87 | String val = redisCommands.get(makeKey(name, partition));
88 | queryCounter.incrementAndGet();
89 | return Optional.ofNullable(Long.parseLong(val));
90 | }
91 |
92 | protected AddState doInc(RedisStringCommands redisCommands, String name, String partition,
93 | int delta) {
94 | final String key = makeKey(name, partition);
95 | if (checkKey.get()) {
96 | validateKeyExists(redisCommands, key, "Key not exists:" + key);
97 | }
98 | long current = redisCommands.incrby(key, delta);
99 | updateCounter.incrementAndGet();
100 | return AddState.success(current - delta, current, 1);
101 | }
102 |
103 | public int removeCache() {
104 | return execKeyCommand((cmd -> {
105 | int keys = 0;
106 | ScanCursor scanCursor = ScanCursor.INITIAL;
107 | ScanArgs scanArgs = ScanArgs.Builder.limit(10).match(cacheName + RedisConstants.KEY_DELIMITER + "*");
108 | while (true) {
109 | KeyScanCursor keyScanCursor = cmd.scan(scanCursor, scanArgs);
110 | if (keyScanCursor.isFinished() || keyScanCursor.getKeys().size() <= 0) {
111 | break;
112 | }
113 | keys += keyScanCursor.getKeys().size();
114 | cmd.del(keyScanCursor.getKeys().toArray(new String[keyScanCursor.getKeys().size()]));
115 | scanCursor = ScanCursor.of(keyScanCursor.getCursor());
116 | }
117 | return keys;
118 | }));
119 | }
120 |
121 | public boolean setKeyValidate(boolean check) {
122 | return checkKey.getAndSet(check);
123 | }
124 |
125 | /**
126 | * 执行命令
127 | * @param func
128 | * @param
129 | * @return
130 | */
131 | protected abstract R execStringCommand(Function, R> func);
132 |
133 | /**
134 | * 执行命令
135 | * @param func
136 | * @param
137 | * @return
138 | */
139 | protected abstract R execScriptingCommand(Function, R> func);
140 |
141 | /**
142 | * 执行命令
143 | * @param func
144 | * @param
145 | * @return
146 | */
147 | protected abstract R execKeyCommand(Function, R> func);
148 |
149 | @Override
150 | public boolean tryCreate(String name, String partition, long nextValue) {
151 | return execStringCommand((cmd) -> cmd.setnx(makeKey(name, partition), Long.toString(nextValue)));
152 | }
153 |
154 | @Override
155 | public boolean tryUpdate(String name, String partition, long nextValueOld, long nextValueNew) {
156 | return execScriptingCommand((cmd) -> doUpdate(cmd, name, partition, nextValueOld, nextValueNew));
157 | }
158 |
159 | @Override
160 | public AddState tryAddAndGet(String name, String partition, int delta, int maxReTry) {
161 | return execStringCommand((cmd) -> doInc(cmd, name, partition, delta));
162 | }
163 |
164 | @Override
165 | public Optional getNextValue(String name, String partition) {
166 | return execStringCommand((cmd) -> doGet(cmd, name, partition));
167 | }
168 |
169 | @Override
170 | public void init() {
171 | execScriptingCommand((cmd) -> loadScript(cmd, RedisConstants.UPDATE_SCRIPT));
172 | }
173 |
174 | @Override
175 | public long getQueryCounter() {
176 | return queryCounter.get();
177 | }
178 |
179 | @Override
180 | public long getUpdateCounter() {
181 | return updateCounter.get();
182 | }
183 |
184 | }
185 |
--------------------------------------------------------------------------------
/sequence-core/src/main/java/com/power4j/kit/seq/persistent/provider/SimpleMongoSynchronizer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 ChenJun (power4j@outlook.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.power4j.kit.seq.persistent.provider;
18 |
19 | import com.mongodb.MongoWriteException;
20 | import com.mongodb.client.FindIterable;
21 | import com.mongodb.client.MongoClient;
22 | import com.mongodb.client.MongoCollection;
23 | import com.mongodb.client.model.Filters;
24 | import com.mongodb.client.model.FindOneAndUpdateOptions;
25 | import com.mongodb.client.model.IndexOptions;
26 | import com.mongodb.client.model.Indexes;
27 | import com.mongodb.client.model.ReturnDocument;
28 | import com.mongodb.client.model.Updates;
29 | import com.mongodb.client.result.UpdateResult;
30 | import com.power4j.kit.seq.persistent.AddState;
31 | import com.power4j.kit.seq.persistent.SeqSynchronizer;
32 | import lombok.AllArgsConstructor;
33 | import lombok.extern.slf4j.Slf4j;
34 | import org.bson.Document;
35 | import org.bson.conversions.Bson;
36 |
37 | import java.time.LocalDateTime;
38 | import java.util.Optional;
39 | import java.util.concurrent.atomic.AtomicLong;
40 | import java.util.concurrent.atomic.AtomicReference;
41 |
42 | /**
43 | * @author CJ (power4j@outlook.com)
44 | * @date 2020/7/17
45 | * @since 1.2
46 | */
47 | @Slf4j
48 | @AllArgsConstructor
49 | public class SimpleMongoSynchronizer implements SeqSynchronizer {
50 |
51 | private final AtomicLong queryCount = new AtomicLong();
52 |
53 | private final AtomicLong updateCount = new AtomicLong();
54 |
55 | private final AtomicReference> collectionRef = new AtomicReference<>();
56 |
57 | private final String dataBaseName;
58 |
59 | private final String collectionName;
60 |
61 | private final MongoClient mongoClient;
62 |
63 | /**
64 | * MongoDB creates new collections when you first store data for the collections.
65 | */
66 | public void createCollection() {
67 | collectionRef.compareAndSet(null, doCreateCollection());
68 | }
69 |
70 | public void dropCollection() {
71 | Optional.ofNullable(collectionRef.get()).ifPresent(col -> col.drop());
72 | }
73 |
74 | private MongoCollection doCreateCollection() {
75 | MongoCollection col = mongoClient.getDatabase(dataBaseName).getCollection(collectionName);
76 | String idxName = col.createIndex(
77 | Indexes.compoundIndex(Indexes.ascending(DocKeys.KEY_SEQ_NAME, DocKeys.KEY_SEQ_PARTITION)),
78 | new IndexOptions().unique(true));
79 | log.info("Index created :{}", idxName);
80 | return col;
81 | }
82 |
83 | private MongoCollection ensureCollection() {
84 | return collectionRef.updateAndGet(col -> (col != null ? col : doCreateCollection()));
85 | }
86 |
87 | protected Bson getSeqSelector(String name, String partition) {
88 | return Filters.and(Filters.eq(DocKeys.KEY_SEQ_NAME, name), Filters.eq(DocKeys.KEY_SEQ_PARTITION, partition));
89 | }
90 |
91 | protected Bson getValueSelector(String name, String partition, Long value) {
92 | return Filters.and(Filters.eq(DocKeys.KEY_SEQ_NAME, name), Filters.eq(DocKeys.KEY_SEQ_PARTITION, partition),
93 | Filters.eq(DocKeys.KEY_SEQ_VALUE, value));
94 | }
95 |
96 | @Override
97 | public boolean tryCreate(String name, String partition, long nextValue) {
98 | MongoCollection col = ensureCollection();
99 | Document document = new Document();
100 | document.put(DocKeys.KEY_SEQ_NAME, name);
101 | document.put(DocKeys.KEY_SEQ_PARTITION, partition);
102 | document.put(DocKeys.KEY_SEQ_VALUE, nextValue);
103 | document.put(DocKeys.KEY_SEQ_CREATE_AT, LocalDateTime.now());
104 | document.put(DocKeys.KEY_SEQ_UPDATE_AT, null);
105 | try {
106 | col.insertOne(document);
107 | return true;
108 | }
109 | catch (MongoWriteException writeException) {
110 | log.error("Ignore Insert error,{}", writeException.getMessage());
111 | return false;
112 | }
113 | }
114 |
115 | @Override
116 | public boolean tryUpdate(String name, String partition, long nextValueOld, long nextValueNew) {
117 | updateCount.incrementAndGet();
118 | MongoCollection col = ensureCollection();
119 | Bson query = getValueSelector(name, partition, nextValueOld);
120 | Bson op = Updates.combine(Updates.set(DocKeys.KEY_SEQ_VALUE, nextValueNew),
121 | Updates.set(DocKeys.KEY_SEQ_UPDATE_AT, LocalDateTime.now()));
122 | UpdateResult result = col.updateOne(query, op);
123 | return result.getModifiedCount() == 1;
124 | }
125 |
126 | @Override
127 | public AddState tryAddAndGet(String name, String partition, int delta, int maxReTry) {
128 | updateCount.incrementAndGet();
129 | MongoCollection col = ensureCollection();
130 | Bson query = getSeqSelector(name, partition);
131 | Bson op = Updates.combine(Updates.inc(DocKeys.KEY_SEQ_VALUE, delta),
132 | Updates.set(DocKeys.KEY_SEQ_UPDATE_AT, LocalDateTime.now()));
133 | Document doc = col.findOneAndUpdate(query, op,
134 | new FindOneAndUpdateOptions().returnDocument(ReturnDocument.BEFORE));
135 | if (doc == null) {
136 | return AddState.fail(1);
137 | }
138 | return AddState.success(doc.getLong(DocKeys.KEY_SEQ_VALUE), doc.getLong(DocKeys.KEY_SEQ_VALUE) + delta, 1);
139 | }
140 |
141 | @Override
142 | public Optional getNextValue(String name, String partition) {
143 | queryCount.incrementAndGet();
144 | MongoCollection col = ensureCollection();
145 | Bson query = getSeqSelector(name, partition);
146 | FindIterable itr = col.find(query);
147 | Document doc = itr.first();
148 | return Optional.ofNullable(doc == null ? null : doc.getLong(DocKeys.KEY_SEQ_VALUE));
149 | }
150 |
151 | @Override
152 | public void init() {
153 | createCollection();
154 | }
155 |
156 | @Override
157 | public void shutdown() {
158 | // nothing to do
159 | }
160 |
161 | @Override
162 | public long getQueryCounter() {
163 | return queryCount.get();
164 | }
165 |
166 | @Override
167 | public long getUpdateCounter() {
168 | return updateCount.get();
169 | }
170 |
171 | interface DocKeys {
172 |
173 | // @formatter:off
174 |
175 | String KEY_SEQ_NAME = "seqName";
176 | String KEY_SEQ_PARTITION = "seqPartition";
177 | String KEY_SEQ_VALUE = "seqNextValue";
178 | String KEY_SEQ_CREATE_AT = "seqCreateTime";
179 | String KEY_SEQ_UPDATE_AT = "seqUpdateTime";
180 |
181 | // @formatter:on
182 |
183 | }
184 |
185 | }
186 |
--------------------------------------------------------------------------------