四、一套代码适配多种数据库之最终方案

前言

经过升级方案的总结分析,我们可以发现,升级方案虽然相较于初步方案有了巨大提升,但是有优化空间:需要修改常量来实现注入不同的mapper实现类,需要添加好多空mapper接口。本文就彻底解决这些问题,并形成最终方案。

一、思路分析

1、service层升级思路分析

  1. 升级方案要求service层注入dao层接口实现时,必须要指定注入bean的名称
  2. 为什么要指定注入bean的名称呢,因为@Autowire注入机制决定,要么一个实现接口,要么bean名称和变量名称保持一致
  3. 咱们先看bean名称和变量名保持一致的方式
  4. 不改动service层代码的情况下,dao层接口变量名称已经和父接口保持了一致,如果想让子接口和service层中的dao层接口变量名称保持一致,就得改父dao层接口的类名。但是多个子dao层接口都用父dao层接口的名称这不冲突了吗,所以也行不通(实际上是行得通的,理解了后面的方案,可以自己试下,后面的方案更好,我就不介绍这种发方式了)。
  5. 然后咱们在回过头看第一种情况:一个实现接口
  6. 一个实现接口猛地一看不可行:父接口和多种数据库的子接口怎么可能只有一个实现???
  7. 首先:多个子接口除了名称其他内容完全一致,能不能只要一个呢?答案是可以,因为dao层接口和mapper.xml映射关系是mapper.xml维护的,那就可以做到多个mapper.xml映射到一个dao层接口上,问题是dao层接口调用实现时调用哪个mapper.xml呢???
  8. 肯定是调用对应数据库的实现,那其他数据库的实现呢?其他数据库实现有地方用到吗?没有,没有那项目启动时候不加载就行了!!!每种数据库的mapper.xml放到同一个文件夹下,启动时候只加载需要的文件夹中的mapper.xml!!!
  9. 那确实解决了每次项目运行时有多个子实现的问题,那还有父接口和子接口两个实现类呢?
  10. 父接口有地方用吗???不是service层用了吗!按刚才的设想,service层用的是子接口,自然就没有地方用父接口了!!!
  11. 那是什么意思???我的意思是只在Spring容器中注册子接口,不注册父接口!!!所有数据库只要一个子接口,再把子接口和父接口放在不同的目录下,在Spring容器中只注入子接口,子接口继承了父接口,所以可以直接调用父接口的实现,保证不直接使用父接口。兄弟,你这野路子给谁学的???不管是野路子还是正路子,只要能解决问题就是好路子。

2、dao层升级思路分析

  1. 按照上面的分析,dao层只保留一个dao层接口并继承父接口
  2. 每种数据库对应一个mapper.xml,所有mapper.xml都映射到同一个子mapper接口,mapper.xml只重写与父mapper.xml语法不一致的SQL

二、项目改造

1、项目改造

  1. 如果自己有项目,可以在自己项目上测试,如果没有或者不想在自己项目测试,可以使用同一套代码适配多种数据库之升级方案的示例代码。如果使用示例项目,先根据README.md正确运行示例项目。
  2. 修改service层代码:回复原状,不在创建多个dao层接口对象或者指定注入bean名称
package com.yu.service.impl;

import com.yu.dao.UserMapper;
import com.yu.entity.User;
import com.yu.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public List<User> list(User user) {
        return userMapper.list(user);
    }

}

  1. 修改dao层代码:只保留一个子接口继承父接口,并与父接口在不同包
package com.yu.dao.ext;

import com.yu.dao.UserMapper;

public interface UserExtMapper extends UserMapper {

}
  1. 修改Mapper.xml:所有子mapper.xml都与dao层的子接口映射,子mapper.xml中实现与父mapper.xml语法不一致的SQL,每种数据库的子mapper.xml放在同一个目录下

mysql/UserMysqlMapper.xml

<?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接口-->
<mapper namespace="com.yu.dao.UserExtMapper">

    <!--继承方式获取表与entity映射关系,方便本mapper.xml使用-->
    <resultMap type="User" id="BaseResultMap" extends="com.yu.dao.UserMapper.BaseResultMap"/>

    <sql id="Base_Column_List">
        <!--使用include方式包含父mapper.xml中的sql,方便本mapper.xml使用-->
        <include refid="com.yu.dao.UserMapper.Base_Column_List"/>
    </sql>

</mapper>

pg/UserPgMapper.xml

