当前位置: 首页 > news >正文

MyBatis:SpringBoot结合MyBatis、MyBatis插件机制的原理分析与实战

在这里插入图片描述

🪁🍁 希望本文能给您带来帮助,如果有任何问题,欢迎批评指正!🐅🐾🍁🐥


文章目录

  • 一、背景
  • 二、Spring Boot项目中结合MyBatis
    • 2.1 数据准备
    • 2.2 pom.xml依赖增加
    • 2.3 application.yml实现
    • 2.4 代码层实现
      • 2.4.1 基于注解的Mapper
      • 2.4.2 基于XML配置的Mapper
  • 三、MyBatis插件机制
    • 3.1 插件概述
    • 3.2 插件的实现步骤
      • 3.2.1 实现Interceptor接口
      • 3.2.2 注册插件
    • 3.3 自定义插件
      • 3.3.1 实现 SQL 执行时间记录插件
        • 3.3.1.1 实现 SQL 执行时间记录代码
        • 3.3.1.2 注册SQL 执行时间记录插件
        • 3.3.1.3 查询时间测试
      • 3.3.2 实现查询结果加密插件
        • 3.3.2.1 实现查询结果加密插件代码
        • 3.3.2.2 注册查询结果加密插件
        • 3.3.2.3 加密结果测试
    • 3.4 插件机制源码分析
      • 3.4.1 插件配置信息加载与解析
      • 3.4.2 代理对象的生成
      • 3.4.3 拦截逻辑的执行
    • 3.5 插件机制的应用场景与注意事项
  • 四、总结

一、背景

MyBatis 是一个非常灵活的持久层框架,它内部封装了 jdbc,使开发者只需要关注 sql 语句本身,而不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程,除了提供了丰富的配置选项和强大的 SQL 映射能力外,它还支持插件机制,允许开发者在 SQL 执行的生命周期中自定义逻辑。本文将详细介绍Spring Boot项目中结合MyBatis、以及MyBatis的插件机制应用,希望本文对您工作有所帮助。


二、Spring Boot项目中结合MyBatis

2.1 数据准备

本次演示用到mysql5.7数据库进行操作

create database if not exists mybatis_demo;use mybatis_demo;create table user(id int unsigned primary key auto_increment comment 'ID',name varchar(100) comment '姓名',age tinyint unsigned comment '年龄',gender tinyint unsigned comment '性别, 1:男, 2:女',phone varchar(11) comment '手机号'
) comment '用户表';insert into user(id, name, age, gender, phone) VALUES (null,'白眉鹰王',55,'1','18800000000');
insert into user(id, name, age, gender, phone) VALUES (null,'金毛狮王',45,'1','18800000001');
insert into user(id, name, age, gender, phone) VALUES (null,'青翼蝠王',38,'1','18800000002');
insert into user(id, name, age, gender, phone) VALUES (null,'紫衫龙王',42,'2','18800000003');
insert into user(id, name, age, gender, phone) VALUES (null,'光明左使',37,'1','18800000004');
insert into user(id, name, age, gender, phone) VALUES (null,'光明右使',48,'1','18800000005');

2.2 pom.xml依赖增加

parent 是集成了父工程

mysql驱动依赖、mybatis的起步依赖、springboot启动web、 lombok 注解

<?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><parent><artifactId>spring-boot-starter-parent</artifactId><groupId>org.springframework.boot</groupId><version>2.7.10</version></parent><groupId>com.wasteland</groupId><artifactId>BlogSourceCode</artifactId><version>0.0.1-SNAPSHOT</version><name>BlogSourceCode</name><description>BlogSourceCode</description><properties><java.version>1.8</java.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding></properties><dependencies><!-- 原本是不需要单独引入mybatis的,只是这个3.4.6版本有source资源方便分析源码 --><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.4.6</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><scope>test</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.16.18</version></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.3</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.38</version></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configuration><source>1.8</source><target>1.8</target><encoding>UTF-8</encoding></configuration></plugin></plugins></build></project>

2.3 application.yml实现

数据库配置:启动类、数据库、用户名、密码

开启端口配置:默认为8080

spring:datasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/mybatis_demo?useSSL=falseusername: adminpassword: 123456
server:port: 8082mybatis:# MyBatis全局配置文件路径config-location: classpath:mybatis-config.xml
# 控制台日志输出记录,开发调试使用
logging:level:com.wasteland.blogsourcecode.mybatisdemo.mapper:debug

2.4 代码层实现

MyBatis 是一个优秀的持久层框架,支持 XML 配置和注解两种方式来实现数据库操作。下面我将分别介绍这两种实现方式,这里先介绍两种实现方式共用的代码部分。

(1)pojo层
建立实体类映射前文中数据表里的字段:

package com.wasteland.blogsourcecode.mybatisdemo.pojo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@NoArgsConstructor //无参构造
@AllArgsConstructor //有参构造
public class User {private Integer id;private String name;private Short age;private Short gender;private String phone;}

(2)Service层

package com.wasteland.blogsourcecode.mybatisdemo.service;import com.wasteland.blogsourcecode.mybatisdemo.pojo.User;import java.util.List;public interface UserService {User findById(Integer id);List<User> findAll();String DelteById(Integer id);String AddUser(User user);String UpdateUser(User user);}

实现类:UserServiceImpl.java

  1. findById(Integer id):根据用户ID查询单个用户信息。

  2. findAll():查询所有用户信息并返回用户列表。

  3. DelteById(Integer id):根据用户ID删除用户信息,成功后返回"删除成功"的消息。

  4. AddUser(User user):向数据库中添加新的用户信息,成功后返回"添加成功"的消息。

  5. UpdateUser(User user):更新用户信息,成功后返回"更新成功"的消息。

