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

手写Mybatis:第13章-通过注解配置执行SQL语句

文章目录

  • 一、目标:注解配置执行SQL
  • 二、设计:注解配置执行SQL
  • 三、实现:注解配置执行SQL
    • 3.1 工程结构
    • 3.2 注解配置执行SQL类图
    • 3.3 脚本语言驱动器
      • 3.3.1 脚本语言驱动器接口
      • 3.3.2 XML语言驱动器
    • 3.4 注解配置构建器
      • 3.4.1 定义增删改查注解
      • 3.4.2 注解配置解析
    • 3.5 Mapper XML解析调用
      • 3.5.1 修改配置项
      • 3.5.2 修改映射构建器助手
      • 3.5.3 XML配置构建器
      • 3.5.4 映射器注册机
  • 四、测试:注解配置执行SQL
    • 4.1 测试环境配置
      • 4.1.1 配置数据源配置
      • 4.1.2 修改IUserDao用户持久层
    • 4.2 单元测试
      • 4.2.1 插入测试
      • 4.2.2 查询测试(多条数据)
      • 4.2.3 修改测试
      • 4.2.4 删除测试
  • 五、总结:注解配置执行SQL

一、目标:注解配置执行SQL

💡 扩展ORM框架功能,实现配置方法注解的方式处理增删改查操作

在这里插入图片描述

  • 日常使用 Mybatis 框架,对于 XML 和注解配置也都是可以共用的,主要基于配置文件 mappers 中,引入的哪类资源。
    • XML 配置:resource="mapper/User_Mapper.xml"
    • 注解配置:class="com.lino.mybatis.test.dao.IUserDao"
  • 两种使用方式的共性:都需要基于这些提供的信息,获取出:SQL语句、入参、出参等,并把这些信息包装成一个整体的映射语句,串联整个流程。

二、设计:注解配置执行SQL

💡 如何通过注解方式处理SQL的配置?

  • 将注解的解析部分与 Mapper XML 进行策略处理,对于不同类型的使用方式,做到解析结果一致。
    • 那么后续的处理 SQL 执行和结果封装等流程就可以正常执行了。
  • 这部分代码逻辑变动,主要以 XMLConfigBuilder 配置构建器的 Mapper 解析开始。
    • 因为只有从这里开始才是判断一个 Mapper 到底是使用了 XML 配置还是注解配置。

在这里插入图片描述

  • 基于这样不同的 mapper 引入的类型信息,则需要在 XMLConfigBuilder 配置构建器,解析 Mapper 元素信息时进行判断,按照不同的获取类型,resource、class 进行不同的解析处理。
  • 只要在解析处理中把这两部分的差异做适配处理,最终后续的流程就可以正常进行。

在这里插入图片描述

  • 首先以加载解析 XML 为入口,处理不同配置 SQL 方式的解析操作。
    • 就是结合原有解析 Mapper XML 配置扩展注解 SQL 配置方式。
  • 具体的处理过程:主要在 XMLConfigBuilder#mapperElement 配置构建器解析 mapper 配置时,处理关于注解的解析部分。
    • 这些注解目前添加 @Select、@Insert、@Update、@Delete
    • 处理解析注解则会向 Configuration 配置项中添加 ResultMap、MappedStatement 信息。
    • 用于后续获取 Mapper 调用 DefaultSqlSession 对应的执行方法时,拿到映射器语句配置进行 SQL 执行和结果封装。

三、实现:注解配置执行SQL

3.1 工程结构

mybatis-step-12
|-src|-main|	|-java|		|-com.lino.mybatis|			|-annotations|			|	|-Delete.java|			|	|-Insert.java|			|	|-Select.java|			|	|-Update.java|			|-binding|			|	|-MapperMethod.java|			|	|-MapperProxy.java|			|	|-MapperProxyFactory.java|			|	|-MapperRegistry.java|			|-builder|			|	|-annotations|			|	|	|-MapperAnnotationBuilder.java|			|	|-xml|			|	|	|-XMLConfigBuilder.java|			|	|	|-XMLMapperBuilder.java|			|	|	|-XMLStatementBuilder.java|			|	|-BaseBuilder.java|			|	|-MapperBuilderAssistant.java|			|	|-ParameterExpression.java|			|	|-SqlSourceBuilder.java|			|	|-StaticSqlSource.java|			|-datasource|			|	|-druid|			|	|	|-DruidDataSourceFacroty.java|			|	|-pooled|			|	|	|-PooledConnection.java|			|	|	|-PooledDataSource.java|			|	|	|-PooledDataSourceFacroty.java|			|	|	|-PoolState.java|			|	|-unpooled|			|	|	|-UnpooledDataSource.java|			|	|	|-UnpooledDataSourceFacroty.java|			|	|-DataSourceFactory.java|			|-executor|			|	|-parameter|			|	|	|-ParameterHandler.java|			|	|-result|			|	|	|-DefaultResultContext.java|			|	|	|-DefaultResultHandler.java|			|	|-resultset|			|	|	|-DefaultResultSetHandler.java|			|	|	|-ResultSetHandler.java|			|	|	|-ResultSetWrapper.java|			|	|-statement|			|	|	|-BaseStatementHandler.java|			|	|	|-PreparedStatementHandler.java|			|	|	|-SimpleStatementHandler.java|			|	|	|-StatementHandler.java|			|	|-BaseExecutor.java|			|	|-Executor.java|			|	|-SimpleExecutor.java|			|-io|			|	|-Resources.java|			|-mapping|			|	|-BoundSql.java|			|	|-Environment.java|			|	|-MappedStatement.java|			|	|-ParameterMapping.java|			|	|-ResultMap.java|			|	|-ResultMapping.java|			|	|-SqlCommandType.java|			|	|-SqlSource.java|			|-parsing|			|	|-GenericTokenParser.java|			|	|-TokenHandler.java|			|-reflection|			|	|-factory|			|	|	|-DefaultObjectFactory.java|			|	|	|-ObjectFactory.java|			|	|-invoker|			|	|	|-GetFieldInvoker.java|			|	|	|-Invoker.java|			|	|	|-MethodInvoker.java|			|	|	|-SetFieldInvoker.java|			|	|-property|			|	|	|-PropertyNamer.java|			|	|	|-PropertyTokenizer.java|			|	|-wrapper|			|	|	|-BaseWrapper.java|			|	|	|-BeanWrapper.java|			|	|	|-CollectionWrapper.java|			|	|	|-DefaultObjectWrapperFactory.java|			|	|	|-MapWrapper.java|			|	|	|-ObjectWrapper.java|			|	|	|-ObjectWrapperFactory.java|			|	|-MetaClass.java|			|	|-MetaObject.java|			|	|-Reflector.java|			|	|-SystemMetaObject.java|			|-scripting|			|	|-defaults|			|	|	|-DefaultParameterHandler.java|			|	|	|-RawSqlSource.java|			|	|-xmltags|			|	|	|-DynamicContext.java|			|	|	|-MixedSqlNode.java|			|	|	|-SqlNode.java|			|	|	|-StaticTextSqlNode.java|			|	|	|-XMLLanguageDriver.java|			|	|	|-XMLScriptBuilder.java|			|	|-LanguageDriver.java|			|	|-LanguageDriverRegistry.java|			|-session|			|	|-defaults|			|	|	|-DefaultSqlSession.java|			|	|	|-DefaultSqlSessionFactory.java|			|	|-Configuration.java|			|	|-ResultContext.java|			|	|-ResultHandler.java|			|	|-RowBounds.java|			|	|-SqlSession.java|			|	|-SqlSessionFactory.java|			|	|-SqlSessionFactoryBuilder.java|			|	|-TransactionIsolationLevel.java|			|-transaction|			|	|-jdbc|			|	|	|-JdbcTransaction.java|			|	|	|-JdbcTransactionFactory.java|			|	|-Transaction.java|			|	|-TransactionFactory.java|			|-type|			|	|-BaseTypeHandler.java|			|	|-IntegerTypeHandler.java|			|	|-JdbcType.java|			|	|-LongTypeHandler.java|			|	|-StringTypeHandler.java|			|	|-TypeAliasRegistry.java|			|	|-TypeHandler.java|			|	|-TypeHandlerRegistry.java|-test|-java|	|-com.lino.mybatis.test|	|-dao|	|	|-IUserDao.java|	|-po|	|	|-User.java|	|-ApiTest.java|-resources|-mybatis-config-datasource.xml

