-
Oracle 查询数据库有多少张表
2017-12-08 15:34:37这个查询的需求很大可能是进行了Oracle数据库导入导出操作,买了新的服务器,你的上司老板叫程序猿们,数据库换个环境,移植到新的服务器上。为了验证导入进去的表有没有少,我们需要查询当前用户下的总表个数。登录...这个查询的需求很大可能是进行了Oracle数据库导入导出操作,买了新的服务器,你的上司老板叫程序猿们,数据库换个环境,移植到新的服务器上。
为了验证导入进去的表有没有少,我们需要查询当前用户下的总表个数。
登录sys用户后通过user_tables表查看当前用户下表的张数。
sql:conn / as sysdba;
sql:select count(*) from user_tables ;
解释:必须是登录到系统的超级用户后后,通过上面sql读取出”用户表“中记录的行数(每个表会有一条记录),即为当前数据库下的表张数。 -
python正版软件多少钱_正版数据库软件需要多少钱
2020-11-30 05:01:47而现在的数据库购买也有多种选择,微软的sql server,甲骨文的oracle等,那正版数据库软件需要多少钱呢?针对中小企业,微软的sql server是最好的选择,价格合理,需要用的功能都有,购买sql server是按照版本、CPU数...在这个信息化时代,数据库是企业用来存储数据的不二选择。而现在的数据库购买也有多种选择,微软的sql server,甲骨文的oracle等,那正版数据库软件需要多少钱呢?
针对中小企业,微软的sql server是最好的选择,价格合理,需要用的功能都有,购买sql server是按照版本、CPU数、用户数来进行授权的。价格几千到几万不等,具体价格要联系经销商。比如sql server 2008标准版自带五用户,价格是3800左右一套。
除了这些之外,数据库还分层次、网状和关系型数据库。
层次结构模型:层次结构模型实质上是一种有根结点的定向有序树。
网状结构模型:按照网状数据结构建立的数据库系统称为网状数据库系统,其典型代表是DBTG(Database Task Group)。用数学方法可将网状数据结构转化为层次数据结构。
关系结构模型:关系式数据结构把一些复杂的数据结构归结为简单的二元关系(即二维表格形式)。例如某单位的职工关系就是一个二元关系。
由关系数据结构组成的数据库系统被称为关系数据库系统。在关系数据库中,对数据的操作几乎全部建立在一个或多个关系表格上,通过对这些关系表格的分类、合并、连接或选取等运算来实现数据的管理。
-
阿里云 mysql登陆地址_阿里云新增数据库登录地址是多少
2021-02-03 15:51:48---------------阿里云新增数据库登录地址是多少,阿里云宝塔数据库。阿里云ECS云服务器2折起,优惠配置多选1,用户实名认证后可购买,每人限1单,第2单起恢复原价;活动配置升级为第四代云服务器,其中入门级配置...全网最新活动请看下方内容或右侧内容!
---------------
阿里云新增数据库登录地址是多少,阿里云宝塔数据库。
阿里云ECS云服务器2折起,优惠配置多选1,用户实名认证后可购买,每人限1单,第2单起恢复原价;活动配置升级为第四代云服务器,其中入门级配置采用突发性能型t5实例,性能均衡配置采用共享型和网络增强型实例,性价比更优。
我们来看看这次阿里云的ecs云服务器2折活动有什么需要注意的:
1、阿里云ECS云服务器2折起的活动什么时候结束?
目前得到的信息是,首购2折的活动是长期有效的哟。所以当你新注册为阿里云用户以后,不要着急直接下单,可以看看比如免费套餐,比如2折购服务器等活动。
2、为什么购买阿里云ECS云服务器2折说已经买过了不让买?
有可能是“同一用户判断”导致系统认为您已经买过了。同一用户是指:根据不同阿里云账号在注册、登录、使用中的关联信息,阿里云判断其实际为同一用户。关联信息举例:同一证件、同一手机号、同一支付账号、同一设备、同一地址等
3、阿里云ECS云服务器2折起入口在哪里?
1)您可以在阿里云最新活动页面的云计算基础服务里找到。(小编经验,一般打折促销都有集中在这里展示哦,所以经常光顾阿里云,应该到这个页面看看)
2)也可以直接复制2折活动页面地址:https://promotion.aliyun.com/ntms/act/qwbk.html
4、为什么还没有购买完成就不能继续买了?
为保证活动公平性,优惠订单10分钟内未完成支付,将自动关闭,请下单后尽快支付;
5、阿里云ECS云服务器2折可以买多个服务器吗?
新老会员完成实名认证后均可购买,每个会员限购1单,新会员限购3台,老会员限购1台,限当前活动页的指定云服务器配置。
在阿里云ecs云服务器上部署数据库后,在平常的操作中可能会遇到些问题,可以先做个大致的了解:
如果您想看更多的在ecs上的数据库的相关操作,请前往以下两个帮助页面查看:
1、ESC数据库密码忘了怎么查看修改?
以常用的mysqld数据库为例:
首先vim /etc/my.cnf
在[mysqld]下加入skip-grant-tables
重启数据库service mysqld restart
直接输入mysql 就可以登陆数据库
输入use mysql;
update user set password=password(重新设置的密码) where user=root;
刷新设置flush privileges;
退出quit;
去掉my.cnf最开始加入的skip-grant-tables
重启数据库service mysqld restart
采用新密码登陆mysql -uroot -p新密码
2、ECS安装Sqlserver数据库备份:
(1)先打开SQLServer依次展开sqlserver根目录,并打开数据库
(2)选择要备份的数据库名,鼠标右键任务->选择备份。
(3)点击添加,选择存放数据库备份文件的地址
(4)在弹出对话框中,点击图示按钮,选择存放数据库备份文件的地址,文件名后缀名为.bak 点击确定。
(5)点击选项,根据自己的需求进行备份设置,设置完成,点击确定,开始备份
3、如何对 ECS Linux 系统中的 MySQL 进行备份导出:
(1)如果您使用的是帮助中心的一键环境配置,那么 MySQL 的安装目录是 /alidata/server/mysql。
如果您将 MySQL 安装到其他目录,您需要输入您MySQL完整的安装路径。
单库备份您可以在服务器上执行如下命令:
/alidata/server/mysql/bin/mysqldump -uroot -p密码 数据库名 > 备份名称.sql
mysqldump 默认不会导出事件表,执行此命令会出现警告 -- Warning: Skipping the data of table mysql.event. Specify the --events option explicitly.
(2)如果您需要导出 MySQL事件,您可以执行如下命令:
/alidata/server/mysql/bin/mysqldump -uroot -p密码 --events --ignore-table=mysql.event 数据库名 > 备份名称.sql
4、ECS安装Sqlserver数据库还原
(1)先打开SQL Server 2005 依次展开sqlserver根目录,并打开数据库
(2)选择要还原的数据库名,鼠标右键任务->选择还原->选择数据库
(3)在弹出对话框中,选择源设备,点击后面的按钮,弹出指定备份对话框,点击添加按钮,选择.bak源文件,找到后,点击确定
(4)选择选项,进行还原设置,勾选“覆盖现有数据”前面的小方框; 点击“还原为”后面的按钮,要还原该数据库的数据文件和日志文件
(5)选定好要还原的数据文件和日志文件之后,点击“确定”按钮,完成数据库还原
5、如何对 ECS Linux 系统中的 MySQL 进行备份导入:
如果您需要导入备份的 .sql 文件,有两个命令方式:(注意:/root/备份名称.sql 为实际备份文件绝对路径)
方式一:可以在 备份名称.sql 文件所在目录中执行如下命令:
/alidata/server/mysql/bin/mysql -uroot -p密码 mysql < 备份名称.sql
方式二:通过执行如下命令:
/alidata/server/mysql/bin/mysql -uroot -p密码
mysql>use 数据库;
mysql>source /root/备份名称.sql;
对于大多数小型或初期项目来说,我们可能常用的做法是先将web、数据库全部安装在一起,后期根据需要来看是否将数据库单独迁移分离。传统物理服务器可以这么多,云服务器也可以如此。
以阿里云ecs云服务器为例,我们来看看在云服务器上搭建数据库的相关内容:
1、如何将阿里云ECS服务器上的自建数据库做迁移?
(1)DTS传输:ECS上的自建数据库到RDS/MongoDB/Redis/DRDS/PetaData/OceanBase的数据迁移,使用数据传输DTS服务即可轻松实现。
(2)增量迁移:对于支持增量迁移的存储引擎,还可以使用DTS在ECS自建数据库不停服的情况下,将数据迁移到目标实例。
(3)从本地数据库迁移:可以将自建库的数据导入到阿里云数据库上,实现业务平滑迁移。不同类型的云数据库,导入数据的方式也不尽相同,具体请根据实际场景选择对应的迁移案例。
2、在ecs上有几种部署数据库的方式:
(1)在ECS(Windows系统)上部署Oracle数据库
(2)在ECS(Linux系统)上部署Oracle数据库
(3)在ECS(Windows系统)上部署SQL Server数据库
(4)在ECS(Linux系统)上部署MySQL数据库
(5)在ECS(Windows系统)上部署MySQL数据库
3、对于ecs+rds方式来说,ECS自建数据库如何与RDS实例间的数据实时同步?
(1)通过 DTS 进行ECS上的自建数据库跟RDS实例间数据同步作业的配置;目前数据传输服务提供的实时同步功能支持的同步架构有限,:
https://help.aliyun.com/document_detail/66731.html
方式1: A->B 即两个实例之间的单向同步,即支持1对1的单向同步,要求实例 B 中同步的对象必须为只读,否则会导致同步链路异常,出现数据不一致的情况。
方式2: A->B/C/D 即一对多的分发式同步架构.这个架构对目标 RDS for MySQL 实例个数没有限制,但是要求目标实例中的同步对象必须为只读,否则会导致同步链路异常,出现数据不一致的情况。
方式3:B/C/D->A 即多对一的数据汇总架构对于这种多对一的同步架构,为了保证同步数据一致性,要求每条同步链路同步的对象不相同。
方式4:A->B->C 即级联架构
方式5: A->B->A 即实例A和实例B之间的双向同步架构
(2)如果要配置RDS->ECS上的自建DB 的反向同步,只要调换下源跟目标实例的实例类型,其他配置类似。
4、为什么需要在ECS上部署数据库:
阿里云有提供相应的高可用数据库架构RDS,但由于RDS具有一定的限制条件,可能无法满足部分生产环境的要求,例如需要使用Oracle数据库、需要使用SQL Server报表服务等,在这种情况下,我们需要考虑在ECS上搭建数据库的方式。
常用数据库包含以下三种:Oracle、MySQL、SQL Server。
-----关注"情侣小站"微信公众号-----
-----获取最新活动资讯-----
-
SQL Server 2008数据库设计与实现(关系数据库实现的通关宝典)--详细书签版
2013-02-06 12:02:03翻译的过程中,译者感到此言不虚:作者从数据库的基本概念到数据库建模,从如何运用规范化原则到如何做成实际的数据库表,从如何保护数据库完整性到如何提高数据库的性能,从数据库的安全机制到并发事务控制,从... -
SQL Server 2008数据库设计与实现(关系数据库实现的通关宝典)--随书源代码
2013-02-06 12:04:00翻译的过程中,译者感到此言不虚:作者从数据库的基本概念到数据库建模,从如何运用规范化原则到如何做成实际的数据库表,从如何保护数据库完整性到如何提高数据库的性能,从数据库的安全机制到并发事务控制,从... -
网上书城选购系统+ACCESS数据库
2008-10-28 10:22:382、用户可以根据积分的多少来打折扣,而积分是通过用户在网上购买书籍量来积累的。 购买越多积分就越多。然后分成普通会员、铜牌会员、银牌会员、金牌会员、管理员 3、网站主颜色 #C4E1FF 页面属性字体12号 4、... -
SAAS多租户实现方案_springboot 实现多租户_基于数据库服务器隔离_或共享数据库服务器_但隔离数据库---...
2021-01-12 16:23:51技术交流QQ群【JAVA,C++,Python,...向厂商定购所需的应用软件服务,按定购的服务多少和时间长短向厂商支付费用,并通 过互联网获得厂商提供的服务。用户不用再购买软件,而改用向提供商租用基于Web的 软件,来管理..技术交流QQ群【JAVA,C++,Python,.NET,BigData,AI】:170933152
1.先来看看什么是saas?
SaaS是Software-as-a-service(软件即服务)它是一种通过Internet提供软件的模式,
厂商将应用软件统一部署在自己的服务器上,客户可以根据自己实际需求,通过互联网
向厂商定购所需的应用软件服务,按定购的服务多少和时间长短向厂商支付费用,并通
过互联网获得厂商提供的服务。用户不用再购买软件,而改用向提供商租用基于Web的
软件,来管理企业经营活动,且无需对软件进行维护,服务提供商会全权管理和维护软件。
2.saas模式有哪有角色?
①服务商:服务商主要是管理租户信息,按照不同的平台需求可能还需要统合整个平台的数据,
作为大数据的基础。服务商在SAAS模式中是提供服务的厂商。
②租户:租户就是购买/租用服务商提供服务的用户,租户购买服务后可以享受相应的产品服务。
现在很多SAAS化的产品都会划分系统版本,不同的版本开放不同的功能,还有基于功
能收费之类的,不同的租户购买不同版本的系统后享受的服务也不一样。
3.saas模式有哪些特点?
①独立性:每个租户的系统相互独立。
②平台性:所有租户归平台统一管理。
③隔离性:每个租户的数据相互隔离。
在以上三个特性里面,SAAS系统中最重要的一个标志就是数据隔离性,租户间的数据完全独立隔离。
4.实现方案?
有3种,也就是数据隔离的方案有3种
a.把不同租户的数据放到不同的服务器上
b.不同租户的数据放到一个服务器上,但新建很多数据库,一个租户用一个数据库
c.不同租户数据放到一个服务器上,并且只建一个数据库,所有租户用一样的表,但是所有表都会有一个租户id,来区分不同的租户.
5.说明.
上一节158,我们基于c方案,实现了多租户.这一节,我们基于a,或者b方案实现多租户.
实际上就是,实现了多数据源的,配置和路由对吧.
6.开始实现代码.
先创建租户公用的数据库:my-tenant-test 这个数据库
然后在公用的数据库my-tenant-test 中:
创建tenant_datasource这个数据表,用来存所有的数据源.
/* Navicat MySQL Data Transfer Source Server : 172.19.128.38 Source Server Version : 50713 Source Host : 172.19.128.38:3306 Source Database : my-tenant-test Target Server Type : MYSQL Target Server Version : 50713 File Encoding : 65001 Date: 2021-01-12 14:47:03 */ SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for tenant_datasource -- ---------------------------- DROP TABLE IF EXISTS `tenant_datasource`; CREATE TABLE `tenant_datasource` ( `id` int(11) NOT NULL AUTO_INCREMENT, `tenant_key` varchar(64) NOT NULL COMMENT '租户key', `tenant_type` int(4) DEFAULT '0' COMMENT '数据库类型,用于识别连接不同的数据库的时候设置驱动的字段,0默认为jdbc', `url` varchar(512) NOT NULL COMMENT '数据库连接URL', `username` varchar(64) NOT NULL COMMENT '数据库连接用户名', `password` varchar(64) DEFAULT NULL COMMENT '数据库连接密码', `active` bit(1) DEFAULT b'1' COMMENT '数据是否有效', `created_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updated_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', PRIMARY KEY (`id`) USING BTREE, KEY `idx_uid` (`tenant_type`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=36 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; -- ---------------------------- -- Records of tenant_datasource -- ---------------------------- INSERT INTO `tenant_datasource` VALUES ('34', 'qJQQX1AsF6oa', null, 'jdbc:mysql://172.19.128.38:3306/tenant-one?characterEncoding=UTF-8&serverTimezone=GMT%2B8', 'root', '123456', '', '2019-05-28 17:56:11', '2021-01-07 16:29:56'); INSERT INTO `tenant_datasource` VALUES ('35', 'qJQQX1AsF6ob', '0', 'jdbc:mysql://172.19.128.38:3306/tenant-two?characterEncoding=UTF-8&serverTimezone=GMT%2B8', 'root', '123456', '', '2020-06-24 11:32:57', '2021-01-07 16:30:04');
7.创建租户1数据库
tenant-one 这个数据库
/* Navicat MySQL Data Transfer Source Server : 172.19.128.38 Source Server Version : 50713 Source Host : 172.19.128.38:3306 Source Database : tenant-one Target Server Type : MYSQL Target Server Version : 50713 File Encoding : 65001 Date: 2021-01-12 14:54:46 */ SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for tenant_user -- ---------------------------- DROP TABLE IF EXISTS `tenant_user`; CREATE TABLE `tenant_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(255) DEFAULT NULL, `password` varchar(255) DEFAULT NULL, `created_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `updated_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC; -- ---------------------------- -- Records of tenant_user -- ---------------------------- INSERT INTO `tenant_user` VALUES ('1', 'one', '123', '2020-11-30 16:20:07', '2020-11-30 16:20:07');
8.创建租户2数据库
tenant-two这个数据库
/* Navicat MySQL Data Transfer Source Server : 172.19.128.38 Source Server Version : 50713 Source Host : 172.19.128.38:3306 Source Database : tenant-two Target Server Type : MYSQL Target Server Version : 50713 File Encoding : 65001 Date: 2021-01-12 14:57:44 */ SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for tenant_user -- ---------------------------- DROP TABLE IF EXISTS `tenant_user`; CREATE TABLE `tenant_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(255) DEFAULT NULL, `password` varchar(255) DEFAULT NULL, `created_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `updated_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC; -- ---------------------------- -- Records of tenant_user -- ---------------------------- INSERT INTO `tenant_user` VALUES ('1', 'two', '123', '2020-11-30 16:19:37', '2020-11-30 16:19:40');
9.创建一个springboot项目,看看工程结构
10.application.yml文件,这里用properties文件也行
server: port: 8080 spring: application: name: tenant-test datasource: url: jdbc:mysql://172.19.128.38:3306/my-tenant-test?characterEncoding=UTF-8&serverTimezone=GMT%2B8 username: root password: "123456" driver-class-name: com.mysql.cj.jdbc.Driver druid: initial-size: 5
11.pom.xml文件
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example.tenant</groupId> <artifactId>tenant-test</artifactId> <version>0.0.1-SNAPSHOT</version> <name>tenant-test</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <spring-boot.version>2.2.7.RELEASE</spring-boot.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.23</version> </dependency> <!-- mybatis-plus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.1</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.3.1</version> </dependency> <!--mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>2.2</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <!--swagger --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.1</version> </dependency> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>4.2.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
12.自定义动态数据源
要实现动态切换数据源,首先需要替换掉默认mybatis使用的数据源,我们自己定义一个数据源
DynamicDataSource
springboot 提供了AbstractRoutingDataSource 根据用户定义的规则选择当前的数据源。
package com.example.tenant.config; import com.alibaba.druid.pool.DruidDataSource; import com.example.tenant.constant.GlobalConstant; import com.example.tenant.dto.TenantDatasourceDTO; import lombok.extern.slf4j.Slf4j; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import org.springframework.util.StringUtils; import javax.sql.DataSource; import java.util.Map; /** * 自定义一个数据源 */ @Slf4j public class DynamicDataSource extends AbstractRoutingDataSource { /** * 用于保存租户key和数据源的映射关系,目标数据源map的拷贝 */ public Map<Object, Object> backupTargetDataSources; /** * 动态数据源构造器 * @param defaultDataSource 默认数据源 * @param targetDataSource 目标数据源映射 */ public DynamicDataSource(DataSource defaultDataSource, Map<Object, Object> targetDataSource){ backupTargetDataSources = targetDataSource; super.setDefaultTargetDataSource(defaultDataSource); // 存放数据源的map super.setTargetDataSources(backupTargetDataSources); // afterPropertiesSet 的作用很重要,它负责解析成可用的目标数据源 super.afterPropertiesSet(); } /** * 必须实现其方法 * 动态数据源类集成了Spring提供的AbstractRoutingDataSource类,AbstractRoutingDataSource * 中获取数据源的方法就是 determineTargetDataSource,而此方法又通过 determineCurrentLookupKey 方法获取查询数据源的key * 通过key在resolvedDataSources这个map中获取对应的数据源,resolvedDataSources的值是由afterPropertiesSet()这个方法从 * TargetDataSources获取的 * * @return */ @Override protected Object determineCurrentLookupKey() { return DataSourceContextHolder.getDBType(); } /** * 添加数据源到目标数据源map中 * @param datasource */ public void addDataSource(TenantDatasourceDTO datasource) { DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setUrl(datasource.getUrl()); druidDataSource.setUsername(datasource.getUsername()); druidDataSource.setPassword(datasource.getPassword()); // 将传入的数据源对象放入动态数据源类的静态map中,然后再讲静态map重新保存进动态数据源中 backupTargetDataSources.put(datasource.getTenantKey(), druidDataSource); super.setTargetDataSources(backupTargetDataSources); super.afterPropertiesSet(); } /** * 是否存在租户key * * @param tenantKey 租户key */ public Boolean existTenantKey(String tenantKey) { if (StringUtils.isEmpty(tenantKey)) { return false; } return backupTargetDataSources.containsKey(tenantKey); } /** * 切换租户 * * @param tenantKey 租户key */ public void switchTenant(String tenantKey) { log.info("切换租户:{},当前线程名称:{}", DataSourceContextHolder.getDBType(), Thread.currentThread().getName()); DataSourceContextHolder.setDBType(tenantKey); } /** * 切换到默认租户信息 */ public void switchDefaultTenant() { if (GlobalConstant.TENANT_CONFIG_KEY.equals(DataSourceContextHolder.getDBType())) { return; } switchTenant(GlobalConstant.TENANT_CONFIG_KEY); } }
13.mybatis数据源配置
配置mybatis数据源使用自定义的动态数据源
package com.example.tenant.config; import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; import com.baomidou.mybatisplus.core.config.GlobalConfig; import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean; import com.example.tenant.constant.GlobalConstant; import com.github.pagehelper.PageHelper; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.annotation.MapperScan; import org.mybatis.spring.mapper.MapperScannerConfigurer; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import com.example.tenant.constant.GlobalConstant.*; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; import java.util.Properties; /** * Mybatis & Mapper & PageHelper 配置 */ @Slf4j @Configuration @MapperScan({"com.example.tenant.mapper"}) public class MybatisConfigurer { /** * 配置事务 * @param dynamicDataSource * @return */ @Bean @Qualifier("transactionManager") public PlatformTransactionManager txManager(@Qualifier("dynamicDataSource") DataSource dynamicDataSource) { return new DataSourceTransactionManager(dynamicDataSource); } /** * 配置文件yml中的默认数据源 * @return */ @Bean(name = "defaultDataSource") @ConfigurationProperties(prefix="spring.datasource") public DataSource getDefaultDataSource() { return DruidDataSourceBuilder.create().build(); } /** * 将动态数据源对象放入spring中管理 * @return */ @Bean public DynamicDataSource dynamicDataSource() { Map<Object, Object> targetDataSources = new HashMap<>(); log.info("将druid数据源放入默认动态数据源对象中"); targetDataSources.put(GlobalConstant.TENANT_CONFIG_KEY, getDefaultDataSource()); return new DynamicDataSource(getDefaultDataSource(), targetDataSources); } /** * 数据库连接会话工厂 * @param dynamicDataSource 自定义动态数据源 * @return * @throws Exception */ @Bean public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dynamicDataSource) throws Exception { MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean(); //配置分页插件,详情请查阅官方文档 PageHelper pageHelper = new PageHelper(); Properties properties = new Properties(); properties.setProperty("pageSizeZero", "true");//分页尺寸为0时查询所有纪录不再执行分页 properties.setProperty("reasonable", "true");//页码<=0 查询第一页,页码>=总页数查询最后一页 properties.setProperty("supportMethodsArguments", "true");//支持通过 Mapper 接口参数来传递分页参数 pageHelper.setProperties(properties); bean.setPlugins(new Interceptor[]{pageHelper}); bean.setDataSource(dynamicDataSource); bean.setMapperLocations(new PathMatchingResourcePatternResolver() .getResources("classpath*:mapper/**/*.xml")); return bean.getObject(); } @Bean public GlobalConfig getGlobalConfig() { GlobalConfig globalConfig = new GlobalConfig(); GlobalConfig.DbConfig dbConfig = new GlobalConfig.DbConfig(); //已删除 dbConfig.setLogicDeleteValue("0"); //未删除 dbConfig.setLogicNotDeleteValue("1"); dbConfig.setLogicDeleteField("active"); globalConfig.setDbConfig(dbConfig); return globalConfig; } @Bean public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory){ return new SqlSessionTemplate(sqlSessionFactory); } // @Bean // public MapperScannerConfigurer mapperScannerConfigurer() { // MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer(); // mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactoryBean"); // mapperScannerConfigurer.setBasePackage(MAPPER_PACKAGE); // // //配置通用Mapper,详情请查阅官方文档 // Properties properties = new Properties(); // properties.setProperty("mappers", MAPPER_INTERFACE_REFERENCE); // properties.setProperty("notEmpty", "false");//insert、update是否判断字符串类型!='' 即 test="str != null"表达式内是否追加 and str != '' // properties.setProperty("IDENTITY", "MYSQL"); // mapperScannerConfigurer.setProperties(properties); // // return mapperScannerConfigurer; // } }
14. 数据源上下文
创建数据源上下文用于统一每次请求的数据源,通过threadlocal确保在一个线程内使用同一个数据源
每个线程创建的适合都会有个threadlocal这个是自动创建的,所以使用threadlocal就可以保证一个线程中使用的是同一个数据源了.
如果不明白为什么,再去看看关于threadlocal的博文.就懂了.
package com.example.tenant.config; /** * 数据源上下文 */ public class DataSourceContextHolder { private static final ThreadLocal<String> contextHolder = new InheritableThreadLocal<String>(); /** * 保存租户id * @param dbType 租户id */ public static void setDBType(String dbType){ contextHolder.set(dbType); } public static String getDBType(){ return contextHolder.get(); } public static void clearDBType(){ contextHolder.remove(); } }
15.初始化数据源
程序启动时从数据库中读取所有租户的数据库连接配置信息,初始化数据源放入动态数据源对象DynamicDataSource的TargetDataSources中
package com.example.tenant.init; import com.example.tenant.config.DataSourceContextHolder; import com.example.tenant.config.DynamicDataSource; import com.example.tenant.dto.TenantDatasourceDTO; import com.example.tenant.entity.Datasource; import com.example.tenant.mapper.DatasourceMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.List; /** * 系统初始化runner * * @version v1.0 * @date 2017年7月24日 * @see @Order注解的执行优先级是按value值从小到大顺序。 */ @Component @Order(value = 1) @Slf4j public class SystemInitRunner implements ApplicationRunner { @Resource private DatasourceMapper tenantDatasourceMapper; @Autowired private DynamicDataSource dynamicDataSource; @Override public void run(ApplicationArguments args) { //租户端不进行服务调用 log.info("==服务启动后,初始化数据源=="); //切换默认数据源 即tenant库的数据源,用于查询tenant表中的所有tenant数据库配置 DataSourceContextHolder.setDBType("default"); //设置所有数据源信息 log.info("获取当前数据源:" + DataSourceContextHolder.getDBType()); List<Datasource> tenantInfoList = tenantDatasourceMapper.selectList(null); for (Datasource info : tenantInfoList) { TenantDatasourceDTO tenantDatasourceDTO = new TenantDatasourceDTO(); BeanUtils.copyProperties(info, tenantDatasourceDTO); dynamicDataSource.addDataSource(tenantDatasourceDTO); } log.info("动态数据源对象中的所有数据源, 已加载数据源个数: {}", dynamicDataSource.backupTargetDataSources.size()); log.info("初始化多租户数据库配置完成..."); } }
16.然后再看一下其他的代码:
数据库实体类:DataSource
package com.example.tenant.entity; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.IdType; import java.util.Date; import com.baomidou.mybatisplus.annotation.TableId; import java.io.Serializable; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; /** * <p> * * </p> * * @author Welisit * @since 2020-06-24 */ @Data @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) @TableName("tenant_datasource") @ApiModel(value="Datasource对象", description="") public class Datasource implements Serializable { private static final long serialVersionUID=1L; @TableId(value = "id", type = IdType.AUTO) private Integer id; private String tenantKey; private Integer tenantType; private String url; private String username; private String password; private Boolean active; private Date createdTime; private Date updatedTime; }
17.User实体类
package com.example.tenant.entity; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import java.io.Serializable; import java.util.Date; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; /** * <p> * * </p> * * @author Welisit * @since 2020-06-24 */ @Data @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) @TableName("tenant_user") @ApiModel(value="User对象", description="") public class User implements Serializable { private static final long serialVersionUID=1L; @TableId(value = "id", type = IdType.AUTO) private Integer id; private String username; private String password; private Date createdTime; private Date updatedTime; }
18.DataSourceMapper.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 namespace="com.example.tenant.mapper.DatasourceMapper"> </mapper>
19.UserMapper.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 namespace="com.example.tenant.mapper.UserMapper"> </mapper>
20.DataSourceMapper类
package com.example.tenant.mapper; import com.example.tenant.entity.Datasource; import com.baomidou.mybatisplus.core.mapper.BaseMapper; /** * <p> * Mapper 接口 * </p> * * @author Welisit * @since 2020-06-24 */ public interface DatasourceMapper extends BaseMapper<Datasource> { }
21.UserMapper类
package com.example.tenant.mapper; import com.example.tenant.entity.User; import com.baomidou.mybatisplus.core.mapper.BaseMapper; /** * <p> * Mapper 接口 * </p> * * @author Welisit * @since 2020-06-24 */ public interface UserMapper extends BaseMapper<User> { }
22.DatasourceServiceImpl类
package com.example.tenant.service.impl; import com.example.tenant.entity.Datasource; import com.example.tenant.mapper.DatasourceMapper; import com.example.tenant.service.DatasourceService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service; /** * <p> * 服务实现类 * </p> * * @author Welisit * @since 2020-06-24 */ @Service public class DatasourceServiceImpl extends ServiceImpl<DatasourceMapper, Datasource> implements DatasourceService { }
23.UserServiceImpl类
package com.example.tenant.service.impl; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.example.tenant.entity.User; import com.example.tenant.mapper.UserMapper; import com.example.tenant.service.UserService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; /** * <p> * 服务实现类 * </p> * * @author Welisit * @since 2020-06-24 */ @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { @Override @Transactional public boolean updateUserName(String userId, String username) { boolean result = update(new UpdateWrapper<User>().lambda().eq(User::getId, userId).set(User::getUsername, username)); // 此处报错,检验数据库数据是否修改,事务是否生效 int i = 10/0; update(new UpdateWrapper<User>().lambda().eq(User::getId, userId).set(User::getUsername, "222")); return result; } }
24.DatasourceService类
package com.example.tenant.service; import com.example.tenant.entity.Datasource; import com.baomidou.mybatisplus.extension.service.IService; /** * <p> * 服务类 * </p> * * @author Welisit * @since 2020-06-24 */ public interface DatasourceService extends IService<Datasource> { }
25.UserService类
package com.example.tenant.service; import com.example.tenant.entity.User; import com.baomidou.mybatisplus.extension.service.IService; /** * <p> * 服务类 * </p> * * @author Welisit * @since 2020-06-24 */ public interface UserService extends IService<User> { boolean updateUserName(String userId, String username); }
26.TenantDatasourceDTO租户数据源类
package com.example.tenant.dto; import lombok.Data; import java.io.Serializable; /** * @ClassName TenantDatasourceDTO * @Description TODO * @Author zzx * @Date 2019/10/17 10:53 * @Verson 1.0 **/ @Data public class TenantDatasourceDTO implements Serializable { private Integer id; /** * 租户key */ private String tenantKey; /** * 数据库类型,用于识别连接不同的数据库的时候设置驱动的字段,0默认为jdbc */ private Integer tenantType; /** * 数据库连接URL */ private String url; /** * 数据库连接用户名 */ private String username; /** * 数据库连接密码 */ private String password; }
27.UserController类
package com.example.tenant.controller; import com.example.tenant.config.DataSourceContextHolder; import com.example.tenant.entity.User; import com.example.tenant.service.UserService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.validation.Valid; /** * <p> * 前端控制器 * </p> * * @author Welisit * @since 2020-06-24 */ @RestController @RequestMapping("/tenant/user") @Slf4j public class UserController { @Autowired private UserService userService; /** * 测试数据源切换 * @param tenantKey * @return */ @GetMapping public User getUser(@RequestParam("tenantkey") String tenantKey) { log.info("更新租户id值为"+tenantKey); //DataSourceContextHolder.setDBType(tenantKey); // User user=new User(); // user.setId(1); // user.setUsername("test"); return userService.getById(1); } /** * 测试事务 * @param tenantKey * @param username * @return */ @GetMapping("update") public Boolean getUser(@RequestParam("tenantkey") String tenantKey, @RequestParam("username") String username) { log.info("更新租户id值为"+tenantKey); DataSourceContextHolder.setDBType(tenantKey); userService.updateUserName("1", username); return true; } }
28.DatasourceController类
package com.example.tenant.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * <p> * 前端控制器 * </p> * * @author Welisit * @since 2020-06-24 */ @RestController @RequestMapping("/tenant/datasource") public class DatasourceController { }
29.GlobalConstant类
package com.example.tenant.constant; /** * 全局常量 */ public final class GlobalConstant { /** * 租户配置数据库的key */ public final static String TENANT_CONFIG_KEY = "default"; public static final String BASE_PACKAGE = "com.example.tenant";//生成代码所在的基础包名称,可根据自己公司的项目修改(注意:这个配置修改之后需要手工修改src目录项目默认的包路径,使其保持一致,不然会找不到类) public static final String MODEL_PACKAGE = BASE_PACKAGE + ".entity";//生成的Model所在包 public static final String MAPPER_PACKAGE = BASE_PACKAGE + ".mapper";//生成的Mapper所在包 public static final String SERVICE_PACKAGE = BASE_PACKAGE + ".service";//生成的Service所在包 public static final String SERVICE_IMPL_PACKAGE = SERVICE_PACKAGE + ".impl";//生成的ServiceImpl所在包 public static final String CONTROLLER_PACKAGE = BASE_PACKAGE + ".controller";//生成的Controller所在包 public static final String MAPPER_INTERFACE_REFERENCE = BASE_PACKAGE + ".core.Mapper";//Mapper插件基础接口的完全限定名 }
30.为了做个统一的处理,拦截处理,拦截所有请求,然后从request的header中获取到租户的信息,然后
在走到controller方法中之前,给controller方法,设置好数据源
package com.example.tenant.config; import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.NoHandlerFoundException; import org.springframework.web.servlet.config.annotation.InterceptorRegistration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.List; /** * Spring MVC 配置 */ @Configuration public class WebMvcConfigurer extends WebMvcConfigurerAdapter { // @Value("${spring.profiles.active}") // private String env;//当前激活的配置文件 // /** // * 全局异常处理模块 // * @param exceptionResolvers // */ // @Override // public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) { // exceptionResolvers.add(new HandlerExceptionResolver() { // public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) { Result result = new Result(); result.setSuccess(false); if (e instanceof ServiceException) {//业务失败的异常,如“账号或密码错误” result.setCode(ResultCode.FAIL).setMessage(e.getMessage()); logger.info(e.getMessage()); } else if (e instanceof NoHandlerFoundException) { result.setCode(ResultCode.NOT_FOUND).setMessage("接口 [" + request.getRequestURI() + "] 不存在"); } else if (e instanceof ServletException) { result.setCode(ResultCode.FAIL).setMessage(e.getMessage()); } else { result.setCode(ResultCode.INTERNAL_SERVER_ERROR).setMessage("接口 [" + request.getRequestURI() + "] 内部错误,请联系管理员"); String message; if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; message = String.format("接口 [%s] 出现异常,方法:%s.%s,异常摘要:%s", request.getRequestURI(), handlerMethod.getBean().getClass().getName(), handlerMethod.getMethod().getName(), e.getMessage()); } else { message = e.getMessage(); } logger.error(message, e); logUtil.sendErrLog(result.getMessage()+":"+message); result.setMessage("系统正在维护中,请稍后尝试"); responseResult(response, result); return new ModelAndView(); } responseResult(response, result); logUtil.sendErrLog(result.getMessage()+":"+e.getMessage()); // return new ModelAndView(); // } // // }); // } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/**").addResourceLocations("classpath:/static/"); super.addResourceHandlers(registry); } //@lidewei 20180929 跨域统一由网关设定 // /** // * 跨域设定 // * @param registry // */ // @Override // public void addCorsMappings(CorsRegistry registry) { // //registry.addMapping("/**"); // registry.addMapping("/**") // .allowedOrigins("*") // .allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE") // .maxAge(3600) // .allowCredentials(true); // } /** * 登录验证拦截器,暂时用不到 * @param registry */ @Override public void addInterceptors(InterceptorRegistry registry) { InterceptorRegistration registration = registry.addInterceptor(new AdminInterceptor()); registration.addPathPatterns("/**"); //所有路径都被拦截 registration.excludePathPatterns( //添加不拦截路径 "你的登陆路径", //登录 "/**/*.html", //html静态资源 "/**/*.js", //js静态资源 "/**/*.css", //css静态资源 "/**/*.woff", "/**/*.ttf" ); } // /** // * 统一应答设定 // * @param response // * @param result // */ // private void responseResult(HttpServletResponse response, Result result) { // response.setCharacterEncoding("UTF-8"); // response.setHeader("Content-type", "application/json;charset=UTF-8"); // response.setStatus(200); // try { // response.getWriter().write(new ObjectMapper().writeValueAsString(result)); // } catch (IOException ex) { // logger.error(ex.getMessage()); // } // } // private String getIpAddress(HttpServletRequest request) { // String ip = request.getHeader("x-forwarded-for"); // if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { // ip = request.getHeader("Proxy-Client-IP"); // } // if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { // ip = request.getHeader("WL-Proxy-Client-IP"); // } // if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { // ip = request.getHeader("HTTP_CLIENT_IP"); // } // if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { // ip = request.getHeader("HTTP_X_FORWARDED_FOR"); // } // if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { // ip = request.getRemoteAddr(); // } // // 如果是多级代理,那么取第一个ip为客户端ip // if (ip != null && ip.indexOf(",") != -1) { // ip = ip.substring(0, ip.indexOf(",")).trim(); // } // // return ip; // } }
31.切换租户数据源拦截器AdminInterceptor类
package com.example.tenant.config; import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; /** * * * @Package: com.*.*.interceptor * @ClassName: AdminInterceptor * @Description:拦截器 * @author: zk * @date: 2019年9月19日 下午2:20:57 */ public class AdminInterceptor implements HandlerInterceptor { /** * 在请求处理之前进行调用(Controller方法调用之前) */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { // System.out.println("执行了TestInterceptor的preHandle方法"); try { //统一拦截(查询当前session是否存在user)(这里user会在每次登陆成功后,写入session) // User user=(User)request.getSession().getAttribute("USER"); // if(user!=null){ // return true; // } String tentantID=request.getHeader("tenantID"); DataSourceContextHolder.setDBType(tentantID); //response.sendRedirect(request.getContextPath()+"你的登陆页地址"); //IOException } catch (Exception e) { e.printStackTrace(); } return true;//如果设置为false时,被请求时,拦截器执行到此处将不会继续操作 //如果设置为true时,请求将会继续执行后面的操作 } /** * 请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后) */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { // System.out.println("执行了TestInterceptor的postHandle方法"); } /** * 在整个请求结束之后被调用,也就是在DispatcherServlet 渲染了对应的视图之后执行(主要是用于进行资源清理工作) */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { // System.out.println("执行了TestInterceptor的afterCompletion方法"); } }
32.启动类TenantApplication
package com.example.tenant; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author welisit * @create 2020-06-24 15:35 */ @SpringBootApplication public class TenantApplication { public static void main(String[] args) { SpringApplication.run(TenantApplication.class, args); } }
33.然后就可以去测试了,我们实现了
只需要请求的时候在request的header中携带:tenantID租户id
或者将来把这个租户id,放到token令牌中,我们就可以在我们的拦截器中把这个租户id,拿出来,并且
在拦截器中,在走controller方法,之前把数据源切换好.这样来实现多租户功能.
用postman去测试一下,可以看到我们把tenantID放到header中携带,过去,就能获取到对应的数据库信息了.
我这里填写的是two这个租户的tenantID,就是访问的two这个租户数据库.
如果我这里填入one这个租户的tenantID那么这个时候就会自动访问one这个租户的数据库了.
34.另外还有一种方式就是:
我们利用nginx,自动去获取域名,做为tenantID,然后用nginx,获取的tenantID自动
给咱们设置到request的header中去,这样我们的程序就不需要去做操作了..
35.租户识别:
租户信息的识别通过Nginx代理来实现,核心思路就是域名中包含租户信息,然后通过Nginx代理时,在请求头和相应头中添加租户的识别信息。
servier { listen 80; server_name ~^(?<sub>.+)\.zane\.com$; #按后缀匹配域名,并截取租户标示 ... location / { ... proxy_set_header tenant $sub; #请求头添加租户标示 add_header tenant $sub; #响应头添加租户标示 } }
可以在nginx中按照上面这个配置.
1.
另外如果微服务之间有相互调用,怎么传递租户,
把这个租户传递到其他调用的微服务去
租户传递 基于SpringCloud各个服务间是通过Feign来通信,那么只要实现一个简单的Feign拦截器就可以。 @Component public class FeignTenantInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { String tenant = DataSourceTenantContextHolder.getCurrentTenant(); template.header("tenant",tenant); } }
2.以及怎么样用注解的方式,来实现数据源的切换
注释指定数据源 @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface Tenant { String value() default DataSourceTenantContextHolder.DEFAULT_TENANT; } 自定义一个租户的注解类 @Aspect @Component @Order(0) @Slf4j public class DatasourceSelectorAspect { @Around("@annotation(tenant)") public Object beforeTenant(ProceedingJoinPoint joinPoint, Tenant tenant) throws Exception{ String sourceTenant = DataSourceTenantContextHolder.getCurrentTenant(); try{ String tenantName = tenant.value(); DataSourceTenantContextHolder.setCurrentTanent(tenantName); }catch (Exception e){ log.warn("",e); } Object result; try { result = joinPoint.proceed(); } catch (Throwable e) { throw new Exception(e); } finally { DataSourceTenantContextHolder.setCurrentTanent(sourceTenant); } return result; } } 通过环绕型的AOP拦截, 在打了注解的方法上进行租户的切换。实现注解指定数据源。
这些都是问题:https://zhuanlan.zhihu.com/p/136674825 可以参考这个文章.
还有其他问题,需要解决的时候再去弄吧.
参考代码:https://download.csdn.net/download/lidew521/14122293
开通了个人技术微信公众号:credream,有需要的朋友可以添加相互学习 -
NoSQL挑战关系型数据库统治地位
2015-01-22 16:13:37然而投资NoSQL就像是买一只新发行的股票,用户没有多少市场历史可供研究。 关键词:NoSQL 传统数据库 数据库工程师 【TechTarget中国原创】 1922年,汽车业巨头亨利·福特曾说:“任何顾客可以将这辆... -
Oracle Database 11g数据库管理艺术--详细书签版
2012-09-30 01:09:45书中内容主要集中在大多数企业常见的问题之上,如安装和升级到oracle database 11g数据库软件、创建数据库、导出和导入数据、数据库的备份与恢复、性能调优,等等。 本书还提供了dba完成本职工作必备的基本的uniix... -
防止数据库被下载的几个方法
2008-09-04 20:20:00前言:很多动态站点大量应用了数据库,数据库理所当然成了一个站点的核心文件。一旦数据库被人下载,极有可能被恶意人士破坏网站。或者窃取资料。实在痛心啊。...我想现在也没有多少连数据库文件名都懒得改 -
阿里云要引领数据库市场?这话没毛病
2017-09-27 00:00:00曾经部署一套业务系统先是买几台物理服务器、几台存储设备,现在是直接采购多少TFLOPS的计算能力、几TB的存储空间;过去用户都是购买数据库软件部署在本地,如今直接采用云端的数据库产品。至于其中的方案是如何构建... -
八种方法防止数据库被下载
2008-12-24 18:07:00我想现在也没有多少连数据库文件名都懒得改的人吧? 至于改成什么,你自己看着办,至少要保证文件名复杂,不可猜测性。当然这个时候你的数据库所在目录是不能开放目录浏览权限的! 2:数据... -
ORACLE数据库智能化管理系统2012
2011-05-30 21:55:46还在为某种数据不同类别所占总数比例及各多少?而手工累加一一计算吗?本系统使用了ABC方法计算得出结果。 查询数据条件参数,智能化表字典辅助批量字段输入功能,使您的工作简单而更加轻松,用起来更加顺手。 查询或... -
ORACLE数据库智能化管理系统2008
2008-10-18 11:14:5812. 还在为某种数据不同类别所占总数比例及各多少?而手工累加一一计算吗? 13. 条件参数及数据变更的智能化字典辅助输入功能,使您的工作更加轻松,用起来更加顺手。 14. 结果数据智能化创建计算,从而进一步使结果... -
数据库相连的消费者分析(二)
2021-03-24 08:22:56多少用户仅消费一次? 每月新客占比 用户分层 RFM 新、老、活跃、回流、流失 用户购买周期(按订单) 用户消费周期描述 用户消费周期分布 用户生命周期(按第一次&最后一次消费) 用户生命周期描述 ... -
一个好用的数据库类
2008-05-14 08:36:06<br>四、总结 ODBC有很多用处,例如我们可以把很多信息按照一定的格式保存在文本中(因为在商业上用别人的数据库都是要买的),然后通过ODBC把这些文本文件映射成表,这些文件的目录就成为一个数据库,这些... -
如何防止Access数据库被下载- -
2006-03-13 15:11:00有什么方法可以防止数据库被人...我想现在也没有多少连数据库文件名都懒得改的人吧? 至于改成什么,你自己看着办,至少要保证文件名复杂,不可猜测性。当然这个时候你的数据库所在目录是不能开放目录浏览权限的!2: -
ORACLE数据库智能化管理系统2009新
2008-11-12 02:33:5512. 还在为某种数据不同类别所占总数比例及各多少?而手工累加一一计算吗? 13. 条件参数及数据变更的智能化字典辅助输入功能,使您的工作更加轻松,用起来更加顺手。 14. 结果数据智能化创建计算,从而进一步使结果... -
ORACLE数据库智能化管理系统2008演示
2008-11-12 02:27:1612. 还在为某种数据不同类别所占总数比例及各多少?而手工累加一一计算吗? 13. 条件参数及数据变更的智能化字典辅助输入功能,使您的工作更加轻松,用起来更加顺手。 14. 结果数据智能化创建计算,从而进一步使结果... -
吉林信息网vip源码修改安全版(数据库)
2011-04-11 18:45:5916后台有会员信息管理面板(管理个人会员和商家会员信息)包含功能有设置为vip,编辑会员资料,删除会员信息,修改会员积分和虚拟币多少。还可以直接进商家个人管理面板管理商家发布的产品信息。 17.信息评论 回复... -
【mysql数据库语法学习】统计一个表中重复数据的数量并进行排序
2019-02-23 20:29:13有一个测试数据集是关于不同省份用户在电商网站的行为记录,包括浏览量和购买,需要统计一下每个省份的用户数量有多少。 【SQL语句】 SELECT Province,count(*) as count FROM user_action GROUP BY Province ... -
线程池默认多少个线程_我需要多少个线程?
2020-06-06 14:55:52线程池默认多少个线程 这取决于您的应用程序。 但是,对于那些希望对如何从生产站点购买的所有昂贵内核中挤出大量资金的人,请多多包涵,我将阐明围绕多线程 Java应用程序的奥秘。 内容针对最典型的Java EE应用程序... -
oracle的购买价格研究
2015-03-14 22:13:10如果你是一个架构师,在数据库选型上除了技术选型,更重要的可能是性价比的...2、按用户数是指连接到oracle的连接数量(包括自然人的连接或机器的连接),但是,现在一般都是B/S的web或者手机端应用,很难算得清多少用 -
想购买DC
2006-03-15 23:00:35DC-Z7590团购价大概是多少呢,和朋友们讨论多次了没...数据库移植经验:一定生成imp的日志,一定先建用户并检查用户的系统授权、对象授权,在数据库mount状态下不可以修改表空间的名字。了解cluster的作用。好久没发... -
我需要多少个线程?
2020-05-10 09:05:39内容针对最典型的Java EE应用程序进行了“优化”,该应用程序具有Web前端,允许最终用户在应用程序内发起许多小交易。 每个交易的重要部分都在等待一些外部资源。 例如从数据库或任何其他集成数据源返回的查询。 ... -
电商用户行为分析案例--天池数据集User Behavior Data from Taobao
2020-05-13 23:45:18用户行为分析过程 说明 本次数据分析基于阿里云天池数据集(用户行为数据集),使用转化漏斗,...PV(总访问量)、日均访问量、UV(用户总数)、有购买行为的用户数量、用户的购物情况、复购率分别是多少? 2、用户行为转 -
RFM模型(用户分析)
2019-08-05 15:57:40模型定义 RFM模型是衡量客户价值和客户创利能力的重要工具和手段...根据美国数据库营销研究所Arthur Hughes的研究,客户数据库中有3个神奇的要素,这3个要素构成了数据分析最好的指标: 最近一次消费 (Recency) 消费... -
2020采购季新用户福利专场,教你如何获得最大优惠!
2020-03-07 15:51:212020阿里云采购季来啦!作为一位新用户,如何在活动中享受更多优惠呢![(活动页面 ...优惠一:爆款产品买一送二,买ECS云服务器送数据库和存储,对于新用户来说可以说是一步到位,解决用户绝大部分需求; 优... -
为什么很多空间这么便宜,你又了解多少呢?
2012-12-16 14:48:15为什么很多空间这么便宜,你又了解多少呢? 其实我觉得现在做空间不好做,很多客户又要求价格便宜,又要求质量要好! 首先来说下价格便宜的 ...4.服务器上所有用户资源共享,包括sql数据库,所以如果 -
阿里云新用户专享1折优惠,云服务器1核2GB只要89元/年
2020-06-25 19:14:14阿里云云小站最近推出的几款云服务器价格就非常便宜,新用户购买的话更是可以享受1折优惠,原价785元的T5型的1核2GB云服务器,现价只要89元/年就可以买到,折算到每天的话只要0.25元。一次性购买3年更是每天只要0.18...