从零开始的 MyBatis 拦截器之旅:实战经验分享

news2024/10/6 10:34:23

文章目录

    • MyBatis拦截器可以做什么?
    • Mybatis核心对象介绍
    • 四大核心对象
    • 如何实现?接口讲解
      • Interceptor接口
      • intercept方法
      • plugin方法
      • setProperties
    • 完整SQL打印拦截器实战
      • 拦截器实现
      • 拦截器注册

MyBatis拦截器可以做什么?

MyBatis拦截器是MyBatis框架提供的扩展机制,它可以在执行SQL语句的过程中拦截和干预,用于对SQL语句进行增强或修改。

MyBatis拦截器可以做以下几件事情:

  1. 拦截SQL语句的执行:拦截器可以在SQL语句执行前后进行拦截,可以在SQL语句执行之前对参数进行处理,也可以在SQL语句执行之后对结果进行处理。
  2. 修改SQL语句:拦截器可以对原始的SQL语句进行修改,可以增加、删除或修改SQL语句的部分内容,以满足一些特定需求。比如可以在SQL语句前后添加额外的条件或修改排序方式。
  3. 记录日志:拦截器可以用于记录SQL语句的执行日志,包括SQL语句的执行时间、执行结果等。这对于系统的性能监控和调优非常有帮助。
  4. 实现分页功能:拦截器可以在执行原始SQL语句之前,根据传入的参数进行分页处理,将查询结果限制在指定的页数和每页的记录数范围内。
  5. 实现缓存功能:拦截器可以在执行SQL语句之前,先检查缓存中是否存在对应的结果,如果存在则直接返回缓存结果,避免不必要的数据库查询操作。
  6. 在很多时候,对表中的数据都需要记录插入时间,修改时间,插入人和修改人,若每次都在插入或修改代码中去设置这些信息,就显得有些冗余。那么此时可以通过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(四):MyBatis核心组件介绍原理解析和源码解读- 夏威夷8080 - 博客园

img

四大核心对象

在Mybatis中,Executor、StatementHandler、ParameterHandler和ResultSetHandler是核心组件,它们分别负责不同的任务。

  1. Executor:统筹全局
    Executor是Mybatis中的执行器,它负责处理数据库的操作。它的职责是接收并执行SQL语句,管理事务的提交和回滚,以及处理缓存。 在Mybatis中,有三种类型的Executor:SimpleExecutor、ReuseExecutor和BatchExecutor,它们分别提供了不同的执行策略。
  2. StatementHandler:执行SQL
    StatementHandler负责处理SQL语句的操作,它是Executor的一个重要组成部分。它的主要职责是创建PreparedStatement对象,设置参数,并执行SQL语句。 StatementHandler可以根据不同的数据库厂商提供的驱动,生成不同的Statement对象,如PreparedStatement、CallableStatement等。
  3. ParameterHandler:参数封装
    ParameterHandler负责处理SQL语句中的参数。它的主要职责是将Java对象中的属性值映射到SQL语句中的参数位置。ParameterHandler可以根据参数的类型,将Java对象的属性值转换为数据库可以接受的类型,并设置到PreparedStatement对象中。
  4. ResultSetHandler:返回结果映射
    ResultSetHandler负责处理SQL语句的结果集。它的主要职责是将查询结果集中的数据映射到Java对象中。ResultSetHandler会根据映射规则,将数据库中的每一行数据转换为Java对象,并将这些对象放入一个集合中返回给调用者。

img

这四个组件在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拦截器默认可以拦截的类型只有四种,即四种接口类型ExecutorStatementHandlerParameterHandlerResultSetHandler。对于我们的自定义拦截器必须使用MyBatis提供的@Intercepts注解来指明我们要拦截的是四种类型中的哪一种接口。

注解描述
@Intercepts标志该类是一个拦截器
@Signature指明该拦截器需要拦截哪一个接口的哪一个方法

@Signature注解的参数:

参数描述
type四种类型接口中的某一个接口,如Executor.class
method对应接口中的某一个方法名,比如Executorquery方法。
args对应接口中的某一个方法的参数,比如Executorquery方法因为重载原因,有多个,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

img

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);可以在这个方法中提前进行拦截对象类型判断,提高性能:

    @Override
    public 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;