3.2 注解配置执行SQL类图

在这里插入图片描述

  • XMLConfigBuilder 配置构建器是解析 Mapper 的入口。
    • 以这条流程线中的方法 mapperElement 开始,处理 XML 解析的基础上,扩展注解的解析处理。
    • 通过从 xml 读取到的 class 配置,通过 Configuration 配置项类的添加 Mapper 方法,启动解析注解类语句的操作。
    • 也就是在 MapperRegistry 映射器注册机,随着 Configuration 配置项调用 addMapper 时所做的注解解析操作。
  • 新增加核心类 MapperAnnotationBuilder 注解配置构建器,就是专门解析注解为主的类。
    • 注解方式的解析主要通过 Method 类,获取方法的入参、出参信息。
    • 以及基于这样的信息,获取到 LanguageDriver 脚本语言驱动器,从而创建出 SqlSession 属性。
  • 再往下执行,就和前面的一样。
    • 获取 Mapper 调用 DefaultSqlSession 对应的方法。
    • 以及从 Configuartion 配置项中获取到解析的 SQL 信息做相应的参数设置和结果包装操作。

3.3 脚本语言驱动器

3.3.1 脚本语言驱动器接口

LanguageDriver.java

package com.lino.mybatis.scripting;import com.lino.mybatis.executor.parameter.ParameterHandler;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.SqlSource;
import com.lino.mybatis.session.Configuration;
import org.dom4j.Element;/*** @description: 脚本语言驱动*/
public interface LanguageDriver {/*** 创建SQL源** @param configuration 配置项* @param script        元素* @param parameterType 参数类型* @return SqlSource SQL源码*/SqlSource createSqlSource(Configuration configuration, Element script, Class<?> parameterType);/*** 创建SQL源(annotation 注解方式)** @param configuration 配置项* @param script        注解名称* @param parameterType 参数类型* @return SqlSource SQL源码*/SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);/*** 创建参数处理器** @param mappedStatement 映射器语句类* @param parameterObject 参数对象* @param boundSql        sql语句* @return ParameterHandler 参数处理器*/ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);
}
  • 通过重载一个 createSqlSource 接口,把 script 的入参设置为 String 类型,来解析注解 SQL 的配置

3.3.2 XML语言驱动器

XMLLanguageDriver.java

package com.lino.mybatis.scripting.xmltags;import com.lino.mybatis.executor.parameter.ParameterHandler;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.SqlSource;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.scripting.defaults.DefaultParameterHandler;
import com.lino.mybatis.scripting.defaults.RawSqlSource;
import com.lino.mybatis.session.Configuration;
import org.dom4j.Element;/*** @description: XML 语言驱动器*/
public class XMLLanguageDriver implements LanguageDriver {@Overridepublic SqlSource createSqlSource(Configuration configuration, Element script, Class<?> parameterType) {// 用XML脚本构建器解析XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);return builder.parseScriptNode();}@Overridepublic SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {// 暂时不解析动态 SQLreturn new RawSqlSource(configuration, script, parameterType);}@Overridepublic ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);}
}
  • 用于解析注解方式的 createSqlSource 方法,其与 XML 解析来说,更加简单。
    • 因为这里不需要提供专门的 XML 脚本构建器。
    • 而是直接按照 SQL 的入参信息,创建 RawSqlSource 即可。

3.4 注解配置构建器

  • Mybatis 框架的实现中,有专门一个 annotations 注解包,来提供用于配置到 DAO 方法上的注解。
  • 这些注解包括了所有的增删改查操作,同时设定一些额外的返回参数等。

3.4.1 定义增删改查注解

