shading-jdbc 4.1.1 + tk.mybatis + pagehelper 1.3.x + spring boot 2.x  是一个很常用的组合,但在使用过程中可能会遇到一些小问题,记录于此:

一、pom依赖

主要有以下几个:

 1 <properties>
2 <java.version>1.8</java.version>
3 <sharding-sphere.version>4.1.1</sharding-sphere.version>
4 <tk.mybatis.version>2.1.5</tk.mybatis.version>
5 <mybatis.starter.version>2.1.3</mybatis.starter.version>
6 <pagehelper.version>1.3.0</pagehelper.version>
7 </properties>
8
9 <dependencyManagement>
10 <dependencies>
11 <!-- spring-boot-->
12 <dependency>
13 <groupId>org.apache.shardingsphere</groupId>
14 <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
15 <version>${sharding-sphere.version}</version>
16 </dependency>
17
18 <!--tk.mybatis-->
19 <dependency>
20 <groupId>tk.mybatis</groupId>
21 <artifactId>mapper-spring-boot-starter</artifactId>
22 <version>${tk.mybatis.version}</version>
23 </dependency>
24
25 <dependency>
26 <groupId>org.mybatis.spring.boot</groupId>
27 <artifactId>mybatis-spring-boot-starter</artifactId>
28 <version>${mybatis.starter.version}</version>
29 </dependency>
30
31 <!--pagehelper-->
32 <dependency>
33 <groupId>com.github.pagehelper</groupId>
34 <artifactId>pagehelper-spring-boot-starter</artifactId>
35 <version>${pagehelper.version}</version>
36 </dependency>
37
38 </dependencies>
39 </dependencyManagement>

二、application.properties

 1 #数据源
