mybatis拦截器和mybatis plus的拦截器

news2024/9/20 9:33:55

MyBatis拦截器和MyBatis Plus的拦截器在概念上是一致的,都是通过拦截器机制对MyBatis的SQL执行过程进行扩展和控制,但是在实现细节和功能上有所差异。MyBatis Plus的拦截器是建立在MyBatis拦截器基础之上,通过封装简化了开发流程,同时也针对MyBatis Plus的特性和功能进行了强化和扩展。在MyBatis Plus项目中使用拦截器时,既可以利用MyBatis Plus内置的拦截器,也能自定义符合自己业务需求的拦截器。

  1. MyBatis拦截器

    • MyBatis自带的拦截器机制允许开发者自定义拦截器,实现org.apache.ibatis.plugin.Interceptor接口,并通过@Intercepts注解来标识要拦截的方法签名。这些拦截器可以在Executor、ParameterHandler、ResultSetHandler和StatementHandler四个核心接口的方法上进行拦截,从而在SQL执行的不同阶段插入自定义逻辑,如日志记录、性能分析、权限控制等。
  2. MyBatis Plus拦截器

    • MyBatis Plus作为MyBatis的增强工具,也提供了拦截器功能,但它的拦截器通常是基于MyBatis拦截器机制封装的,并针对MyBatis Plus自身的特性进行了扩展和优化。例如,MyBatis Plus内置了一些特定功能的拦截器,如分页插件、性能分析插件等,这些都是针对MyBatis Plus的使用场景设计的。
    • MyBatis Plus的拦截器使用起来相对更为便捷,因为它提供了一些开箱即用的拦截器,同时用户也可以自定义拦截器以满足个性化需求,这部分的实现仍然是基于MyBatis拦截器体系,只是MyBatis Plus对这一部分进行了良好的封装和简化。

MyBatis Inteceptor是使用JDK的动态代理来实现的,所以它只能对接口进行拦截
里面两个很重要的注解是:@Intercepts、@Signature
@Intercepts : 标记要拦截的方法签名
@Signature : 方法签名,唯一的标记一个接口的方法 

MyBatis所有的代理拦截都是通过 InterceptorChain.pluginAll(Object target) 来实现的,会对如下四个对象进行拦截:

1.Executor : 作用是执行SQL语句(所有的sql),并且对事务、缓存等提供统一接口,它负责处理 SQL 语句的编译、参数设置以及结果集的映射等工作。(在这一层上做拦截的权限会更大),所有与数据库的实际交互操作都是通过 Executor 接口及其实现类来完成的。在 MyBatis 中,默认的 Executor 执行器是 SimpleExecutorSimpleExecutor 是最基础的执行器,它的特点是每次执行 SQL 都会创建一个新的 PreparedStatement 对象,然后执行 SQL 并关闭 PreparedStatement

以下是 Executor 接口在 MyBatis 中扮演的关键角色:

  1. SQL 执行: Executor 接口提供了执行 SQL 语句的方法,这些方法接收已解析的 MappedStatement 对象作为参数,后者封装了 SQL 语句以及相关的参数映射和结果映射信息。

  2. 事务管理: Executor 通常与事务管理紧密结合,确保在执行 SQL 时能够参与到当前的数据库事务中,并根据配置执行提交或回滚。

  3. 缓存控制: Executor 也参与 MyBatis 的二级缓存机制,负责缓存数据的读取和刷新。

  4. 执行策略: MyBatis 提供了多种 Executor 的实现,如 SimpleExecutorReuseExecutor 和 BatchExecutor 等,每种执行器都有其特定的行为特点:

    • SimpleExecutor:每次执行 SQL 语句都会打开一个新的 Statement。
    • ReuseExecutor:复用已存在的 Statement 对象,以减少创建 Statement 的开销。
    • BatchExecutor:针对批量操作优化,允许在一次连接中执行一组 SQL 语句,并行发出多个更新操作。

在 MyBatis 的配置文件中,可以通过 <settings> 标签下的 defaultExecutorType 属性来设置默认的执行器类型。同时,也可以在具体的 Mapper 配置中覆盖这一默认设置。

