详解如何使用Mybatis的拦截器

 更新时间:2024年03月07日 08:21:14   作者:一只爱撸猫的程序猿  
MyBatis 拦截器是 MyBatis 提供的一个强大特性,它允许你在 MyBatis 执行其核心逻辑的关键节点插入自定义逻辑,从而改变 MyBatis 的默认行为,本文给大家详细介绍了如何使用Mybatis的拦截器,需要的朋友可以参考下

MyBatis 拦截器是 MyBatis 提供的一个强大特性,它允许你在 MyBatis 执行其核心逻辑的关键节点插入自定义逻辑,从而改变 MyBatis 的默认行为。这类似于面向切面编程(AOP),允许开发者在不改变原有代码的情况下,增强或修改原有功能。

在 MyBatis 中,拦截器可以应用于以下四种类型的对象:

Executor:负责 MyBatis 中的 SQL 执行过程,拦截 Executor 可以在 SQL 执行前后添加自定义逻辑。

Executor 的核心职责

在 MyBatis 中,Executor 接口定义了数据库操作的核心方法,如 updatequerycommitrollback 等。MyBatis 提供了几种 Executor 的实现,例如 SimpleExecutorReuseExecutorBatchExecutor 等,它们各自有不同的特点和用途。

SimpleExecutor 为例,它是最基本的 Executor 实现,每次操作都会创建一个新的 Statement 对象。

源码解析

以下是 Executor 接口中 query 方法的一个简化版实现,展示了 MyBatis 如何执行一个查询操作:

public class SimpleExecutor extends BaseExecutor {

    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        // 准备 SQL
        Statement stmt = null;
        try {
            Configuration configuration = ms.getConfiguration();
            StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql);
            stmt = prepareStatement(handler, ms.getStatementLog());
            return handler.query(stmt, resultHandler);
        } finally {
            closeStatement(stmt);
        }
    }

    private Statement prepareStatement(StatementHandler handler, Log statementLog) {
        Statement stmt;
        Connection connection = getConnection(statementLog);
        stmt = handler.prepare(connection, transaction.getTimeout());
        handler.parameterize(stmt);
        return stmt;
    }
}

这段代码展示了查询操作的基本流程:创建 StatementHandler,准备和参数化 Statement,执行查询,最后关闭 Statement

拦截器的实现和应用

拦截器允许开发者在 MyBatis 核心操作执行的关键节点插入自定义逻辑。拦截器需要实现 Interceptor 接口,并定义要拦截的目标和方法。

当 MyBatis 初始化时,它会检测配置中的拦截器,并在执行相应操作时调用这些拦截器。拦截器中的 intercept 方法将在目标方法执行时被调用。

public class ExampleExecutorInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 在 SQL 执行前的逻辑
        System.out.println("Before executing query");

        // 执行原有逻辑
        Object returnValue = invocation.proceed();

        // 在 SQL 执行后的逻辑
        System.out.println("After executing query");

        return returnValue;
    }
}

在这个例子中,intercept 方法实现了在查询执行前后打印消息的逻辑。invocation.proceed() 是关键,它触发了原本的操作(如查询)。通过这种方式,开发者可以在不修改原有代码的基础上增强或改变 MyBatis 的行为。

ParameterHandler:负责 MyBatis 中的参数处理,通过拦截 ParameterHandler,可以在 SQL 语句绑定参数前后添加自定义逻辑。

ParameterHandler 的工作原理

ParameterHandler 的主要职责是为 SQL 语句绑定正确的参数值。在执行 SQL 之前,MyBatis 会通过 ParameterHandler 接口的实现类来遍历方法传入的参数,并将它们设置到 JDBC 的 PreparedStatement 中。

MyBatis 默认提供了 DefaultParameterHandler 类作为 ParameterHandler 接口的实现,用于处理参数的设置工作。

源码解析

下面是一个简化的 ParameterHandler 使用示例,演示了如何在 MyBatis 中处理参数绑定:

public class DefaultParameterHandler implements ParameterHandler {
    private final MappedStatement mappedStatement;
    private final Object parameterObject;
    private final BoundSql boundSql;
    private final TypeHandlerRegistry typeHandlerRegistry;

