文章目录
- SpringBoot 2.7.X 一套代码适配多种数据库讲解(图文详细)
- 1 简介
- 1.1 概述
- 1.2 环境安装
- 1.3 测试脚本
- 2 基于Mybatis 方式
- 2.1 添加DatabaseIdProvider配置
- 2.2 在Mybatis的XML中,指定SQL语句的databaseId标签
- 2.3 控制器接口样例
- 2.4 呈现效果
- 3 基于MP框架Wrapps条件构造器方式
- 3.1 框架整合
- 3.2 业务接口实现层
- 3.3 控制器层接口
- 3.4 Yaml多数据源配置
- 3.5 呈现效果
SpringBoot 2.7.X 一套代码适配多种数据库讲解(图文详细)
1 简介
1.1 概述
- 随着日新月异的项目变化,应对不同客户的数据库安全需求,避免企业项目重复繁琐的工作量。故需要项目同时适配多种数据库,如MYSQL、Oracle、PostgreSQL、SQL Server、kingBaseES等…,提升项目交付能力,从而获取更多效益和口碑!
1.2 环境安装
-
PostgreSQL 15数据库安装: https://blog.csdn.net/weixin_44187730/article/details/129958012
-
MySQL 5.7数据库安装: https://blog.csdn.net/weixin_44187730/article/details/101287509
-
Oracle 12c数据库安装: https://blog.csdn.net/weixin_44187730/article/details/107543314
1.3 测试脚本
- Oracle
-- ----------------------------
-- 1. 测试-Oracle 格式表结构
-- ----------------------------
create table ed_sys_user (
user_id number(20) not null,
dept_id number(20) default null,
user_name varchar2(40) not null,
nick_name varchar2(40) not null,
user_type varchar2(10) default 'sys_user',
email varchar2(50) default '',
phone_number varchar2(11) default '',
sex char(1) default '0',
avatar varchar2(100) default '',
password varchar2(100) default '',
status char(1) default '0',
del_flag char(1) default '0',
login_ip varchar2(128) default '',
login_date date,
create_by varchar2(64),
create_time date,
update_by varchar2(64) default '',
update_time date,
remark varchar2(500) default ''
);
alter table ed_sys_user add constraint pk_sys_user primary key (user_id);
comment on table ed_sys_user is '用户信息表';
comment on column ed_sys_user.user_id is '用户ID';
comment on column ed_sys_user.dept_id is '部门ID';
comment on column ed_sys_user.user_name is '用户账号';
comment on column ed_sys_user.nick_name is '用户昵称';
comment on column ed_sys_user.user_type is '用户类型(sys_user系统用户)';
comment on column ed_sys_user.email is '用户邮箱';
comment on column ed_sys_user.phone_number is '手机号码';
comment on column ed_sys_user.sex is '用户性别(0男 1女 2未知)';
comment on column ed_sys_user.avatar is '头像路径';
comment on column ed_sys_user.password is '密码';
comment on column ed_sys_user.status is '帐号状态(0正常 1停用)';
comment on column ed_sys_user.del_flag is '删除标志(0代表存在 2代表删除)';
comment on column ed_sys_user.login_ip is '最后登录IP';
comment on column ed_sys_user.login_date is '最后登录时间';
comment on column ed_sys_user.create_by is '创建者';
comment on column ed_sys_user.create_time is '创建时间';
comment on column ed_sys_user.update_by is '更新者';
comment on column ed_sys_user.update_time is '更新时间';
comment on column ed_sys_user.remark is '备注';
-- ----------------------------
-- 初始化-用户信息表数据
-- ----------------------------
insert into ed_sys_user values(1, 103, 'Sysadmin', '超级管理员', 'sys_user', '111106@163.com', '15888888888', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', sysdate, 'Sysadmin', sysdate, 'Sysadmin', sysdate,'超级管理员-Oracle数据库数据');
- MySQL
-- ----------------------------
-- 1. 测试-Mysql 格式表结构
-- ----------------------------
drop table if exists ed_sys_user;
create table ed_sys_user (
user_id bigint(20) not null comment '用户ID',
dept_id bigint(20) default null comment '部门ID',
user_name varchar(30) not null comment '用户账号',
nick_name varchar(30) not null comment '用户昵称',
user_type varchar(10) default 'sys_user' comment '用户类型(sys_user系统用户)',
email varchar(50) default '' comment '用户邮箱',
phone_number varchar(11) default '' comment '手机号码',
sex char(1) default '0' comment '用户性别(0男 1女 2未知)',
avatar varchar(100) default '' comment '头像地址',
password varchar(100) default '' comment '密码',
status char(1) default '0' comment '帐号状态(0正常 1停用)',
del_flag char(1) default '0' comment '删除标志(0代表存在 2代表删除)',
login_ip varchar(128) default '' comment '最后登录IP',
login_date datetime comment '最后登录时间',
create_by varchar(64) default '' comment '创建者',
create_time datetime comment '创建时间',
update_by varchar(64) default '' comment '更新者',
update_time datetime comment '更新时间',
remark varchar(500) default null comment '备注',
primary key (user_id)
) engine=innodb comment = '用户信息表';
-- ----------------------------
-- 初始化-用户信息表数据
-- ----------------------------
insert into ed_sys_user values(1, 103, 'Sysadmin', '超级管理员', 'sys_user', '99099@163.com', '15888888888', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', sysdate(), 'Sysadmin', sysdate(), 'Sysadmin', sysdate(), '超级管理员-Mysql数据库中数据');
- PostgreSQL
-- ----------------------------
-- 1. 测试-postgresSql 格式表结构
-- ----------------------------
drop table if exists ed_sys_user;
create table if not exists ed_sys_user
(
user_id int8,
dept_id int8,
user_name varchar(30) not null,
nick_name varchar(30) not null,
user_type varchar(10) default 'sys_user'::varchar,
email varchar(50) default ''::varchar,
phone_number varchar(11) default ''::varchar,
sex char default '0'::bpchar,
avatar varchar(100) default ''::varchar,
password varchar(100) default ''::varchar,
status char default '0'::bpchar,
del_flag char default '0'::bpchar,
login_ip varchar(128) default ''::varchar,
login_date timestamp,
create_by varchar(64) default ''::varchar,
create_time timestamp,
update_by varchar(64) default ''::varchar,
update_time timestamp,
remark varchar(500) default null::varchar,
constraint "ed_sys_user_pk" primary key (user_id)
);
comment on table ed_sys_user is '用户信息表';
comment on column ed_sys_user.user_id is '用户ID';
comment on column ed_sys_user.dept_id is '部门ID';
comment on column ed_sys_user.user_name is '用户账号';
comment on column ed_sys_user.nick_name is '用户昵称';
comment on column ed_sys_user.user_type is '用户类型(sys_user系统用户)';
comment on column ed_sys_user.email is '用户邮箱';
comment on column ed_sys_user.phone_number is '手机号码';
comment on column ed_sys_user.sex is '用户性别(0男 1女 2未知)';
comment on column ed_sys_user.avatar is '头像地址';
comment on column ed_sys_user.password is '密码';
comment on column ed_sys_user.status is '帐号状态(0正常 1停用)';
comment on column ed_sys_user.del_flag is '删除标志(0代表存在 2代表删除)';
comment on column ed_sys_user.login_ip is '最后登陆IP';
comment on column ed_sys_user.login_date is '最后登陆时间';
comment on column ed_sys_user.create_by is '创建者';
comment on column ed_sys_user.create_time is '创建时间';
comment on column ed_sys_user.update_by is '更新者';
comment on column ed_sys_user.update_time is '更新时间';
comment on column ed_sys_user.remark is '备注';
-- ----------------------------
-- 初始化-用户信息表数据
-- ----------------------------
insert into ed_sys_user values(1, 103, 'Sysadmin', '超级管理员', 'sys_user', '99099@163.com', '15888888888', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', now(), 'Sysadmin', now(), 'Sysadmin', now(), '超级管理员-postgresSql数据库数据');
2 基于Mybatis 方式
2.1 添加DatabaseIdProvider配置
package com.xs.dsy.config;
import org.apache.ibatis.mapping.DatabaseIdProvider;
import org.apache.ibatis.mapping.VendorDatabaseIdProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Properties;
/**
* @Copyright (C), 2016-2023 MP
* @ClassName: DsyDateSourceConfg
* @Author: hf
* @Date: 2023/4/24 15:50
* @Description: 动态数据源Provider配置
*/
@Configuration
public class DsyDateSourceConfig {
/**
* 自动识别使用的数据库类型
* 在mapper.xml中databaseId的值就是跟这里的value值对应,Key值必须是数据库供应商的标识ID,
*
* 数据库标识ID名称: dataSource.getConnection().getMetaData().getDatabaseProductName();
*
* 如果没有databaseId选择则说明该sql适用所有数据库
*/
@Bean
public DatabaseIdProvider getDatabaseIdProvider() {
DatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider();
Properties properties = new Properties();
properties.setProperty("Oracle", "oracle");
properties.setProperty("MySQL", "mysql");
properties.setProperty("PostgreSQL", "PostgreSQL");
databaseIdProvider.setProperties(properties);
return databaseIdProvider;
}
}
2.2 在Mybatis的XML中,指定SQL语句的databaseId标签
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xs.dsy.ed.mapper.EdSysUserMapper">
<!--MySQL数据库指定-->
<select id="all" databaseId="mysql" resultType="com.xs.dsy.ed.domain.EdSysUser">
SELECT * FROM ED_SYS_USER
</select>
<!--Oracle数据库指定-->
<select id="all" databaseId="oracle" resultType="com.xs.dsy.ed.domain.EdSysUser">
SELECT * FROM ed_sys_user
</select>
<!--PostgreSQL数据库指定-->
<select id="all" databaseId="PostgreSQL" resultType="com.xs.dsy.ed.domain.EdSysUser">
SELECT * FROM ed_sys_user
</select>
</mapper>
2.3 控制器接口样例
package com.xs.dsy.ed.controller;
import com.xs.common.core.controller.BaseController;
import com.xs.common.core.domain.PageQuery;
import com.xs.common.core.domain.R;
import com.xs.common.core.page.TableDataInfo;
import com.xs.common.core.validate.AddGroup;
import com.xs.common.core.validate.EditGroup;
import com.xs.dsy.ed.domain.EdSysUser;
import com.xs.dsy.ed.domain.bo.EdSysUserBo;
import com.xs.dsy.ed.domain.vo.EdSysUserVo;
import com.xs.dsy.ed.service.EdSysUserService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.sql.DataSource;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
/**
* 用户信息-前端控制器
*
* @author Lion Li
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/user")
public class EdSysUserController extends BaseController {
private final EdSysUserService userService;
private final DataSource dataSource;
/**
* 查询用户信息列表
*/
@GetMapping("/queryAll")
public R<Object> queryAll() throws SQLException {
//1.0 获取数据源信息
HashMap<String, Object> map = new HashMap<>();
Connection connection = dataSource.getConnection();
DatabaseMetaData metaData = connection.getMetaData();
map.put("driverName", metaData.getDriverName());
map.put("driverVersion", metaData.getDriverVersion());
map.put("databaseProductName", metaData.getDatabaseProductName());
map.put("databaseProductVersion", metaData.getDatabaseProductVersion());
//2.0 查询数据
List<EdSysUser> rows = userService.all();
map.put("rows", rows);
return R.ok(map);
}
}
2.4 呈现效果
- 当项目配置的数据源是不同的数据库时,同一个接口会自动切换查询不同的数据库,从而只需注意部分语法兼容,不影响原有业务逻辑。
3 基于MP框架Wrapps条件构造器方式
3.1 框架整合
-
SpringBoot+ Mybatis Plus框架整合: https://blog.csdn.net/weixin_44187730/article/details/101620137
-
SpringBoot + MP 多数据源整合: https://blog.csdn.net/weixin_44187730/article/details/103504300
3.2 业务接口实现层
package com.xs.dsy.ed.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.xs.common.core.domain.PageQuery;
import com.xs.common.core.page.TableDataInfo;
import com.xs.common.utils.StringUtils;
import com.xs.dsy.ed.domain.EdSysUser;
import com.xs.dsy.ed.domain.bo.EdSysUserBo;
import com.xs.dsy.ed.domain.vo.EdSysUserVo;
import com.xs.dsy.ed.mapper.EdSysUserMapper;
import com.xs.dsy.ed.service.EdSysUserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* 用户 业务层处理
*
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class EdSysUserServiceImpl implements EdSysUserService {
private final EdSysUserMapper baseMapper;
/**
* 查询用户信息
*/
@Override
public EdSysUserVo queryById(Long userId) {
return baseMapper.selectVoById(userId, EdSysUserVo.class);
}
/**
* 查询用户信息列表
*/
@Override
public TableDataInfo<EdSysUserVo> queryPageList(EdSysUserBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<EdSysUser> lqw = buildQueryWrapper(bo);
Page<EdSysUserVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw, EdSysUserVo.class);
return TableDataInfo.build(result);
}
/**
* 查询用户信息列表
*/
@Override
public List<EdSysUserVo> queryList(EdSysUserBo bo) {
LambdaQueryWrapper<EdSysUser> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw, EdSysUserVo.class);
}
private LambdaQueryWrapper<EdSysUser> buildQueryWrapper(EdSysUserBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<EdSysUser> lqw = Wrappers.lambdaQuery();
lqw.eq(bo.getDeptId() != null, EdSysUser::getDeptId, bo.getDeptId());
lqw.like(StringUtils.isNotBlank(bo.getUserName()), EdSysUser::getUserName, bo.getUserName());
lqw.like(StringUtils.isNotBlank(bo.getNickName()), EdSysUser::getNickName, bo.getNickName());
lqw.eq(StringUtils.isNotBlank(bo.getUserType()), EdSysUser::getUserType, bo.getUserType());
lqw.eq(StringUtils.isNotBlank(bo.getEmail()), EdSysUser::getEmail, bo.getEmail());
lqw.eq(StringUtils.isNotBlank(bo.getPhoneNumber()), EdSysUser::getPhoneNumber, bo.getPhoneNumber());
lqw.eq(StringUtils.isNotBlank(bo.getSex()), EdSysUser::getSex, bo.getSex());
lqw.eq(StringUtils.isNoneBlank(bo.getAvatar()), EdSysUser::getAvatar, bo.getAvatar());
lqw.eq(StringUtils.isNotBlank(bo.getPassword()), EdSysUser::getPassword, bo.getPassword());
lqw.eq(StringUtils.isNotBlank(bo.getStatus()), EdSysUser::getStatus, bo.getStatus());
lqw.eq(StringUtils.isNotBlank(bo.getLoginIp()), EdSysUser::getLoginIp, bo.getLoginIp());
lqw.eq(bo.getLoginDate() != null, EdSysUser::getLoginDate, bo.getLoginDate());
return lqw;
}
/**
* 新增用户信息
*/
@Override
public Boolean insertByBo(EdSysUserBo bo) {
EdSysUser add = BeanUtil.toBean(bo, EdSysUser.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setUserId(add.getUserId());
}
return flag;
}
/**
* 修改用户信息
*/
@Override
public Boolean updateByBo(EdSysUserBo bo) {
EdSysUser update = BeanUtil.toBean(bo, EdSysUser.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(EdSysUser entity) {
//TODO 做一些数据校验,如唯一约束
}
/**
* 批量删除用户信息
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if (isValid) {
//TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteBatchIds(ids) > 0;
}
}
3.3 控制器层接口
package com.xs.dsy.ed.controller;
import com.xs.common.core.controller.BaseController;
import com.xs.common.core.domain.PageQuery;
import com.xs.common.core.domain.R;
import com.xs.common.core.page.TableDataInfo;
import com.xs.common.core.validate.AddGroup;
import com.xs.common.core.validate.EditGroup;
import com.xs.dsy.ed.domain.EdSysUser;
import com.xs.dsy.ed.domain.bo.EdSysUserBo;
import com.xs.dsy.ed.domain.vo.EdSysUserVo;
import com.xs.dsy.ed.service.EdSysUserService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.sql.DataSource;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
/**
* 用户信息-前端控制器
*
* @author Lion Li
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/user")
public class EdSysUserController extends BaseController {
private final EdSysUserService userService;
/**
* 查询用户信息列表
*/
@GetMapping("/list")
public TableDataInfo<EdSysUserVo> list(EdSysUserBo bo, PageQuery pageQuery) {
return userService.queryPageList(bo, pageQuery);
}
/**
* 查询用户信息列表
*/
@GetMapping("/all")
public TableDataInfo<EdSysUserVo> all(EdSysUserBo bo, PageQuery pageQuery) {
return userService.queryPageList(bo, pageQuery);
}
/**
* 获取用户信息详细信息
*
* @param userId 主键
*/
@GetMapping("/{userId}")
public R<EdSysUserVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long userId) {
return R.ok(userService.queryById(userId));
}
/**
* 新增用户信息
*/
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody EdSysUserBo bo) {
return toAjax(userService.insertByBo(bo));
}
/**
* 修改用户信息
*/
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody EdSysUserBo bo) {
return toAjax(userService.updateByBo(bo));
}
/**
* 删除用户信息
*
* @param userIds 主键串
*/
@DeleteMapping("/{userIds}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] userIds) {
return toAjax(userService.deleteWithValidByIds(Arrays.asList(userIds), true));
}
}
3.4 Yaml多数据源配置
--- # 数据源配置
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
# 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/content
dynamic:
# 性能分析插件(有性能损耗 不建议生产环境使用)
p6spy: true
# 设置默认的数据源或者数据源组,默认值即为 master
primary: postgres
# 严格模式 匹配不到数据源则报错
strict: true
datasource:
# 主库数据源
master:
type: ${spring.datasource.type}
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://10.15.20.11:13307/ry_vue_db_0322?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
username: root
password: OC_sql_11
# 从库数据源
# slave:
# lazy: true
# type: ${spring.datasource.type}
# driverClassName: com.mysql.cj.jdbc.Driver
# url: jdbc:mysql://10.15.20.11:13307/ry_vue_db_0322?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
# username: root
# password: OC_sql_11
oracle:
type: ${spring.datasource.type}
driverClassName: oracle.jdbc.OracleDriver
url: jdbc:oracle:thin:@//127.0.0.1:1521/orcl
username: SYS as SYSDBA
password: OC11
hikari:
connectionTestQuery: SELECT 1 FROM DUAL
postgres:
type: ${spring.datasource.type}
driverClassName: org.postgresql.Driver
url: jdbc:postgresql://10.15.20.11:5432/test_db?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true
username: postgres
password: postgres
# sqlserver:
# type: ${spring.datasource.type}
# driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
# url: jdbc:sqlserver://localhost:1433;DatabaseName=tempdb;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true
# username: SA
# password: root
hikari:
# 最大连接池数量
maxPoolSize: 20
# 最小空闲线程数量
minIdle: 10
# 配置获取连接等待超时的时间
connectionTimeout: 30000
# 校验超时时间
validationTimeout: 5000
# 空闲连接存活最大时间,默认10分钟
idleTimeout: 600000
# 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟
maxLifetime: 1800000
# 连接测试query(配置检测连接是否有效)
connectionTestQuery: SELECT 1
# 多久检查一次连接的活性
keepaliveTime: 30000
redis:
# 地址
host: 10.15.20.11
# 端口,默认为6379
port: 61379
# 数据库索引
database: 0
# 密码(如没有密码请注释掉)
# password:
# 连接超时时间
timeout: 10s
# 是否开启ssl
ssl: false
redisson:
# redis key前缀
keyPrefix:
# 线程池数量
threads: 4
# Netty线程池数量
nettyThreads: 8
# 单节点配置
singleServerConfig:
# 客户端名称
clientName: ${ruoyi.name}
# 最小空闲连接数
connectionMinimumIdleSize: 8
# 连接池大小
connectionPoolSize: 32
# 连接空闲超时,单位:毫秒
idleConnectionTimeout: 10000
# 命令等待超时,单位:毫秒
timeout: 3000
# 发布和订阅连接池大小
subscriptionConnectionPoolSize: 50
server:
servlet:
context-path: /
port: 1300