Mybatis-Plus入门系列(20) -兼容多种数据库

news2025/1/23 22:27:58

有道无术,术尚可求,有术无道,止于术。

文章目录

    • 前言
    • 方案分析
      • 1. 分页
      • 2. XML自定义SQL
    • 案例演示
      • 1. 配置
      • 2. 简单分页查询
      • 3. 带方言的分页查询
    • 参考

前言

在我们实际开发软件产品过程中,数据库的类型可能不是确定的,也有客户会有要求必须用什么数据库,比如很多政府机构要求必须使用国产数据库,所以我们在开发时,需要适配多种数据库。

MySQLOraclePostgreSQL、达梦等数据库在进行增删改查时,都是基于美国国家标准局制定的SQL标准,比如SQL-92SQL-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中,可以看到具体获取方言逻辑,mysqlmariadbclickhouseoceanbase等数据库都是使用mysql方言。
在这里插入图片描述
oracle达梦数据库用的是oracle方言:
在这里插入图片描述
MYSQL 数据库分页语句使用LIMIT组装:
在这里插入图片描述
ORACLE 数据库分页语句使用ROWNUM、ROW_ID组装:
在这里插入图片描述
综上: 在分页时,适配多种数据库只需要在分页插件中设置数据库类型即可。

2. XML自定义SQL

调用MPAPI进行增删改查时,比如调用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=》oracle
  private Properties properties;
  // 获取数据库厂商标识ID(databaseId),eg:oracle
  @Override
  public 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;
  }

  @Override
  public 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 null
      return null;
    }
    return productName;
  }
  // 从数据源中获取数据库产品名称,比如: Oracle
  private String getDatabaseProductName(DataSource dataSource) throws SQLException {
    try (Connection con = dataSource.getConnection()) {
      DatabaseMetaData metaData = con.getMetaData();
      return metaData.getDatabaseProductName();
    }
  }
}

MybatisXML中编写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. 配置

搭建工程,集成MPOracleMysql很简单,这里就不赘述了。

在配置文件中,添加对应的OracleMysql连接地址:

spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: oracle.jdbc.OracleDriver
    url: jdbc:oracle:thin:@127.0.0.1:1521:ORCL
    username: root
    password: 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 {
     @Bean
    public 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;
    }

    @Bean
    public 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

参考

接下来我们简单了解下OracleMysql的一些区别,便于开发。

数据类型对照:

数据库对比项类型
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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/363665.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PHP面试题

PHP相关 php7新特性 1.类型的声明 php7可以声明函数传参的类型和返回值的类型&#xff0c;比如可以用int&#xff0c;string声明参数和返回值的类型&#xff0c;如下&#xff1a; 代码&#xff1a;declare(strict_types1); function add(int $a,int $b):int{ return $a$b;…

深入浅出C++ ——手撕AVL树

文章目录前言一、AVL 树介绍二、AVL树节点的定义三、AVL树的插入四、AVL树的旋转五、AVL树的验证六、AVL树的删除七、AVL树的性能八、AVL树的实现前言 在前面的文章中介绍了map / multimap / set / multiset 容器&#xff0c;这几个容器的底层都是按照二叉搜索树来实现的。但是…

paddlepaddle目标检测

目录 1 参考链接 2 环境 3 数据集准备 4 训练 train.py 5 导出预测模型 6 预测 源码来自作者 夜雨飘零1&#xff0c;我对参考链接的代码略有修改&#xff0c;网盘地址 链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;ipl5 1 参考链接 博客地址 基…

Linux 实现鼠标侧边键实现代码与网页的前进、后退

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

文案女王彭芳如何转变为“百万发售系统”创始人?我们来探个究竟!

智多星老师 她的输出跟智多星老师几乎毫无二致&#xff0c;是抄袭还是纯属巧合呢&#xff1f; 你们问的这个问题我也想知道&#xff0c;为了了解真相&#xff0c;我让我的一个学生把那个叫“彭芳老师”的视频给我看&#xff0c;当看到她的简介时&#xff0c;我非常震惊&#…

启智社区“我为开源狂”第六期活动小白教程之基础活跃榜

一、写在前面 春天来啦~启智社区第六期活动也来啦&#xff01; 有奖金的哦~~ 基础活跃榜奖金根据用户活跃程度进行100-300元的激励。 挑战升级榜需要用户完成相应任务&#xff0c;达标者可获得300-1000元的激励。 邀请助力榜根据用户邀请情况进行积分累加&#xff0c;按实际达…

游戏策划想要了解编程和引擎是应该从unity入手还是ue4入手?

建议 考虑自身的职业规划考虑本公司引擎使用情况考虑自身兴趣爱好学习引擎的同时多拆解市面上主流游戏、做游戏数据及系统分析 区别 除去以上内容&#xff0c;说下unity和ue的学习及使用区别&#xff1a; 适用类型&#xff1a; 3D – 两个引擎都具有强大的3D功能&#xff0…

ctcdecode安装