下面是Executor接口的定义:

public interface Executor {
    ResultHandler NO_RESULT_HANDLER = null;

    int update(MappedStatement var1, Object var2) throws SQLException;

    <E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4, CacheKey var5, BoundSql var6) throws SQLException;

    <E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4) throws SQLException;

    <E> Cursor<E> queryCursor(MappedStatement var1, Object var2, RowBounds var3) throws SQLException;

    List<BatchResult> flushStatements() throws SQLException;

    void commit(boolean var1) throws SQLException;

    void rollback(boolean var1) throws SQLException;

    CacheKey createCacheKey(MappedStatement var1, Object var2, RowBounds var3, BoundSql var4);

    boolean isCached(MappedStatement var1, CacheKey var2);

    void clearLocalCache();

    void deferLoad(MappedStatement var1, MetaObject var2, String var3, CacheKey var4, Class<?> var5);

    Transaction getTransaction();

    void close(boolean var1);

    boolean isClosed();

    void setExecutorWrapper(Executor var1);
}

2.StatementHandler : 作用是对 statement 进行预处理,并且提供统一的原子的增、删、改、查接口。(如果要在SQL执行前进行拦截的话,拦截这里就可以了),在 MyBatis 的执行链路中,StatementHandler 起到了承上启下的作用,它介于 SqlSession 和 JDBC Statement 之间。

  1. SQL 解析与动态 SQL 结合: 根据 Mapper 映射文件中的 SQL 语句以及参数对象,解析得到最终的可执行 SQL 语句。对于动态 SQL(如 <if><foreach> 等标签),StatementHandler 会负责将这些标签解析为实际的 SQL 片段。

  2. 参数设置: 将用户传入的方法参数,按照一定的规则绑定到 JDBC Statement 对象上,比如设置 PreparedStatement 的参数值。

  3. 结果集映射: 根据 Mapper 映射文件中的 resultMap 配置,将 JDBC ResultSet 映射为 Java 对象,便于上层应用处理。

  4. 执行 SQL 语句: 最终执行 JDBC Statement,包括执行查询、更新等操作。

StatementHandler负责将最终的 SQL 语句和参数传递给 JDBC PreparedStatement 或 Statement。上年龄的老程序员上学的时候肯定都写过那种调用原生 JDBC API 操作数据库的代码,自己写sql,提交或者回滚,关闭数据库连接之类的,相比现在繁琐了很多。

  1. Statement:

    • Statement 是最基本的用于执行静态 SQL 语句的对象,可以发送简单的 SQL 查询到数据库。
    • 编写 SQL 语句时直接包含变量值,例如 String sql = "SELECT * FROM users WHERE id = 1";
    • 如果同一段 SQL 语句需要反复执行多次且每次参数不同,使用 Statement 会导致 SQL 注入的风险增加。
    • 不支持预编译,对于每次执行,数据库都需要解析和优化 SQL 语句,效率相对较低。
  2. PreparedStatement:

    • PreparedStatement 对象能够存储并执行预编译的 SQL 语句,它可以接受参数化输入。
    • 参数化的 SQL 语句带有占位符(通常以问号 '?' 表示),在执行时传入具体的值,例如 String sql = "SELECT * FROM users WHERE id = ?";
    • 预编译 SQL 可以提高性能,特别是当 SQL 语句被重复执行时,因为数据库只需要解析一次 SQL,后续只需替换参数即可执行,减少了解析和编译的时间。
    • PreparedStatement 可有效防止 SQL 注入攻击,因为它会确保传入的数据被正确转义并作为单独的数据项处理,而不是当作 SQL 代码的一部分。
    • 对于批量操作,PreparedStatement 更加灵活和高效,可以通过循环设置参数然后一次性执行多个插入、更新等操作。

