Mybatis-Plus入门系列(20) -兼容多种数据库
有道无术,术尚可求,有术无道,止于术。
文章目录
- 前言
- 方案分析
- 1. 分页
- 2. XML自定义SQL
- 案例演示
- 1. 配置
- 2. 简单分页查询
- 3. 带方言的分页查询
- 参考
前言
在我们实际开发软件产品过程中,数据库的类型可能不是确定的,也有客户会有要求必须用什么数据库,比如很多政府机构要求必须使用国产数据库,所以我们在开发时,需要适配多种数据库。
MySQL
、Oracle
、PostgreSQL
、达梦等数据库在进行增删改查时,都是基于美国国家标准局制定的SQL
标准,比如SQL-92
、SQL-99
。
但是每个数据库厂商实际的SQL
会有较小差异,也就是数据库方言,大家最熟知的就是MySQL
分页使用limit
,Oracle分页使用
rownum`。
MyBatis-Plus
支持各种标准 SQL
的数据库,接下来我们实际演示如何使用MyBatis-Plus
适配各种数据库。
方案分析
1. 分页
很多数据库分页SQL
使用方式都不大相同,MyBatis-Plus
内置分页插件PaginationInnerInterceptor
已支持多种数据库,官网说明:
在使用内置分页插件时,可以设置数据库的类型:
@Configuration
@MapperScan("com.pearl.pay.mapper") //持久层扫描
@EnableTransactionManagement //启用事务管理
public class MybatisPlusConfig {@Bean@ConditionalOnMissingBean(MybatisPlusInterceptor.class)public MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();paginationInnerInterceptor.setDbType(DbType.MYSQL);interceptor.addInnerInterceptor(paginationInnerInterceptor);return interceptor;}
}
内置分页插件在执行SQL
时,会根据当前数据库类型获取分页方言。
在分页方言工厂类DialectFactory
中,可以看到具体获取方言逻辑,mysql
、mariadb
、clickhouse
、oceanbase
等数据库都是使用mysql
方言。
oracle
、达梦数据库
用的是oracle
方言:
MYSQL
数据库分页语句使用LIMIT
组装:
ORACLE
数据库分页语句使用ROWNUM、ROW_ID
组装:
综上: 在分页时,适配多种数据库只需要在分页插件中设置数据库类型即可。
2. XML自定义SQL
调用MP
的API
进行增删改查时,比如调用xxMpper.selectList()
时,因为MP
在构建SQL
时,都是使用的基础标准,所以一般不存在兼容问题。但是我们自己在XML
文件中编写SQL
,就需要注意各种数据库匹配兼容问题了。
Mybatis
本身已经做了多数据库支持,只需要告诉框架用的是什么数据库,可以根据不同的数据库厂商执行不同的语句。
Mybatis
中的DatabaseIdProvider
(数据库厂商标识提供者)接口声明了获取厂商标识的方法,标识可用于以后为每种数据库类型构建不同的查询,该机制支持多个厂商或版本。
public interface DatabaseIdProvider {default void setProperties(Properties p) {// NOP}// 根据数据源获取数据库厂商标识String getDatabaseId(DataSource dataSource) throws SQLException;
}
Mybatis
也提供了VendorDatabaseIdProvider
实现类:
public class VendorDatabaseIdProvider implements DatabaseIdProvider {// 支持的数据库厂商(需要自己定义),比如: Oracle=》oracleprivate Properties properties;// 获取数据库厂商标识ID(databaseId),eg:oracle@Overridepublic String getDatabaseId(DataSource dataSource) {if (dataSource == null) {throw new NullPointerException("dataSource cannot be null");}try {return getDatabaseName(dataSource);} catch (Exception e) {LogHolder.log.error("Could not get a databaseId from dataSource", e);}return null;}@Overridepublic void setProperties(Properties p) {this.properties = p;}// 根据产品名称,获取对应的databaseId。private String getDatabaseName(DataSource dataSource) throws SQLException {String productName = getDatabaseProductName(dataSource);if (this.properties != null) {for (Map.Entry<Object, Object> property : properties.entrySet()) {if (productName.contains((String) property.getKey())) {return (String) property.getValue();}}// no match, return nullreturn null;}return productName;}// 从数据源中获取数据库产品名称,比如: Oracleprivate String getDatabaseProductName(DataSource dataSource) throws SQLException {try (Connection con = dataSource.getConnection()) {DatabaseMetaData metaData = con.getMetaData();return metaData.getDatabaseProductName();}}
}
在Mybatis
的XML
中编写SQL
时,有个databaseId
属性,可以指定当前语句块属于哪个数据库类型,比如:
<mapper namespace="org.pearl.mybatis.demo.dao.UserMapper"><select id="selectOneById" resultType="org.pearl.mybatis.demo.pojo.entity.User" databaseId="mysql">select * from user where user_id = #{id}</select>
</mapper>
综上:我们只需要配置DatabaseIdProvider
中支持哪些数据库,然后在XML
中针对每种数据库方言编写查询语句,并添加databaseId
属性,Mybatis
会在启动时获取数据源使用的哪个类型数据库,然后执行配置了当前数据库对应的语句
案例演示
1. 配置
搭建工程,集成MP
、Oracle
、Mysql
很简单,这里就不赘述了。
在配置文件中,添加对应的Oracle
、Mysql
连接地址:
spring:datasource:type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: oracle.jdbc.OracleDriverurl: jdbc:oracle:thin:@127.0.0.1:1521:ORCLusername: rootpassword: root#driver-class-name: com.mysql.cj.jdbc.Driver#url: jdbc:mysql://127.0.0.1:3306/d_account?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai#username: root#password: root
在MP
中添加配置类:
@Configuration
@MapperScan("com.pearl.pay.mapper") //持久层扫描
@EnableTransactionManagement //启用事务管理
public class MybatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(DataSource dataSource,DatabaseIdProvider databaseIdProvider) throws SQLException {// MP插件MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();// 获取当前数据源对应的数据库类型,添加分页插件String databaseId = databaseIdProvider.getDatabaseId(dataSource);DbType dbType = DbType.getDbType(databaseId);paginationInnerInterceptor.setDbType(dbType);interceptor.addInnerInterceptor(paginationInnerInterceptor);return interceptor;}@Beanpublic DatabaseIdProvider databaseIdProvider() {// 数据库厂商提供者DatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider();Properties p = new Properties();p.setProperty("Oracle", "oracle");p.setProperty("Mysql", "mysql");databaseIdProvider.setProperties(p);return databaseIdProvider;}
}
2. 简单分页查询
因为MyBatis-Plus
内置分页插件已经做了适配,简单的(没有数据库方言)分页查询不用自己写代码适配。
首先添加一个分页查询:
IPage<User> test(Page<User> page);
<select id="test" resultType="com.pearl.entity.User">select * from user</select>
数据源配置的是Oracle
,没有配置databaseId
,测试SQL
语句打印如下:
SELECT * FROM ( SELECT TMP.*, ROWNUM ROW_ID FROM ( select * from user) TMP WHERE ROWNUM <=10) WHERE ROW_ID > 0
数据源配置为Mysql
,测试SQL
语句打印如下:
select * from user LIMIT 10
3. 带方言的分页查询
需求: 查询时间节点小于当前时间的数据。
Oracle
当前时间使用的是sysdate
函数,Mysql
使用的是now()
函数,这个时候就需要手动去兼容了。
编写两个重名的查询语句,针对不同的数据库厂商编写SQL
,并配置对应的databaseId
<select id="test" resultType="com.pearl.entity.User" databaseId="mysql">select * from user t <![CDATA[ where t.create_time <= now()]]></select><select id="test" resultType="com.pearl.entity.User" databaseId="oracle">select * from user t <![CDATA[ where t.create_time <= sysdate ]]></select>
也可以使用if
语句,判断当前数据库类型,添加不同语句(推荐)。
<select id="test" resultType="com.pearl.entity.User">select * from user t<where><if test="_databaseId == 'mysql'"><![CDATA[ AND t.create_time <= now()]]></if><if test="_databaseId == 'oracle'"><![CDATA[ AND t.create_time <= sysdate ]]></if></where></select>
切换数据库,执行如下:
select * from user t where t.create_time <= now() LIMIT 10
SELECT * FROM ( SELECT TMP.*, ROWNUM ROW_ID FROM ( select * from user t where t.create_time <= sysdate ) TMP WHERE ROWNUM <=10) WHERE ROW_ID > 0
参考
接下来我们简单了解下Oracle
和Mysql
的一些区别,便于开发。
数据类型对照:
数据库 | 对比项 | 类型 |
---|---|---|
MySQL | 数据类型 | INTEGER、SMALLINT、TINYINT、MEDIUMINT、BIGINT |
Oracle | 数据类型 | number |
MySQL | 日期和时间 | date、timestamp、timestamp |
Oracle | 日期和时间 | date、timestamp |
MySQL | 字符类型 | char、varchar |
Oracle | 字符类型 | char、varchar、varchar2、nvarchar、nvarchar2 |
MySQL | 大字段 | LONGTEXT |
Oracle | 大字段 | clob |
常用函数对照:
数据库 | 对比项 | 函数 |
---|---|---|
MySQL | 获取字符串长度 | char_length(str) |
Oracle | 获取字符串长度 | length(str) |
MySQL | 生成随机序列 | UUID() |
Oracle | 生成随机序列 | sys_guid() |
MySQL | 时间转换为字符串 | date_format(NOW(),‘%Y-%m-%d’) |
Oracle | 时间转换为字符串 | to_char(sysdate, ‘YYYY-MM-DD’) |
MySQL | 字符串型时间转换为时间 | str_to_date(‘2019-01-01’,‘%Y-%m-%d’) |
Oracle | 字符串型时间转换为时间 | to_date(‘2019-01-01’, ‘YYYY-MM-DD’) |
MySQL | 包含时分秒的函数转换 | date_format(NOW(),‘%Y-%m-%d %H:%i:%s’) |
Oracle | 包含时分秒的函数转换 | str_to_date(‘2019-01-01’,‘%Y-%m-%d %H:%i:%s’) |
MySQL | 当前时间 | now() |
Oracle | 当前时间 | sysdate |
其他:
数据库 | 对比项 | 支持 |
---|---|---|
MySQL | 引号 | 双引号和单引号 |
Oracle | 引号 | 只能识别单引号 |
MySQL | 字符串连接符 | concat()函数 |
Oracle | 字符串连接符 | 可用双竖线连接字符串 |
MySQL | 分页 | limit |
Oracle | 分页 | rownum |
相关文章:

Mybatis-Plus入门系列(20) -兼容多种数据库
有道无术,术尚可求,有术无道,止于术。 文章目录前言方案分析1. 分页2. XML自定义SQL案例演示1. 配置2. 简单分页查询3. 带方言的分页查询参考前言 在我们实际开发软件产品过程中,数据库的类型可能不是确定的,也有客户…...

JetPack板块—Android X解析
Android Jetpack简述 AndroidX 是Android团队用于在Jetpack中开发,测试,打包,发布和版本管理的开源项目。相比于原来的Android Support库,AndroidX 可以称得上是一次重大的升级改进。 和Support库一样,AndroidX与Android 操作系…...

C++学习笔记-数字
当我们使用数字时,通常我们使用原始数据类型,例如 int,short,long,float 和 double 等。数字数据类型,它们的可能值和取值范围在讨论 C 数据类型时已经解释了。 C 定义数字 我们已经在之前笔记的各种实例…...

Nginx——Nginx的基础原理
摘要 Nginx 是俄罗斯人编写的十分轻量级的 HTTP 服务器,是一个高性能的HTTP和反向代理服务器,同时也是一个 IMAP/POP3/SMTP 代理服务器。Nginx 是由俄罗斯人 Igor Sysoev 为俄罗斯访问量第二的 Rambler.ru 站点开发的,它已经在该站点运行超过两年半了。…...

服务端开发Java之备战秋招面试篇1
在这个面试造火箭工作拧螺丝的时代背景下,感觉不是很好,不过还好也是拿到了还行的offer,准备去实习了,接下来就是边实习边准备秋招了,这半年把(技术栈八股文面经算法题项目)吃透,希望…...

【C++的OpenCV】第三课-OpenCV图像加载和显示
我们开始学习OpenCV一、OpenCV加载图片和显示图片1.1 imread()函数的介绍1.2 cv::namedWindow()函数的介绍1.4 imshow()函数介绍1.5 Mat容器介绍二、 代码实例(带注释)2.1 代码2.2 执行结果一、OpenCV加载图片和显示图片 本章节中,将会学习到…...

【面试1v1实景模拟】Spring事务 一文到底
老面👴:小伙子,了解Spring的事务吗? 解读🔔:这个必须了解,不了解直接挂~😂😂😂,但面试官肯定不是想听你了解两个字,他是想让你简单的介绍下。 笑小枫🍁:了解,事务在逻辑上是一组操作,要么执行,要不都不执行。主要是针对数据库而言的,比如说 MySQL。为…...

Neuron Selectivity Transfer 原理与代码解析
paper:Like What You Like: Knowledge Distill via Neuron Selectivity Transfercode:https://github.com/megvii-research/mdistiller/blob/master/mdistiller/distillers/NST.py本文的创新点本文探索了一种新型的知识 - 神经元的选择性知识,…...

vue项目关闭子页面,并更新父页面的数据
今天下午是一个非常痛苦的,想要实现一个功能: 父页面打开了一个新的页面(浏览器打开一个新的窗口),并在子页面提交数据之后,父页面的数据要同步更新。 难点:父页面是一个表格列表,…...

第五次作业:修改redis的配置文件使得windows的图形界面客户端可以连接redis服务器
1. 安装 Redis 依赖 Redis 是基于 C语言编写的,因此首先需要安装 Redis 所需要的 gcc 依赖: yum install -y gcc tcl 2、上传安装文件 将下载好的 redis-6.2.7.tar.gz 安装包上传到虚拟机的任意目录(一般推荐上传到 /usr/local/src目录&am…...

【11】FreeRTOS的延时函数
目录1.延时函数-介绍2.相对延时函数-解析2.1函数prvAddCurrentTaskToDelayedList-解析2.3滴答定时器中断服务函数xPortSysTickHandler()-解析2.4函数taskSWITCH_DELAYED_LISTS() -解析3.延时函数-实验4.总结1.延时函数-介绍 函数描述vTaskDelay()相对延时xTaskDelayUntil()绝对…...

Vue页面组成及常用属性
一、Vue页面组成 目前的项目中,Vue页面都是采用组件套娃的形式,由一个一个的组件拼接而成整个页面。一个组件就是一个.vue文件。组件通常由template和script两部分组成: template部分:页面展示的具体元素内容,比如文字…...

j6-IO流泛型集合多线程注解反射Socket
IO流 1 JDK API的使用 2 io简介 输入流用来读取in 输出流用来写出Out 在Java中,根据处理的数据单位不同,分为字节流和字符流 继承结构 java.io包: File 字节流:针对二进制文件 InputStream --FileInputStream --BufferedInputStre…...

创业能否成功?这几个因素很重要!
创业能否成功?这几个因素很重要! 2023-02-22 19:06:53 大家好,我是你们熟悉而又陌生的好朋友梦龙,一个创业期的年轻人 上周末跟朋友一起钓鱼,他跟吐槽现在生意越来越难做。他是我身边可以说是创业很成功的例子&#…...

Bmp图片格式介绍
Bmp图片格式介绍 介绍 BMP是英文Bitmap(位图)的简写,它是Windows操作系统中的标准图像文件格式,能够被多种Windows应用程序所支持。随着Windows操作系统的流行与丰富的Windows应用程序的开发,BMP位图格式理所当然地被…...

Day4 leetcode
Day4 啊啊啊啊,什么玩意,第一次因为测评没过,约好的面试取消了,好尴尬呀,还有一家厦门的C/C电话面,是一家我还挺喜欢的公司,面的稀烂,只能安慰自己我现在手上至少有一个offer 有效括…...

Java设计模式-原型模式
1、定义 原型模式是一种创建型模式,用于创建重复的对象,并且保证性能。原型模式创建的对象是由原型对象自身创建的,是原型对象的一个克隆,和原型对象具有相同的结构和相同的值。 2、适用场景 创建对象时我们不仅仅需要创建一个新…...

2023年度最新且最详细Ubuntu的安装教程
目录 准备ISO镜像 1.去官网下载镜像,或者找有镜像源的网站下载 阿里云镜像站 2. 如果服务器是打算直接把底层系统安装为Ubuntu的话还需制作系统U盘 安装 1.新建虚拟机调整基础配置 2.打开电源,进入安装界面(到这一步就跟u盘安装步骤一致…...

unix高级编程-fork之后父子进程共享文件
~/.bash_profile:每个用户都可使用该文件输入专用于自己使用的shell信息,当用户登录时,该文件仅仅执行一次!默认情况下,他设置一些环境变量,执行用户的.bashrc文件. 这里我看到的是centos的操作,但我用的是debian系的ubuntu,百度了一下发现debian的在这里…...

vue+echarts:柱状图横向展示和竖向展示
第021个点击查看专栏目录本示例是显示柱状图,分别是横向展示和纵向展示。关键是X轴和Y轴的参数互换。 文章目录横向示例效果横向示例源代码(共81行)纵向示例效果纵向示例源代码(共81行)相关资料参考专栏介绍横向示例效…...

SealOS 一键安装 K8S
环境 # 查看系统发行版 $ cat /etc/os-release NAME"CentOS Linux" VERSION"7 (Core)" ID"centos" ID_LIKE"rhel fedora" VERSION_ID"7" PRETTY_NAME"CentOS Linux 7 (Core)" ANSI_COLOR"0;31" CPE_NA…...

python网络编程详解
最近在看《UNIX网络编程 卷1》和《FREEBSD操作系统设计与实现》这两本书,我重点关注了TCP协议相关的内容,结合自己后台开发的经验,写下这篇文章,一方面是为了帮助有需要的人,更重要的是方便自己整理思路,加…...

ICRA 2023 | 首个联合暗光增强和深度估计的自监督方法STEPS
原文链接:https://www.techbeat.net/article-info?id4629 作者:郑宇鹏 本文中,我们提出了STEPS,第一个自监督框架来联合学习图像增强和夜间深度估计的方法。它可以同时训练图像增强网络和深度估计网络,并利用了图像增…...

基于react+nodejs+mysql开发用户中心,用于项管理加入的项目的用户认证
基于reactnodejsmysql开发用户中心,用于项管理加入的项目的用户认证用户中心功能介绍页面截图后端采用架构user表projects表project_user表仓库地址用户中心功能介绍 用户中心项目,用于统一管理用户信息、登录、注册、鉴权等 功能如下: 用…...

mapreduce与yarn
文章目录一、MapReduce1.1、MapReduce思想1.2、MapReduce实例进程1.3、MapReduce阶段组成1.4、MapReduce数据类型1.5、MapReduce关键类1.6、MapReduce执行流程1.6.1、Map阶段执行流程1.6.2、Map的shuffle阶段执行流程1.6.3、Reduce阶段执行流程1.7、MapReduce实例WordCount二、…...

鲲鹏云服务器上使用 traceroute 命令跟踪路由
traceroute 命令跟踪路由 它由遍布全球的几万局域网和数百万台计算机组成,并通过用于异构网络的TCP/IP协议进行网间通信。互联网中,信息的传送是通过网中许多段的传输介质和设备(路由器,交换机,服务器,网关…...

代码随想录算法训练营第47天 || 198.打家劫舍 || 213.打家劫舍II || 337.打家劫舍III
代码随想录算法训练营第47天 || 198.打家劫舍 || 213.打家劫舍II || 337.打家劫舍III 198.打家劫舍 题目介绍 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统&…...

JVM调优方式
对JVM内存的系统级的调优主要的目的是减少GC的频率和Full GC的次数。 1.Full GC 会对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个堆进行回收,所以比较慢,因此应该尽可能减少Full GC的次数。 2.导致Full GC的原因 1)年老…...

机器学习模型监控的 9 个技巧
机器学习 (ML) 模型是非常敏感的软件;它们的成功使用需要进行仔细监控以确保它们可以正常工作。当使用所述模型的输出自动做出业务决策时尤其如此。这意味着有缺陷的模型通常会对终端客户的体验产生真正的影响。因此,监控输入数据(和输出&…...

Linux 实现鼠标侧边键实现代码与网页的前进、后退
前言 之前一直是使用windows进行开发,最近转到linux后使用VsCode编写代码。 但是不像在win环境下,使用鼠标侧边键可以实现代码的前向、后向跳转。浏览网页时也不行(使用Alt Left可以后退)。 修改键盘映射实在没有那么方便&…...