@Override
    public 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 {

	@Override
	public 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语句的id
			String 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)) {
						// 该分支是动态sql
						Object obj = boundSql.getAdditionalParameter(propertyName);
						sql = sql.replaceFirst("\\?",
								Matcher.quoteReplacement(getParameterValue(obj)));
					} else {
						// 打印出缺失,提醒该参数缺失并防止错位
						sql = sql.replaceFirst("\\?", "缺失");
					}
				}
			}
		}
		return sql;
	}

	@Override
	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}

	@Override
	public 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();
		//配置数据库方言 为oracle
		properties.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
	@Primary
	public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
		//设置分页的拦截器
		PageInterceptor pageInterceptor = new PageInterceptor();
		//创建插件需要的参数集合
		Properties properties = new Properties();
		//配置数据库方言 为oracle
		properties.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
	@Primary
	public DataSourceTransactionManager mysqlTransactionManager(DataSource dataSource) {
		return new DataSourceTransactionManager(dataSource);
	}

	@Bean
	@Primary
	public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) throws Exception {
		return new SqlSessionTemplate(sqlSessionFactory);
	}

}

参考文章:
https://blog.csdn.net/wb1046329430/article/details/111501755

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1032590.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

某手新版本sig3参数算法还原

Frida Native层主动调用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81…

#循循渐进学51单片机#指针基础与1602液晶的初步认识#not.11

1、把本节课的指针相关内容&#xff0c;反复学习3到5遍&#xff0c;彻底弄懂指针是怎么回事&#xff0c;即使是死记硬背也要记住&#xff0c;等到后边用的时候可以实现顿悟。学会指针&#xff0c;就是突破了C语言的一道壁垒。 2&#xff0c;1602所有的指令功能都应用一遍&#…

内网穿透,轻松实现PostgreSQL数据库公网远程连接!

文章目录 前言1. 安装postgreSQL2. 本地连接postgreSQL3. Windows 安装 cpolar4. 配置postgreSQL公网地址5. 公网postgreSQL访问6. 固定连接公网地址7. postgreSQL固定地址连接测试 前言 PostgreSQL是一个功能非常强大的关系型数据库管理系统&#xff08;RDBMS&#xff09;,下…

外汇天眼:CFTC处罚Advantage Futures 39.5万美元

美国商品期货交易委员会&#xff08;CFTC&#xff09;对Advantage Futures处以39.5万美元罚款&#xff0c;原因是其监管不力。 CFTC今天发布了一项命令&#xff0c;同时提起并解决了对Advantage Futures LLC的指控&#xff0c;这是一家注册的期货佣金经纪商&#xff0c;因未能…

Linux命令基础

一、linux目录结构。 Linux没有windows的盘的概念&#xff0c;是一个树形的结构。唯一的根目录为/&#xff0c;所有的文件都在他下面。 描述方式也与windows有所不同 二、命令基础格式。 command [-options] [parameter]&#xff08;[ ]表示可选的&#xff09; command:必…

智思Ai企联系统去授权版本+uniapp前后端(内含教程)

智思AI企联系统是一款企业级AI系统&#xff0c;与普通版AI产品相比具备显著差异。该系统允许企业按需选择和定制二开任意功能&#xff0c;以满足不同企业的个性化需求和场景要求。企业可以根据实际业务需求扩展和改进系统功能模块&#xff0c;使之更好地适应企业独特需求。

ESP8266 WiFi物联网智能插座—电能计量

目录 1、芯片功能 2、性能指标 3、寄存器说明 4、UART通信协议 4.1、写操作帧格式和时序 4.2、读操作帧格式和时序 4.3、读取全电参数数据包 4.4、配置波特率 4.5、UART保护机制 5、功能说明 5.1、电流电压瞬态波形计量 5.2、有功功率 5.3、有功功率防潜动 5.4、电能计量 5.5、…

【C++】静态成员变量 ( 静态成员变量概念 | 静态成员变量声明 | 静态成员变量初始化 | 静态成员变量访问 | 静态成员变量生命周期 )

文章目录 一、静态成员变量概念1、静态成员变量引入2、静态成员变量声明3、静态成员变量初始化4、静态成员变量访问5、静态成员变量生命周期 二、完整代码示例 一、静态成员变量概念 1、静态成员变量引入 在 C 类中 , 静态成员变量 又称为 静态属性 ; 静态成员归属 : 静态成员…

一百八十二、大数据离线数仓——离线数仓从Kafka采集、最终把结果数据同步到ClickHouse的完整数仓流程(待续)