工作后用了hibernate或者是mybatis感觉喷喷香,因为这些框架都帮我们做了。MyBatis 默认情况下使用的是 PreparedStatement。在 MyBatis 中,当你在映射文件中使用参数占位符(如 #{})时,MyBatis 会自动帮你创建并使用 PreparedStatement 来执行 SQL 语句。通过这种方式,MyBatis 能够防止 SQL 注入攻击,并且充分利用 PreparedStatement 的预编译特性,提高 SQL 语句的执行效率。

当然MyBatis 也允许你选择使用 Statement,但这通常不建议,尤其是在处理含有用户输入的 SQL 查询时,以避免安全风险。如果确实需要使用非预编译的 SQL,可以通过 MyBatis 的 ${} 占位符来引用变量,但这将不会享受到预编译带来的安全性和性能优势。不过请注意,最佳实践是始终使用预编译语句以确保安全性。

3.ResultSetHandler : 作用是对返回结果ResultSet进行处理。通常不会直接实现 ResultSetHandler 接口,因为 MyBatis 已经提供了一个默认的实现类 DefaultResultSetHandler,并且在大多数情况下,我们可以通过编写 resultMap 或者直接使用注解来完成结果集到 Java 对象的映射。它主要负责将从数据库查询得到的结果集(ResultSet)转换成 Java 对象。具体功能如下:

  1. 结果集解析: 当 SQL 查询被执行并返回一个 ResultSet 时,ResultSetHandler 会遍历 ResultSet 中的每一行数据。

  2. 对象映射: 根据 MyBatis 的映射配置(如 resultMap 或通过注解标注的映射关系),将每一行的列数据映射到对应的 Java 对象的属性上。

  3. 类型转换: 在映射过程中,ResultSetHandler 会与 MyBatis 的 TypeHandler 一起工作,将数据库中的原始数据类型转换为 Java 类型。

  4. 集合构建: 如果查询结果需要转换为一个集合(如 List、Set 等),ResultSetHandler 会将一个个映射好的 Java 对象添加到集合中。

  5. 嵌套结果映射: 对于复杂的查询结果,ResultSetHandler 还能处理嵌套结果映射,即将多个表关联查询的结果映射到嵌套的 Java 对象结构中。


4.PameterHandler : 作用是对参数进行赋值。它是 MyBatis 框架中的一个重要组件,它的主要作用是处理 SQL 执行时的参数绑定。在 MyBatis 中,当我们调用 Mapper 方法执行 SQL 时,传递给该方法的参数需要经过 ParameterHandler 处理后才能正确绑定到预编译的 SQL 语句中。

具体来说,ParameterHandler 的功能包括:

  1. 类型转换: 将方法参数从 Java 类型转换为 JDBC 可识别的类型。例如,将 Java 日期类型转换为符合数据库日期格式的 java.sql.Date 或 java.sql.Timestamp 类型。

  2. 参数设置: 根据 SQL 语句中占位符(#{...} 或 ${...})的位置和数量,将转换后的参数值设置到 PreparedStatement 中。

  3. 动态 SQL 处理: 对于包含动态 SQL 片段的语句,ParameterHandler 负责解析条件表达式,并根据表达式的值决定是否将参数值插入到 SQL 语句中。

那这四大金刚彼此之间有关系吗?肯定是有的,Executor负责实际执行 SQL 语句并管理事务,Executor 接口中定义了一系列的方法,这些方法用来执行 MappedStatement 中的 SQL 以及相关操作,包括 CRUD(增删改查)以及其他数据库操作。StatementHandler负责将最终的 SQL 语句和参数传递给 JDBC PreparedStatement 或 Statement,并在结果返回时进行处理。StatementHandler 在执行 SQL 之前会对 SQL 语句进行动态处理(如动态 SQL),之后将参数通过 ParameterHandler 绑定到 SQL 语句中,并在执行后通过 ResultSetHandler 处理结果集。

大致了解了这些概念,再看Interceptor接口:

package org.apache.ibatis.plugin;

import java.util.Properties;

public interface Interceptor {
    Object intercept(Invocation var1) throws Throwable;
    //自Java 8起,接口中允许包含具有具体实现的方法,这些方法被称为“默认方法”。在接口定义中使用default关键字修饰的方法不需要实现类去实现它,但实现类可以选择覆盖(override)这个方法。这样做的目的是在不破坏现有实现类的情况下向接口添加新的方法,提高接口的扩展性。
    default Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    default void setProperties(Properties properties) {
    }
}

我们看一下mybatis为我们提供的Interceptor实现类。

比如PaginationInterceptor,它就是基于Interceptor实现的一个强大的分页插件,可以看出它对statement 进行预处理,作用于prepare阶段。

@Intercepts({@Signature(
    type = StatementHandler.class,
    method = "prepare",
    args = {Connection.class, Integer.class}
)})
public class PaginationInterceptor extends AbstractSqlParserHandler implements Interceptor 

如下是StatementHandler能做的操作:

public interface StatementHandler {
    Statement prepare(Connection var1, Integer var2) throws SQLException;

    void parameterize(Statement var1) throws SQLException;

    void batch(Statement var1) throws SQLException;

    int update(Statement var1) throws SQLException;

    <E> List<E> query(Statement var1, ResultHandler var2) throws SQLException;

    <E> Cursor<E> queryCursor(Statement var1) throws SQLException;

    BoundSql getBoundSql();

    ParameterHandler getParameterHandler();
}

在prepare阶段setFetchSize设置提取的数量。

    public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
        ErrorContext.instance().sql(this.boundSql.getSql());
        Statement statement = null;

        try {
            statement = this.instantiateStatement(connection);
            this.setStatementTimeout(statement, transactionTimeout);
            this.setFetchSize(statement);
            return statement;
        } catch (SQLException var5) {
            this.closeStatement(statement);
            throw var5;
        } catch (Exception var6) {
            this.closeStatement(statement);
            throw new ExecutorException("Error preparing statement.  Cause: " + var6, var6);
        }
    }

    protected void setFetchSize(Statement stmt) throws SQLException {
        Integer fetchSize = this.mappedStatement.getFetchSize();
        if (fetchSize != null) {
            stmt.setFetchSize(fetchSize);
        } else {
            Integer defaultFetchSize = this.configuration.getDefaultFetchSize();
            if (defaultFetchSize != null) {
                stmt.setFetchSize(defaultFetchSize);
            }

        }
    }

我们可以在mybatis-config.xml配置这个插件。

<plugins>
    <plugin interceptor="com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor">
        <!-- 设置每页显示条数,默认值是5 -->
        <property name="pageSizeZero" value="true"/>
        <!-- 开启 count 的 join 优化,只针对 left join -->
        <property name="countSqlParserEnabled" value="true"/>
    </plugin>
</plugins>

使用分页查询时 在 Java 代码中,使用 MyBatis-Plus 提供的 Page 类来进行分页查询。 

Page<User> page = new Page<>(currentPage, pageSize);
List<User> users = userMapper.selectPage(page, wrapper); // wrapper 是一个条件构造器
Long total = page.getTotal(); // 获取总记录数
List<User> records = page.getRecords(); // 获取当前页的记录列表

我们自己也可以对Interceptor进行实现:

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;

import java.util.Properties;

//@Intercepts({
//    @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
    // 可以添加更多需要拦截的方法
//})
@Component
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class})})
public class CustomExecutorInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 1.在执行update方法前的操作
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
        BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
        // 打印 SQL 语句
        String sql = boundSql.getSql();
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        System.out.println("Executing SQL: [" + mappedStatement.getId() + "] => " + sql);


        // 2.调用原来的方法
        Object result = invocation.proceed(); 
        // 3.在执行update方法后的操作

        return result;
    }

    @Override
    public Object plugin(Object target) {
        // 返回一个代理对象,使得目标对象的方法调用可以被拦截器拦截
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 设置拦截器的属性
    }
}