    public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
        this.mappedStatement = mappedStatement;
        this.parameterObject = parameterObject;
        this.boundSql = boundSql;
        this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
    }

    @Override
    public void setParameters(PreparedStatement ps) {
        ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings != null) {
            for (int i = 0; i < parameterMappings.size(); i++) {
                ParameterMapping parameterMapping = parameterMappings.get(i);
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    Object value;
                    String propertyName = parameterMapping.getProperty();
                    if (boundSql.hasAdditionalParameter(propertyName)) {
                        value = boundSql.getAdditionalParameter(propertyName);
                    } else if (parameterObject == null) {
                        value = null;
                    } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                        value = parameterObject;
                    } else {
                        MetaObject metaObject = mappedStatement.getConfiguration().newMetaObject(parameterObject);
                        value = metaObject.getValue(propertyName);
                    }
                    TypeHandler typeHandler = parameterMapping.getTypeHandler();
                    JdbcType jdbcType = parameterMapping.getJdbcType();
                    if (value == null && jdbcType == null) {
                        jdbcType = configuration.getJdbcTypeForNull();
                    }
                    typeHandler.setParameter(ps, i + 1, value, jdbcType);
                }
            }
        }
    }
}

在上述代码中,setParameters 方法是核心,它负责将参数值绑定到 PreparedStatement。首先,它会遍历所有的 ParameterMapping,这些映射信息定义了如何从参数对象中获取具体的值。然后,它使用相应的 TypeHandler 来处理参数值的类型转换,并将转换后的值设置到 PreparedStatement 中。

拦截器的应用

通过拦截 ParameterHandlersetParameters 方法,可以在参数绑定前后插入自定义逻辑。例如,可以在参数绑定之前对参数进行日志记录,或者对参数值进行额外的处理。

@Intercepts({
    @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class)
})
public class ExampleParameterHandlerInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
        // 在参数绑定前的自定义逻辑
        System.out.println("Before parameter binding");

        // 继续执行原逻辑
        Object result = invocation.proceed();

        // 在参数绑定后的自定义逻辑
        System.out.println("After parameter binding");

        return result;
    }
}

在这个例子中,intercept 方法会在 setParameters 被调用时执行,允许开发者在参数被绑定到 SQL 语句之前和之后执行自定义代码。

ResultSetHandler:负责处理 JDBC 返回的 ResultSet 结果集,拦截 ResultSetHandler 可以在结果集处理前后添加自定义逻辑。

ResultSetHandler 的工作原理

当 MyBatis 执行查询操作后,会得到一个 ResultSetResultSetHandler 的职责就是遍历这个 ResultSet,并将其行转换为 Java 对象。MyBatis 中默认的 ResultSetHandler 实现是 DefaultResultSetHandler

源码解析

下面是 DefaultResultSetHandler 处理结果集的一个简化版示例,帮助理解其工作机制:

public class DefaultResultSetHandler implements ResultSetHandler {

    private final MappedStatement mappedStatement;
    private final RowBounds rowBounds;

    public DefaultResultSetHandler(MappedStatement mappedStatement, RowBounds rowBounds) {
        this.mappedStatement = mappedStatement;
        this.rowBounds = rowBounds;
    }

    @Override
    public <E> List<E> handleResultSets(Statement stmt) throws SQLException {
        ResultSet rs = stmt.getResultSet();
        List<E> resultList = new ArrayList<>();
        while (rs.next()) {
            E resultObject = getRowValue(rs);
            resultList.add(resultObject);
        }
        return resultList;
    }

    private <E> E getRowValue(ResultSet rs) throws SQLException {
        // 实际的结果对象映射逻辑
        // 这里通常会涉及到 ResultMap 的处理
        return ...;
    }
}

在这个简化的例子中,handleResultSets 方法遍历 ResultSet,对每一行调用 getRowValue 方法将其转换为一个 Java 对象,最终返回一个对象列表。

拦截器的应用

通过拦截 ResultSetHandlerhandleResultSets 方法,开发者可以在结果集被处理成 Java 对象前后插入自定义逻辑。这可以用于额外的结果处理,比如对查询结果的后处理或审计日志记录等。

@Intercepts({
    @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = Statement.class)
})
public class ExampleResultSetHandlerInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 在结果集处理前的逻辑
        System.out.println("Before handling result sets");

        // 执行原有逻辑
        Object result = invocation.proceed();

        // 在结果集处理后的逻辑
        System.out.println("After handling result sets");

        return result;
    }
}

在这个示例中,intercept 方法在 handleResultSets 被调用时执行,允许在结果集转换为 Java 对象的前后执行自定义代码。

StatementHandler:负责对 JDBC Statement 的操作,通过拦截 StatementHandler,可以在 SQL 语句被执行前后添加自定义逻辑。

StatementHandler 的工作原理