package com.wasteland.blogsourcecode.mybatisdemo.service.impl;import com.wasteland.blogsourcecode.mybatisdemo.mapper.UserMapper;
import com.wasteland.blogsourcecode.mybatisdemo.pojo.User;
import com.wasteland.blogsourcecode.mybatisdemo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper userMapper;@Overridepublic User findById(Integer id) {return userMapper.findById(id);}@Overridepublic List<User> findAll() {return userMapper.findAll();}@Overridepublic String DelteById(Integer id) {userMapper.DelteById(id);return "删除成功";}@Overridepublic String AddUser(User user) {userMapper.AddUser(user);return "添加成功";}@Overridepublic String UpdateUser(User user) {userMapper.UpdateUser(user);return "更新成功";}
}

(3)Controller层
userService 自动注入了实现类,通过实现类来进行操作。

package com.wasteland.blogsourcecode.mybatisdemo.controller;import com.wasteland.blogsourcecode.mybatisdemo.pojo.User;
import com.wasteland.blogsourcecode.mybatisdemo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;@RestController
public class UserController {@Autowiredprivate UserService userService;@RequestMapping("/findById")public User findById(Integer id){return  userService.findById(id);}@RequestMapping("/findAll")public List<User> findAll() {return userService.findAll();}@RequestMapping("/AddUser")public String AddUser(User user){return  userService.AddUser(user);}@RequestMapping("/DelteById")public String DelteById(Integer id){return  userService.DelteById(id);}@RequestMapping("/UpdateUser")public String UpdateUser(User user){return  userService.UpdateUser(user);}}

2.4.1 基于注解的Mapper

注解实现和xml配置实现这两种方式主要在于它们的mapper层不一样。

package com.wasteland.blogsourcecode.mybatisdemo.mapper;import com.wasteland.blogsourcecode.mybatisdemo.pojo.User;
import org.apache.ibatis.annotations.*;
import java.util.List;@Mapper
public interface UserMapper {@Select("select * from user where id = #{id}")User findById(Integer id);@Select("select * from user")List<User> findAll();@Delete("delete from user where id = #{id}")void DelteById(Integer id);@Insert("insert into user(name,age,gender,phone) values(#{name},#{age},#{gender},#{phone})")void AddUser(User user);@Update("update user set name = #{name},age = #{age},gender = #{gender},phone = #{phone} where id = #{id}")void UpdateUser(User user);
}

2.4.2 基于XML配置的Mapper

如果是xml配置的实现方式,需要编写xml配置文件:一个全局xml配置文件,一个mapper层接口的映射xml文件。
mapper接口

package com.wasteland.blogsourcecode.mybatisdemo.mapper;import com.wasteland.blogsourcecode.mybatisdemo.pojo.User;
import org.apache.ibatis.annotations.*;
import java.util.List;@Mapper
public interface UserMapper {User findById(Integer id);List<User> findAll();void DelteById(Integer id);void AddUser(User user);void UpdateUser(User user);
}

mapper接口映射xml配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wasteland.blogsourcecode.mybatisdemo.mapper.UserMapper"><select id="findById" resultType="com.wasteland.blogsourcecode.mybatisdemo.pojo.User">select * from user where id = #{id}</select><select id="findAll" resultType="com.wasteland.blogsourcecode.mybatisdemo.pojo.User">select * from user</select><delete id="DelteById" parameterType="int">delete from user where id = #{id}</delete><insert id="AddUser" parameterType="com.wasteland.blogsourcecode.mybatisdemo.pojo.User">insert into user(name,age,gender,phone) values(#{name},#{age},#{gender},#{phone})</insert><update id="UpdateUser" parameterType="com.wasteland.blogsourcecode.mybatisdemo.pojo.User">update user set name = #{name},age = #{age},gender = #{gender},phone = #{phone} where id = #{id}</update>
</mapper>

全局xml配置文件
mybatis-config.xml 定义了数据库连接、日志设置、别名等全局配置。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration><!-- 如下设置只是做介绍而已,实际工作按需使用 --><settings><!-- 开启延迟加载的全局开关 --><setting name="lazyLoadingEnabled" value="true"/><!-- 当启用延迟加载时,任何延迟属性都会加载其所有的关联属性 --><setting name="aggressiveLazyLoading" value="false"/><!-- 允许单条SQL返回多结果集(需要兼容的驱动) --><setting name="multipleResultSetsEnabled" value="true"/><!-- 使用列标签代替列名称 --><setting name="useColumnLabel" value="true"/><!-- 允许 JDBC 支持生成的键值 --><setting name="useGeneratedKeys" value="true"/><!-- 配置默认的执行器。SIMPLE:普通的执行器;REUSE:执行器会重用预处理语句;BATCH:执行器会重用预处理语句和批量更新 --><setting name="defaultExecutorType" value="SIMPLE"/><!-- 设置超时时间 --><setting name="defaultStatementTimeout" value="25"/><!-- 是否开启自动驼峰命名规则(camel case)映射 --><setting name="mapUnderscoreToCamelCase" value="true"/></settings><!-- 环境配置 --><environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://101.37.160.246:3306/mybatisdemo?useSSL=false&amp;serverTimezone=UTC"/><property name="username" value="admin"/><property name="password" value="123456"/></dataSource></environment></environments><!-- Mapper 映射文件 --><mappers><mapper resource="mapper/UserMapper.xml"/><!-- 添加其他映射器文件 --></mappers></configuration>

值得一提的是:这里的全局配置文件可以去掉,然后把配置都配在前文的application.yml中,能达到一样的效果。记住一下加载的顺序即可:加载 mybatis-config.xml——>加载 application.properties/yml 中的 MyBatis 配置——>应用编程式配置(通过 Java Config),它的优先级和加载顺序刚好相反。


三、MyBatis插件机制

3.1 插件概述

一般开源框架都会留一个口子去让开发者自行扩展,从而完成逻辑增强,比如说Spring框架里的BeanPostProcessor接口,开发者实现它可以在对象初始化前后做一些操作;再比如Spring Cloud框架里的PropertySourceLocator接口,开发者实现它可以做服务配置的外部加载;MyBatis同样留了拓展点,Mybatis留的拓展点我们通常称为Mybatis的插件机制,其实从本质上来说它就是一个拦截器,是JDK动态代理和责任链设计模式的结合而出的产物。