注册拦截器的三种方法:

1.如果你的应用是基于 Spring Boot 的,并且使用了 MyBatis-Spring-Boot-Starter,可以创建一个配置类来注册自定义的 MyBatis 拦截器。以下是一个示例:

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement
public class MybatisConfig extends MybatisAutoConfiguration {

    @Bean
    public CustomExecutorInterceptor mybatisInterceptor() {
        return new CustomExecutorInterceptor(); // 假设你有一个名为 CustomExecutorInterceptor 的自定义拦截器类
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory(ResourcePatternResolver resolver) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource()); // dataSource() 是你的数据源 Bean
        factoryBean.setPlugins(new Interceptor[]{mybatisInterceptor()});
        // 其他配置,如设置mapperLocations等
        factoryBean.setMapperLocations(resolver.getResources("classpath:mappers/*.xml"));
        return factoryBean.getObject();
    }
    
    // 其他配置...
}

2.使用 MyBatis 的 Configuration 配置文件注册拦截器。

<configuration>
    <!-- ... -->
    <plugins>
        <plugin interceptor="com.example.CustomExecutorInterceptor" />
    </plugins>
</configuration>

3.注解式注册(适用于Spring环境)在 Spring 环境下,也可以使用 @Intercepts 注解来定义拦截器,并配合 @Component 注解将其注册为 Spring Bean,就如同上面的CustomExecutorInterceptor 实现类,下面是大致格式:

import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;

import java.lang.reflect.Method;

@Component
@Intercepts({
    @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
    // 其他需要拦截的方法签名
})
public class YourInterceptorClass implements Interceptor {
    // 实现 Interceptor 接口的方法
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 拦截逻辑
        return invocation.proceed();
    }

    // 其他方法...
}

那我们知道了这些拦截器和这四大接口有什么用呢?如果你想比初级程序员牛逼那么一点点,知道一些总没坏处,说不定被裁员了下次面试就问到你这些了。比如我们可以说,我要实现自定义数据库入库字段进行加密,除了笨方法每次入库的时候对字段加密一下。我们还可以写点初级程序员看了一蒙圈的代码装下B,这样用拦截器优雅的进行加密操作:

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;

import java.sql.PreparedStatement;
import java.util.Properties;

@Intercepts({@Signature(type = StatementHandler.class, method = "setParameters", args = {PreparedStatement.class})})
public class EncryptionInterceptor implements Interceptor {

    private static final String ENCRYPTION_KEY = "your_encryption_key"; // 加密密钥

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        BoundSql boundSql = statementHandler.getBoundSql();
        Object parameterObject = boundSql.getParameterObject();

        if (parameterObject != null) {
            // 这里只是一个简单的示例,实际项目中应根据业务需求进行适当的加密算法封装
            // 对参数对象的各个属性进行加密处理
            encrypt(parameterObject);
        }

        return invocation.proceed();
    }

    private void encrypt(Object parameterObject) {
        // 实现你的加密逻辑,这里仅为示例
        // 例如,假设参数对象是个 Map
        if (parameterObject instanceof Map) {
            Map<String, Object> params = (Map<String, Object>) parameterObject;
            for (Map.Entry<String, Object> entry : params.entrySet()) {
                entry.setValue(encryptValue(entry.getValue()));
            }
        }
        // 其他类型的参数对象处理...
    }

    private Object encryptValue(Object value) {
        // 实现加密方法,此处仅为示意
        return "encrypted_" + value.toString(); 
    }

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

    @Override
    public void setProperties(Properties properties) {
        // 设置拦截器的属性,例如密钥等
    }
}

这样,每当 MyBatis 准备执行 SQL 时,就会触发 EncryptionInterceptor 的 intercept 方法,从而对参数进行加密。当然,为了能在查询结果中解密数据,你还需要在结果映射的地方进行相应的处理,例如在 ResultMap 中使用自定义的 TypeHandler 进行解密。

TypeHandler接口。在 MyBatis 中,TypeHandler 是一个接口,它负责在 Java 类型和 JDBC 类型之间进行转换。TypeHandler 主要应用于两个场合。

  1. 设置参数: 当 MyBatis 向预编译的 SQL 语句(PreparedStatement)设置参数时,会根据参数的 Java 类型查找对应的 TypeHandler,由 TypeHandler 负责将 Java 类型转换为 JDBC 可识别的数据类型,并将其设置到 PreparedStatement 中。

  2. 获取结果: 当从 ResultSet 中获取结果时,同样会根据目标 Java 类型查找对应的 TypeHandler,由 TypeHandler 负责将从 ResultSet 中读取的 JDBC 数据类型转换成对应的 Java 类型。