一、目的 经过6个月的奋斗&#xff0c;项目的离线数仓部分终于可以上线了&#xff0c;因此整理一下离线数仓的整个流程&#xff0c;既是大家提供一个案例经验&#xff0c;也是对自己近半年的工作进行一个总结。 二、项目背景 项目行业属于交通行业&#xff0c;因此数据具有很…

please choose a certificate and try again.(-5)报错怎么解决

the server you want to connect to requests identification,please choose a certificate and try again.(-5)

英语——分享篇——每日100词——301-400

straight——str街道(熟词street)aight八(形似eight)——街道上的八条路是笔直的 valley——v维生素(编码)all所有(熟词)ey鳄鱼(拼音)——维生素被所有的鳄鱼在山谷里吃掉了 deer——d狗(编码)ee眼睛(象形)r小草(编码)——狗眼睛看着小草变成一只鹿 goose——goo900(象形)se色(…

git:二、git的本地配置+工作区域和文件状态+git add/commit/log +git reset回退版本

git的使用方式 命令行&#xff08;最常用&#xff09;图形化界面IDE插件/拓展&#xff08;次常用&#xff09; git的本地/系统配置 之前的文章提到过git的全局配置。如下&#xff1a; git config --global user.name "ss" git config --global user.email "…

[杂谈]-八进制数

八进制数 文章目录 八进制数1、概述2、八进制数的表示2.1 八进制数2.2 以八进制计数2.3 二进制数补零 3、八进制到十进制转换4、十进制到八进制转换5、二进制到八进制转换示例6、八进制到二进制和十进制转换示例7、总结 1、概述 八进制编号系统是另一种使用基数为8计数系统&am…

医疗革命的关键推手,看AIGC弥合医疗差距的未来之路

随着科技的飞速进步&#xff0c;医疗水平在过去几十年里取得了巨大的突破。这些科技创新不仅改变了我们对健康和医疗的认知&#xff0c;也深刻地塑造了社会的现状。其中&#xff0c;人工智能作为医疗领域的一项前沿技术&#xff0c;正以前所未有的方式影响着我们的生活。它不仅…

CUDA和cuDNN的安装

参考资料&#xff1a;https://zhuanlan.zhihu.com/p/83971195 目录 CUDA和cuDNN介绍安装验证 CUDA和cuDNN介绍 CUDA(ComputeUnified Device Architecture)&#xff0c;是显卡厂商NVIDIA推出的运算平台。 CUDA是一种由NVIDIA推出的通用并行计算架构&#xff0c;该架构使GPU能够…

python-docx办公自动化批量生成离职证明

关注公众号&#xff1a;Python Lab 首先&#xff0c;在网络找到这样一份模板内容&#xff0c;可以根据这么模板进行排版 这是存放在Excel中的数据&#xff0c;根据数据遍历其中的内容&#xff0c;写入word当中 完整代码实现 from docx import Document import pandas as pd …

sqlmap tamper脚本编写

文章目录 tamper脚本是什么&#xff1f;指定tamper脚本运行sqlmap安全狗绕过tamper脚本 tamper脚本是什么&#xff1f; SQLMap 是一款SQL注入神器&#xff0c;可以通过tamper 对注入payload 进行编码和变形&#xff0c;以达到绕过某些限制的目的。但是有些时候&#xff0c;SQLM…

SLAM从入门到精通(参数处理)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 在编写ros程序的过程中&#xff0c;很多时候需要不停修改程序的参数。比如说&#xff0c;我们有一个配置文件。在程序还没有运行之前&#xff0c;我…

实至名归!优维科技荣膺NIISA联盟2022年度双项技术创新奖

日前&#xff0c;国家互联网数据中心产业技术创新战略联盟&#xff08;以下简称&#xff1a;NIISA联盟&#xff09;2022年度技术创新奖评选结果公布&#xff01;经过激烈角逐&#xff0c;优维科技脱颖而出&#xff0c;荣膺双项大奖&#xff01; “EasyCore—CMDB超融合数据库”…

移植FreeRTOS的STM32F103双轮平衡小车(开源,代码文末)

耗时大概三四天吧&#xff0c;主要时间还是花在硬件方面上&#xff0c; ps&#xff1a;之前因为还想再完善一点&#xff0c;就一直放在草稿里&#xff0c;并不是今天才写的。面试官看到希望理解。。。 引言 1、系统概述 1.1、设计任务 利用stm32f103作为主控&#xff0c;移…