从零开始的 MyBatis 拦截器之旅:实战经验分享
文章目录
- MyBatis拦截器可以做什么?
- Mybatis核心对象介绍
- 四大核心对象
- 如何实现?接口讲解
- Interceptor接口
- intercept方法
- plugin方法
- setProperties
- 完整SQL打印拦截器实战
- 拦截器实现
- 拦截器注册
MyBatis拦截器可以做什么?
MyBatis拦截器是MyBatis框架提供的扩展机制,它可以在执行SQL语句的过程中拦截和干预,用于对SQL语句进行增强或修改。
MyBatis拦截器可以做以下几件事情:
- 拦截SQL语句的执行:拦截器可以在SQL语句执行前后进行拦截,可以在SQL语句执行之前对参数进行处理,也可以在SQL语句执行之后对结果进行处理。
- 修改SQL语句:拦截器可以对原始的SQL语句进行修改,可以增加、删除或修改SQL语句的部分内容,以满足一些特定需求。比如可以在SQL语句前后添加额外的条件或修改排序方式。
- 记录日志:拦截器可以用于记录SQL语句的执行日志,包括SQL语句的执行时间、执行结果等。这对于系统的性能监控和调优非常有帮助。
- 实现分页功能:拦截器可以在执行原始SQL语句之前,根据传入的参数进行分页处理,将查询结果限制在指定的页数和每页的记录数范围内。
- 实现缓存功能:拦截器可以在执行SQL语句之前,先检查缓存中是否存在对应的结果,如果存在则直接返回缓存结果,避免不必要的数据库查询操作。
- 在很多时候,对表中的数据都需要记录插入时间,修改时间,插入人和修改人,若每次都在插入或修改代码中去设置这些信息,就显得有些冗余。那么此时可以通过Mybatis提供的拦截器加上我们自定义的拦截器实现对在需要记录的操作人信息sql执行前,自动补充这些信息,也就是所谓的对Mybatis的核心对象进行增强。这里只拦截Executor对象,给更新的sql语句动态的增加参数。(可参考https://www.cnblogs.com/zys2019/p/16966866.html )
具体例子: 我们常用的分页插件
Pagehelper
其实就是一个拦截器实现
Mybatis核心对象介绍
MyBatis的主要的核心部件有以下几个:
- Configuration:初始化基础配置,比如MyBatis的别名等,一些重要的类型对象,如插件,映射器,ObjectFactory和typeHandler对象,MyBatis所有的配置信息都维持在Configuration对象之中。
- SqlSessionFactory:SqlSession工厂。
- SqlSession:作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要的数据库增删改查功能。
- Executor:MyBatis的内部执行器,它负责调用StatementHandler操作数据库,并把结果集通过ResultSetHandler进行自动映射,另外,它还处理二级缓存的操作。
- StatementHandler:MyBatis直接在数据库执行SQL脚本的对象。另外它也实现了MyBatis的一级缓存。
- ParameterHandler:负责将用户传递的参数转换成JDBC Statement所需要的参数。是MyBatis实现SQL入参设置的对象。
- ResultSetHandler:负责将JDBC返回的ResultSet结果集对象转换成List类型的集合。是MyBatis把ResultSet集合映射成POJO的接口对象。
- TypeHandler:负责Java数据类型和JDBC数据类型之间的映射和转换。
- MappedStatement:MappedStatement维护了一条<select|update|delete|insert>节点的封装。
- SqlSource :负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回。
- BoundSql:表示动态生成的SQL语句以及相应的参数信息。
四大核心对象
在Mybatis中,Executor、StatementHandler、ParameterHandler和ResultSetHandler是核心组件,它们分别负责不同的任务。
Executor:统筹全局
Executor是Mybatis中的执行器,它负责处理数据库的操作。它的职责是接收并执行SQL语句,管理事务的提交和回滚,以及处理缓存。 在Mybatis中,有三种类型的Executor:SimpleExecutor、ReuseExecutor和BatchExecutor,它们分别提供了不同的执行策略。StatementHandler:执行SQL
StatementHandler负责处理SQL语句的操作,它是Executor的一个重要组成部分。它的主要职责是创建PreparedStatement对象,设置参数,并执行SQL语句。 StatementHandler可以根据不同的数据库厂商提供的驱动,生成不同的Statement对象,如PreparedStatement、CallableStatement等。ParameterHandler:参数封装
ParameterHandler负责处理SQL语句中的参数。它的主要职责是将Java对象中的属性值映射到SQL语句中的参数位置。ParameterHandler可以根据参数的类型,将Java对象的属性值转换为数据库可以接受的类型,并设置到PreparedStatement对象中。ResultSetHandler:返回结果映射
ResultSetHandler负责处理SQL语句的结果集。它的主要职责是将查询结果集中的数据映射到Java对象中。ResultSetHandler会根据映射规则,将数据库中的每一行数据转换为Java对象,并将这些对象放入一个集合中返回给调用者。
这四个组件在Mybatis中协同工作,完成了从数据库操作到Java对象映射的整个过程。Executor负责整体的控制和协调,StatementHandler负责处理SQL语句的操作,ParameterHandler负责处理参数,ResultSetHandler负责处理结果集。它们各自分工明确,相互配合,共同完成数据库操作和对象映射的任务。
如何实现?接口讲解
- 写一个实现org.apache.ibatis.plugin.Interceptor接口的拦截器类,并实现其中的方法。
- 添加@Intercepts注解,写上需要拦截的对象和方法,以及方法参数。
- Spring项目注意添加@Component注解即可,使其成为Spring管理的一个Bean。
Interceptor接口
@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),@Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class}),@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
public class MyInterceptor implements Interceptor {
MyBatis
拦截器默认可以拦截的类型只有四种,即四种接口类型Executor
、StatementHandler
、ParameterHandler
和ResultSetHandler
。对于我们的自定义拦截器必须使用MyBatis
提供的@Intercepts
注解来指明我们要拦截的是四种类型中的哪一种接口。
注解 | 描述 |
---|---|
@Intercepts | 标志该类是一个拦截器 |
@Signature | 指明该拦截器需要拦截哪一个接口的哪一个方法 |
@Signature
注解的参数:
参数 | 描述 |
---|---|
type | 四种类型接口中的某一个接口,如Executor.class 。 |
method | 对应接口中的某一个方法名,比如Executor 的query 方法。 |
args | 对应接口中的某一个方法的参数,比如Executor 中query 方法因为重载原因,有多个,args 就是指明参数类型,从而确定是具体哪一个方法。 |
MyBatis
拦截器默认会按顺序拦截以下的四个接口中的所有方法:
org.apache.ibatis.executor.Executor //拦截执行器方法
org.apache.ibatis.executor.statement.StatementHandler //拦截SQL语法构建处理
org.apache.ibatis.executor.parameter.ParameterHandler //拦截参数处理
org.apache.ibatis.executor.resultset.ResultSetHandler //拦截结果集处理
具体是拦截这四个接口对应的实现类:
org.apache.ibatis.executor.CachingExecutor
org.apache.ibatis.executor.statement.RoutingStatementHandler
org.apache.ibatis.scripting.defaults.DefaultParameterHandler
org.apache.ibatis.executor.resultset.DefaultResultSetHandler
intercept方法
进行拦截的时候要执行的方法。该方法参数Invocation
类中有三个字段:
private final Object target;private final Method method;private final Object[] args;
可通过这三个字段分别获取下面的信息:
Object target = invocation.getTarget();//被代理对象
Method method = invocation.getMethod();//代理方法
Object[] args = invocation.getArgs();//方法参数
plugin方法
插件用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理,可以决定是否要进行拦截进而决定要返回一个什么样的目标对象,官方提供了示例:return Plugin.wrap(target, this);
可以在这个方法中提前进行拦截对象类型判断,提高性能:
@Overridepublic Object plugin(Object target) {//只对要拦截的对象生成代理if(target instanceof StatementHandler){//调用插件return Plugin.wrap(target, this);}return target;}
MyBatis拦截器用到责任链模式+动态代理+反射机制;
所有可能被拦截的处理类都会生成一个代理类,如果有N个拦截器,就会有N个代理,层层生成动态代理是比较耗性能的。而且虽然能指定插件拦截的位置,但这个是在执行方法时利用反射动态判断的,初始化的时候就是简单的把拦截器插入到了所有可以拦截的地方。所以尽量不要编写不必要的拦截器。另外我们可以在调用插件的地方添加判断,只要是当前拦截器拦截的对象才进行调用,否则直接返回目标对象本身,这样可以减少反射判断的次数,提高性能。
setProperties
如果我们拦截器需要用到一些变量参数,而且这个参数是支持可配置的,类似Spring
中的@Value("${}")
从application.properties
文件获取自定义变量属性,这个时候我们就可以使用这个方法。
private String property1;
private int property2;@Overridepublic void setProperties(Properties properties) {// 从配置文件中获取属性值this.property1 = properties.getProperty("property1");this.property2 = Integer.parseInt(properties.getProperty("property2"));}
在setProperties
方法中,我们可以通过传入的Properties
对象获取配置文件中的属性值,并进行相应的处理。
然后,在MyBatis的配置文件中注册自定义的拦截器:
<configuration><!-- 其他配置 --><plugins><plugin interceptor="com.example.CustomInterceptor"><property name="property1" value="value1" /><property name="property2" value="2" /></plugin></plugins>
</configuration>
完整SQL打印拦截器实战
拦截器实现
@Slf4j
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),@Signature(type = Executor.class,method = "query",args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class,BoundSql.class}
), @Signature(type = Executor.class,method = "query",args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
)})
public class SqlPrintInterceptor implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {try {// 获取xml中的一个select/update/insert/delete节点,是一条SQL语句MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];Object parameter = null;// 获取参数,if语句成立,表示sql语句有参数,参数格式是map形式if (invocation.getArgs().length > 1) {parameter = invocation.getArgs()[1];log.info("SQL打印拦截器参数 = " + parameter);}// 获取到节点的id, 即sql语句的idString sqlId = mappedStatement.getId();//log.info("sqlId = " + sqlId);// BoundSql就是封装myBatis最终产生的sql类BoundSql boundSql = mappedStatement.getBoundSql(parameter);// 获取节点的配置Configuration configuration = mappedStatement.getConfiguration();// 获取到最终的sql语句String sql = getSql(configuration, boundSql, sqlId);log.info("SQL打印拦截器完整SQL = " + sql);} catch (Exception e) {e.printStackTrace();}// 执行完上面的任务后,不改变原有的sql执行过程return invocation.proceed();}// 封装了一下sql语句,使得结果返回完整xml路径下的sql语句节点id + sql语句private static String getSql(Configuration configuration, BoundSql boundSql, String sqlId) {String sql = showSql(configuration, boundSql);StringBuilder str = new StringBuilder(100);str.append(sqlId);str.append(":");str.append(sql);return str.toString();}// 如果参数是String,则添加单引号, 如果是日期,则转换为时间格式器并加单引号; 对参数是null和不是null的情况作了处理private static String getParameterValue(Object obj) {String value = null;if (obj instanceof String) {value = "'" + obj.toString() + "'";} else if (obj instanceof Date) {DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT,DateFormat.DEFAULT, Locale.CHINA);value = "'" + formatter.format(new Date()) + "'";} else {if (obj != null) {value = obj.toString();} else {value = "";}}return value;}// 进行?的替换private static String showSql(Configuration configuration, BoundSql boundSql) {// 获取参数Object parameterObject = boundSql.getParameterObject();List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();// sql语句中多个空格都用一个空格代替String sql = boundSql.getSql().replaceAll("[\\s]+", " ");if (CollectionUtils.isNotEmpty(parameterMappings) && parameterObject != null) {// 获取类型处理器注册器,类型处理器的功能是进行java类型和数据库类型的转换TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();// 如果根据parameterObject.getClass()可以找到对应的类型,则替换if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {sql = sql.replaceFirst("\\?",Matcher.quoteReplacement(getParameterValue(parameterObject)));} else {// MetaObject主要是封装了originalObject对象,提供了get和set的方法用于获取和设置originalObject的属性值,// 主要支持对JavaBean、Collection、Map三种类型对象的操作MetaObject metaObject = configuration.newMetaObject(parameterObject);for (ParameterMapping parameterMapping : parameterMappings) {String propertyName = parameterMapping.getProperty();if (metaObject.hasGetter(propertyName)) {Object obj = metaObject.getValue(propertyName);sql = sql.replaceFirst("\\?",Matcher.quoteReplacement(getParameterValue(obj)));} else if (boundSql.hasAdditionalParameter(propertyName)) {// 该分支是动态sqlObject obj = boundSql.getAdditionalParameter(propertyName);sql = sql.replaceFirst("\\?",Matcher.quoteReplacement(getParameterValue(obj)));} else {// 打印出缺失,提醒该参数缺失并防止错位sql = sql.replaceFirst("\\?", "缺失");}}}}return sql;}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {}
}
拦截器注册
主要是这一句bean.setPlugins(new Interceptor[]{ new SqlPrintInterceptor()});
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {//设置分页的拦截器PageInterceptor pageInterceptor = new PageInterceptor();//创建插件需要的参数集合Properties properties = new Properties();//配置数据库方言 为oracleproperties.setProperty("helperDialect", "mysql");//配置分页的合理化数据properties.setProperty("reasonable", "true");pageInterceptor.setProperties(properties);SqlSessionFactoryBean bean = new SqlSessionFactoryBean();bean.setPlugins(new Interceptor[]{ new SqlPrintInterceptor()});bean.setDataSource(dataSource);bean.setMapperLocations(resolveMapperLocations());//重点2 指定包扫描,当前这个数据源对应的mapper.xml文件在哪个resource下的包里,这里路径就指定哪里return bean.getObject();}
完整注册配置类
@Slf4j
@Configuration
@MapperScan(basePackages = {"com.*.dao"}, sqlSessionTemplateRef = "sqlSessionTemplate")
//重点1
// 指定包扫描,当前这个数据源对应的mapper.java文件放在哪个包下,这里路径就指定哪里
public class MySQLDataSourceConfig {@Bean@Primary@ConfigurationProperties(prefix = "spring.datasource.mysql")//重点3 这里对应yml的当前数据源的前缀public DataSource dataSource() {return DataSourceBuilder.create().build();}@Bean@Primarypublic SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {//设置分页的拦截器PageInterceptor pageInterceptor = new PageInterceptor();//创建插件需要的参数集合Properties properties = new Properties();//配置数据库方言 为oracleproperties.setProperty("helperDialect", "mysql");//配置分页的合理化数据properties.setProperty("reasonable", "true");pageInterceptor.setProperties(properties);SqlSessionFactoryBean bean = new SqlSessionFactoryBean();//设置拦截器!!!!bean.setPlugins(new Interceptor[]{ new SqlPrintInterceptor()});bean.setDataSource(dataSource);bean.setMapperLocations(resolveMapperLocations());//重点2 指定包扫描,当前这个数据源对应的mapper.xml文件在哪个resource下的包里,这里路径就指定哪里return bean.getObject();}/*** 获取多个路径下的mapper** @return*/public Resource[] resolveMapperLocations() {ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();List<String> mapperLocations = new ArrayList<>();mapperLocations.add("classpath:com/mapper/**/*.xml");List<Resource> resources = new ArrayList();if (!CollectionUtils.isEmpty(mapperLocations)) {for (String mapperLocation : mapperLocations) {try {Resource[] mappers = resourceResolver.getResources(mapperLocation);resources.addAll(Arrays.asList(mappers));} catch (IOException e) {//log.error("Get myBatis resources happened exception", e);}}}return resources.toArray(new Resource[0]);}@Bean@Primarypublic DataSourceTransactionManager mysqlTransactionManager(DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}@Bean@Primarypublic SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) throws Exception {return new SqlSessionTemplate(sqlSessionFactory);}}
参考文章:
https://blog.csdn.net/wb1046329430/article/details/111501755
相关文章:

从零开始的 MyBatis 拦截器之旅:实战经验分享
文章目录 MyBatis拦截器可以做什么?Mybatis核心对象介绍四大核心对象如何实现?接口讲解Interceptor接口intercept方法plugin方法setProperties 完整SQL打印拦截器实战拦截器实现拦截器注册 MyBatis拦截器可以做什么? MyBatis拦截器是MyBatis…...

网络编程day05(IO多路复用)
今日任务: TCP多路复用的客户端、服务端: 服务端代码: #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <unistd.h> …...

人声分离网站,帮你快速提取视频中的人声和背景音乐
今天给大家带来一个可以分离人声的网站——音分轨,他运用人工智能算法可以将音频中的人声部分和音乐部分分离,使我们的视频制作过程可以更方便。 我们点击右下角“选择文件”上传一个音频,上传好音频后,人工智能就开始处理我们上传…...

计算机网络常见问题
1.谈一谈对OSI七层模型和TCP/IP四层模型的理解? 1.1.为什么要分层? 在计算机中网络是个复杂的系统,不同的网络与网络之间由于协议,设备,软件等各种原因在协调和通讯时容易产生各种各样的问题。例如:各物流…...

上PICO,沉浸式观看亚运直播,参与跨国界游戏竞技
备受瞩目的杭州第19届亚运会,将于9月23日正式开幕。据悉,这也是有史以来项目最多的一届亚运会,除部分传统奥运项目外,还包含武术、藤球、板球、克柔术、柔术等亚洲特色项目,以及霹雳舞、电子竞技等深受年轻人喜爱的新兴…...
无重复字符的最长子串 - 力扣(LeetCode)
3. 无重复字符的最长子串 - 力扣(LeetCode) 给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。 示例 1: 输入: s "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长…...
企业行政许可的种类有哪些?
从行政许可的性质、功能和适用条件的角度来说,大体可以划分为五类:普通许可、特许、认可、核准、登记。 1.普通许可 普通许可是一种允许符合特定条件的相对方行使某种权利的行为。在许多情况下,需要普通许可的活动都与国家安全、公共安全息…...

Flink--4、DateStream API(执行环境、源算子、基本转换算子)
星光下的赶路人star的个人主页 注意力的集中,意象的孤立绝缘,便是美感的态度的最大特点 文章目录 1、DataStream API1.1 执行环境(Execution Environment)1.1.1 创建执行环境 1.2 执行模式(Execution Mode)…...

#循循渐进学51单片机#指针基础与1602液晶的初步认识#not.11
1、把本节课的指针相关内容,反复学习3到5遍,彻底弄懂指针是怎么回事,即使是死记硬背也要记住,等到后边用的时候可以实现顿悟。学会指针,就是突破了C语言的一道壁垒。 2,1602所有的指令功能都应用一遍&#…...

Lua学习笔记:探究package
前言 本篇在讲什么 理解Lua的package 本篇需要什么 对Lua语法有简单认知 对C语法有简单认知 依赖Visual Studio工具 本篇的特色 具有全流程的图文教学 重实践,轻理论,快速上手 提供全流程的源码内容 ★提高阅读体验★ 👉 ♠ 一级…...

【面试经典150 | 双指针】三数之和
文章目录 写在前面Tag题目来源题目解读解题思路方法一:暴力枚举方法二:双指针 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法,两到三天更新一篇文章,欢迎催更…… 专栏内容以分析题目为主,并附带一些对…...
现代卷积网络实战系列3:PyTorch从零构建AlexNet训练MNIST数据集
1、AlexNet AlexNet提出了一下5点改进: 使用了Dropout,防止过拟合使用Relu作为激活函数,极大提高了特征提取效果使用MaxPooling池化进行特征降维,极大提高了特征提取效果首次使用GPU进行训练使用了LRN局部响应归一化(…...

Django系列:Django应用(app)的创建与配置
Django系列 Django应用(app)的创建与配置 作者:李俊才 (jcLee95):https://blog.csdn.net/qq_28550263 邮箱 :291148484163.com 本文地址:https://blog.csdn.net/qq_28550263/article…...
Linux查看程序和动态库依赖的动态库
一. 前言 在一些时候,我们需要知道一个程序或者动态库所依赖的动态库有哪些。比如,当我们运行一个程序的时候,发现可能会报错,提示找不到某个符号,这时我们就需要知道程序依赖了什么库,从而添加对应需要的动…...
vue3 无法使用pnpm安装依赖 或 Cannot find module preinstall.js
创建.npmrc文件在根目录 shamefully-hoisttrue auto-install-peerstrue strict-peer-dependenciesfalse删除 node_modules 和 pnpm-lock.yaml 文件 重新 pnpm i 就可以啦...

C/C++连接数据库,包含完整代码。
C/C连接数据库 本篇文章意在简洁明了的在linux环境下使用C/C连接远程数据库,并对数据库进行增删查改等操作。我所使用的环境是centos7,不要环境除环境配置外,代码是大同小异的。完整代码在最底部!!! 1.前…...

AUTOSAR词典:CAN驱动Mailbox配置技术要点全解析
AUTOSAR词典:CAN驱动Mailbox配置技术要点全解析 前言 首先,请问大家几个小小问题,你清楚: AUTOSAR框架下的CAN驱动关键词定义吗?是不是有些总是傻傻分不清楚呢?CAN驱动Mailbox配置过程中有哪些关键配置参…...

C语言 coding style
头文件 The #define Guard #define的保护文件的唯一性,防止被多重包含 格式 : <PROJECT>_< FILE>_H_ PROJECT : XS FILE : MV_CTR 头文件的包含顺序 C System FilesOther LibrariesUser LibraryConditional include 作用域 局部变量 -变量定义时需要…...
Python办公自动化之PDF
Python操作PDF 1、Python操作PDF概述2、批量拆分3、批量合并4、提取内容(文字)5、提取内容(表格)6、提取图片7、PDF添加水印8、加密与解密1、Python操作PDF概述 Python操作PDF主要有两个库:PyPDF2和pdfplumber PyPDF2是一个用于处理PDF文件的Python第三方库 官网文档参考:…...
【每日一题Day331】LC2560打家劫舍 IV | 二分查找 + 贪心
打家劫舍 IV【LC2560】 沿街有一排连续的房屋。每间房屋内都藏有一定的现金。现在有一位小偷计划从这些房屋中窃取现金。 由于相邻的房屋装有相互连通的防盗系统,所以小偷 不会窃取相邻的房屋 。 小偷的 窃取能力 定义为他在窃取过程中能从单间房屋中窃取的 最大金额…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...

Linux 文件类型,目录与路径,文件与目录管理
文件类型 后面的字符表示文件类型标志 普通文件:-(纯文本文件,二进制文件,数据格式文件) 如文本文件、图片、程序文件等。 目录文件:d(directory) 用来存放其他文件或子目录。 设备…...
云计算——弹性云计算器(ECS)
弹性云服务器:ECS 概述 云计算重构了ICT系统,云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台,包含如下主要概念。 ECS(Elastic Cloud Server):即弹性云服务器,是云计算…...

【OSG学习笔记】Day 18: 碰撞检测与物理交互
物理引擎(Physics Engine) 物理引擎 是一种通过计算机模拟物理规律(如力学、碰撞、重力、流体动力学等)的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互,广泛应用于 游戏开发、动画制作、虚…...

屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!
5月28日,中天合创屋面分布式光伏发电项目顺利并网发电,该项目位于内蒙古自治区鄂尔多斯市乌审旗,项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站,总装机容量为9.96MWp。 项目投运后,每年可节约标煤3670…...

【2025年】解决Burpsuite抓不到https包的问题
环境:windows11 burpsuite:2025.5 在抓取https网站时,burpsuite抓取不到https数据包,只显示: 解决该问题只需如下三个步骤: 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...
JDK 17 新特性
#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持,不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的ÿ…...

【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...

如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...