通过自定义 TypeHandler,我们可以实现一些特殊类型的转换,例如:

  • 将日期字符串转换为 Java Date 类型或 LocalDate 类型。
  • 将数据库中的整数或字符串转换为枚举类型。
  • 对敏感数据(如密码)进行加密/解密处理。

大致例子:

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

public class EncryptedStringTypeHandler extends BaseTypeHandler<String> {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
        // 在这里实现加密逻辑并将加密后的字符串设置到 PreparedStatement
        String encryptedValue = encrypt(parameter);
        ps.setString(i, encryptedValue);
    }

    @Override
    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
        // 从 ResultSet 获取加密过的字符串并解密
        String encryptedValue = rs.getString(columnName);
        return decrypt(encryptedValue);
    }

    @Override
    public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        // 同上,根据列索引获取并解密
        String encryptedValue = rs.getString(columnIndex);
        return decrypt(encryptedValue);
    }

    @Override
    public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        // 从 CallableStatement 获取并解密
        String encryptedValue = cs.getString(columnIndex);
        return decrypt(encryptedValue);
    }

    // 假设encrypt和decrypt是加密解密的方法
    private String encrypt(String value) {...}
    private String decrypt(String encryptedValue) {...}
}

然后在 MyBatis 配置文件中注册这个自定义的 TypeHandler,并在映射文件中引用它,以便在设置参数和获取结果时进行自动的加密解密处理。

<configuration>
  <!-- ...其它配置... -->
  <typeHandlers>
    <typeHandler handler="com.example.security.DecryptTypeHandler"/>
  </typeHandlers>
</configuration>

或者是用注解来注入这个Handler。

import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;

@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes(MyCustomType.class)
public class MyCustomTypeHandler implements TypeHandler<MyCustomType> {
    // ...
}

最后,在你的 ResultMap 中引用这个自定义的 TypeHandler:

<resultMap id="UserResultMap" type="com.example.entity.User">
  <!-- ...其它字段... -->
  <result column="encrypted_field" property="plainTextField" jdbcType="VARCHAR" typeHandler="com.example.security.DecryptTypeHandler"/>
</resultMap>

<select id="selectUser" resultMap="UserResultMap">
  SELECT * FROM users
</select>

再比如我们如果用Mybatis Plus的时候,经常用BaseMapper里的方法对数据库进行增删改查的操作很方便,它也跟这四大对象有关系,MyBatis Plus 的 Mapper 接口起作用的时机与标准 MyBatis 类似,但在 MyBatis Plus 中进行了进一步的增强和简化,具体过程如下:

  1. 初始化阶段

    • 当 MyBatis Plus 初始化 SqlSessionFactory 时,会读取配置文件,其中包含了对 Mapper 接口所在包的扫描信息,通过 MapperScan 注解或 XML 配置来实现自动扫描。
    • MyBatis Plus 会自动识别那些继承自 BaseMapper 或使用特定注解的 Mapper 接口,并为其创建相应的代理对象。
  2. SqlSession 使用阶段

    • 当应用通过 SqlSession 调用 getMapper(Class<T> type) 方法时,由于已经预先扫描和注册了 Mapper 接口,所以能立即获得对应的代理对象。
  3. 执行方法调用阶段

    • 开发者调用 MyBatis Plus 的 Mapper 接口(如继承自 BaseMapper 的 UserMapper 接口)中的方法,这些方法包含了基本的 CRUD 操作以及可能的自定义扩展方法。
    • 代理对象接收到方法调用后,MyBatis Plus 会根据方法签名和已注册的元数据信息动态生成 SQL 语句,并利用 MyBatis 的执行链(Executor、ParameterHandler、ResultSetHandler 等)来执行 SQL 操作。
    • MyBatis Plus 通过强大的元数据处理能力,可以根据实体类的属性自动生成 SQL 语句,避免了手动编写大量的 SQL 或 XML 映射文件。

最后想说的是其实这些感觉也不需要死记硬背,做一个普通程序员大致有个了解就行了,希望如上啰啰嗦嗦的废话能给你的工作带来一些帮助。

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

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