一、写在前面&#xff1a;ctcdecode代码较早&#xff0c;安装过程有许多坑。本文章为ctcdecode安装成功的记录&#xff0c;可能存在不适用的情况&#xff0c;欢迎大家补充。二、致谢&#xff1a;感谢文章https://blog.csdn.net/u011550545/article/details/87926995提供的宝贵参…

HashMap(JDK1.8)源码+底层数据结构分析

HashMap 简介底层数据结构分析 JDK1.8 之前JDK1.8 之后 HashMap 源码分析 构造方法put 方法get 方法resize 方法 HashMap 常用方法测试 感谢 changfubai 对本文的改进做出的贡献&#xff01; HashMap 简介 HashMap 主要用来存放键值对&#xff0c;它基于哈希表的 Map 接口实现…

【React npm】从零搭建react脚手架,发布组件库到npm,并实现按需加载(二)

发布react组件库前情回顾介绍搭建脚手架配置babelrc配置jsconfig写入组件demo修改主入口文件配置生产环境webpack配置package.json发布实现按需加载前情回顾 前面写过一篇&#xff0c;发布单个组件到npm的&#xff1a; https://blog.csdn.net/tuzi007a/article/details/12911…

Anaconda环境配置

1.进入清华大学镜像网站Index of /anaconda/archive/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror&#xff0c;下载稳定版Anaconda3-5.2.0&#xff0c;如下图。2.放到整理好的文件夹中&#xff0c;双击安装包进行安装。3.安装过程中需要改变的默认值如下&#xff…

Linux 基础知识之文件系统

目录一、文件系统1.文件种类2.Linux和Windows文件后缀的不同3.查看文件类型3.绝对路径与相对路径二、系统分区三、目录结构一、文件系统 1.文件种类 Linux中一切皆文件。目光所及&#xff0c;皆是文件。文件的种类共有七种&#xff0c;每种文件都有自己的独特标识&#xff1a;…

MYSQL 密码修改 (四种方式)

注 &#xff1a; 我们所谓的密码修改肯定是先指的是 你已经清楚用户的原密码&#xff0c;是对原密码进行了修改并不是你忘记了密码&#xff0c;然后设置新密码~&#xff01;&#xff01;方式一 &#xff1a; 使用 mysqladmin示例 &#xff1a; [rootbogon ~]# mysqladmin -uroo…

python文件编译为pyc后运行

一、pyc文件我们开发一个python脚本&#xff0c;文件的后缀为.py。如果运行这个py文件&#xff0c;Python内部会先将源码文件&#xff08;.py文件&#xff09;编译成字节码&#xff08;byte code&#xff09;文件&#xff08;.pyc文件&#xff09;。接着运行编译后的字节码&…

【Spark分布式内存计算框架——离线综合实战】5. 业务报表分析

第三章 业务报表分析 一般的系统需要使用报表来展示公司的运营情况、 数据情况等&#xff0c;本章节对数据进行一些常见报表的开发&#xff0c;广告数据业务报表数据流向图如下所示&#xff1a; 具体报表的需求如下&#xff1a; 相关报表开发说明如下&#xff1a; 第一、数据…

【总结】python3启动web服务引发的一系列问题

背景 在某行的实施项目&#xff0c;需要使用python3环境运行某些py脚本。 由于行内交付的机器已自带python3 &#xff0c;没有采取自行安装python3&#xff0c;但是运行python脚本时报没有tornado module。 错误信息 ModuleNotFoundError&#xff1a;No module named ‘torn…

Unity截屏时将背景的透明度设为0

常用的截屏函数是&#xff1a; UnityEngine.ScreenCapture.CaptureScreenshot(fileName, 5); //5代表dpi大小&#xff0c;数字越大越清晰但是这样保存图片是不能将黑色背景的透明度设为0&#xff0c;最终还是24bit图。 如果将背景透明度设为0而渲染物体透明度设为255&#xff…

学插画的线上机构排名

学插画哪个线上机构好&#xff0c;5个靠谱的插画网课推荐&#xff01;给大家梳理了国内5家专业的插画师培训班&#xff0c;最新5大插画班排行榜&#xff0c;各有优势和特色&#xff01; 一&#xff1a;插画线上培训机构排名 1、轻微课&#xff08;五颗星&#xff09; 主打课程有…

【C语言】函数栈帧的创建与销毁

Yan-英杰的主页 悟已往之不谏 知来者之可追 目录 ​0.ebp和esp是如何来维护栈帧的呢&#xff1f; 1.为什么局部变量的值不初始化是随机的&#xff1f; ​2.局部变量是怎么创建的&#xff1f; ​3 .函数是如何传参的&#xff1f;传参的顺序是怎样的 4.函数是如何调用的 ​…

scrapy-redis分布式爬虫学习记录

目录 1. scrapy-redis是什么&#xff1f; 2. scrapy-redis工作原理 3.分布式架构 4. scrapy-redis的源码分析 5. 部署scrapy-redis 6. scrapy-redis的基本使用 6.1 redis数据库基本表项 6.2 在scrapy项目的基础进行更改 7. redis数据转存入mysql数据库 课程推荐&#…