前面也说到了MyBatis插件本质上是一个拦截器,那么它能拦截哪些类和哪些方法呢?MyBatis中针对四大组件提供了扩展机制,这四个组件分别是:
在这里插入图片描述

MyBatis中所允许拦截的类和方法如下:

  • Executor【SQL 执行器】【update,query,commit,rollback】
  • StatementHandler【SQL 语法构建器对象】【prepare,parameterize,batch,update,query等】
  • ParameterHandler【参数处理器】【getParameterObject,setParameters等】
  • ResultSetHandler【结果集处理器】【handleResultSets,handleOuputParameters等】

3.2 插件的实现步骤

实现一个MyBatis插件主要分为以下几个步骤:

  1. 实现Interceptor接口
  2. 使用@Intercepts@Signature注解定义拦截点
  3. 在Mybatis的全局配置文件中注册插件

补充说明:

由于MyBatis插件是可以对 MyBatis中四大组件对象的方法进行拦截,那拦截器拦截哪个类的哪个方法如何知道,@Intercepts注解用来标识一个类为MyBatis插件,并指定该插件要拦截的方法。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {  Signature[] value();
}

@Signature注解用来指定要拦截的目标类、目标方法和方法参数。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {// 拦截的类Class<?> type();// 拦截的方法String method();// 拦截方法的参数    Class<?>[] args();
} 

3.2.1 实现Interceptor接口

首先,我们需要实现org.apache.ibatis.plugin.Interceptor接口:

import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;import java.util.Properties;@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class ExamplePlugin implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {// 在目标方法执行前的逻辑Object result = invocation.proceed();// 在目标方法执行后的逻辑return result;}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {// 设置插件的属性}
}

在上述代码中,@Intercepts 注解定义了拦截器的拦截点,type 指定了要拦截的对象类型,method 指定了要拦截的方法,args 指定了方法参数类型。intercept 方法是拦截器的核心逻辑,plugin 方法用于创建目标对象的代理,setProperties 方法用于设置插件的属性。

3.2.2 注册插件

在 MyBatis 配置文件(mybatis-config.xml)中注册插件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><plugins><plugin interceptor="com.example.plugin.ExecutionTimePlugin"/><property name="someProperty" value="someValue"/></plugin></plugins>
</configuration>

3.3 自定义插件

3.3.1 实现 SQL 执行时间记录插件

下面是一个实际的插件示例,演示如何使用插件记录 SQL 语句的执行时间。但是其实这个记录并不是特别精准,其中额外包含了 jdbc创建连接和预编译的时间。

3.3.1.1 实现 SQL 执行时间记录代码

PerformanceMonitorPlugin

package com.wasteland.blogsourcecode.mybatisdemo.plugin;import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.util.Properties;/*** @author wasteland* @create 2025-04-08*/
@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})
})
public class PerformanceMonitorPlugin implements Interceptor {private static final Logger logger = LoggerFactory.getLogger(PerformanceMonitorPlugin.class);private static final String dataFormat = "yyyy-MM-dd HH:mm:ss.SSS";// 慢查询阈值(毫秒)private long slowQueryThreshold = 1000;@Overridepublic Object intercept(Invocation invocation) throws Throwable {// 获取执行SQL的相关信息MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];Object parameter = invocation.getArgs()[1];String sqlId = mappedStatement.getId();BoundSql boundSql = mappedStatement.getBoundSql(parameter);String sql = boundSql.getSql();long startTime = System.currentTimeMillis();try {// 执行原方法return invocation.proceed();} finally {long costTime = System.currentTimeMillis() - startTime;// 记录日志if (costTime > slowQueryThreshold) {logger.warn("慢SQL执行耗时: {}ms > {}ms, SQL ID: {}, SQL: {}",costTime, slowQueryThreshold, sqlId, sql);} else {logger.debug("SQL执行耗时: {}ms, SQL ID: {}, SQL: {}", costTime, sqlId, sql);}// 可以在这里将统计信息存入数据库或监控系统}}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {// 可以从配置中读取慢查询阈值String threshold = properties.getProperty("slowQueryThreshold");if (threshold != null) {this.slowQueryThreshold = Long.parseLong(threshold);}}
}
3.3.1.2 注册SQL 执行时间记录插件

然后在全局配置文件里注册定义好的拦截器

在这里插入图片描述

3.3.1.3 查询时间测试

查询时间如下图

在这里插入图片描述

3.3.2 实现查询结果加密插件

实现对查询结果中的电话号码phone进行MD5加密。

3.3.2.1 实现查询结果加密插件代码

a. DigestUtils
加密算法实现

package com.wasteland.blogsourcecode.mybatisdemo.plugin;import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;/*** @author wasteland* @create 2025-04-08*/
public class DigestUtils {private static final char[] HEX_CHARS = "0123456789abcdef".toCharArray();/*** 计算字符串的MD5值* @param input 输入字符串* @return 32位小写MD5值*/public static String md5(String input) {return digest(input, "MD5");}/*** 计算字符串的SHA-1值* @param input 输入字符串* @return 40位小写SHA-1值*/public static String sha1(String input) {return digest(input, "SHA-1");}/*** 计算字符串的SHA-256值* @param input 输入字符串* @return 64位小写SHA-256值*/public static String sha256(String input) {return digest(input, "SHA-256");}/*** 计算字符串的SHA-512值* @param input 输入字符串* @return 128位小写SHA-512值*/public static String sha512(String input) {return digest(input, "SHA-512");}/*** 通用摘要计算方法* @param input 输入字符串* @param algorithm 算法名称* @return 摘要字符串*/private static String digest(String input, String algorithm) {try {MessageDigest md = MessageDigest.getInstance(algorithm);byte[] bytes = md.digest(input.getBytes(StandardCharsets.UTF_8));return bytesToHex(bytes);} catch (NoSuchAlgorithmException e) {throw new RuntimeException(e);}}/*** 字节数组转十六进制字符串* @param bytes 字节数组* @return 十六进制字符串*/private static String bytesToHex(byte[] bytes) {char[] hexChars = new char[bytes.length * 2];for (int i = 0; i < bytes.length; i++) {int v = bytes[i] & 0xFF;hexChars[i * 2] = HEX_CHARS[v >>> 4];hexChars[i * 2 + 1] = HEX_CHARS[v & 0x0F];}return new String(hexChars);}/*** Base64编码* @param input 输入字符串* @return Base64编码结果*/public static String base64Encode(String input) {return Base64.getEncoder().encodeToString(input.getBytes(StandardCharsets.UTF_8));}/*** Base64解码* @param input Base64编码字符串* @return 解码后的原始字符串*/public static String base64Decode(String input) {byte[] decodedBytes = Base64.getDecoder().decode(input);return new String(decodedBytes, StandardCharsets.UTF_8);}/*** 计算字符串的HMAC-SHA256签名* @param data 要签名的数据* @param key 密钥* @return HMAC-SHA256签名*/public static String hmacSha256(String data, String key) {try {javax.crypto.Mac mac = javax.crypto.Mac.getInstance("HmacSHA256");mac.init(new javax.crypto.spec.SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));byte[] result = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));return bytesToHex(result);} catch (Exception e) {throw new RuntimeException(e);}}
}