<?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接口-->
<mapper namespace="com.yu.dao.ext.UserExtMapper">

    <!--继承方式获取表与entity映射关系,方便本mapper.xml使用-->
    <resultMap type="User" id="BaseResultMap" extends="com.yu.dao.UserMapper.BaseResultMap"/>

    <sql id="Base_Column_List">
        <!--也可以使用include方式包含父mapper.xml中的sql,方便本mapper.xml使用,因为SQL不通用,所以这里不能直接包含-->
        <!--<include refid="com.yu.dao.UserMapper.Base_Column_List"/>-->
        select
        id,
        name
        <!--TODO 特别注意,系统也有user表,所以这里需要加上模式-->
        from multi_database.user
    </sql>

    <select id="list" parameterType="User" resultMap="BaseResultMap">
        <include refid="Base_Column_List"/>
        <where>
            <if test="name != null  and name != ''">
                and name like concat('%', #{name}, '%')
            </if>
        </where>
    </select>

</mapper>
  1. 只扫描子dao层接口
package com.yu;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
// TODO 只扫描子dao层接口
@MapperScan(basePackages = "com.yu.**.dao.ext")
public class MultiDatabaseApplication {
    public static void main(String[] args) {
        SpringApplication.run(MultiDatabaseApplication.class, args);
    }
}

  1. 只加载父mapper.xml和当前数据库的子mapper.xml;也可以通过手动注册sqlSessionFactory对象,这里只配置父mapper.xml,注册时在根据驱动类型把对应的子mapper.xml加进去
database:
  # mysql: classpath*:mapper/mysql/*Mapper.xml ;postgres: classpath*:mapper/pg/*Mapper.xml
  son-mapper: classpath*:mapper/pg/*Mapper.xml
# MyBatis配置
mybatis:
  # 配置mapper的扫描,父mapper.xml和当前子mapper.xml TODO 需要加载父mapper.xml,使用**时不要把所有的子mapper.xml加载进来
  mapperLocations: classpath*:mapper/*Mapper.xml,${database.son-mapper}
  1. 测试MySQL方式

用户列表接口测试

  1. 修改为PostgreSQL数据库
# 数据库配置 TODO 根据实际情况修改
database:
  ip: xxx
  # mysql: 3306 ;postgres: 5432
  port: 5432
  database: demo
  # postgres需要
  schema: multi_database
  username: xxx
  password: xxx
  # TODO 数据库对应的mapper.xml路径
  # mysql: classpath*:mapper/mysql/*Mapper.xml ;postgres: classpath*:mapper/pg/*Mapper.xml
  son-mapper: classpath*:mapper/pg/*Mapper.xml
  # mysql: com.mysql.cj.jdbc.Driver ;postgres: org.postgresql.Driver
  driver-class-name: org.postgresql.Driver
  # mysql: jdbc:mysql://${database.ip}:${database.port}/${database.database}?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8
  # postgres: jdbc:postgresql://${database.ip}:${database.port}/${database.database}?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8&&currentSchema=${database.schema},sys_catalog
  url: jdbc:postgresql://${database.ip}:${database.port}/${database.database}?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8&&currentSchema=${database.schema},sys_catalog
# 数据源配置
spring:
  datasource:
    #  数据源
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: ${database.driver-class-name}
    url: ${database.url}
    username: ${database.username}
    password: ${database.password}
# MyBatis配置
mybatis:
  # 搜索指定包别名
  typeAliasesPackage: com.yu.**.entity
  # 配置mapper的扫描,父mapper.xml和当前子mapper.xml TODO 需要加载父mapper.xml,使用**时不要把所有的子mapper.xml加载进来
  mapperLocations: classpath*:mapper/*Mapper.xml,${database.son-mapper}
  # 加载全局的配置文件
  configLocation: classpath:mybatis/mybatis-config.xml
  1. 测试PostgreSQL方式

用户列表接口测试

三、方案总结

总结下本方案添加一种新数据库时需要哪些操作:

  1. 添加新数据库驱动包,无可厚非,没问题
  2. 添加新数据库配置,无可厚非,没问题
  3. 修改数据库对应的mapper.xml路径,无可厚非,没问题(也可以通过手动注册sqlSessionFactory的方式,通过判断驱动类型自动添加)
  4. service层无需改动
  5. dao层无需改动,添加新模块时需要同时添加父接口和子接口
  6. 添加mapper.xml,并实现与父mapper.xml中不一致的增删改查语句,不管与父mapper.xml SQL是否完全一致,都推荐创建子mapper.xml;各个子mapper.xml之间相互独立。

总结下本方案切换新数据库时需要哪些操作:

  1. 修改数据库配置
  2. 修改数据库对应的mapper.xml路径(通过手动注册sqlSessionFactory的方式时,不需要这一步)
  3. 没了!!!本套方案切换数据库时,只改动数据库配置即可

总结下本方案添加新功能时需要哪些操作:

  1. 添加父mapper.xml
  2. 添加所有数据库对应的mapper.xml(实际上可以先不用添加,直接在每种数据库环境下测试,当父mapper.xml不适用时再添加子mapper.xml,而子mapper.xml中只添加父mapper.xml中不适用的SQL)
  3. 每种数据库环境环境下测试新功能

总结下本方案功能添加新操作时需要哪些操作:

  1. 在父mapper.xml中添加一种默认实现
  2. 每种数据库环境环境下测试功能的新操作
  3. 当父mapper.xml不适用时再在子mapper.xml添加SQL

代码改动量很少,每种数据库的实现也没有耦合在一个mapper文件中,很理想,后续会整理数据库数据迁移和项目适配的经验。

四、注意

  1. 示例中的 TODO 是需要注意的地方,如果运行项目报错可以着重看下 TODO 部分
  2. dao层接口中父接口和子接口在不同包下,配置dao层包扫描时只配置子接口路径(@MapperScan(basePackages = “com.yu.**.dao.ext”))
  3. 配置mapper.xml的扫描时,如果使用多层路径通配符**,会导致所有的mapper.xml全部加载,dao层调用实现时报错
  4. 如果不使用多层路径通配符**,就要保证多模块项目中的mapper.xml路径深度一致,业务层路径使用单层路径通配符*代替
  5. 同一套代码适配多种数据库之最终方案的示例代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

那你为何对我三笑留情

感谢支持,沉淀自己,帮助他人!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值