dble做 saas化 租户独立数据库

需求:
springboot + mybatis + dble 做一个租户系统后台 每个租户独立数据库 便于数据管理、和数据迁移等

实现:

1. liunx 服务器 安装dble 具体步骤百度、和mycat 差不多

这里贴下我简单的配置:

<?xml version="1.0"?>
<!DOCTYPE dble:schema SYSTEM "schema.dtd">
<dble:schema xmlns:dble="http://dble.cloud/" version="1.0">

  <schema name="mycat_test"  sqlMaxLimit="100">
        <!-- 全局表 -->
		<table name="company" primaryKey="id" type="global" dataNode="dn1,3"/> 
  </schema> 

    <dataNode name="dn1" dataHost="hostdb" database="db_1"/>
    <dataNode name="3" dataHost="hostdb" database="db_2"/>
    
    <dataHost name="hostdb" maxCon="500" minCon="10" balance="0" switchType="-1" slaveThreshold="100">
        <heartbeat>select user()</heartbeat>
		<writeHost host="hostM" url="xxxxx:3306" user="rw" password="rw123456"/>
    </dataHost>
    
</dble:schema>

其他配置按照别人的安装步骤实现就行、最后连上数据库、如下:
在这里插入图片描述

2. 项目引入配置
参考文章: springboot + dble 项目demo
它的底部有附件demo 项目 下载下来:

  1. 改数据库配置
  2. 改拦截器
    在这里插入图片描述
  3. 启动 先执行
  浏览器中输入localhost:8080/set/dn1     

这个是往session 里面放入租户标识、 其实也就是dble对应schema.xml 里面配置的数据节点 dataNode 的名字

    、、、、、、
    <dataNode name="dn1" dataHost="hostdb" database="db_1"/>
    <dataNode name="3" dataHost="hostdb" database="db_2"/>
    、、、、、、、
	之后,输入localhost:8080/count
  1. 查看执行结果 最终就是查询不同的库了

====>拓展 我们项目引入遇到问题 、 拦截器不起作用

原因是:我们的项目是分模块的、所以多个模块下边都存在dome实体对象、typeAliasesPackage 默认只能扫描某一个路径下,或以逗号等分割的 几个路径下的内容,不支持通配符和正则,采用重写的方式解决
因为重写了、所以导致我们配置的拦截器不起作用、

解决办法:
重写类:

package com.rp.framework.config;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import javax.sql.DataSource;

import com.rp.framework.interceptor.MybatisTenantInterceptor;
import org.apache.ibatis.io.VFS;
import org.apache.ibatis.session.AutoMappingBehavior;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.ClassUtils;

/**
 * Mybatis支持*匹配扫描包
 * 
 * @author ruoyi
 */
@Configuration
public class MyBatisConfig
{
    @Autowired
    private Environment env;

    static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";

    public static String setTypeAliasesPackage(String typeAliasesPackage)
    {
        ResourcePatternResolver resolver = (ResourcePatternResolver) new PathMatchingResourcePatternResolver();
        MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver);
        typeAliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + ClassUtils.convertClassNameToResourcePath(typeAliasesPackage) + "/" + DEFAULT_RESOURCE_PATTERN;
        try
        {
            List<String> result = new ArrayList<String>();
            Resource[] resources = resolver.getResources(typeAliasesPackage);
            if (resources != null && resources.length > 0)
            {
                MetadataReader metadataReader = null;
                for (Resource resource : resources)
                {
                    if (resource.isReadable())
                    {
                        metadataReader = metadataReaderFactory.getMetadataReader(resource);
                        try
                        {
                            result.add(Class.forName(metadataReader.getClassMetadata().getClassName()).getPackage().getName());
                        }
                        catch (ClassNotFoundException e)
                        {
                            e.printStackTrace();
                        }
                    }
                }
            }
            if (result.size() > 0)
            {
                HashSet<String> h = new HashSet<String>(result);
                result.clear();
                result.addAll(h);
                typeAliasesPackage = String.join(",", (String[]) result.toArray(new String[0]));
            }
            else
            {
                throw new RuntimeException("mybatis typeAliasesPackage 路径扫描错误,参数typeAliasesPackage:" + typeAliasesPackage + "未找到任何包");
            }
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }

        return typeAliasesPackage;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception
    {
        String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage");
        String mapperLocations = env.getProperty("mybatis.mapperLocations");
        typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage);
        VFS.addImplClass(SpringBootVFS.class);

        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
        sessionFactory.setConfiguration(mybatisConfiguration());
        sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
        return sessionFactory.getObject();
    }

    @Bean
    public org.apache.ibatis.session.Configuration mybatisConfiguration()
    {
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        // 全局映射器启用缓存
        configuration.setCacheEnabled(false);
        // 查询时,关闭关联对象即时加载以提高性能
        configuration.setLazyLoadingEnabled(false);
        // 对于未知的SQL查询,允许返回不同的结果集以达到通用的效果
        configuration.setMultipleResultSetsEnabled(true);
        // 允许使用列标签代替列名
        configuration.setUseColumnLabel(true);
        // 给予被嵌套的resultMap以字段-属性的映射支持 FULL,PARTIAL
        configuration.setAutoMappingBehavior(AutoMappingBehavior.PARTIAL);
        // 对于批量更新操作缓存SQL以提高性能 BATCH,SIMPLE,设置为BATCH有个缺陷就是无法获取update、delete返回的行数
        configuration.setDefaultExecutorType(ExecutorType.SIMPLE);
        // 允许在嵌套语句上使用行边界。如果允许,设置false。
        configuration.setSafeRowBoundsEnabled(false);
        // 设置关联对象加载的形态,此处为按需加载字段(加载字段由SQL指 定),不会加载关联表的所有字段,以提高性能
        configuration.setAggressiveLazyLoading(false);
        // 数据库超过30秒仍未响应则超时
        configuration.setDefaultStatementTimeout(30);
        //驼峰命名,如果不进行配置,数据库中有下划线的字段是不可以转换成驼峰命名的
        configuration.setMapUnderscoreToCamelCase(true);

        // 添加拦截器
        configuration.addInterceptor(new MybatisTenantInterceptor());
        return configuration;
    }
}

在这里面添加了config配置、引入了我们写的拦截器类

 // 添加拦截器
  configuration.addInterceptor(new MybatisTenantInterceptor());

从而就能抓取到执行sql 、添加dble 租户数据库标识了
拦截器依旧是链接里面的那个拦截器、我还是贴下代码吧

package com.rp.framework.interceptor;

import com.rp.framework.util.ShiroUtils;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import java.sql.Connection;
import java.util.Properties;

@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class MybatisTenantInterceptor implements Interceptor {
    private static final String preState="/*!dble:datanode=";
    private static final String afterState="*/";

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler=(StatementHandler)invocation.getTarget();
        MetaObject metaStatementHandler= SystemMetaObject.forObject(statementHandler);
        Object object=null;
        //分离代理对象链
        while(metaStatementHandler.hasGetter("h")){
            object=metaStatementHandler.getValue("h");
            metaStatementHandler= SystemMetaObject.forObject(object);
        }
        statementHandler=(StatementHandler)object;
        String sql=(String)metaStatementHandler.getValue("delegate.boundSql.sql");

        //获取租户id
        Integer node= ShiroUtils.getTenantId();
        if(node!=null) {
            sql = preState + node + afterState + sql;
        }

        System.out.println("sql is "+sql);
        metaStatementHandler.setValue("delegate.boundSql.sql",sql);
        Object result = invocation.proceed();
        System.out.println("Invocation.proceed()");
        return result;
    }

    @Override
    public Object plugin(Object target) {

        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        String prop1 = properties.getProperty("prop1");
        String prop2 = properties.getProperty("prop2");
        System.out.println(prop1 + "------" + prop2);
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值