  • 添加四个注解 @Insert、@Delete、@Update、@Select

Insert.java

package com.lino.mybatis.annotations;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @description: insert 语句注解*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Insert {String[] value();
}

Delete.java

package com.lino.mybatis.annotations;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @description: delete 语句注解*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Delete {String[] value();
}

Update.java

package com.lino.mybatis.annotations;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @description: update 语句注解*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Update {String[] value();
}

Select.java

package com.lino.mybatis.annotations;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @description: select 语句注解*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Select {String[] value();
}
  • Mybatis 源码中,除了上面的四个注解,还有如 @Arg、@InsertProvider、@Param、@ResultMap 等注解参数配置。

3.4.2 注解配置解析

MapperAnnotationBuilder.java

package com.lino.mybatis.builder.annotation;import com.lino.mybatis.annotations.Delete;
import com.lino.mybatis.annotations.Insert;
import com.lino.mybatis.annotations.Select;
import com.lino.mybatis.annotations.Update;
import com.lino.mybatis.binding.MapperMethod;
import com.lino.mybatis.builder.MapperBuilderAssistant;
import com.lino.mybatis.mapping.SqlCommandType;
import com.lino.mybatis.mapping.SqlSource;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;/*** @description: 注解配置构建器 Mapper*/
public class MapperAnnotationBuilder {private final Set<Class<? extends Annotation>> sqlAnnotationTypes = new HashSet<>();private Configuration configuration;private MapperBuilderAssistant assistant;private Class<?> type;public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {String resource = type.getName().replace(".", "/") + ".java (best guess)";this.assistant = new MapperBuilderAssistant(configuration, resource);this.configuration = configuration;this.type = type;sqlAnnotationTypes.add(Select.class);sqlAnnotationTypes.add(Insert.class);sqlAnnotationTypes.add(Update.class);sqlAnnotationTypes.add(Delete.class);}public void parse() {String resource = type.toString();if (!configuration.isResourceLoaded(resource)) {assistant.setCurrentNamespace(type.getName());Method[] methods = type.getMethods();for (Method method : methods) {if (!method.isBridge()) {// 解析语句parseStatement(method);}}}}/*** 解析语句** @param method 方法*/private void parseStatement(Method method) {Class<?> parameterTypeClass = getParameterType(method);LanguageDriver languageDriver = getLanguageDriver(method);SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);if (sqlSource != null) {final String mappedStatementId = type.getName() + "." + method.getName();SqlCommandType sqlCommandType = getSqlCommandType(method);boolean isSelect = sqlCommandType == SqlCommandType.SELECT;String resultMapId = null;if (isSelect) {resultMapId = parseResultMap(method);}// 调用助手类assistant.addMappedStatement(mappedStatementId,sqlSource,sqlCommandType,parameterTypeClass,resultMapId,getReturnType(method),languageDriver);}}/*** 重点:DAO方法的返回类型,如果为 List 则需要获取集合中的对象类型** @param method 方法* @return 对象类型*/private Class<?> getReturnType(Method method) {Class<?> returnType = method.getReturnType();if (Collection.class.isAssignableFrom(returnType)) {Type returnTypeParameter = method.getGenericReturnType();if (returnTypeParameter instanceof ParameterizedType) {Type[] actualTypeArguments = ((ParameterizedType) returnTypeParameter).getActualTypeArguments();if (actualTypeArguments != null && actualTypeArguments.length == 1) {returnTypeParameter = actualTypeArguments[0];if (returnTypeParameter instanceof Class) {returnType = (Class<?>) returnTypeParameter;} else if (returnTypeParameter instanceof ParameterizedType) {returnType = (Class<?>) ((ParameterizedType) returnTypeParameter).getRawType();} else if (returnTypeParameter instanceof GenericArrayType) {Class<?> componentType = (Class<?>) ((GenericArrayType) returnTypeParameter).getGenericComponentType();// (issue #525) support List<byte[]>returnType = Array.newInstance(componentType, 0).getClass();}}}}return returnType;}private String parseResultMap(Method method) {// generateResultMapNameStringBuilder suffix = new StringBuilder();for (Class<?> c : method.getParameterTypes()) {suffix.append("-");suffix.append(c.getSimpleName());}if (suffix.length() < 1) {suffix.append("-void");}String resultMapId = type.getName() + "." + method.getName() + suffix;// 添加 ResultMapClass<?> returnType = getReturnType(method);assistant.addResultMap(resultMapId, returnType, new ArrayList<>());return resultMapId;}private SqlCommandType getSqlCommandType(Method method) {Class<? extends Annotation> type = getSqlAnnotationType(method);if (type == null) {return SqlCommandType.UNKNOWN;}return SqlCommandType.valueOf(type.getSimpleName().toUpperCase(Locale.ENGLISH));}private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) {try {Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);if (sqlAnnotationType != null) {Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);return buildSqlSourceFromStrings(strings, parameterType, languageDriver);}return null;} catch (Exception e) {throw new RuntimeException("Could not find value method on SQL annotation. Cause: " + e);}}private SqlSource buildSqlSourceFromStrings(String[] strings, Class<?> parameterTypeClass, LanguageDriver languageDriver) {final StringBuilder sql = new StringBuilder();for (String fragment : strings) {sql.append(fragment);sql.append(" ");}return languageDriver.createSqlSource(configuration, sql.toString(), parameterTypeClass);}private Class<? extends Annotation> getSqlAnnotationType(Method method) {for (Class<? extends Annotation> type : sqlAnnotationTypes) {Annotation annotation = method.getAnnotation(type);if (annotation != null) {return type;}}return null;}private LanguageDriver getLanguageDriver(Method method) {Class<?> langClass = configuration.getLanguageRegistry().getDefaultDriverClass();return configuration.getLanguageRegistry().getDriver(langClass);}private Class<?> getParameterType(Method method) {Class<?> parameterType = null;Class<?>[] parameterTypes = method.getParameterTypes();for (Class<?> clazz : parameterTypes) {if (!RowBounds.class.isAssignableFrom(clazz) && !ResultHandler.class.isAssignableFrom(clazz)) {if (parameterType == null) {parameterType = clazz;} else {parameterType = MapperMethod.ParamMap.class;}}}return parameterType;}
}
  • 自定义注解的解析配置,主要在 MapperAnnotationBuilder 类中完成。
    • 整个类在构造函数中配置需要解析的注解,并提供解析方法处理语句的解析。
    • 这个类的解析操作,都是基于 Method 来获取参数类型、返回类型、注解方法等,完成解析过程。
  • 整个解析的核心流程。
    • 根据 Method#getParameterTypes 方法获取入参类型。
    • Configuration 配置项中获取默认的 LanguageDriver 脚本语言驱动。
    • 以及基于注解所提供的配置信息,也就是 value 值中的 SQL 来创建 SqlSource 语句。
  • 这些基本的信息创建完成以后,则根据 SqlCommandType 的命令类型为 SELECT 时,创建出 ResultMap 信息。
    • 这个 ResultMap 会被写入到 Configuration 配置项的 Map<String, ResultMap> resultMaps
  • 整体准备好这些基本配置以后,则会调用 MapperBuilderAssistant 映射构建器助手,存入映射器语句。往后的流程都是一样。
  • 注意getReturnType(Method method) 方法的使用。它有一个非常核心的问题点,在于要拿到方法的返回类型:
    • 如果是普通的基本类型或者对象类型,直接就可以返回。
    • 如果是集合类型,则需要通过 Collection.class.isAssignableFrom 判断,再进行集合中参数类型的获取。
      • 例如:List<User> 则需要根据 method.getGenericReturnType() 获取返回类型,并判断是否为 Class 进行返回具体的类型。

3.5 Mapper XML解析调用

