需求:
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 项目 下载下来:
- 改数据库配置
- 改拦截器
- 启动 先执行
浏览器中输入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
- 查看执行结果 最终就是查询不同的库了
====>拓展 我们项目引入遇到问题 、 拦截器不起作用
原因是:我们的项目是分模块的、所以多个模块下边都存在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);
}
}