2 spring.shardingsphere.datasource.names=ds0
3 spring.shardingsphere.sharding.default-data-source-name=ds0
4 spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource
5 spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.cj.jdbc.Driver
6 spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://127.0.0.1:3306/testdb?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8
7 spring.shardingsphere.datasource.ds0.username=root
8 spring.shardingsphere.datasource.ds0.password=***
9 #分表
10 spring.shardingsphere.sharding.tables.t_order_logic.actual-data-nodes=ds0.t_order_$->{0..1}
11 spring.shardingsphere.sharding.tables.t_order_logic.table-strategy.inline.sharding-column=order_id
12 spring.shardingsphere.sharding.tables.t_order_logic.table-strategy.inline.algorithm-expression=t_order_$->{order_id % 2}
13 spring.shardingsphere.props.sql.show=true
14 #精确路由
15 spring.shardingsphere.sharding.tables.t_order_logic_0.actual-data-nodes=ds0.t_order_0
16 spring.shardingsphere.sharding.tables.t_order_logic_1.actual-data-nodes=ds0.t_order_1
17 #mybatis
18 mybatis.mapper-locations=classpath*:mapper/*.xml

注:

* 这里只演示了单库分表的最小配置

* 注意里面有一个“精确路由”的配置,有时候我们明确知道数据就在某个具体分表上,但是sql的where条件中又不包含sharding-key,就可以参考上述配置,当查询t_order_logic_0时,直接路由到ds0.t_order_0这张表

t_order_0/t_order_1表结构如下:

 1 CREATE TABLE `t_order_0` (
2 `order_id` bigint(20) unsigned NOT NULL,
3 `order_name` varchar(255) NOT NULL,
4 `order_date` varchar(255) NOT NULL,
5 PRIMARY KEY (`order_id`)
6 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
7
8 CREATE TABLE `t_order_1` (
9 `order_id` bigint(20) unsigned NOT NULL,
10 `order_name` varchar(255) NOT NULL,
11 `order_date` varchar(255) NOT NULL,
12 PRIMARY KEY (`order_id`)
13 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

三、mybatis自动生成tk.mybatis风格的代码

3.1 先配置plugin

<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.7</version>
<configuration>
<configurationFile>src/main/resources/generator/generatorConfig.xml</configurationFile>
<overwrite>true</overwrite>
<verbose>true</verbose>
</configuration>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
<version>4.1.5</version>
</dependency>
</dependencies>
</plugin>

3.2 再配置generatorConfig.xml

 1 <?xml version="1.0" encoding="UTF-8"?>
2
3 <!DOCTYPE generatorConfiguration
4 PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
5 "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
6
7 <generatorConfiguration>
8
9 <context id="Mysql" targetRuntime="MyBatis3" defaultModelType="flat">
10 <property name="beginningDelimiter" value="`"/>
11 <property name="endingDelimiter" value="`"/>
12
13 <plugin type="tk.mybatis.mapper.generator.MapperPlugin">
14 <property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>
15 <property name="caseSensitive" value="true"/>
16 <property name="useActualColumnNames" value="true"/>
17 <property name="beginningDelimiter" value="`"/>
18 <property name="endingDelimiter" value="`"/>
19 </plugin>
20
21
22 <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
23 connectionURL="jdbc:mysql://127.0.0.1:3306/testdb"
24 userId="root"
25 password="***"/>
26
27 <javaModelGenerator
28 targetPackage="test.entity"
29 targetProject="src/test/java"/>
30
31 <sqlMapGenerator targetPackage="mapper" targetProject="src/test/resources"/>
32
33 <javaClientGenerator targetPackage="test.mapper"
34 targetProject="src/test/java"
35 type="XMLMAPPER">
36 <property name="enableSubPackages" value="true"/>
37 </javaClientGenerator>
38
39 <!-- 注:生成的OrderEntity,实际对应的表名为t_order_0,并非逻辑表t_order_logic-->
40 <!-- 解决办法有2个:-->
41 <!-- 1. 生成后,手动修改@Table(name = "`t_order_0`")为@Table(name = "`t_order_logic`")-->
42 <!-- 2. 直接在db中创建一个t_order_logic的表,仅用于生成代码-->
43 <table tableName="t_order_0" domainObjectName="OrderEntity"
44 enableCountByExample="false" enableUpdateByExample="false"
45 enableDeleteByExample="false" enableSelectByExample="false"
46 selectByExampleQueryId="false">
47 <generatedKey column="order_id" sqlStatement="MYSQL" identity="true"/>
48 </table>
49
50 </context>
51 </generatorConfiguration>

四、分布式id的问题

sharding-jdbc内置了snowflake算法,但是集成tk.mybatis生成记录后,并不能马上返回自动生成的id值,如下图:

从输出的sql语句上看,sharding-jdbc改写了sql语句,附加了order_id字段,并用snowflake算法生成了新id,但是insert成功后,entity的orderId仍为null。

tips: 要开启sharding-jdbc的snowflake功能,需要修改下面2点

1. application.properties中必须指定snowflake

1 spring.shardingsphere.sharding.tables.t_order_logic.key-generator.column=order_id
2 spring.shardingsphere.sharding.tables.t_order_logic.key-generator.type=SNOWFLAKE

2. 表结构上的自增主键id,需要把entity类的自动生成主键注释掉

如果insert成功后,要拿到新的id值,建议id字段在insert前就手动赋值,参考下面的做法,直接调用内置的snowflake生成器:

创建一个IdService服务:

 1 package com.cnblogs.yjmyzz.sharding.jdbc.demo.service;
2
3 import com.cnblogs.yjmyzz.sharding.jdbc.demo.utils.NetworkUtils;
4 import org.apache.shardingsphere.core.strategy.keygen.SnowflakeShardingKeyGenerator;
5 import org.springframework.beans.factory.InitializingBean;
6 import org.springframework.stereotype.Service;
7
8 import java.math.BigInteger;
9 import java.util.Properties;
10 import java.util.concurrent.atomic.AtomicInteger;
11
12 @Service("idService")
13 public class IdService implements InitializingBean {
14 private AtomicInteger serverIndex = new AtomicInteger(0);
15
16 private static final SnowflakeShardingKeyGenerator keyGenerator = new SnowflakeShardingKeyGenerator();
17
18 public long nextId() {
19 return (Long) keyGenerator.generateKey();
20 }
21
22 @Override
23 public void afterPropertiesSet() throws Exception {
24 BigInteger ipAddrBigInt = NetworkUtils.ipAddressToBigInt(NetworkUtils.getLocalHostAddress());
25 //假设服务器最多512台
26 BigInteger base = new BigInteger("512");
27 serverIndex.set(ipAddrBigInt.mod(base).intValue());
28 synchronized (this) {
29 Properties prop = keyGenerator.getProperties();
30 prop.setProperty("worker.id", serverIndex.toString());
31 keyGenerator.setProperties(prop);
32 }
33 }
34 }

注:snowflake算法要求设置1个唯一的worker.id,防止多个服务器生成相同的id,这里我们取服务器的ip地址,转换成数字,再对集群节点数取模,保证整个集群内唯一。

这里有一个小工具类,用于取服务器ip地址:

  1 package com.cnblogs.yjmyzz.sharding.jdbc.demo.utils;
2
3
4 import java.math.BigInteger;
5 import java.net.InetAddress;
6 import java.net.NetworkInterface;
7 import java.net.UnknownHostException;
8 import java.util.Enumeration;
9
10 public final class NetworkUtils {
11 public NetworkUtils() {
12 }
13
14 public static String getLocalHostAddress() {
15 InetAddress addr = null;
16
17 try {
18 addr = getLocalHostLANAddress();
19 } catch (UnknownHostException var2) {
20 return "";
21 }
22
23 return addr != null ? addr.getHostAddress() : "";
24 }
25
26 public static InetAddress getLocalHostLANAddress() throws UnknownHostException {
27 try {
28 InetAddress candidateAddress = null;
29 Enumeration ifaces = NetworkInterface.getNetworkInterfaces();
30
31 while (ifaces.hasMoreElements()) {
32 NetworkInterface iface = (NetworkInterface) ifaces.nextElement();
33 Enumeration inetAddrs = iface.getInetAddresses();
34
35 while (inetAddrs.hasMoreElements()) {
36 InetAddress inetAddr = (InetAddress) inetAddrs.nextElement();
37 if (!inetAddr.isLoopbackAddress()) {
38 if (inetAddr.isSiteLocalAddress()) {
39 return inetAddr;
40 }
41
42 if (candidateAddress == null) {
43 candidateAddress = inetAddr;
44 }
45 }
46 }
47 }
48
49 if (candidateAddress != null) {
50 return candidateAddress;
51 } else {
52 InetAddress jdkSuppliedAddress = InetAddress.getLocalHost();
53 if (jdkSuppliedAddress == null) {
54 throw new UnknownHostException("The JDK InetAddress.getLocalHost() method unexpectedly returned null.");
55 } else {
56 return jdkSuppliedAddress;
57 }
58 }
59 } catch (Exception var5) {
60 UnknownHostException unknownHostException = new UnknownHostException("Failed to determine LAN address: " + var5);
61 unknownHostException.initCause(var5);
62 throw unknownHostException;
63 }
64 }
65
66 public static BigInteger ipAddressToBigInt(String ipInString) {
67 ipInString = ipInString.replace(" ", "");
68 byte[] bytes;
69 if (ipInString.contains(":")) {
70 bytes = ipv6ToBytes(ipInString);
71 } else {
72 bytes = ipv4ToBytes(ipInString);
73 }
74
75 return new BigInteger(bytes);
76 }
77
78 public static String bigIntToIpAddress(BigInteger ipInBigInt) {
79 byte[] bytes = ipInBigInt.toByteArray();
80 byte[] unsignedBytes = bytes;
81
82 try {
83 String ip = InetAddress.getByAddress(unsignedBytes).toString();
84 return ip.substring(ip.indexOf(47) + 1).trim();
85 } catch (UnknownHostException var4) {
86 throw new RuntimeException(var4);
87 }
88 }
89
90 private static byte[] ipv6ToBytes(String ipv6) {
91 byte[] ret = new byte[17];
92 ret[0] = 0;
93 int ib = 16;
94 boolean comFlag = false;
95 if (ipv6.startsWith(":")) {
96 ipv6 = ipv6.substring(1);
97 }
98
99 String[] groups = ipv6.split(":");
100
101 for (int ig = groups.length - 1; ig > -1; --ig) {
102 if (groups[ig].contains(".")) {
103 byte[] temp = ipv4ToBytes(groups[ig]);
104 ret[ib--] = temp[4];
105 ret[ib--] = temp[3];
106 ret[ib--] = temp[2];
107 ret[ib--] = temp[1];
108 comFlag = true;
109 } else {
110 int temp;
111 if ("".equals(groups[ig])) {
112 for (temp = 9 - (groups.length + (comFlag ? 1 : 0)); temp-- > 0; ret[ib--] = 0) {
113 ret[ib--] = 0;
114 }
115 } else {
116 temp = Integer.parseInt(groups[ig], 16);
117 ret[ib--] = (byte) temp;
118 ret[ib--] = (byte) (temp >> 8);
119 }
120 }
121 }
122
123 return ret;
124 }
125
126 private static byte[] ipv4ToBytes(String ipv4) {
127 byte[] ret = new byte[5];
128 ret[0] = 0;
129 int position1 = ipv4.indexOf(".");
130 int position2 = ipv4.indexOf(".", position1 + 1);
131 int position3 = ipv4.indexOf(".", position2 + 1);
132 ret[1] = (byte) Integer.parseInt(ipv4.substring(0, position1));
133 ret[2] = (byte) Integer.parseInt(ipv4.substring(position1 + 1, position2));
134 ret[3] = (byte) Integer.parseInt(ipv4.substring(position2 + 1, position3));
135 ret[4] = (byte) Integer.parseInt(ipv4.substring(position3 + 1));
136 return ret;
137 }
138
139 public static boolean ipCheck(String tip, String[][] myRange) {
140 boolean flag = false;
141 BigInteger tbig = ipAddressToBigInt(tip);
142 int rangeLength = myRange.length;
143
144 for (int i = 0; i < rangeLength; ++i) {
145 for (int j = 0; j < myRange[i].length; ++j) {
146 BigInteger sbig = ipAddressToBigInt(myRange[i][j]);
147 ++j;
148 BigInteger ebig = ipAddressToBigInt(myRange[i][j]);
149 if (tbig.compareTo(sbig) == 0) {
150 flag = true;
151 break;
152 }
153
154 if (tbig.compareTo(sbig) == 1 && tbig.compareTo(ebig) == -1) {
155 flag = true;
156 break;
157 }
158 }
159 }
160
161 return flag;
162 }
163
164 class IpRange {
165 private String[][] ipRange;
166
167 public IpRange(String[][] ip) {
168 this.ipRange = ip;
169 }
170
171 public String getIpAt(int row, int column) {
172 return this.ipRange[row][column];
173 }
174 }
175 }

有了IdService后,在insert前,调用nextId()生成新id,直接赋值给entity即可。

五、MapperScan包名问题

MapperScan有2个,1个是mybatis自带的,1个是tk.mybatis的,集成tk.mybatis时,注意要使用tk.mybatis的MapperScan

六、PageHelper的count问题

PageHelper为了算总记录条数,会改写原始sql,做1次count,比如:

select order_id, order_name, order_date from t_order_logic_0 where order_date='2020-09-06';

会首先被改写为:

select count(0) from t_order_logic_0 where order_date='2020-09-06';

然后再由sharding-jdbc改写成:(精确路由)

select count(0) from t_order_0 where order_date='2020-09-06';

对于简单语句,这样没什么问题。但是如果原始语句上,有一些聚合函数或group by,比如下面这样: 

如上图,加了group by 后,下面的语句

SELECT count(0) FROM (SELECT order_id FROM t_order_logic WHERE order_date = ? GROUP BY order_id) table_count

sharding-jdbc并不能正确解析为t_order_0,仍然还是t_order_logic

SELECT count(0) FROM (SELECT order_id FROM t_order_logic WHERE order_date = ? GROUP BY order_id) table_count ::: [2020-09-06]

解决办法:pagehelper对于count语句,允许用户自定义,只要在原来的语句id,加上“_COUNT”

1     <select id="selectList2" resultMap="BaseResultMap">
2 select order_id from t_order_logic${tableIndex} where order_date=#{orderDate} group by order_id
3 </select>
4
5 <select id="selectList2_COUNT" resultType="long">
6 select count(1) from t_order_logic${tableIndex} where order_date=#{orderDate} group by order_id
7 </select>

再运行一下:

现在就正常了。  

示例代码已经上传到github,地址:https://github.com/yjmyzz/shardingjdbc-mybatis-pagehelper-springboot-demo

shading-jdbc 4.1.1 + tk.mybatis + pagehelper 1.3.x +spring boot 2.x 使用注意事项的更多相关文章

  1. MyBatis初级实战之一:Spring Boot集成

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  2. 使用mybatis-generator插件结合tk.mybatis自动生成mapper二三事

    本篇文章将介绍使用spring boot框架,引入mybatis-generator插件,结合tk.mybatis自动生成Mapper和Entity的一整套流程,其中包括最重要的踩坑与填坑.     ...

  3. 62. mybatis 使用PageHelper不生效【从零开始学Spring Boot】

    [从零开始学习Spirng Boot-常见异常汇总] 在Spirng Boot中集成了PageHelper,然后也在需要使用分页的地方加入了如下代码: PageHelper.startPage(1,1 ...

  4. Spring Boot集成MyBatis的2种方式

    目录 写在前面 准备工作 配置数据库驱动 配置数据源 原生集成MyBatis 依赖配置 注册MyBatis核心组件 定义并使用映射器 通过MyBatis-Spring-Boot-Starter集成 默 ...

  5. Spring Boot 2.X(二):集成 MyBatis 数据层开发

    MyBatis 简介 概述 MyBatis 是一款优秀的持久层框架,支持定制化 SQL.存储过程以及高级映射.它采用面向对象编程的方式对数据库进行 CRUD 的操作,使程序中对关系数据库的操作更方便简 ...

  6. spring boot集成mybatis(1)

    Spring Boot 集成教程 Spring Boot 介绍 Spring Boot 开发环境搭建(Eclipse) Spring Boot Hello World (restful接口)例子 sp ...

  7. Spring Boot 整合 Mybatis 实现 Druid 多数据源详解

    摘要: 原创出处:www.bysocket.com 泥瓦匠BYSocket 希望转载,保留摘要,谢谢! “清醒时做事,糊涂时跑步,大怒时睡觉,独处时思考” 本文提纲一.多数据源的应用场景二.运行 sp ...

  8. spring boot 整合 mybatis 以及原理

    同上一篇文章一样,spring boot 整合 mybatis过程中没有看见SqlSessionFactory,sqlsession(sqlsessionTemplate),就连在spring框架整合 ...

  9. spring boot配置mybatis和事务管理

    spring boot配置mybatis和事务管理 一.spring boot与mybatis的配置 1.首先,spring boot 配置mybatis需要的全部依赖如下: <!-- Spri ...

  10. Spring Boot 2.x基础教程:使用MyBatis访问MySQL

    之前我们已经介绍了两种在Spring Boot中访问关系型数据库的方式: 使用spring-boot-starter-jdbc 使用spring-boot-starter-data-jpa 虽然Spr ...

随机推荐

  1. 第一章 Kafka 配置部署及SASL_PLAINTEXT安全认证

    系列文章目录 第一章 Kafka 配置部署及SASL_PLAINTEXT安全认证 第二章  Spring Boot 整合 Kafka消息队列 生产者 第三章  Spring Boot 整合 Kafka ...

  2. 代码视角-神经网络-Python 实现(上)

    说明: 就是巩固一下认识而已, 也是找了篇网上大佬的文章, 看了下写得还行, 抄一抄, 权当编程练习了, 目的成为了, 从代码的角度去认识这些, 莫名其妙的, 让人生畏的, 但其实简单的, 生物学名词 ...

  3. #React中类组件中关于回调函数的一个问题

    在ES6中,类中定义的方法,是放在原型对象的,供实例对象引用. //创建一个Person类 class Person { constructor(name,age) { this.name = nam ...

  4. Kafka King 推荐一款漂亮、现代、实用的kafka客户端

    Kafka King 一个漂亮.现代.实用的kafka客户端,使用python flet.flutter构建. Github主页:https://github.com/Bronya0/Kafka-Ki ...

  5. WPF 的Image 控件 设置 Image.Source 的数据源,可能存在跨线程调用的问题。

    相信很多WPF 的开发,应该都很多用到 Image 这个控件来显示图片.这个图片的来源可以来自各种各样的方式获取到. 我们的组内白板.批注的扫码的功能也用到这个去生成二维码,生成后,二维码显示不出来, ...

  6. java LocalDateTime 加减当前时间

      LocalDateTime 可以对当前时间进行加减,在LocalDateTime类中,以plus打头的方法是增加某项时间,如plusDays的请求参数表示将要增加的天数,但是可以为负值:以minu ...

  7. 使用Spring AOP 和自定义注解统一API返回值格式

    摘要:统一接口返回值格式后,可以提高项目组前后端的产出比,降低沟通成本.因此,在借鉴前人处理方法的基础上,通过分析资料,探索建立了一套使用Spring AOP和自定义注解无侵入式地统一返回数据格式的方 ...

  8. 2023人形全能赛v831代码(包括YOLOv2识别和扫码以及颜色识别)

    v831 import time, math from maix import nn, camera, display, image import serial class YOLOv2: def _ ...

  9. 【语义分割专栏】3:Segnet原理篇

    目录 前言 背景介绍 Segnet核心剖析 池化索引(pooling Indices) 其他细节 编码器解码器的对称结构 Segnet模型代码 结语 参考资料 前言 本篇文章收录于语义分割专栏,如果对 ...

  10. 伪共享FalseShare

    伪共享FalseShare 什么是共享 下图是计算的基本结构.L1.L2.L3分别表示一级缓存.二级缓存.三级缓存,越靠近CPU的缓存,速度越快,容量也越小.所以L1缓存很小但很快,并且紧靠着在使用它 ...