  • Mybatis 的源码中,是基于 XML 配置构建器解析 Mapper 时进行判断处理,是 xml 还是注解。
  • 如果是注解则会调用到 MapperRegistry#addMapper 方法,并开始执行解析注解的相关操作。

3.5.1 修改配置项

Configuration.java

package com.lino.mybatis.session;import com.lino.mybatis.binding.MapperRegistry;
import com.lino.mybatis.datasource.druid.DruidDataSourceFactory;
import com.lino.mybatis.datasource.pooled.PooledDataSourceFactory;
import com.lino.mybatis.datasource.unpooled.UnpooledDataSourceFactory;
import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.executor.SimpleExecutor;
import com.lino.mybatis.executor.parameter.ParameterHandler;
import com.lino.mybatis.executor.resultset.DefaultResultSetHandler;
import com.lino.mybatis.executor.resultset.ResultSetHandler;
import com.lino.mybatis.executor.statement.PreparedStatementHandler;
import com.lino.mybatis.executor.statement.StatementHandler;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.Environment;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.ResultMap;
import com.lino.mybatis.reflection.MetaObject;
import com.lino.mybatis.reflection.factory.DefaultObjectFactory;
import com.lino.mybatis.reflection.factory.ObjectFactory;
import com.lino.mybatis.reflection.wrapper.DefaultObjectWrapperFactory;
import com.lino.mybatis.reflection.wrapper.ObjectWrapperFactory;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.scripting.LanguageDriverRegistry;
import com.lino.mybatis.scripting.xmltags.XMLLanguageDriver;
import com.lino.mybatis.transaction.Transaction;
import com.lino.mybatis.transaction.jdbc.JdbcTransactionFactory;
import com.lino.mybatis.type.TypeAliasRegistry;
import com.lino.mybatis.type.TypeHandlerRegistry;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;/*** @description: 配置项*/
public class Configuration {/*** 环境*/protected Environment environment;/*** 映射注册机*/protected MapperRegistry mapperRegistry = new MapperRegistry(this);/*** 映射的语句,存在Map里*/protected final Map<String, MappedStatement> mappedStatements = new HashMap<>(16);/*** 结果映射,存在Map里*/protected final Map<String, ResultMap> resultMaps = new HashMap<>(16);...public ResultMap getResultMap(String id) {return resultMaps.get(id);}public void addResultMap(ResultMap resultMap) {resultMaps.put(resultMap.getId(), resultMap);}
}
  • 添加 Map<String, ResultMap> resultMaps 结果映射的列表。

3.5.2 修改映射构建器助手

MapperBuilderAssistant.java

package com.lino.mybatis.builder;import com.lino.mybatis.mapping.*;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.session.Configuration;
import java.util.ArrayList;
import java.util.List;/*** @description: 映射构建器助手,建造者*/
public class MapperBuilderAssistant extends BaseBuilder {private String currentNamespace;private String resource;public MapperBuilderAssistant(Configuration configuration, String resource) {super(configuration);this.resource = resource;}public String getCurrentNamespace() {return currentNamespace;}public void setCurrentNamespace(String currentNamespace) {this.currentNamespace = currentNamespace;}public String applyCurrentNamespace(String base, boolean isReference) {if (base == null) {return null;}if (isReference) {if (base.contains(".")) {return base;}} else {if (base.startsWith(currentNamespace + ".")) {return base;}if (base.contains(".")) {throw new RuntimeException("Dots are not allowed in element names, please remove it from " + base);}}return currentNamespace + "." + base;}/*** 添加映射器语句*/public MappedStatement addMappedStatement(String id, SqlSource sqlSource, SqlCommandType sqlCommandType,Class<?> parameterType, String resultMap, Class<?> resultType,LanguageDriver lang) {// 给id加上namespace前缀:com.lino.mybatis.test.dao.IUserDao.queryUserInfoByIdid = applyCurrentNamespace(id, false);MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlCommandType, sqlSource, resultType);// 结果映射, 给 MappedStatement#resultMapssetStatementResultMap(resultMap, resultType, statementBuilder);MappedStatement statement = statementBuilder.build();// 映射语句信息,建造完存放到配置项中configuration.addMappedStatement(statement);return statement;}private void setStatementResultMap(String resultMap, Class<?> resultType, MappedStatement.Builder statementBuilder) {// 因为暂时还没有在 Mapper XML 中配置 Map 返回结果,所以这里返回的是 nullresultMap = applyCurrentNamespace(resultMap, true);List<ResultMap> resultMaps = new ArrayList<>();if (resultMap != null) {String[] resultMapNames = resultMap.split(",");for (String resultMapName : resultMapNames) {resultMaps.add(configuration.getResultMap(resultMapName.trim()));}}/** 通常使用 resultType 即可满足大部分场景* <select id="queryUserInfoById" resultType="com.lino.mybatis.test.po.User">* 使用 resultType 的情况下,Mybatis 会自动创建一个 ResultMap,基于属性名称映射列到 JavaBean 的属性上。*/else if (resultType != null) {ResultMap.Builder inlineResultMapBuilder = new ResultMap.Builder(configuration, statementBuilder.id() + "-Inline", resultType, new ArrayList<>());resultMaps.add(inlineResultMapBuilder.build());}statementBuilder.resultMaps(resultMaps);}public ResultMap addResultMap(String id, Class<?> type, List<ResultMapping> resultMappings) {ResultMap.Builder inlineResultMapBuilder = new ResultMap.Builder(configuration, id, type, resultMappings);ResultMap resultMap = inlineResultMapBuilder.build();configuration.addResultMap(resultMap);return resultMap;}
}
  • 修改 applyCurrentNamespace 方法,添加对命名空间的全称判断。
  • 修改 setStatementResultMap 方法, 完善 resultMap != null 时的逻辑处理。
  • 添加 addResultMap 方法,添加返回值集合。

3.5.3 XML配置构建器

XMLConfigBuilder

package com.lino.mybatis.builder.xml;import com.lino.mybatis.builder.BaseBuilder;
import com.lino.mybatis.datasource.DataSourceFactory;
import com.lino.mybatis.io.Resources;
import com.lino.mybatis.mapping.Environment;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.transaction.TransactionFactory;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.xml.sax.InputSource;
import javax.sql.DataSource;
import java.io.InputStream;
import java.io.Reader;
import java.util.List;
import java.util.Properties;/*** @description: XML配置构建器,建造者模式,集成BaseBuilder*/
public class XMLConfigBuilder extends BaseBuilder {.../*** <mappers>* <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>* <mapper resource="org/mybatis/builder/BlogMapper.xml"/>* <mapper resource="org/mybatis/builder/PostMapper.xml"/>* </mappers>*/private void mapperElement(Element mappers) throws Exception {List<Element> mapperList = mappers.elements("mapper");for (Element e : mapperList) {String resource = e.attributeValue("resource");String mapperClass = e.attributeValue("class");// XML解析if (resource != null && mapperClass == null) {InputStream inputStream = Resources.getResourceAsStream(resource);// 在for循环里每个mapper都重新new一个XMLMapperBuilder,来解析XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource);mapperParser.parse();}// Annontation 注解解析else if (resource == null && mapperClass != null) {Class<?> mapperInterface = Resources.classForName(mapperClass);configuration.addMapper(mapperInterface);}}}
}
  • XMLConfigBuilder 配置构建器的 Mapper 解析处理中,根据从 XML 配置获取到的 resource、class 分别进行判断解析。
  • 如果 resource 为空,mapperClass 不为空,则进行注解的解析处理。
    • 在这段代码中则是根据 mapperClass 获取对应的接口,并通过 Configuration#addMapper 方法,添加到配置项中。
    • 而这个 Mapper 的添加会调用到 MapperRegistry 进而调用注解解析操作。

3.5.4 映射器注册机

MapperRegistry.java

package com.lino.mybatis.binding;import cn.hutool.core.lang.ClassScanner;
import com.lino.mybatis.builder.annotation.MapperAnnotationBuilder;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.SqlSession;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;/*** @description: 映射器注册机*/
public class MapperRegistry {private Configuration configuration;public MapperRegistry(Configuration configuration) {this.configuration = configuration;}/*** 将已添加的映射器代理加入到HashMap*/private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>(16);public <T> T getMapper(Class<T> type, SqlSession sqlSession) {final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);if (mapperProxyFactory == null) {throw new RuntimeException("Type " + type + " is not known to the MapperRegistry.");}try {return mapperProxyFactory.newInstance(sqlSession);} catch (Exception e) {throw new RuntimeException("Error getting mapper instance. Cause: " + e, e);}}public <T> void addMapper(Class<T> type) {/* Mapper 必须是接口才会注册 */if (type.isInterface()) {if (hasMapper(type)) {// 如果重复添加,报错throw new RuntimeException("Type " + type + " is already known to the MapperRegistry.");}// 注册映射器代理工厂knownMappers.put(type, new MapperProxyFactory<>(type));// 解析注解类语句配置MapperAnnotationBuilder parser = new MapperAnnotationBuilder(configuration, type);parser.parse();}}public <T> boolean hasMapper(Class<T> type) {return knownMappers.containsKey(type);}public void addMappers(String packageName) {Set<Class<?>> mapperSet = ClassScanner.scanPackage(packageName);for (Class<?> mapperClass : mapperSet) {addMapper(mapperClass);}}
}
  • addMapper 方法中,根据 Class 注册完映射器代理工厂后,则开始进行解析注解操作。

四、测试:注解配置执行SQL

4.1 测试环境配置

4.1.1 配置数据源配置

<?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><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://127.0.0.1:3306/mybatis?useUnicode=true&amp;characterEncoding=utf8"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource></environment></environments><mappers><!--XML配置--><!--        <mapper resource="mapper/User_Mapper.xml"/>--><!--注解配置--><mapper class="com.lino.mybatis.test.dao.IUserDao"/></mappers>
</configuration>

4.1.2 修改IUserDao用户持久层

IUserDao.java

package com.lino.mybatis.test.dao;import com.lino.mybatis.annotations.Delete;
import com.lino.mybatis.annotations.Insert;
import com.lino.mybatis.annotations.Select;
import com.lino.mybatis.annotations.Update;
import com.lino.mybatis.test.po.User;
import java.util.List;/*** @Description: 用户持久层*/
public interface IUserDao {/*** 根据ID查询用户信息** @param uId ID* @return User 用户*/@Select("SELECT id, userId, userName, userHead FROM user WHERE id = #{id}")User queryUserInfoById(Long uId);/*** 根据用户对象查询用户信息** @param user 用户* @return User 用户*/@Select("SELECT id, userId, userName, userHead FROM user WHERE id = #{id} and userId = #{userId}")User queryUserInfo(User user);/*** 查询用户对象列表** @return List<User> 用户列表*/@Select("SELECT id, userId, userName, userHead FROM user")List<User> queryUserInfoList();/*** 更新用户信息** @param user 用户对象* @return 受影响的行数*/@Update("UPDATE user SET userName = #{userName} WHERE id = #{id}")int updateUserInfo(User user);/*** 新增用户信息** @param user 用户对象* @return 受影响的行数*/@Insert("INSERT INTO user (userId, userName, userHead, createTime, updateTime) VALUES (#{userId}, #{userName}, #{userHead}, now(), now())")int insertUserInfo(User user);/*** 根据ID删除用户信息** @param uId ID* @return 受影响的行数*/@Delete("DELETE FROM user WHERE userId = #{userId}")int deleteUserInfoByUserId(String uId);
}

4.2 单元测试

4.2.1 插入测试

ApiTest.java

@Test
public void test_insertUserInfo() {// 1.获取映射器对象IUserDao userDao = sqlSession.getMapper(IUserDao.class);// 2.测试验证User user = new User();user.setUserId("10002");user.setUserName("张三");user.setUserHead("1_05");userDao.insertUserInfo(user);logger.info("测试结果:{}", "Insert OK");// 3.提交事务sqlSession.commit();
}

测试结果

16:32:32.510 [main] INFO  c.l.mybatis.builder.SqlSourceBuilder - 构建参数映射 property:userId propertyType:class java.lang.String
16:32:32.510 [main] INFO  c.l.mybatis.builder.SqlSourceBuilder - 构建参数映射 property:userName propertyType:class java.lang.String
16:32:32.510 [main] INFO  c.l.mybatis.builder.SqlSourceBuilder - 构建参数映射 property:userHead propertyType:class java.lang.String
16:32:33.171 [main] INFO  c.l.m.d.pooled.PooledDataSource - Created connention 597190999.
16:32:33.213 [main] INFO  c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:"10002"
16:32:33.213 [main] INFO  c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:"张三"
16:32:33.213 [main] INFO  c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:"1_05"
16:32:33.213 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果:Insert OK

在这里插入图片描述

