Mybatis-Plus 租户使用
Mybatis-Plus 租户使用
文章目录
- Mybatis-Plus 租户使用
- 一. 前言
- 1.1 租户存在的意义
- 1.2 租户框架
- 二. Mybatis-plus 租户
- 2.1 租户处理器
- 2.2 前置准备
- 1. 依赖
- 2. 表及数据准备
- 3. 代码生成器
- 2.3 使用
- 三. 深入使用
- 3.1 前言
- 3.2 租户主体设值,取值
- 3.3 部分表全量db操作
- 3.4 全量db操作
- 3.5 自由控制全量/租户对db操作
- 3.6 feign传递租户
- 四. 源码地址
一. 前言
1.1 租户存在的意义
多租户数据隔离与管理是在一个共享的软件应用程序中托管多个租户(客户)的数据,并确保每个租户的数据完全隔离、安全和可管理。需要支持相同数据(记录)可由具有不同容量的多个租户。
1.2 租户框架
mybatis-plus租户插件使用
由于业务数据,都需要通过对数据库的crud,而此时mybatis-plus作为orm框架,提供了租户功能,我们可以合理利用拓展,应用于自己的业务范畴中
二. Mybatis-plus 租户
2.1 租户处理器
我们通过实现TenantLineHandler,即可实现对数据库中的表,进行租户过滤
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| TenantLineHandler | TenantLineHandler | 租户处理器( TenantId 行级 ) |
实现租户过滤
- 这里我们通过匿名内部类实现TenantLineHandler。(也可以新建立一个类实现)
- 向MybatisPlusInterceptor添加我们所实现的匿名内部类
- 注入bean
ps:配置完后,就可实现对表进行租户筛选,读者们可自行测试
由于后面我对该代码进行了加工,该代码会出现一些自定义逻辑在里面,但原理是不变的
/*** @author zhanghp* @since 2023/11/20 14:29*/
@Configuration
public class TenantConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(TenantProperties properties) {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {@Overridepublic Expression getTenantId() {// 默认租户为1return new LongValue(1);}@Overridepublic String getTenantIdColumn() {return properties.getTenantId();}// 这是 default 方法,默认返回 false 表示所有表都需要拼多租户条件@Overridepublic boolean ignoreTable(String tableName) {return false;}}));return interceptor;}}
2.2 前置准备
1. 依赖
各依赖版本继承至父依赖,可自行查看
<dependencies><!-- 启动 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- 配置解析 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency><!-- aop --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId><scope>provided</scope></dependency><!-- test --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!-- mybatis-plus --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId></dependency><!-- mysql jdbc --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><!-- 代码生成器 --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-generator</artifactId></dependency><!-- 生成器模版引擎 --><dependency><groupId>org.apache.velocity</groupId><artifactId>velocity-engine-core</artifactId></dependency><!-- alibaba thread local --><dependency><groupId>com.alibaba</groupId><artifactId>transmittable-thread-local</artifactId></dependency><!-- lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!-- hutool all --><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId></dependency></dependencies>
2. 表及数据准备
完整配置:application.yml
这里我沿用了Spring自带的属性配置spring.sql.init,初始化数据库的schema,data。
建表sql | 数据
当启动项目后,自动进行建表,初始化表及相应的数据
spring.io官网对配置文件的概述:可自行搜索
spring.sql.init解锁更多使用方式
- 指定数据库配置
- 指定出初始化的建表sql,及对应的数据
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: zhp.1221url: jdbc:mysql://127.0.0.1:3306/test?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTCsql:init:# 建表sqlschema-locations: classpath:db/schema.sql# 数据data-locations: classpath:db/data.sql# 生成模式mode: always
3. 代码生成器
调用代码生成Generate.java,自动生成表实体类的相关类
这里就不进行展示了,自动代码生成若有不懂的地方可看Mybatis plus 自动生成代码与自定义模板
2.3 使用
🌳MpTest.java:测试对db的增删改查
/*** @author zhanghp* @since 2023/11/20 14:21*/
public class MpTest extends AppTest {@Resourceprivate DemoMapper demoMapper;@Testpublic void select() {demoMapper.selectList(null);}@Testpublic void insert() {demoMapper.insert(Demo.builder().id(100).age(18).name("草莓熊").build());}@Testpublic void update() {demoMapper.updateById(Demo.builder().id(100).age(20).name("皮卡").build());}@Testpublic void delete() {demoMapper.deleteById(Demo.builder().id(100).build());}}
select查询结果日志:

insert插入日志:

update修改日志:

delete删除日志:

三. 深入使用
3.1 前言
Saas多租户使用时,其实面临着许多技术问题,我拿几个公司所遇见的经典问题进行举例
- 如何传递tenat_id
- 有一些配置表,永远不需要租户过滤
- 在一个请求里,不需要租户过滤
- 在一个请求里,部分数据db需要租户过滤,部分数据db不需要租户过滤
- 微服务feign传递(由于我的代码项目是单体架构,我只简单赘述如何实现)
- …
3.2 租户主体设值,取值
一个租户的设值,一般分为两种:
- 一个请求里,附带租户值,一般放在请求头(header)
- 自定义设值引用
设值后,需要找个地方存储,这里引用alibaba的TransmittableThreadLocal进行值的存储,也方便后续取值,所以这里新建立一个TenantContextHolder类
TENAN_ID:进行值的存储与读取
TENANT_SKIP:进行租户是否过滤,也就是对db是否进行租户过滤的判断(where tenant_id = ?),下一节会讲到
@UtilityClass:对所有方法加static,对类加final,不允许new
/*** @author zhanghp* @since 2023/11/20 20:28*/
@Data
@UtilityClass
public class TenantContextHolder {/*** tenant_id*/private final ThreadLocal<Long> TENAT_ID = new TransmittableThreadLocal<>();/*** 获取租户* @return 租户id*/public Long getTenantId() {return TENAT_ID.get();}/*** 设置租户* @param tenantId 租户*/public void setTenantId(Long tenantId) {TENAT_ID.set(tenantId);}/*** 清空租户信息*/public void clear() {TENAT_ID.remove();}}
当有了这个类后,那我们需要重新实现TenantLineHandler,由于之前已经定义了一个TenantConfig的bean来对租户处理器并注入到容器中,若我们再次定义一个会导致bean冗余,让spring找不到使用哪个,进而导致启动报错。
那么问题来了,有没有一种方式我既要能控制新定义的TenangLineHandler存在,老的也兼容呢?
这时我们使用一下@ConditionalOnMissingBean使用在TenantConfig上,和@Order使用在DeepTenantConfig即可,读者可自行浏览
DeepTenantConfig
@ConditionalOnProperty(prefix = “custom.tenant”, value = “deep-config”, havingValue = “true”):
代表我是否要使用这个bean注入,为什么要有这个注解来控制bean注入呢?是因为要兼容上一章的租户使用TenantConfig,选择注入哪个,若没有该注解,则都会默认注入DeepTenantConfig的bean,控制该bean的注入在application.yml里的custom.tenant.deep-config
/*** @author zhanghp* @since 2023/11/24 17:51*/
@Configuration
public class DeepTenantConfig {@Bean@Order(Integer.MIN_VALUE)@ConditionalOnProperty(prefix = "custom.tenant", value = "deep-config", havingValue = "true")public MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {@Overridepublic Expression getTenantId() {// 获取租户Long tenantId = TenantContextHolder.getTenantId();// 租户为空则返回空if (tenantId == null) {return new NullValue();}// 返回租户return new LongValue(tenantId);}// 这是 default 方法,默认返回 false 表示所有表都需要拼多租户条件@Overridepublic boolean ignoreTable(String tableName) {// 租户为空,则对该表不进行租户的操作Long tenantId = TenantContextHolder.getTenantId();if (tenantId == null) {return Boolean.TRUE;}return false;}}));return interceptor;}}
这些都处理好后,我们在配置中把custom.tenant.deep-config设置为true,这样读取的是我们上面定义的bean
我们在创建一个请求处理器类,进行租户的处理
TenantInterceptor
当一次请求进来,会进行租户值当处理,并存储在刚才我们所建立的TenantContextHolder类里,进行租户值处理
/*** @author zhanghp* @date 2023-11-25 13:48*/
public class TenantInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {if (StrUtil.isNotBlank("tenant-id")) {TenantContextHolder.setTenantId(Convert.toLong(tenantId));}return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {TenantContextHolder.clear();}
}
将拦截器添加到拦截器注册类里
/*** @author zhanghp* @date 2023-11-25 16:53*/
@Configuration
public class CustomInterceptorConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(getRequestContextInterceptor());}@Beanpublic TenantInterceptor getRequestContextInterceptor() {return new TenantInterceptor();}
}
现在我们创建一个controller
/*** <p>* mybatis - demo表 前端控制器* </p>** @author zhp* @since 2023-11-20*/
@RestController
@RequestMapping("/zhanghp/demo")
public class DemoController {@Resourceprivate DemoService demoService;@GetMapping("/get")public void getall(){List<Demo> all = demoService.getAll();if (IterUtil.isEmpty(all)) {return;}all.forEach(System.out::println);}
}
现在我们打开apifox/postman,进行一次请求操作,看是否会生效
- http://localhost:8888/zhanghp/demo/get
- 请求头添加tenant_id的值
发送请求

结果日志打印

3.3 部分表全量db操作
⭐️场景:
其实在业务中,有一些配置表,字典表其实是不需要租户的,就比如城市配置表,城市就这些,加租户也是没有任何意义的
⭐️解决:
实现其实也是很简单,只需要在DeepTenantConfig里的的ignoreTables方法进行一些逻辑处理就可以
实现步骤:
- 在application.yml进行表的配置
- 读取配置,并在DeepTenantConfig添加自己的逻辑
这里由于读取了配置,我喜欢把自定义的配置定义一个类,通过该类对配置读取,读者可自行查看,这里就不赘述了
配置:
custom:tenant:# 是否注入deepTenantConfig的bean开关deep-config: true# 租户字段名称tenant-id: tenant_id# 忽略租户的表ignore-tables:- demo01- demo02
逻辑新增
/*** @author zhanghp* @since 2023/11/24 17:51*/
@Configuration
public class DeepTenantConfig {@Bean@Order(Integer.MIN_VALUE)@ConditionalOnProperty(prefix = "custom.tenant", value = "deep-config", havingValue = "true")public MybatisPlusInterceptor mybatisPlusInterceptor(TenantProperties properties) {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {@Overridepublic Expression getTenantId() {// 获取租户Long tenantId = TenantContextHolder.getTenantId();// 租户为空则返回空if (tenantId == null) {return new NullValue();}// 返回租户return new LongValue(tenantId);}@Overridepublic String getTenantIdColumn() {return properties.getTenantId();}// 这是 default 方法,默认返回 false 表示所有表都需要拼多租户条件@Overridepublic boolean ignoreTable(String tableName) {Long tenantId = TenantContextHolder.getTenantId();if (tenantId == null) {return Boolean.TRUE;}// 忽略表对配置为空,则所有表都进行租户操作if (IterUtil.isEmpty(properties.getIgnoreTables())) {return false;}// 指定的表永远不进行租户操作if (properties.getIgnoreTables().contains(tableName)) {return true;}return false;}}));return interceptor;}
}
读者自行练习就可,就不再演示
3.4 全量db操作
⭐️场景:
在一个请求里,这个请求涉及到的表是不需要租户过滤,那么读者就会想到,那在上一节的配置文件加啊。
这里的一个请求不需要对A表进行过滤,但另一个请求对A是需要进行租户过滤的,那这时加配置文件是行不通的
⭐️解决:
- TenantContextHolder里定义一个过滤租户的threadlocal
- 在DeepTenantConfig的ignoreTables()方法新增逻辑判断
/*** @author zhanghp* @since 2023/11/20 20:28*/
@Data
@UtilityClass
public class TenantContextHolder {/*** tenant_id*/private final ThreadLocal<Long> TENAT_ID = new TransmittableThreadLocal<>();/*** 租户过滤标识*/private final ThreadLocal<Boolean> TENANT_SKIP = new TransmittableThreadLocal<>();/*** 获取租户* @return 租户id*/public Long getTenantId() {return TENAT_ID.get();}/*** 设置租户* @param tenantId 租户*/public void setTenantId(Long tenantId) {TENAT_ID.set(tenantId);}/*** 设置是否过滤的标识*/public void setTenantSkip() {TENANT_SKIP.set(Boolean.TRUE);}/*** 获取是否跳过租户过滤的标识* @return true-过滤 false-不过滤*/public Boolean getTenantSkip() {return TENANT_SKIP.get() != null && TENANT_SKIP.get();}/*** 清空租户信息*/public void clear() {TENAT_ID.remove();clearSkip();}/*** 清空租户过滤标识*/public void clearSkip(){TENANT_SKIP.remove();}}
DeepTenantConfig
/*** @author zhanghp* @since 2023/11/24 17:51*/
@Configuration
public class DeepTenantConfig {@Bean@Order(Integer.MIN_VALUE)@ConditionalOnProperty(prefix = "custom.tenant", value = "deep-config", havingValue = "true")public MybatisPlusInterceptor mybatisPlusInterceptor(TenantProperties properties) {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {@Overridepublic Expression getTenantId() {// 获取租户Long tenantId = TenantContextHolder.getTenantId();// 租户为空则返回空if (tenantId == null) {return new NullValue();}// 返回租户return new LongValue(tenantId);}@Overridepublic String getTenantIdColumn() {return properties.getTenantId();}// 这是 default 方法,默认返回 false 表示所有表都需要拼多租户条件@Overridepublic boolean ignoreTable(String tableName) {// 1.是否跳过租户对该表对操作if (TenantContextHolder.getTenantSkip()) {return Boolean.TRUE;}// 2.租户为空,则对该表不进行租户的操作Long tenantId = TenantContextHolder.getTenantId();if (tenantId == null) {return Boolean.TRUE;}// 3.忽略表对配置为空,则所有表都进行租户操作if (IterUtil.isEmpty(properties.getIgnoreTables())) {return false;}// 4.指定的表永远不进行租户操作if (properties.getIgnoreTables().contains(tableName)) {return true;}return false;}}));return interceptor;}}
在controller层新加一个TenantContextHolder.setTenantSkip()即可
@GetMapping("/get2")public void getall2(){TenantContextHolder.setTenantSkip();List<Demo> all = demoService.list();if (IterUtil.isEmpty(all)) {return;}all.forEach(System.out::println);}
读者自行练习即可
3.5 自由控制全量/租户对db操作
⭐️场景:
在一个请求里,进行了对A表和B表操作,但是A表是不需要租户,而B表又是需要租户
⭐️解决:
- 对A表的操作提取为一个a方法,对B的操作提取出一个b方法
- 利用上节的TENANT_SKIP让a方法跳过租户
- 当a方法执行后,利用切面进行TENANT_SKIP的清空
- 这时b方法就会带着租户对B表进行处理
这里我写了一个注解@TenanClear
作用域:方法,类
作用:执行当前方法里的所有db操作,都不带租户,通过属性globalFlag进行控制后续方法是否添加租户过滤
ps:是=实现原理在 TenantClearAspect
/*** @author zhanghp* @since 2023/11/21 20:41*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TenantClear {/*** <p>执行该方法后,是否全局过滤租户标志</p>* <ul>* <li>true:执行完该线程之前,后续对db操作不添加租户操作</li>* <li>false:执行完当前方法后,后续方法的db操作添加租户操作</li>* </ul>** @return 默认后续不添加租户操作*/boolean globalFlag() default true;
}
该注解的作用:当添加该注解后当前方法进行的所有db操作都不带租户处理,后续方法执行是否带租户,通过属性globalFlag决定
创建a方法,并创建b方法
在a方法添加@TenantClear(globalFlag = false),然后在controller层先调用a,在调用b
输出结果,a不带租户,b带租户
/*** <p>* mybatis - demo表 服务实现类* </p>** @author zhp* @since 2023-11-20*/
@Service
public class DemoServiceImpl extends ServiceImpl<DemoMapper, Demo> implements DemoService {@Overridepublic void b() {super.list();}@Override@TenantClear(globalFlag = false)public void a(){super.list();}}
在controller层调用
@GetMapping("/tenant-clear")public void tenantClear(){Console.log("执行a方法");demoService.a();Console.log("执行b方法");demoService.b();}
结果打印

3.6 feign传递租户
由于本项目是单体项目,不涉及微服务,在这里只是简单概述
实现步骤:
- 实现feign自带的原生扩展RequestInterceptor类
- 重写apply方法,处理tenant-id的传递
public class CustomerFeignInterceptor implements RequestInterceptor {public CustomerFeignInterceptor() {}public void apply(RequestTemplate requestTemplate) {if (TenantContextHolder.getTenantId() == null) {log.debug("租户ID为空,feign不传递");} else {requestTemplate.header("tenant-id", new String[]{TenantContextHolder.getTenantId().toString()});}}
}
四. 源码地址
⭐️gitee:https://gitee.com/zhp1221/java/tree/master/lab_02_mybatis_plus/tenant
相关文章:
Mybatis-Plus 租户使用
Mybatis-Plus 租户使用 文章目录 Mybatis-Plus 租户使用一. 前言1.1 租户存在的意义1.2 租户框架 二. Mybatis-plus 租户2.1 租户处理器2.2 前置准备1. 依赖2. 表及数据准备3. 代码生成器 2.3 使用 三. 深入使用3.1 前言3.2 租户主体设值,取值3.3 部分表全量db操作3…...
vue el-table (固定列+滚动列)【横向滚动条】确定滚动条是在列头还是列尾
效果图: 代码实现: html: <script src"//unpkg.com/vue2/dist/vue.js"></script> <script src"//unpkg.com/element-ui2.15.14/lib/index.js"></script> <div id"app" style&quo…...
⑦【Redis GEO 】Redis常用数据类型:GEO [使用手册]
个人简介:Java领域新星创作者;阿里云技术博主、星级博主、专家博主;正在Java学习的路上摸爬滚打,记录学习的过程~ 个人主页:.29.的博客 学习社区:进去逛一逛~ Redis GEO ⑦Redis GEO 基本操作命令1.geoadd …...
LeetCode 2304. 网格中的最小路径代价:DP
【LetMeFly】2304.网格中的最小路径代价:DP 力扣题目链接:https://leetcode.cn/problems/minimum-path-cost-in-a-grid/ 给你一个下标从 0 开始的整数矩阵 grid ,矩阵大小为 m x n ,由从 0 到 m * n - 1 的不同整数组成。你可以…...
c 实用化的文本终端实时显示摄像头视频
因为采用yuv格式,帧率都很低。图像会拖影。把图像尺寸尽量缩小,能大大改善。现在最麻烦的是图像上有黑色的闪影,不知是为啥?如是framebuffer引起的就无解了。终于找到问题了,是在显示前加了一条用黑色清屏造成的&#…...
CSS中常用的伪类选择器
一 、伪类(不存在的类,特殊的类) -伪类用来描述一个元素的特殊状态 比如:第一个元素,被点击的元素,鼠标移入的元素 -特点:一般请情况下,使用:开头 1、 :first-child …...
【python学习】中级篇-数据库操作:SQLite
SQLite是一个轻量级的数据库引擎,它可以嵌入到各种应用程序中。以下是SQLite的基本用法: 创建数据库文件 import sqlite3# 连接到一个不存在的数据库文件,如果文件不存在,将会自动创建一个新的数据库文件 conn sqlite3.connect…...
汇编-PROTO声明过程
64位汇编 64 模式中,PROTO 伪指令指定程序的外部过程,示例如下: ExitProcess PROTO ;指定外部过程,不需要参数.code main PROCmov ebx, 0FFFFFFFFh mov ecx,0 ;结束程序call ExitProcess ;调用外部过程main ENDP END 32位…...
MYSQL事务操作
...
自动化测试——自动卸载软件
📢专注于分享软件测试干货内容,欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!📢交流讨论:欢迎加入我们一起学习!📢资源分享:耗时200小时精选的「软件测试」资…...
Linux - 系统调用(syscall)
说明 基于riscv64 soc linux_5.10.4平台,通过新增一个系统调用深入了解下系统调用实现原理。 简介 Linux 软件运行环境分为用户空间和内核空间,默认情况下,用户进程无法访问内核,既不能访问内核所在的内存空间,也不…...
c语言-冒泡排序
冒泡排序原理: 冒泡排序是一种简单直观的排序算法,它重复地遍历待排序的元素序列,比较相邻的两个元素,如果它们的顺序不符合要求(例如升序要求前面的元素小于后面的元素),则交换它们的位置。遍历…...
Mysql面经
Select语句的执行顺序 1、from 子句组装来自不同数据源的数据; 2、where 子句基于指定的条件对记录行进行筛选; 3、group by 子句将数据划分为多个分组; 4、使用聚集函数进行计算;AVG() SUM() MAX() MIN() COUNT() 5、使用 havin…...
1panel可视化Docker面板安装与使用
官网地址1Panel - 现代化、开源的 Linux 服务器运维管理面板 文章目录 目录 文章目录 前言 一、环境准备 二、使用步骤 1.安装命令 2.一些命令 3.使用 总结 前言 一、环境准备 虚拟机centos 已经安装好docker和 Docker Compose 或者都没安装 1panel会帮你自动安装 二、使用…...
es6中的import导入模块 和 export导出模块
es6中的import导入模块 和 export导出模块 一、定义二、使用1.默认导出导入2..命名导出导入3.命名导出(Named Export)与默认导出(Default Export)结合使用 三、总结 一、定义 功能:用于导入和导出模块的内容。 静态加载…...
WordPress插件开发教程手册 — 钩子(Hooks)
钩子是用一段代码添加/修改另外一段代码的方式,是 WordPress插件和主题与 WordPress 内核交互的基础,钩子在 WordPress 内核中也被广泛使用。WordPress 中有两种钩子,Action 和 Filter。使用钩子时,我们需要先编写一个自定义函数作…...
Python开发运维:Celery连接Redis
目录 一、理论 1.Celery 二、实验 1.Windows11安装Redis 2.Python3.8环境中配置Celery 3.celery的多目录结构异步执行 4.celery简单结构下的定时任务 三、问题 1.Celery命令报错 2.执行Celery命令报错 3.Win11启动Celery报ValueErro错误 4.Pycharm 无法 import 同目…...
JSP:JDBC
JDBC(Java Data Base Connectivity的缩写)是Java程序操作数据库的API,也是Java程序与数据库相交互的一门技术。 JDBC是Java操作数据库的规范,由一组用Java语言编写的类和接口组成,它对数据库的操作提供基本方法&#…...
能否在一台电脑上安全地登录多个Facebook账号?
Facebook是一个流量大、用户多的平台,许多人可能需要在一台设备上管理多个Facebook账号,无论是出于个人或职业需求,都能带来极大地便利。然而,保持每个账号的安全性和隐私性却是一个挑战。本文将介绍如何在一台电脑上安全地登录多…...
Banana Pi [BPi-R3-Mini] 回顾和主线 ImmortalWrt 固件支持
BananaPi BPi-R3 Mini 采用 MediaTek 830(4 个 A53,最高 2.0 GHz),具有 2 个 2.5 GbE、AX4200 2.4G/5G 无线和 USB 2.0 端口。它还具有两个 M.2 连接器,可用于 NVMe SSD 和 5G 模块(板上包含 Nano SIM 插槽…...
iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘
美国西海岸的夏天,再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至,这不仅是开发者的盛宴,更是全球数亿苹果用户翘首以盼的科技春晚。今年,苹果依旧为我们带来了全家桶式的系统更新,包括 iOS 26、iPadOS 26…...
DAY 47
三、通道注意力 3.1 通道注意力的定义 # 新增:通道注意力模块(SE模块) class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...
UE5 学习系列(三)创建和移动物体
这篇博客是该系列的第三篇,是在之前两篇博客的基础上展开,主要介绍如何在操作界面中创建和拖动物体,这篇博客跟随的视频链接如下: B 站视频:s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...
pam_env.so模块配置解析
在PAM(Pluggable Authentication Modules)配置中, /etc/pam.d/su 文件相关配置含义如下: 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块,负责验证用户身份&am…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...
工程地质软件市场:发展现状、趋势与策略建议
一、引言 在工程建设领域,准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具,正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...
UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)
UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中,UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化…...
AI病理诊断七剑下天山,医疗未来触手可及
一、病理诊断困局:刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断",医生需通过显微镜观察组织切片,在细胞迷宫中捕捉癌变信号。某省病理质控报告显示,基层医院误诊率达12%-15%,专家会诊…...
JS设计模式(4):观察者模式
JS设计模式(4):观察者模式 一、引入 在开发中,我们经常会遇到这样的场景:一个对象的状态变化需要自动通知其他对象,比如: 电商平台中,商品库存变化时需要通知所有订阅该商品的用户;新闻网站中࿰…...
比较数据迁移后MySQL数据库和OceanBase数据仓库中的表
设计一个MySQL数据库和OceanBase数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...
