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. ServletContext相关

    简介 如何得到对象 有什么作用 1.获取全局配置参数 2.获取web工程中的资源 3.存取数据,servlet间共享数据 域对象 ServlerContext的生命周期 ServletContext ...

  2. idea创建类时默认添加头部注释信息

  3. Lynx-字节跳动跨平台框架多端兼容Android, iOS, Web 原生渲染

    介绍 字节跳动近期开源的跨平台框架Lynx被视为一项重要的技术创新.相较于市场上已有的解决方案如React Native (RN) 和Flutter,Lynx具有独特的特性. 首先,Lynx采用轻量级 ...

  4. Python单元测试标准库unittest简单学习

    1.背景 当需要测试较为复杂的module,class或者系统的功能时,如果一个一个的去测试就会显得很麻烦,如果每项测试又有一定的配置或者设置的话,比如每个测试都要新建一个对象之类的,那就更麻烦了.单 ...

  5. Solon Ai Flow 编排开发框架发布预告(效果预览)

    Solon Ai 在推出 Solon Ai Mcp 后,又将推出 Solon Ai Flow. 1.Solon Ai Flow 是个啥? Solon Ai Flow 是一个智能体编排开发框架(基于 s ...

  6. An internal error occurred during: "Polling news feeds". javax/xml/bind/JAXBContext

    WindowPerferences取消Enable automatic news polling的勾选

  7. Github如何创建添加开源许可license

    我们点击项目上方的 Add file,选择 creat new file 名称填写 LICENSE, 右侧便会出现按钮 Choose a license template 选择你要的证书,填写 年份 ...

  8. .NET外挂系列:5. harmony 中补丁参数的有趣玩法(下)

    一:背景 1. 讲故事 开局一张表,故事全靠编,为了能够承上启下,先把参数列表放出来. 参数名 说明 __instance 访问非静态方法的实例(类似 this). __result 获取/修改返回值 ...

  9. WindowsPE文件格式入门11.资源表

    https://www.bpsend.net/thread-411-1-1.html 资源表 资源的管理方式采用windows资源管理器目录的管理方式,一般有三层目录. 根目录 结构体IMAGE_RE ...

  10. CRM 价格更新

    FUNCTION zcrm_reprice_bt. *"------------------------------------------------------------------- ...