StatementHandler 主要有三个实现类:SimpleStatementHandlerPreparedStatementHandlerCallableStatementHandler,分别对应于 JDBC 的 StatementPreparedStatementCallableStatement。这些类处理 SQL 的不同执行方式,其中 PreparedStatementHandler 是最常用的,因为它支持参数化的 SQL 语句,有助于提高性能和安全性。

源码解析

以下是 StatementHandler 接口的一个简化版实现(PreparedStatementHandler),演示了 MyBatis 如何准备和执行 SQL 语句:

public class PreparedStatementHandler implements StatementHandler {

    private final MappedStatement mappedStatement;
    private final Object parameterObject;
    private final BoundSql boundSql;
    private final ParameterHandler parameterHandler;

    public PreparedStatementHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
        this.mappedStatement = mappedStatement;
        this.parameterObject = parameterObject;
        this.boundSql = boundSql;
        this.parameterHandler = mappedStatement.getConfiguration().newParameterHandler(mappedStatement, parameterObject, boundSql);
    }

    @Override
    public PreparedStatement prepare(Connection connection) throws SQLException {
        String sql = boundSql.getSql();
        PreparedStatement pstmt = connection.prepareStatement(sql);
        parameterHandler.setParameters(pstmt);
        return pstmt;
    }

    @Override
    public int update(PreparedStatement pstmt) throws SQLException {
        pstmt.execute();
        return pstmt.getUpdateCount();
    }

    @Override
    public <E> List<E> query(PreparedStatement pstmt, ResultHandler resultHandler) throws SQLException {
        pstmt.execute();
        return resultSetHandler.handleResultSets(pstmt);
    }
}

在这个简化的实现中,prepare 方法负责创建 PreparedStatement 并通过 ParameterHandler 设置参数。updatequery 方法则用于执行 SQL 语句并处理执行结果。

拦截器的应用

通过拦截 StatementHandler 的方法,可以在 SQL 语句执行的关键节点加入自定义逻辑。例如,可以拦截 prepare 方法,在 SQL 语句被准备之前或之后添加逻辑:

@Intercepts({
    @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class ExampleStatementHandlerInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 在 SQL 语句准备前的逻辑
        System.out.println("Before preparing the SQL statement");

        // 执行原有逻辑
        Object result = invocation.proceed();

        // 在 SQL 语句准备后的逻辑
        System.out.println("After preparing the SQL statement");

        return result;
    }
}

在这个示例中,intercept 方法会在 prepare 方法执行时被调用,允许开发者在 SQL 语句被准备和执行前后插入自定义代码。

实际案例

让我们在一个 Spring Boot 项目中结合 MyBatis 的 ExecutorParameterHandlerResultSetHandler、和 StatementHandler 构建一个实际案例。我们将创建一个简单的用户管理系统,其中包含用户的添加、查询功能,并通过拦截器在关键节点添加日志记录、性能监控等自定义逻辑。

步骤 1: 定义实体和映射文件

首先,定义一个 User 实体:

public class User {
    private Integer id;
    private String name;
    private String email;

    // Getters and setters...
}

然后,创建一个 MyBatis 映射文件 UserMapper.xml

<mapper namespace="com.example.mybatisdemo.mapper.UserMapper">
    <insert id="insertUser" parameterType="User">
        INSERT INTO users (name, email) VALUES (#{name}, #{email})
    </insert>
    
    <select id="getUserById" parameterType="int" resultType="User">
        SELECT * FROM users WHERE id = #{id}
    </select>
</mapper>

步骤 2: 定义 Mapper 接口

@Mapper
public interface UserMapper {
    void insertUser(User user);
    User getUserById(int id);
}

步骤 3: 定义拦截器

  • Executor 拦截器 - 记录执行时间:
@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 ExecutorInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object result = invocation.proceed();
        long endTime = System.currentTimeMillis();
        System.out.println("Execution time: " + (endTime - startTime) + "ms");
        return result;
    }
}
  • ParameterHandler 拦截器 - 记录参数信息:
@Intercepts({
    @Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class})
})
public class ParameterHandlerInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
        System.out.println("Parameters: " + parameterHandler.getParameterObject());
        return invocation.proceed();
    }
}
  • ResultSetHandler 拦截器 - 在结果集后打印日志:
@Intercepts({
    @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
public class ResultSetHandlerInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object result = invocation.proceed();
        System.out.println("Result: " + result);
        return result;
    }
}
  • StatementHandler 拦截器 - 修改 SQL 语句:
@Intercepts({
    @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class StatementHandlerInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler handler = (StatementHandler) invocation.getTarget();
        BoundSql boundSql = handler.getBoundSql();
        String sql = boundSql.getSql();
        System.out.println("Original SQL: " + sql);
        
        // 此处可根据需要修改 sql
        // String modifiedSql = sql.replace(...);

        return invocation.proceed();
    }
}

步骤 4: 注册拦截器

在 Spring Boot 配置类中注册拦截器:

@Configuration
public class MyBatisConfig {
    
    @Bean
    public ExecutorInterceptor executorInterceptor() {
        return new ExecutorInterceptor();
    }

    @Bean
    public ParameterHandlerInterceptor parameterHandlerInterceptor() {
        return new ParameterHandlerInterceptor();
    }

    @Bean
    public ResultSetHandlerInterceptor resultSetHandlerInterceptor() {
        return new ResultSetHandlerInterceptor();
    }

    @Bean
    public StatementHandlerInterceptor statementHandlerInterceptor() {
        return new StatementHandlerInterceptor();
    }
}

步骤 5: 使用 Mapper 进行操作

在你的服务层或控制器中,使用 UserMapper 来执行

数据库操作:

@Service
public class UserService {
    
    @Autowired
    private UserMapper userMapper;

    public void createUser(User user) {
        userMapper.insertUser(user);
    }

    public User getUser(int id) {
        return userMapper.getUserById(id);
    }
}

当你执行 createUser 或 getUser 方法时,你的拦截器将会被触发,你可以看到控制台上打印的相关信息,以及 MyBatis 如何处理 SQL 操作以及如何通过拦截器介入这些过程。

以上就是详解如何使用Mybatis的拦截器的详细内容,更多关于Mybatis拦截器使用的资料请关注脚本之家其它相关文章!

相关文章

  • MyBatis与Spring整合过程实例解析

    MyBatis与Spring整合过程实例解析

    这篇文章主要介绍了MyBatis与Spring整合过程实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-02-02
  • Java实战项目之斗地主和斗牛游戏的实现

    Java实战项目之斗地主和斗牛游戏的实现

    读万卷书不如行万里路,只学书上的理论是远远不够的,只有在实战中才能获得能力的提升,本篇文章手把手带你用Java实现一个斗地主和一个斗牛游戏,大家可以在过程中查缺补漏,提升水平
    2021-11-11
  • Spring P标签的使用详解

    Spring P标签的使用详解

    这篇文章主要介绍了Spring P标签的使用详解,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • JAVA深入探究之Method的Invoke方法

    JAVA深入探究之Method的Invoke方法

    这篇文章主要给大家介绍了关于JAVA深入探究之Method的Invoke方法的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01
  • Java数据结构之散列表(动力节点Java学院整理)

    Java数据结构之散列表(动力节点Java学院整理)

    散列表(Hash table,也叫哈希表),是根据关键字(key value)而直接进行访问的数据结构。这篇文章给大家介绍了java数据结构之散列表,包括基本概念和散列函数相关知识,需要的的朋友参考下吧
    2017-04-04
  • springboot 多模块将dao(mybatis)项目拆分出去

    springboot 多模块将dao(mybatis)项目拆分出去

    这篇文章主要介绍了springboot 多模块将dao(mybatis)项目拆分出去,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-05-05
  • String字符串转BigDecimal时,报NumberFormatException异常的解决

    String字符串转BigDecimal时,报NumberFormatException异常的解决

    这篇文章主要介绍了String字符串转BigDecimal时,报NumberFormatException异常的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-07-07
  • 详解 Java中日期数据类型的处理之格式转换的实例

    详解 Java中日期数据类型的处理之格式转换的实例

    这篇文章主要介绍了详解 Java中日期数据类型的处理之格式转换的实例的相关资料,日期以及时间格式处理,在Java中时间格式一般会涉及到的数据类型包括Calendar类和Date类,需要的朋友可以参考下
    2017-08-08
  • 详解Java如何使用集合来实现一个客户信息管理系统

    详解Java如何使用集合来实现一个客户信息管理系统

    读万卷书不如行万里路,只学书上的理论是远远不够的,只有在实战中才能获得能力的提升,本篇文章手把手带你用Java 集合实现一个客户信息管理系统,大家可以在过程中查缺补漏,提升水平
    2021-11-11
  • Java中Base64和File之间互转代码示例

    Java中Base64和File之间互转代码示例

    这篇文章主要给大家介绍了关于Java中Base64和File之间互转的相关资料,Base64是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64就是一种基于64个可打印字符来表示二进制数据的方法,需要的朋友可以参考下
    2023-08-08

最新评论