b. EncryptingResultSetHandler
定义EncryptingResultSetHandler对结果集进行加密处理。

package com.wasteland.blogsourcecode.mybatisdemo.plugin;import com.wasteland.blogsourcecode.mybatisdemo.pojo.User;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.resultset.ResultSetHandler;import java.sql.CallableStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;/*** @author wasteland* @create 2025-04-08*/
public class EncryptingResultSetHandler implements ResultSetHandler {private final ResultSetHandler resultSetHandler;public EncryptingResultSetHandler(ResultSetHandler resultSetHandler) {this.resultSetHandler = resultSetHandler;}@Overridepublic List<Object> handleResultSets(Statement stmt) throws SQLException {// 使用委托对象处理结果集List<Object> result = this.resultSetHandler.handleResultSets(stmt);// 假设我们有一个User对象,并且知道密码字段名为"password"// 对密码进行“加密”操作(这里只是示例,实际应该是解密)if (result instanceof List) {List<?> resultList = (List<?>) result;for (Object item : resultList) {if (item instanceof User) {User user = (User) item;String encryptedPassword = encryptPassword(user.getPhone());user.setPhone(encryptedPassword);}}}return result;}private String encryptPassword(String password) {// 这里应该是你的加密逻辑,为了演示,我们使用一个简单的替换逻辑return DigestUtils.md5(password);}@Overridepublic <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException {return null;}@Overridepublic void handleOutputParameters(CallableStatement cs) throws SQLException {}// 其他方法...
}

c. ResultSetHandlerHandleResultSetsPlugin
最后定义拦截器,对ResultSetHandler#handleResultSets进行拦截。

package com.wasteland.blogsourcecode.mybatisdemo.plugin;import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.*;
import org.springframework.stereotype.Component;import java.sql.Statement;
import java.util.Properties;/*** @author wasteland* @create 2025-04-08*/
@Component
@Intercepts({@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})})
public class ResultSetHandlerHandleResultSetsPlugin implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {Statement stmt = (Statement) invocation.getArgs()[0];// 创建自定义的 EncryptingResultSetHandlerEncryptingResultSetHandler customResultSetHandler = new EncryptingResultSetHandler((ResultSetHandler) invocation.getTarget());
//        Object result = invocation.proceed();// 使用自定义的 EncryptingResultSetHandler 重新处理结果集return customResultSetHandler.handleResultSets(stmt);}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {// 可以为插件配置属性}
}
3.3.2.2 注册查询结果加密插件

然后在全局配置文件里注册定义好的拦截器。

在这里插入图片描述

3.3.2.3 加密结果测试

最后加密效果如下图

在这里插入图片描述

3.4 插件机制源码分析

在了解了插件机制原理和如何实现自定义插件后,我们这时候去深入到源码去分析,在分析过程中带着3个问题看:对象是如何实例化的? 插件的实例对象如何添加到拦截器链中的? 组件对象的代理对象是如何产生的?

3.4.1 插件配置信息加载与解析

我们定义好了一个拦截器,那我们怎么告诉MyBatis呢?我们会把它注册在全局配置文件中。

在这里插入图片描述

对应的解析代码发生在XMLConfigBuilder#pluginsElement

private void pluginElement(XNode parent) throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) {// 获取拦截器String interceptor = child.getStringAttribute("interceptor");// 获取配置的Properties属性Properties properties = child.getChildrenAsProperties();// 根据配置文件中配置的插件类的全限定名 进行反射初始化Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();// 将属性添加到Intercepetor对象interceptorInstance.setProperties(properties);// 添加到配置类的InterceptorChain属性,InterceptorChain类维护了一个List<Interceptor>configuration.addInterceptor(interceptorInstance);}}
}

主要做了以下工作:

  1. 遍历解析 plugins 标签下每个 plugin 标签
  2. 根据解析的类信息创建 Interceptor 对象
  3. 调用 setProperties 方法设置属性
  4. 将拦截器添加到 Configuration 类的 InterceptorChain 拦截器链中

对应的时序图如下:

在这里插入图片描述

3.4.2 代理对象的生成

前文也说过,插件机制可以MyBatis中四大组件进行方法拦截,接下来来看具体如何方法拦截生成了代理对象。

Executor 代理对象(Configuration#newExecutor

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {executorType = executorType == null ? defaultExecutorType : executorType;Executor executor;if (ExecutorType.BATCH == executorType) {executor = new BatchExecutor(this, transaction);} else if (ExecutorType.REUSE == executorType) {executor = new ReuseExecutor(this, transaction);} else {executor = new SimpleExecutor(this, transaction);}if (cacheEnabled) {executor = new CachingExecutor(executor);}// 生成Executor代理对象逻辑return (Executor) interceptorChain.pluginAll(executor);
}

ParameterHandler 代理对象(Configuration#newParameterHandler

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject,BoundSql boundSql) {// 创建ParameterHandler// 生成ParameterHandler代理对象逻辑 ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement,parameterObject, boundSql);return (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
}

ResultSetHandler 代理对象(Configuration#newResultSetHandler

public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds,ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) {ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler,resultHandler, boundSql, rowBounds);// 生成ResultSetHandler代理对象逻辑return (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
}