  • 从测试日志信息和数据库的截图,可以看到数据已经插入到数据库,验证通过
  • 注意:执行完 SQL 以后,还执行一次 sqlSession.commit()
    • 这是因为在 DefaultSqlSessionFactory#openSession 开启 Session 创建事务工厂的时候,传入给事务工厂构造函数的事务是否自动提交为 false
    • 所以这里就需要我们去手动提交事务,否则是不会插入到数据库中的。

4.2.2 查询测试(多条数据)

ApiTest.java

@Test
public void test_queryUserInfoList() {// 1.获取映射器对象IUserDao userDao = sqlSession.getMapper(IUserDao.class);// 2.测试验证: 对象参数List<User> users = userDao.queryUserInfoList();logger.info("测试结果:{}", JSON.toJSONString(users));
}

测试结果

16:40:47.699 [main] INFO  c.l.m.s.defaults.DefaultSqlSession - 执行查询 statement:com.lino.mybatis.test.dao.IUserDao.queryUserInfoList parameter:null
16:40:48.361 [main] INFO  c.l.m.d.pooled.PooledDataSource - Created connention 1192171522.
16:40:48.395 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果:[{"id":1,"userHead":"1_04","userId":"10001","userName":"小零哥"},{"id":4,"userHead":"1_05","userId":"10002","userName":"张三"}]
  • 现在我们再查询结果的时候,就可以查询到2条记录的集合了,这说明我们添加的 MapperMethod#execute 调用 sqlSession.selectList(command.getName(), param) 是测试通过的。

4.2.3 修改测试

ApiTest.java

@Test
public void test_updateUserInfo() {// 1.获取映射器对象IUserDao userDao = sqlSession.getMapper(IUserDao.class);// 2.测试验证int count = userDao.updateUserInfo(new User(1L, "10001", "小灵哥"));logger.info("测试结果:{}", count);// 3.提交事务sqlSession.commit();
}

测试结果

16:44:11.979 [main] INFO  c.l.m.d.pooled.PooledDataSource - Created connention 597190999.
16:44:12.027 [main] INFO  c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:"小灵哥"
16:44:12.028 [main] INFO  c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:1
16:44:12.037 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果:1

在这里插入图片描述