相关文章

动物解剖流程3d仿真展示动画支持反复观看和使用

在兽医专业的广袤领域中&#xff0c;动物解剖学作为基石学科&#xff0c;为组织胚胎学、生理学、病理解剖学、外科手术学、临床诊断学等科研教学提供了坚实的基础。而如今&#xff0c;随着科技的飞速发展&#xff0c;我们迎来了一个全新的学习时代——3D数字动物解刨虚拟仿真实…

ESP32-Thonny 拍摄图片到SD卡

前言&#xff1a; 代码运行在Thonny 添加main.py到单片机中&#xff1a; 可以先运行一下试试&#xff1a;会输出以下信息&#xff1a; 没有问题的话&#xff08;SD卡挂载成功&#xff0c;摄像头初始化成功&#xff09;运行一次主程序后&#xff0c;闪光灯会闪烁一下。 代码&…

autodesk系列软件安装错误1603,手动安装Autodesk Desktop Licensing Service之后,启动服务提示错误1067

一般Autodesk Desktop Licensing Service这个服务没安装或者不正常会导致autodesk系列软件安装错误1603或者其他报错。 手动安装Autodesk Desktop Licensing Service之后&#xff0c;启动服务提示错误1067&#xff0c; 解决方法如下 打开autoremove点击扩展功能&#xff0c;输…

一维递归:递去

示例&#xff1a; /*** brief how about recursive-forward-1? show you here.* author wenxuanpei* email 15873152445163.com(query for any question here)*/ #define _CRT_SECURE_NO_WARNINGS//support c-library in Microsoft-Visual-Studio #include <stdio.h>…

李廉洋:4.23黄金休市之后一蹶不振,原油小幅度上涨。晚间策略!

美国利率居高不下&#xff0c;降低了黄金等非收益资产的吸引力。今天的经济数据可能会影响美联储的利率决定&#xff0c;从而可能影响金价走势。美国货币政策对黄金价格的影响&#xff0c;美元走强以及对美国利率持续高企的预期&#xff0c;正对金价施加额外压力。美联储对持续…

​Gu‘reum 工作室在The Sandbox推出 2024 年农历新年活动!

通过区块链游戏分享韩国文化并建立社区&#xff01; 去年 12 月&#xff0c;Gurenum 工作室 在The Sandbox 元宇宙上发起了 2023 年年末 Lan Party 直播活动。 https://sandboxgame.medium.com/gureum-studio-hosts-a-year-end-lan-party-in-the-sandbox-metaverse-b9a3fc6e7b9…

我的世界服务器设计思路应该是什么样?

我的世界服务器设计思路可以从这4个方面展开&#xff1a;1.选择你喜欢的东西&#xff1b;2.认识你的极限&#xff1b;3.注入新鲜元素&#xff1b;4.让服务器变得享受且有回报。 1.选择你喜欢的东西 设计服务器的首要规则是创造一些你自己会积极享受玩的东西。没有人愿意花费宝…

js some对比forEach

some&#xff1a;return true可以停止循环 forEach&#xff1a;return true无法停止循环 <!DOCTYPE html> <html ng-app"my_app"><head><script type"text/javascript">const array [10, 20, 30];const targetValue 10;// 检测…

2010年认证杯SPSSPRO杯数学建模C题(第一阶段)高校图书馆的智能服务全过程文档及程序

2010年认证杯SPSSPRO杯数学建模 C题 高校图书馆的智能服务 原题再现&#xff1a; 图书馆源于保存记事的习惯。图书馆是为读者在馆内使用文献而提供的专门场所。而高校的图书馆为教学和科研服务&#xff0c;具有服务性和学术性强的特点。   现在的高校图书馆存在着许多不良的…

刷题训练之二分查找

> 作者&#xff1a;დ旧言~ > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;熟练掌握二分查找算法 > 毒鸡汤&#xff1a;学习&#xff0c;学习&#xff0c;再学习 ! 学&#xff0c;然后知不足。 > 专栏选自&#xff1a;刷题…

计算IP地址总个数的方法及其应用

