MyBatis 参数映射机制实践记录
作者:山高自有客行路
核心组件
1. SqlSessionFactory
MyBatis 的入口点是 SqlSessionFactory
,它负责创建 SqlSession
实例。每个 SqlSession
都代表一个与数据库的会话,并且在该会话中可以执行 SQL 操作。
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); try (SqlSession session = sqlSessionFactory.openSession()) { // 使用 session 进行 CRUD 操作 }
2. SqlSession
SqlSession
提供了用于执行语句、提交或回滚事务以及获取映射器实例的方法。它是线程不安全的,因此应该在方法作用域内使用(即每次调用时创建和关闭)。
UserMapper mapper = session.getMapper(UserMapper.class); User user = mapper.getUserById(100);
3. Mapper Interface
Mapper 接口定义了与数据库交互的方法,这些方法通常对应于 SQL 语句。MyBatis 通过动态代理的方式实现了这些接口,使得你可以在代码中像调用普通 Java 方法一样调用它们。
public interface UserMapper { @Select("SELECT * FROM users WHERE id = #{id}") User getUserById(@Param("id") int id); }
参数处理的具体流程
ParameterHandler 和 TypeHandler
- ParameterHandler:如前所述,这是 MyBatis 处理 SQL 语句参数的核心组件。它根据 SQL 语句中的占位符设置相应的值。
- TypeHandler:用于将 Java 类型转换为 JDBC 类型,反之亦然。你可以注册自定义的
TypeHandler
来支持特定的数据类型。
内部工作原理
- 解析 SQL 语句:当 MyBatis 解析 XML 或注解定义的 SQL 语句时,它会识别出所有的
#{}
和${}
占位符,并记录下它们的位置。 - 创建 ParameterHandler:对于每一个需要执行的 SQL 语句,MyBatis 都会创建一个
ParameterHandler
实例。这个实例包含了如何填充 SQL 语句中占位符的信息。 - 设置参数值:
ParameterHandler
会遍历所有占位符,并根据传入的参数对象(可以是简单类型、Java Bean、Map 或集合)来设置对应的值。如果参数是一个复杂类型,MyBatis 会递归地访问其属性,直到找到匹配的值。 - 应用 TypeHandler:对于每一个参数值,MyBatis 会查找并应用适当的
TypeHandler
来确保正确的数据类型转换。 - 执行 SQL:最后,带有正确参数值的 SQL 语句被发送到数据库进行执行。
参数映射的实现细节
单个参数
对于单个参数的情况,MyBatis 可以直接使用参数名或默认名称(如 param1
)。如果你希望提高代码的可读性,建议总是使用 @Param
注解明确指定参数名。
@Select("SELECT * FROM users WHERE id = #{userId}") User getUserById(@Param("userId") int id);
多个参数
当有多个参数时,使用 @Param
注解是非常重要的,因为它可以让 MyBatis 知道每个参数的名字,从而在 SQL 中通过名字引用它们。在springBoot的1.x版本/单独使用mybatis(使用@Param注解来指定SQL语句中的参数名),因为在编译时,生成的字节码文件当中,不会保留Mapper接口中方法的形参名称,而是使用var1、var2、...这样的形参名字,此时要获取参数值时,就要通过@Param注解来指定SQL语句中的参数名。
@Select("SELECT * FROM orders WHERE user_id = #{userId} AND status = #{status}") List<Order> findOrdersByUserIdAndStatus(@Param("userId") int userId, @Param("status") String status);
Java Bean 或 Map 参数
对于复杂的参数类型,如 Java Bean 或 Map,MyBatis 会自动解析对象的属性或 Map 的键值对,允许你在 SQL 中通过属性名或键名直接引用这些值。
@Select("SELECT * FROM products WHERE category = #{category} AND price < #{maxPrice}") List<Product> findProducts(ProductCriteria criteria);
数组和集合参数
数组和集合类型的参数可以通过 <foreach>
标签来处理,这使得你可以遍历集合并在 SQL 中插入多值条件。
<select id="getCategoriesByIds" resultType="Category"> SELECT * FROM categories WHERE id IN <foreach item="id" collection="ids" open="(" separator="," close=")"> #{id} </foreach> </select>
动态 SQL
MyBatis 支持动态 SQL,允许根据运行时条件构造 SQL 语句。这包括使用 <if>
, <choose>
, <when>
, <otherwise>
, <trim>
, <where>
, <set>
, 和 <foreach>
标签。
<select id="findActiveBlogWithTitleLike" resultType="Blog"> SELECT * FROM BLOG WHERE state = 'ACTIVE' <if test="title != null"> AND title like #{title} </if> </select>
高级特性
自定义 TypeHandler
有时你需要自定义数据类型之间的转换逻辑。例如,处理枚举类型或自定义日期格式。你可以通过实现 TypeHandler
接口并注册到 MyBatis 中来完成这一需求。
public class EnumTypeHandler<E extends Enum<E>> implements TypeHandler<E> { private final Class<E> type; public EnumTypeHandler(Class<E> type) { if (type == null) throw new IllegalArgumentException("Type argument cannot be null"); this.type = type; } @Override public void setParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException { if (parameter == null) { ps.setNull(i, Types.VARCHAR); } else { ps.setString(i, parameter.name()); } } @Override public E getResult(ResultSet rs, String columnName) throws SQLException { return rs.getString(columnName) == null ? null : Enum.valueOf(type, rs.getString(columnName)); } @Override public E getResult(ResultSet rs, int columnIndex) throws SQLException { return rs.getString(columnIndex) == null ? null : Enum.valueOf(type, rs.getString(columnIndex)); } @Override public E getResult(CallableStatement cs, int columnIndex) throws SQLException { return cs.getString(columnIndex) == null ? null : Enum.valueOf(type, cs.getString(columnIndex)); } }
然后在 MyBatis 配置文件中注册这个 TypeHandler
:
<typeHandlers> <typeHandler javaType="com.example.MyEnum" handler="com.example.EnumTypeHandler"/> </typeHandlers>
插件机制
MyBatis 还提供了插件机制,允许开发者拦截和修改 MyBatis 的内部行为,如拦截 SQL 执行、结果映射等。这对于性能监控、日志记录等功能非常有用。
@Intercepts({@Signature(type= Executor.class, method = "update", args = {MappedStatement.class, Object.class})}) public class ExamplePlugin implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { // 在这里添加你的逻辑 return invocation.proceed(); } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { // 设置插件属性 } }
性能优化和最佳实践
1. 批量操作
对于大批量的数据操作,考虑使用批量插入、更新或删除,以减少数据库交互次数。可以使用 <foreach>
标签结合批处理功能。
<insert id="batchInsertUsers"> INSERT INTO users (name, email) VALUES <foreach collection="list" item="user" separator=","> (#{user.name}, #{user.email}) </foreach> </insert>
2. 缓存策略
合理配置一级缓存和二级缓存,避免不必要的重复查询。一级缓存是基于 SqlSession
的,而二级缓存可以在多个 SqlSession
之间共享。
<mapper namespace="com.example.UserMapper"> <cache/> <!-- 其他映射语句 --> </mapper>
3. 减少不必要的参数传递
只传递必要的参数,以减少内存消耗和提高性能。对于复杂对象,尽量只传递需要的字段。
4. 文档化和一致性
保持代码风格一致,并为接口和方法添加适当的文档说明,有助于团队协作和维护。
错误处理与调试
1. SQL 日志输出
开启 SQL 日志输出可以帮助你调试和优化 SQL 语句。MyBatis 支持多种日志框架,如 Log4j、SLF4J 等。
# log4j.properties log4j.logger.org.apache.ibatis=DEBUG log4j.logger.java.sql.Connection=DEBUG log4j.logger.java.sql.Statement=DEBUG log4j.logger.java.sql.PreparedStatement=DEBUG
2. 异常处理
确保在捕获异常时提供足够的信息,以便快速定位问题。可以使用 AOP 或者手动捕获异常,并记录详细的错误信息。
try { // 数据库操作 } catch (PersistenceException e) { logger.error("Database operation failed", e); throw e; }
到此这篇关于MyBatis 参数映射机制的文章就介绍到这了,更多相关MyBatis 参数映射内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!