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

文章目录

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

    @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

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

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

相关文章

软件测试面试题 —— 整理与解析(4)

&#x1f60f;作者简介&#xff1a;博主是一位测试管理者&#xff0c;同时也是一名对外企业兼职讲师。 &#x1f4e1;主页地址&#xff1a;【Austin_zhai】 &#x1f646;目的与景愿&#xff1a;旨在于能帮助更多的测试行业人员提升软硬技能&#xff0c;分享行业相关最新信息。…

自动化测试:为什么需要框架

前两天跟老板出去做pre-sales. 主要是去卖我们的自动化测试服务&#xff0c;工具用的是HP UFT。做过自动化的人应该知道&#xff0c;UFT在自动化测试领域已经算是最好的工具之一了。客户是个有技术背景的人&#xff0c;所以不那么好忽悠。我们准备了一大堆自动化测试优点的幻灯…

推荐一个AI人工智能技术网站(一键收藏,应有尽有)

1、Mental AI MentalAI&#xff08;https://ai.ciyundata.com/&#xff09;是一种基于星火大模型和文心大模型的知识增强大语言模型&#xff0c;专注于自然语言处理&#xff08;NLP&#xff09;领域的技术研发。 它具备强大的语义理解和生成能力&#xff0c;能够处理各种复杂的…

【效率提升】maven 转 gradle 实战 | 京东云技术团队

一、灵魂三问 1、gradle 是什么&#xff1f; 一个打包工具&#xff0c; 是一个开源构建自动化工具&#xff0c;足够灵活&#xff0c;可以构建几乎任何类型的软件&#xff0c;高性能、可扩展、能洞察等。其中洞察&#xff0c;可以用于分析构建过程中数据&#xff0c;提供分析参…

想学python找不到合适的书籍?它来了!入门python只需要这一本书就够了!

想学python找不到合适的书籍&#xff1f;看了视频还是不知如何下手&#xff1f; 《python王者归来》 它来了&#xff01;由清华大学出版社出版&#xff01;入门python只需要这一本书就够了&#xff01; 【PDF版领取见文末】 这是一本python入门书。无论你是计算机专业的大学生…

C语言之字符函数字符串函数篇(1)

目录 前言 求字符串长度 strlen strlen统计的是字符串\0之前的字符串长度 字符指针 strlen的返回值是无符号整型 strlen的三种模拟实现 计数器 函数递归 指针_指针 长度不受限制的字符串函数 strcpy strcpy会将源字符串中的 \0 拷贝到目标空间 strcpy参数目标空…

echarts添加点击事件

实现效果&#xff1a;点击图表&#xff0c;弹出该数据下对应得详情 官方文档&#xff1a; 封装的图表组件中&#xff1a; 点击获取点击得对象&#xff0c;进而将需要的参数传给父组件&#xff0c;在父组件中再去请求接口获取更多信息 this.chart.on(click, (params)> {th…

Docker 安装Redis(集群)

3主3从redis集群配置 1、新建6个docker容器 redis 实例 docker run -d --name redis-node-1 --net host --privilegedtrue -v /data/redis/share/redis-node-1:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6381 docker run -d --name redis-node-2 --ne…

聚焦云原生安全|如何为5G边缘云和工业互联网应用筑牢安全防线

9月22日&#xff0c;2023年中国信息通信业发展高层论坛5G工业互联网分论坛在北京顺利举办。 作为国内云原生安全领导厂商&#xff0c;安全狗受邀出席此次活动。 据悉&#xff0c;中国信息通信业发展高层论坛是致力于研究信息通信业发展新问题、新趋势&#xff0c;推动信息通信…

使用vite插件进行低代码平台自定义开发(手机版自定义范例)

前言 Youtube上的前端网红「Theo」在React文档仓库发起了一个Pull request&#xff0c;号召React文档不要再默认推荐CRA(create react app)&#xff0c;而是应该将Vite作为构建应用的首选。 vite的影响力已经从vue蔓延到了react&#xff0c;可见在前端工程化开发中&#xff0c…

如何使用ArcGIS Pro将等高线转DEM

通常情况下&#xff0c;我们拿到的等高线数据一般都是CAD格式&#xff0c;如果要制作三维地形模型&#xff0c;使用栅格格式的DEM数据是更好的选择&#xff0c;这里就为大家介绍一下如何使用ArcGIS Pro将等高线转DEM&#xff0c;希望能对你有所帮助。 创建TIN 在工具箱中选择“…

如何构建基于大模型的App

ChatGPT 的出现让大模型再一次成为业界的关注热点&#xff0c;然而&#xff0c;并不是每个组织都要去训练及生成大模型的&#xff0c;而且各个组织的技术积累和计算资源也不太允许这样去做。更多的时候&#xff0c; 我们还是基于大模型开发业务应用。所谓智能原生&#xff08;A…

SpringMVC-请求与相应

一、环境准备 <dependencies><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version><scope>provided</scope> //确定范围避免与tomcat冲突</de…

vivado乘法器IP核进行无符号与有符号数相乘问题的验证

本文验证乘法器IP核Multiplier进行无符号(unsigned)与有符号数(signed)相乘的正确性&#xff0c;其中也遇到了一些问题&#xff0c;做此记录。 配套工程&#xff1a;https://download.csdn.net/download/weixin_48412658/88354179 文章目录 问题的讨论验证过程IP核配置例化乘…

python+nodejs+php+springboot+vue 导师双选系统

为了直观显示系统的功能&#xff0c;运用用例图这样的工具显示分析的结果。分析的导师功能如下。导师管理导师选择信息&#xff0c;管理项目&#xff0c;管理项目提交并对学员提交的项目进行指导。 为了直观显示系统的功能&#xff0c;运用用例图这样的工具显示分析的结果。分析…

win11、win10使用python代码打开和关闭wifi热点的正确方法

问题一 win10、win11&#xff0c;可以在任务栏的WIFI图标启动移动热点&#xff0c;但是无法设置SSID和密码。在网上搜索好久&#xff0c;无解。 万能的网络解决不了&#xff0c;只能自己动手解决了。 问题二 我当前的WiFi驱动程序不支持承载网络&#xff0c;如果我输入netsh…

若依cloud -【 100 ~ 103 】

100 分布式日志介绍 | RuoYi 分布式日志就相当于把日志存储在不同的设备上面。比如若依项目中有ruoyi-modules-file、ruoyi-modules-gen、ruoyi-modules-job、ruoyi-modules-system四个应用&#xff0c;每个应用都部署在单独的一台机器里边&#xff0c;应用对应的日志的也单独存…

【MySQL基础 | 中秋特辑】多表查询详细总结

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【MySQL学习专栏】&#x1f388; 本专栏旨在分享学习MySQL的一点学习心得&#xff0c;欢迎大家在评论区讨论&#x1f48c; 目录 一、多表…

SI3262:国产NFC+MCU+防水触摸按键三合一SoC芯片

目录 SI3262简介特点结构框图芯片特性 SI3262简介 Si3262是高度集成ACD低功耗MCUNFC15通道防水触摸按键的SoC芯片。 其MCU模块具有低功耗、Low Pin Count、宽电压工作范围&#xff0c;集成了13/14/15/16位精度的ADC、LVD、UART、SPI、I2C、TIMER、WUP、IWDG、RTC、TSC等丰富的…