IP地址是计算机网络中用于唯一标识和定位设备的数字地址&#xff0c;是Internet Protocol&#xff08;IP&#xff09;的核心组成部分。计算IP地址的总个数是网络规划和管理中的重要任务之一&#xff0c;本文将介绍计算IP地址总个数的方法及其应用。 IP地址查询&#xff1a;IP数…

STM32 学习13 低功耗模式与唤醒

STM32 学习13 低功耗模式与唤醒 一、介绍1. STM32低功耗模式功能介绍2. 常见的低功耗模式&#xff08;1&#xff09;**睡眠模式 (Sleep Mode)**:&#xff08;2&#xff09;**停止模式 (Stop Mode)**:&#xff08;3&#xff09;**待机模式 (Standby Mode)**: 二、睡眠模式1. 进入…

基于CAPL的BIN文件解析

&#x1f345; 我是蚂蚁小兵&#xff0c;专注于车载诊断领域&#xff0c;尤其擅长于对CANoe工具的使用&#x1f345; 寻找组织 &#xff0c;答疑解惑&#xff0c;摸鱼聊天&#xff0c;博客源码&#xff0c;点击加入&#x1f449;【相亲相爱一家人】&#x1f345; 玩转CANoe&…

管理 Python 项目的艺术:在 PyCharm 中使用虚拟环境(以BPnP为例)

在 PyCharm 中使用虚拟环境对于 Python 项目开发具有多方面的重要作用&#xff0c;这些作用体现在提升项目管理的效率、保障代码的可运行性以及维护项目的长期稳定性等方面。以下是使用虚拟环境的几个关键好处&#xff1a; 1. 依赖管理和隔离 虚拟环境允许每个项目拥有…

Adobe Photoshop CC 2017无法打开解决方案

Adobe Photoshop CC 2017双击无反应&#xff0c;右键以管理员身份运行也没有反应 解决方案&#xff1a; 事件查看器中查看应用程序的事件 如果找到程序报错事件&#xff0c;网上下载ZXPSignLib-minimal.dll文件替换错误模块路径位置的该文件即可 ZXPSignLib-minimal.dll下载地…

20240423-bitlocker加密怎么解除

20240423-bitlocker加密怎么解除 2024/4/23 15:42 百度&#xff1a;bitlocker加密怎么解除 https://baijiahao.baidu.com/s?id1779469107112180136&wfrspider&forpc Bitlocker加密怎么解除 解除BitLocker加密的步骤如下&#xff1a; 打开“控制面板”&#xff1a;在…

重学java 20.面向对象 继承 下

对未来真正的慷慨&#xff0c;是把一切献给现在 —— 24.4.22 super和this 学习目标&#xff1a; 01.继承中构造方法的特点 02.super和this的具体使用 03.继承的特点 04.练习&#xff1a;为父类中private属性赋值 一、继承中构造方法的特点 1.注意&#xff1a; 在new子类对象时…

项目开发过程中的错误记录

一、VUE版本一致但是运行不起来。 管理员运行命令提示符 切换到可以看到package.json的目录下面 npm install --registryhttps://registry.npmmirror.com npm install npm run dev 二、git pull拉取不下来远程项目 三、ssm项目运行时&#xff0c;tomcat输出日志乱码 解决&am…

红黑树封装map和set(模拟实现)

troop主页 临近五一放假&#xff0c;祝大家节日快乐&#xff0c;我们的学习还在路上&#xff01;&#xff01; 在前面一章我们已经实现出了红黑树的基本框架&#xff0c;现在我们要有这个底层结构去封装map和set。 一 红黑树的迭代器 但在这之前我们要先把红黑树的迭代器实现出…

SRS WebRTC Whip 和 Whep 部署体验问题

whip 報錯 404 webrtc推流 小窗口一闪而过&#xff0c;然后查看f12回复404的报错信息 chrome版本&#xff1a; 正在检查更新 版本 123.0.6312.123&#xff08;正式版本&#xff09; &#xff08;64 位&#xff09; centos 7.9 源码安装部署&#xff0c; 代码分支5.0 完全按…