  • 这里测试验证把 ID=1 的用户,userName 修改为 小灵哥,通过测试日志和数据库截图,测试通过。

4.2.4 删除测试

ApiTest.java

@Test
public void test_deleteUserInfoByUserId() {// 1.获取映射器对象IUserDao userDao = sqlSession.getMapper(IUserDao.class);// 2.测试验证int count = userDao.deleteUserInfoByUserId("10002");logger.info("测试结果:{}", count == 1);// 3.提交事务sqlSession.commit();
}

测试结果

16:47:54.591 [main] INFO  c.l.m.d.pooled.PooledDataSource - Created connention 597190999.
16:47:54.633 [main] INFO  c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:"10002"
16:47:54.643 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果:true

在这里插入图片描述

  • 这里把数据表中 userId = '10002' 的用户删除掉,通过测试日志和数据库截图,测试通过。

五、总结:注解配置执行SQL

  • 在原有解析 Mapper XML 的基础上扩展了使用注解方式的解析和处理,让整个框架功能更加完善。同时在扩展注解功能的结构时候,可以看到整个整合过程并不复杂,更多是类似模块式的拼装,通过开发出一个注解解析构建器,并在 Mapper 注册过程中完成调用和解析操作。
    • 所以如果一个框架的整体设计是完善的,那么在功能的迭代和添加过程中也会是非常清晰容易的。
  • 在整个内容的实现中,主要以串联核心流程为主,剔除掉一些分支过程。这主要是因为很多分支过程都是在处理一些各类场景的情况。
    • 而我们学习源码是需要掌握主脉络且不被太多分支流程干扰的,才能把主干流程理顺。
  • 本章处理注解时,只是添加了 @Insert、@Delete、@Update、@Select 四个注解。

相关文章:

手写Mybatis:第13章-通过注解配置执行SQL语句

文章目录 一、目标&#xff1a;注解配置执行SQL二、设计&#xff1a;注解配置执行SQL三、实现&#xff1a;注解配置执行SQL3.1 工程结构3.2 注解配置执行SQL类图3.3 脚本语言驱动器3.3.1 脚本语言驱动器接口3.3.2 XML语言驱动器 3.4 注解配置构建器3.4.1 定义增删改查注解3.4.2…...

spring security - 快速整合 springboot

1.引入依赖<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spr…...

NPM 常用命令(二)

目录 1、npm bugs 1.1 配置 browser registry 2、npm cache 2.1 概要 2.2 详情 2.3 关于缓存设计的说明 2.4 配置 cache 3、 npm ci 3.1 描述 3.2 配置 install-strategy legacy-bundling global-style omit strict-peer-deps foreground-scripts ignore-s…...

ctfhub ssrf(3关)

文章目录 内网访问伪协议读取文件扫描端口 内网访问 根据该题目&#xff0c;是让我们访问127.0.0.1/falg.php&#xff0c;访问给出的链接后用bp抓包&#xff0c;修改URL&#xff0c;发送后得到flag&#xff1a; 伪协议读取文件 这题的让我们用伪协议&#xff0c;而网站的目录…...

跨源资源共享(CORS)Access-Control-Allow-Origin

1、浏览器的同源安全策略 没错&#xff0c;就是这家伙干的&#xff0c;浏览器只允许请求当前域的资源&#xff0c;而对其他域的资源表示不信任。那怎么才算跨域呢&#xff1f; 请求协议http,https的不同域domain的不同端口port的不同 好好好&#xff0c;大概就是这么回事啦&…...

【嵌入式软件开发 】学习笔记

本文主要记录 【嵌入式软件开发】 学习笔记&#xff0c;参考相关大佬的文章 1.RTOS 内功修炼笔记 RTOS内功修炼记&#xff08;一&#xff09;—— 任务到底应该怎么写&#xff1f; RTOS内功修炼记&#xff08;二&#xff09;—— 优先级抢占式调度到底是怎么回事&#xff1f;…...

CentOS 7上安装Python 3.11.5,支持Django

CentOS 7上安装Python 3.11.5,支持Django 今天安装django&#xff0c;报了“Django - deterministicTrue requires SQLite 3.8.3 or higher upon running python manage.py runserver”。查了一番资料&#xff0c;记录下来。 参考链接&#xff1a; 参考链接: Django的web项目…...

COMPFEST 15H「组合数学+容斥」

Problem - H - Codeforces 题意&#xff1a; 定义一个集合S为T的孩子是&#xff0c;对于S中的每一个元素x&#xff0c;在T中都能找到x1。 给定n&#xff0c;k&#xff0c;每一个集合中的元素x必须满足 1 < x < k 1<x<k 1<x<k且 c n t [ x ] < 1 cnt[x…...

react快速开始(三)-create-react-app脚手架项目启动;使用VScode调试react

文章目录 react快速开始(三)-create-react-app脚手架项目启动&#xff1b;使用VScode调试react一、create-react-app脚手架项目启动1. react-scripts2. 关于better-npm-runbetter-npm-run安装 二、使用VScode调试react1. 浏览器插件React Developer Tools2. 【重点】用 VSCode …...

【C++入门】string类常用方法(万字详解)

目录 1.STL简介1.1什么是STL1.2STL的版本1.3STL的六大组件1.4STL的缺陷 2.string类的使用2.1C语言中的字符串2.2标准库中的string类2.3string类的常用接口说明 &#xff08;只讲解最常用的接口&#xff09;2.3.1string类对象的常见构造2.3.2 string类对象的容量操作2.3.3string…...

大数据错误

question1 : Could not locate Hadoop executable: D:\hadoop-3.3.1\bin\winutils.exe - 【已解决】Could not locate executable E:\Hadoop\bin\winutils.exe in the Hadoop binaries._could not locate executable e:\hadoop-3.3.1\bin\wi_君问归期魏有期的博客-CSDN博客 q…...

【Node.js】Express-Generator:快速生成Express应用程序的利器

在Node.js世界中&#xff0c;Express是一个广泛使用的、强大的Web应用程序框架。它为开发者提供了一系列的工具和选项&#xff0c;使得创建高效且可扩展的Web应用程序变得轻而易举。然而&#xff0c;对于初学者来说&#xff0c;配置和初始化Express应用程序可能会有些困难。为了…...

SpringMVC的工作流程及入门

目录 一、概述 ( 1 ) 是什么 ( 2 ) 作用 二、工作流程 ( 1 ) 流程 ( 2 ) 步骤 三、入门实例 ( 1 ) 入门实例 ( 2 ) 静态资源处理 给我们带来的收获 一、概述 ( 1 ) 是什么 SpringMVC是一个基于Java的Web应用开发框架&#xff0c;它是Spring Framework的一部…...

logging.level的含义及设置 【java 日志 (logback、log4j)】

日志级别 trace<debug<info<warn<error<fatal 常用的有&#xff1a;debug&#xff0c;info&#xff0c;warn&#xff0c;error 通常我们想设置日志级别&#xff0c;会用到 logging.level.rootinfo logging.level设置日志级别&#xff0c;后面跟生效的区域。r…...

第 3 章 栈和队列(链栈)

1. 背景说明 链栈是指用单链表实现的栈&#xff0c;其存储结构为链式存储&#xff0c;实现类似于队列的链式实现&#xff0c;不过在插入元素时链栈在头部插入&#xff0c;而 链式队列在尾部插入&#xff0c;本示例中实现为带头结点的链栈&#xff0c;即栈顶元素为栈指针的下一…...

嵌入式面试-经典问题

1、c语言内存模型 2、C语言中的变量定义在什么地方 3、C语言代码如何运行的、关于栈的相关 4、指针函数与函数指针的区分 5、Static关键字的作用 6、const作用 7、进程与线程的区别 8、链表与数组的区别 9、#define宏定义与typedef的区别...

ZLMeidaKit在Windows上启动时:计算机中丢失MSVCR110.dll,以及rtmp推流后无法转换为flv视频流解决

场景 ZLMediaKit在Windows上实现Rtmp流媒体服务器以及模拟rtmp推流和http-flv拉流播放&#xff1a; ZLMediaKit在Windows上实现Rtmp流媒体服务器以及模拟rtmp推流和http-flv拉流播放_zlm流媒体服务器_霸道流氓气质的博客-CSDN博客 按照以上教程启动MediaServer.exe时提示&am…...

项目(智慧教室)第二部分,人机交互页面实现,

使用软件&#xff1a; 1.BmCvtST.exe 这是stm32Cubemx工程下的带三方软件。存在STemWin中。 作用&#xff1a; 图片变成.c文件格式。 2.CodeBlock 3.模拟器工程&#xff08;具体请看上一节&#xff09; 一。emWin环境的搭建 1.codeBlock下载 开源免费。 2.使用stm的C…...

【docker】docker的一些常用命令-------从小白到大神之路之学习运维第92天

目录 一、安装docker-ce 1、从阿里云下载docker-cer.epo源 2、下载部分依赖 3、安装docker 二、启用docker 1、启动docker和不启动查看docker version 2、启动服务查看docker version 有什么区别&#xff1f;看到了吗&#xff1f; 3、看看docker启动后的镜像仓库都有什…...

ubuntu18.04.6的安装教程

目录 一、下载并安装virtualbox virtualbox7.0.8版本的安装 二、Ubuntu的下载与安装 ubuntu18.04.6操作系统 下载 安装 一、下载并安装virtualbox VirtualBox是功能强大的x86和AMD64/Intel64虚拟化企业和家庭使用的产品。VirtualBox不仅是面向企业客户的功能极其丰富的高…...

Spring是如何解决Bean的循环依赖:三级缓存机制

1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间‌互相持有对方引用‌,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...

根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的----NTFS源代码分析--重要

根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的 第一部分&#xff1a; 0: kd> g Breakpoint 9 hit Ntfs!ReadIndexBuffer: f7173886 55 push ebp 0: kd> kc # 00 Ntfs!ReadIndexBuffer 01 Ntfs!FindFirstIndexEntry 02 Ntfs!NtfsUpda…...

破解路内监管盲区:免布线低位视频桩重塑停车管理新标准

城市路内停车管理常因行道树遮挡、高位设备盲区等问题&#xff0c;导致车牌识别率低、逃费率高&#xff0c;传统模式在复杂路段束手无策。免布线低位视频桩凭借超低视角部署与智能算法&#xff0c;正成为破局关键。该设备安装于车位侧方0.5-0.7米高度&#xff0c;直接规避树枝遮…...

链式法则中 复合函数的推导路径 多变量“信息传递路径”

非常好&#xff0c;我们将之前关于偏导数链式法则中不能“约掉”偏导符号的问题&#xff0c;统一使用 二重复合函数&#xff1a; z f ( u ( x , y ) , v ( x , y ) ) \boxed{z f(u(x,y),\ v(x,y))} zf(u(x,y), v(x,y))​ 来全面说明。我们会展示其全微分形式&#xff08;偏导…...

Canal环境搭建并实现和ES数据同步

作者&#xff1a;田超凡 日期&#xff1a;2025年6月7日 Canal安装&#xff0c;启动端口11111、8082&#xff1a; 安装canal-deployer服务端&#xff1a; https://github.com/alibaba/canal/releases/1.1.7/canal.deployer-1.1.7.tar.gz cd /opt/homebrew/etc mkdir canal…...

Appium下载安装配置保姆教程(图文详解)

目录 一、Appium软件介绍 1.特点 2.工作原理 3.应用场景 二、环境准备 安装 Node.js 安装 Appium 安装 JDK 安装 Android SDK 安装Python及依赖包 三、安装教程 1.Node.js安装 1.1.下载Node 1.2.安装程序 1.3.配置npm仓储和缓存 1.4. 配置环境 1.5.测试Node.j…...

ubuntu清理垃圾

windows和ubuntu 双系统&#xff0c;ubuntu 150GB&#xff0c;开发用&#xff0c;基本不装太多软件。但是磁盘基本用完。 1、查看home目录 sudo du -h -d 1 $HOME | grep -v K 上面的命令查看$HOME一级目录大小&#xff0c;发现 .cache 有26GB&#xff0c;.local 有几个GB&am…...

在MobaXterm 打开图形工具firefox

目录 1.安装 X 服务器软件 2.服务器端配置 3.客户端配置 4.安装并打开 Firefox 1.安装 X 服务器软件 Centos系统 # CentOS/RHEL 7 及之前&#xff08;YUM&#xff09; sudo yum install xorg-x11-server-Xorg xorg-x11-xinit xorg-x11-utils mesa-libEGL mesa-libGL mesa-…...

自建 dnslog 回显平台:渗透测试场景下的隐蔽回显利器

&#x1f50d; 背景介绍 在渗透测试与红队评估过程中&#xff0c;DNS 外带&#xff08;DNS Exfiltration&#xff09; 是一种常见且隐蔽的通信通道。由于多数目标环境默认具备外网 DNS 解析能力&#xff0c;即便在 无回显、无文件上传权限 的条件下&#xff0c;仍可通过 DNS 请…...

2025年渗透测试面试题总结-腾讯[实习]安全研究员(题目+回答)

安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 腾讯[实习]安全研究员 1. 自我介绍 2. SQL二次注入原理 3. 二次注入修复方案 4. SQL注入绕WAF&#xff…...