StatementHandler 代理对象(Configuration#newStatementHandler

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement,Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {// => 创建路由功能的StatementHandler,根据MappedStatement中的StatementType创建对应的 StatementHandlerStatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject,rowBounds, resultHandler, boundSql);return (StatementHandler) interceptorChain.pluginAll(statementHandler);
}

通过查看源码会发现,所有代理对象的生成都是通过InterceptorChain#pluginAll方法来创建的,InterceptorChain#pluginAll内部通过遍历 Interceptor#plugin 方法来创建代理对象,并将生成的代理对象又赋值给 target,如果存在多个拦截器的话,生成的代理对象会被另一个代理对象所代理,从而形成一个代理链,执行的时候,依次执行所有拦截器的拦截逻辑代码,再跟进去。

// org.apache.ibatis.plugin.InterceptorChain
public Object pluginAll(Object target) {for (Interceptor interceptor : interceptors) {target = interceptor.plugin(target);}return target;
}// org.apache.ibatis.plugin.Interceptor
@Override
public Object plugin(Object target) {return Plugin.wrap(target, this);
}public static Object wrap(Object target, Interceptor interceptor) {// 1.解析该拦截器所拦截的所有接口及对应拦截接口的方法Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);Class<?> type = target.getClass();// 2.获取目标对象实现的所有被拦截的接口Class<?>[] interfaces = getAllInterfaces(type, signatureMap);// 3.目标对象有实现被拦截的接口,生成代理对象并返回if (interfaces.length > 0) {// 通过JDK动态代理的方式实现return Proxy.newProxyInstance(type.getClassLoader(),interfaces,new Plugin(target, interceptor, signatureMap));}// 目标对象没有实现被拦截的接口,直接返回原对象return target;}

对应的时序图如下:

在这里插入图片描述

3.4.3 拦截逻辑的执行

MyBatis 框架中执行Executor、ParameterHandler、ResultSetHandler和StatementHandler中的方法时真正执行的是代理对象对应的方法,所以执行方法实际是调用InvocationHandler#invoke方法(Plugin类实现InvocationHandler接口),下面是Plugin#invoke方法

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {Set<Method> methods = signatureMap.get(method.getDeclaringClass());if (methods != null && methods.contains(method)) {return interceptor.intercept(new Invocation(target, method, args));}return method.invoke(target, args);} catch (Exception e) {throw ExceptionUtil.unwrapThrowable(e);}
}

注意:同一个组件对象的同一个方法是可以被多个拦截器进行拦截的,配置在最前面的拦截器最先被代理,但是执行的时候却是最外层的先执行,即包裹顺序和执行顺序相反

在这里插入图片描述

3.5 插件机制的应用场景与注意事项

应用场景

  • SQL 日志记录:记录 SQL 语句及其执行时间,方便调试和优化。
  • 参数验证和修改:在 SQL 执行前对参数进行验证和修改,确保数据的正确性和安全性。
  • 查询结果处理:对查询结果进行处理,如数据脱敏、格式转换等。
  • 性能监控:监控 SQL 执行时间、执行次数等,帮助优化系统性能。

注意事项

  • 插件的实现要尽量简洁高效,避免增加额外的性能开销。
  • 插件的配置要合理,避免过度使用插件导致代码复杂度增加。
  • 插件的执行顺序是根据配置文件中的顺序决定的,可以根据需要调整插件的执行顺序。

四、总结

本文介绍了SpringBoot项目结合MyBatis的快速构建方式,然后介绍了MyBatis 插件的实现步骤、插件机制的原理以及插件机制实战。MyBatis 插件机制提供了一种灵活的方式,允许开发者在 SQL 执行的各个阶段插入自定义逻辑,极大地增强了 MyBatis 的扩展能力。本文仅介绍了插件机制的源码,在后面的文章中,会详细介绍MyBatis的核心源码,分析其核心组件的作用以及组件的执行时机。


创作不易,如果有帮助到你的话请给点个赞吧!我是Wasteland,下期文章再见!

在这里插入图片描述

相关文章:

MyBatis:SpringBoot结合MyBatis、MyBatis插件机制的原理分析与实战

&#x1fa81;&#x1f341; 希望本文能给您带来帮助&#xff0c;如果有任何问题&#xff0c;欢迎批评指正&#xff01;&#x1f405;&#x1f43e;&#x1f341;&#x1f425; 文章目录 一、背景二、Spring Boot项目中结合MyBatis2.1 数据准备2.2 pom.xml依赖增加2.3 applicat…...

【数据结构】3.单链表专题

文章目录 单链表的实现0、准备工作1、链表的打印2、尾插3、头插4、尾删5、头删6、查找指定数据的位置7、在指定位置之前插入数据8、在指定位置之后插入数据9、删除指定位置的数据10、删除指定位置之后的数据11、单链表的销毁 单链表的实现 什么是单链表呢&#xff1f;单链表可…...

**Microsoft Certified Professional(MCP)** 认证考试

1. MCP 认证考试概述 MCP&#xff08;Microsoft Certified Professional&#xff09;是微软认证体系中的一项入门级认证&#xff0c;旨在验证考生在微软产品和技术&#xff08;如 Windows Server、Azure、SQL Server、Microsoft 365&#xff09;方面的技能。2020 年&#xff0…...

C++学习之游戏服务器开发git命令

目录 1.服务器需求分析 2.面向框架编程简介 3.ZINX框架初始 4.回显标准输入 5.VS结合GIT 6.完善readme范例 7.添加退出功能 8.添加命令处理类 9.添加日期前缀思路 10.添加日期前缀功能 1.服务器需求分析 zinx 描述 zinx 框架是一个处理多路 IO 的框架。在这个框架中提…...

Maven 多仓库与镜像配置全攻略:从原理到企业级实践

Maven 多仓库与镜像配置全攻略&#xff1a;从原理到企业级实践 一、核心概念&#xff1a;Repository 与 Mirror 的本质差异 在 Maven 依赖管理体系中&#xff0c;repository与mirror是构建可靠依赖解析链的两大核心组件&#xff0c;其核心区别如下&#xff1a; 1. Repositor…...

无锁队列--知识分享

目录 无锁队列 无锁队列是什么 为什么需要无锁队列 队列的类型 无锁队列的分类 ringbuffer&#xff08;SPSC&#xff09; ret_ring&#xff08;MPMC&#xff09; 无锁队列 无锁队列是什么 无锁队列通过原子操作来实现线程安全的队列&#xff0c;属于非阻塞队列 …...

Flask快速入门

1.安装 Flask 要使用 Flask&#xff0c;你需要先安装它。打开终端&#xff0c;运行以下命令&#xff1a; pip install flask 2.创建文件结构 3.app.py from flask import Flask&#xff1a;从 flask 库中导入 Flask 类。app Flask(__name__)&#xff1a;创建一个 Flask 应…...

LeetCode -- Flora -- edit 2025-04-16

1.两数之和 1. 两数之和 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案&#xff0c;并且你不能使用两次相同的元素。 你可以按…...

【Unity笔记】实现可视化配置的Unity按键输入管理器(按下/长按/松开事件 + UnityEvent绑定)

【Unity笔记】实现可视化配置的Unity按键输入管理器 适用于角色控制、技能触发的Unity按键输入系统&#xff0c;支持UnityEvent事件绑定、长按/松开监听与启用开关 一、引言 在 Unity 游戏开发中&#xff0c;处理键盘输入是最常见的交互方式之一。尤其是角色控制、技能释放、菜…...

SpringMVC学习(请求与响应。常见参数类型接收与响应。@RequestParam、@RequestBody的使用)(详细示例)

目录 一、请求与响应。(RequestMapping) &#xff08;1&#xff09;使用注解RequestMapping对业务模块区分。 StudentController。 TeacherController。 &#xff08;2&#xff09;Apifox请求与响应。 "/student/login"。 "/teacher/login"。 二、常见参数…...

springboot 切面拦截自定义注解

使用切面来拦截被该注解标记的方法 依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId> </dependency>1. 定义自定义注解 import java.lang.annotation.ElementType; imp…...

QT —— 信号和槽(自定义信号和槽函数)

QT —— 信号和槽&#xff08;自定义信号和槽函数&#xff09; 自定义信号和槽函数一、自定义信号函数规范1. 声明位置2. 返回值与实现3. 参数与重载 二、自定义槽函数规范1. 声明位置&#xff08;不同版本差异&#xff09;2. 返回值与实现3. 参数与重载 三、信号发射规范1. 基…...

朋克编码以潮玩语言讲述中国文化|益民艺术馆展演东方潮力

朋克编码于广州益民艺术馆推出“艺术家潮玩”系列主题展&#xff0c;将传统文化元素融入 潮玩设计&#xff0c;并通过数字科技与空间场景创新&#xff0c;讲述中国故事、传递东方美学。 展览作品结合太空猿等原创 IP 与“中式元素”视觉符号&#xff0c;引发观众情感共鸣。“我…...

TA学习之路——2.2 模型与材质基础

1.模型基础 1.1 图形渲染管线 1.2 模型实现的原理 点连成线,线构成面,面构成模型。 1.2 UV UV例如一个正方体的纸盒展开,平铺在一个二维的坐标系中。 模型的每一个顶点在三维空间和二维空间中都能一 一对应。在二维坐标系中的顶点对应的位置就是顶点的纹理坐标。 因此…...

helm的go模板语法学习

1、helm chart 1.0、什么是helm&#xff1f; 介绍&#xff1a;就是个包管理器。理解为java的maven、linux的yum就好。 安装方法也可参见官网&#xff1a; https://helm.sh/docs/intro/install 通过前面的演示我们知道&#xff0c;有了helm之后应用的安装、升级、查看、停止都…...

Windows 图形显示驱动开发-WDDM 1.2功能—Windows 8 中的 DirectX 功能改进(一)

Windows 8包括 Microsoft DirectX 功能改进&#xff0c;使开发人员、最终用户和系统制造商受益。 功能改进在以下几个方面&#xff1a; 像素格式 (5551、565、4444) &#xff1a;在低功耗硬件配置下&#xff0c;DirectX 应用程序的性能更高。双精度着色器功能&#xff1a;高级…...

软件测试|App测试面试相关问题(2)

一、App 稳定怎么做的?Monkey 怎么用(App 稳定测试)? 稳定性这块&#xff0c;我们当时用的是SDK 自动的一个Monkey 工具进行测试的&#xff0c;其实Monkey工具主要通过模拟用户发送伪随机时间去操作软件&#xff0c;通过执行Monkey 命令&#xff0c;它会自动出报告&#xff…...

模拟电路需要了解的一些基础知识(部分)

基本的单路元件 1. 电阻&#xff1b;特性&#xff1a;阻碍电流流动&#xff0c;消耗电能并转化为热能&#xff08;遵循欧姆定律&#xff09;。是无源元件&#xff0c;应用&#xff1a;限流、分压、发热等&#xff1b; 2. 电容&#xff1b;特性&#xff1a;存储电荷和电场能&am…...

[特殊字符] MySQL MCP 开发实战:打造智能数据库操作助手

&#x1f4a1; 简介&#xff1a;本文详细介绍如何利用MCP&#xff08;Model-Control-Panel&#xff09;框架开发MySQL数据库操作工具&#xff0c;使AI助手能够直接执行数据库操作。 &#x1f4da; 目录 引言MCP框架简介项目架构设计开发环境搭建核心代码实现错误处理策略运行和…...

软考备考(一)学习笔记

一、软考介绍 计算机软考,计算机技术与软件专业技术资格(水平)考试 一年考试两次: 一次上旬(5月底),下旬一次(11月初) 初级资格:程序员 中级资格: 软件设计师 高级资格: 系统架构设计师 初级: 科目一:计算机硬软件基础知识 150min 笔试、选择 科目二:程序设…...

Linux环境变量

目录 环境变量 基本概念 常见环境变量 查看环境变量方法 测试PATH 测试HOME 和环境变量相关的命令 环境变量的组织方式 通过代码如何获取环境变量 通过系统调用获取或设置环境变量 ​编辑 环境变量通常是具有全局属性的 实验 环境变量 基本概念 环境变量(environment variables…...

跨浏览器书签同步方案:WebDAV + Floccus插件实操指南

FloccusWebDAV能够帮助把多个不同浏览器书签统一私有化管理&#xff0c;以下是介绍&#xff1a; Floccus 是一个允许用户在不同浏览器和设备之间私密同步书签的扩展&#xff0c;开源地址&#xff1a;https://github.com/floccusaddon/floccusWebDAV是一种基于HTTP的协议&#…...

银河麒麟系统 达梦8 安装 dlask 框架后端环境

适配的一套环境为 dmPython2.5.8 dmSQLAlchemy1.4.39 Flask2.0.3 Flask-Cors3.0.10 Flask-SQLAlchemy2.5.1 SQLAlchemy1.4.54 Werkzeug2.2.2其中 # sqlalchemy-dm1.4.39 通过dmdbms目录内文件进行源码安装 (MindSpore) [ma-user python]$pwd /home/syl/dmdbms/drivers/python…...

代码随想录算法训练营Day31

力扣738.单调递增的数字【medium】 力扣968.监控二叉树【hard】 一、力扣738.单调递增的数字【medium】 题目链接&#xff1a;力扣738.单调递增的数字 视频链接&#xff1a;代码随想录 1、思路 先将整数转为字符串变成可迭代对象&#xff0c;再转为列表从后向前遍历&#xff…...

LeetCode Hot100 刷题笔记(10)—— ACM格式输入输出练习

目录 Trick: 1. 只有输出 2. 单组_AB 3. 多组_AB_EOF形式 4. 多组_AB_T组形式 5. 多组_AB_零尾形式 6. 单组_一维数组 7. 多组_二维数组_T组形式 8. 单组_二维数组 9. 多组_二维数组_T组形式 10. 单组_字符串 11. 多组_字符串_T组形式 12. 单组_二维字符数组 13. 多组_带空格的…...

iPaaS集成平台在制造业有哪些应用场景

在制造业迈向智能化的进程中&#xff0c;“数据不通”“系统割裂”“响应迟缓”等问题如同隐形的锁链&#xff0c;束缚着企业转型升级的步伐。面对设备、系统、供应链之间错综复杂的连接需求&#xff0c;传统定制化开发周期长、成本高&#xff0c;难以满足快速变化的业务需求。…...

【Docker项目实战】使用Docker部署Gitblit服务器

【Docker项目实战】使用Docker部署Gitblit服务器 一、Gitblit介绍1.1 Gitblit 介绍1.2 主要特点 二、本次实践规划2.1 本地环境规划2.2 本次实践介绍 三、本地环境检查3.1 检查Docker服务状态3.2 检查Docker版本3.3 检查docker compose 版本 四、下载Gitblit镜像五、部署Gitbli…...

基于瑞芯微RK3562 四核 ARM Cortex-A53 + 单核 ARM Cortex-M0——Linux应用开发手册

前 言 本文主要介绍TL3562-MiniEVM评估板的AMP(Asymmetric Multi-processing)开发案例,适用开发环境如下: Windows开发环境:Windows 7 64bit、Windows 10 64bit Linux开发环境:VMware16.2.5、Ubuntu20.04.6 64bit U-Boot:U-Boot-2017.09 Kernel:Linux-5.10.209 Lin…...

并查集(力扣1971)

并查集的功能&#xff1a;判断两个节点是否在同一个集合中/将两个节点加入同一集合中。模板如下&#xff1a; #include<iostream> #include<vector> using namespace std; const int n 1e6 5;//视题目具体节点数量而定&#xff0c;比节点数量稍大即可 vector<…...

Pinpoint - 大型分布式系统的 APM(应用性能管理)工具

文章目录 一、关于 Pinpoint最新版本&#xff08;2024/10/23&#xff09;-- v3.0.1PHP, PYTHON 二、概述支持的模块 一、关于 Pinpoint Pinpoint 是一个用于大型分布式系统的 APM&#xff08;应用性能管理&#xff09;工具&#xff0c;由 Java / PHP/PYTHON 编写。 受 Dapper …...

高级java每日一道面试题-2025年4月10日-微服务篇[Nacos篇]-Nacos的服务健康检查机制是如何工作的?

如果有遗漏,评论区告诉我进行补充 面试官: Nacos的服务健康检查机制是如何工作的&#xff1f; 我回答: Nacos 服务健康检查机制详解 Nacos 的服务健康检查机制是确保服务高可用性和可靠性的核心功能之一。它通过定期检测服务实例的状态来判断它们是否健康&#xff0c;并据此…...

JavaScript:表单及正则表达式验证

今天我要介绍的是在JavaScript中关于表单验证内容的知识点介绍&#xff1a; 关于表单验证&#xff0c;我接下来则直接将内容以及效果显示出来并作注解&#xff0c;这样可以清晰看见这个表达验证的妙用&#xff1a; <form id"ff" action"https://www.baidu.…...

Android 应用数据分布目录结构解析

在Android系统中&#xff0c;/data目录下的几个关键路径有不同的用途&#xff0c;主要涉及应用数据存储和用户媒体文件管理,具体如下&#xff1a; 1. /data/user/0/ 路径别名&#xff1a;等同于 /data/data/&#xff08;旧路径&#xff0c;仍兼容&#xff09;。 用途&#xff…...

Spring Boot 中的自动配置原理

2025/4/6 向全栈工程师迈进&#xff01; 一、自动配置 所谓的自动配置原理就是遵循约定大约配置的原则&#xff0c;在boot工程程序启动后&#xff0c;起步依赖中的一些bean对象会自动的注入到IOC容器中。 在讲解Spring Boot 中bean对象的管理的时候&#xff0c;我们注入bean对…...

Java内部类详解

在Java中&#xff0c;内部类是一种强大的特性&#xff0c;允许将一个类定义在另一个类的内部。内部类提供了更好的封装性&#xff0c;能够访问外部类的成员&#xff0c;并常用于实现事件监听、适配器模式等场景。本文将深入探讨四种内部类&#xff1a;成员内部类、静态内部类、…...

台账自动统计——餐饮物资管理台账——仙盟共创平台——未来之窗

分类表 自动统计 创作不易&#xff0c;使用地址&#xff1a;https://mp.weixin.qq.com/s/Ok3wuSYAPhd-6N8DrK7jwg 餐饮物资管理台账自动统计能够实时、精准地呈现库存数量。通过对采购入库、领用出库、盘点盈亏等数据的自动记录与计算&#xff0c;管理者随时可获取准确库存信息…...

Function Calling是什么?

Function Calling&#xff08;函数调用&#xff09;是大型语言模型&#xff08;如GPT、Claude等&#xff09;中的一项关键功能&#xff0c;允许模型根据用户输入的需求&#xff0c;智能识别并返回结构化函数调用请求&#xff0c;从而与外部工具、API或代码进行交互。以下是详细…...

[学习] C语言数据结构深度解析:八种树结构与应用场景详解(代码示例)

C语言数据结构深度解析&#xff1a;八种树结构与应用场景详解 好吧&#xff0c;今天我们来研究树&#xff01;C语言中的树。 树是计算机科学中最重要的非线性数据结构之一&#xff0c;广泛应用于操作系统、数据库、编译器、图形学等领域。本文将通过C语言代码示例&#xff0c…...

【从零实现高并发内存池】Page Cache 从理解设计到全面实现

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;博客仓库&#xff1a;https://gitee.com/JohnKingW/linux_test/tree/master/lesson &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &…...

6 CMD 与 PowerShell 指令大全、C 程序终端运行、字符编码切换指南

1 CMD 与 PowerShell 常用指令 在命令行环境中高效运行程序&#xff0c;掌握终端的基本操作命令至关重要。无论是 Windows 系统下的 CMD&#xff08;命令提示符&#xff09;还是 PowerShell&#xff0c;它们都配备了一系列实用的命令&#xff0c;助力我们管理文件、执行程序以及…...

为啥mac日历打不开浏览器

问题 换了新电脑后&#xff0c;mac上的日历总是没法同步google日历信息&#xff0c;导致经常错过会议 尝试mac日历上添加账户&#xff0c;结果到了打开浏览器缓解总是卡住&#xff0c;打不开浏览器&#xff08;safari&#xff09; 解决 检查默认浏览器设置确保已将所需的浏览…...

spring:注解@PostConstruct、@PreDestroy

这两个注解的功能类似标签中的init-method和destroy-method。分别在构造方法调用之后和实例释放资源之前被调用。 注解类&#xff1a; package com.annotation.dao.impl;import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation…...

Androidjetpack之viewmodel的原理分析

前言 viewmodel是jetpack中比较重要的一个组件。如果还没有学习viewmodel不知道怎么写代码什么的&#xff0c;可以看一下我之前写得文章。 jetpack之ViewModel的简单使用https://blog.csdn.net/i_xiang_la_shi/article/details/147218033?fromshareblogdetail&sharetype…...

springboot启动动态定时任务

1.自定义定时任务线程池 package com.x.devicetcpserver.global.tcp.tcpscheduler;import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotatio…...

Dify智能体平台源码二次开发笔记(7) - 优化知识库pdf识别(2)

目录 前言 设计方案 代码具体优化 前言 补充前篇的一些优化。 场景是识别pdf文档&#xff0c;但还需要把pdf文档中的图片也保存下来&#xff0c;在知识库增强检索的时候&#xff0c;直接可以显示图片。 设计方案 1、保存知识库中的图片 2、存入我们的文件服务器中&#xff0…...

Linux——进程通信

我们知道&#xff0c;进程具有独立性&#xff0c;各进程之间互不干扰&#xff0c;但我们为什么还要让其联系&#xff0c;建立通信呢&#xff1f;比如&#xff1a;数据传输&#xff0c;资源共享&#xff0c;通知某个事件&#xff0c;或控制某个进程。因此&#xff0c;让进程间建…...

AF3 create_alignment_db_sharded脚本create_shard函数解读

AlphaFold3 create_alignment_db_sharded 脚本在源代码的scripts/alignment_db_scripts文件夹下。 该脚本中的 create_shard 函数的功能是将一部分链&#xff08;shard_files&#xff09;中的所有对齐文件写入一个 .db 文件&#xff0c;并返回这些链的索引信息&#xff08;字节…...

Jetpack Compose 实现主页面与局部页面独立刷新的最佳实践

在 Jetpack Compose 开发中&#xff0c;我们经常遇到这样的需求&#xff1a;主页面包含局部页面&#xff0c;主页面刷新时需要更新局部页面&#xff0c;同时局部页面也需要能独立刷新。本文将介绍几种优雅的实现方案。 核心需求 主页面刷新时能触发局部页面更新局部页面能独立…...

KingbaseES之数据库审计

项目提出要配置数据库审计,来满足分保测评得要求.正好最近做过审计测试,还原下审计配置. 一.开启审计 [kingbaserack1 ~]$ vi /data/data_mysql/kingbase.conf [kingbaserack1 ~]$ sys_ctl -D /data/data_mysql restart grep -r shared_preload_libraries /data/data_mysql/k…...

类的加载过程

1、加载 双亲委派模型&#xff08;启动类》扩展类》应用类&#xff09; 2、验证 文件格式验证&#xff08;Class 文件格式检查&#xff09;元数据验证&#xff08;字节码语义检查&#xff09;字节码验证&#xff08;程序语义检查&#xff09;符号引用验证&#xff08;类的正确…...