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

Mybatis学习笔记(二)

Mybatis 缓存机制

简要描述:MyBatis提供了两级缓存机制来提高查询性能,一级缓存是SqlSession级别的,二级缓存是Mapper级别的。

核心概念

  • 一级缓存:SqlSession级别,默认开启,生命周期与SqlSession相同
  • 二级缓存:Mapper级别,需要手动开启,可以跨SqlSession共享
  • 缓存键:根据SQL语句、参数、分页等信息生成唯一标识
  • 缓存失效:增删改操作会清空相关缓存

一级缓存(SqlSession级别)

简要描述:一级缓存是SqlSession级别的缓存,默认开启,同一个SqlSession中相同的查询会直接从缓存中获取结果。

核心概念

  • 作用域:SqlSession级别,不同SqlSession之间不共享
  • 生命周期:与SqlSession相同,SqlSession关闭时缓存清空
  • 缓存策略:LRU(最近最少使用)
  • 自动管理:无需手动配置,MyBatis自动管理

工作原理

// 一级缓存示例
public void testFirstLevelCache() {SqlSession sqlSession = sqlSessionFactory.openSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);// 第一次查询,从数据库获取User user1 = mapper.selectById(1L);System.out.println("第一次查询:" + user1);// 第二次查询,从一级缓存获取(相同SqlSession)User user2 = mapper.selectById(1L);System.out.println("第二次查询:" + user2);// 验证是否为同一个对象System.out.println("是否为同一对象:" + (user1 == user2)); // truesqlSession.close();
}

缓存失效场景

public void testFirstLevelCacheInvalidation() {SqlSession sqlSession = sqlSessionFactory.openSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);// 第一次查询User user1 = mapper.selectById(1L);// 执行更新操作,一级缓存被清空User updateUser = new User();updateUser.setId(1L);updateUser.setName("Updated Name");mapper.updateById(updateUser);// 再次查询,重新从数据库获取User user2 = mapper.selectById(1L);System.out.println("更新后查询:" + user2.getName()); // "Updated Name"sqlSession.commit();sqlSession.close();
}

手动清除一级缓存

public void testClearFirstLevelCache() {SqlSession sqlSession = sqlSessionFactory.openSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);// 第一次查询User user1 = mapper.selectById(1L);// 手动清除一级缓存sqlSession.clearCache();// 再次查询,重新从数据库获取User user2 = mapper.selectById(1L);System.out.println("清除缓存后是否为同一对象:" + (user1 == user2)); // falsesqlSession.close();
}

一级缓存配置

<!-- 在mybatis-config.xml中配置一级缓存 -->
<settings><!-- 一级缓存作用域:SESSION(默认)或STATEMENT --><setting name="localCacheScope" value="SESSION"/>
</settings>

二级缓存(Mapper级别)

简要描述:二级缓存是Mapper级别的缓存,可以跨SqlSession共享,需要手动开启和配置。

核心概念

  • 作用域:Mapper级别,同一个Mapper的不同SqlSession可以共享
  • 序列化:缓存对象需要实现Serializable接口
  • 线程安全:支持并发访问
  • 可配置:支持多种缓存实现和配置选项

开启二级缓存

<!-- 1. 在mybatis-config.xml中开启二级缓存 -->
<settings><setting name="cacheEnabled" value="true"/>
</settings><!-- 2. 在Mapper XML中配置缓存 -->
<mapper namespace="com.example.mapper.UserMapper"><!-- 开启二级缓存 --><cache/><!-- 或者详细配置 --><cache eviction="LRU"           <!-- 缓存回收策略:LRU, FIFO, SOFT, WEAK -->flushInterval="60000"    <!-- 刷新间隔(毫秒) -->size="512"               <!-- 缓存大小 -->readOnly="false"         <!-- 是否只读 -->type="org.mybatis.caches.ehcache.EhcacheCache"/> <!-- 自定义缓存实现 --><!-- 查询语句 --><select id="selectById" parameterType="long" resultType="User" useCache="true">SELECT id, name, age, email FROM user WHERE id = #{id}</select><!-- 更新语句 --><update id="updateById" parameterType="User" flushCache="true">UPDATE user SET name = #{name}, age = #{age} WHERE id = #{id}</update>
</mapper>

实体类序列化

// 实体类需要实现Serializable接口
public class User implements Serializable {private static final long serialVersionUID = 1L;private Long id;private String name;private Integer age;private String email;// getter和setter方法
}

二级缓存测试

public void testSecondLevelCache() {// 第一个SqlSessionSqlSession sqlSession1 = sqlSessionFactory.openSession();UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);User user1 = mapper1.selectById(1L);sqlSession1.close(); // 关闭后数据进入二级缓存// 第二个SqlSessionSqlSession sqlSession2 = sqlSessionFactory.openSession();UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);User user2 = mapper2.selectById(1L); // 从二级缓存获取sqlSession2.close();// 虽然是不同的SqlSession,但数据相同System.out.println("用户信息相同:" + user1.equals(user2)); // true
}

缓存配置与优化

简要描述:MyBatis提供了丰富的缓存配置选项,可以根据业务需求进行精细化配置。

全局缓存配置

<settings><!-- 开启二级缓存 --><setting name="cacheEnabled" value="true"/><!-- 一级缓存作用域 --><setting name="localCacheScope" value="SESSION"/><!-- 延迟加载全局开关 --><setting name="lazyLoadingEnabled" value="true"/><!-- 积极延迟加载开关 --><setting name="aggressiveLazyLoading" value="false"/>
</settings>

Mapper级别缓存配置

<!-- 基本缓存配置 -->
<cache/><!-- 详细缓存配置 -->
<cache eviction="LRU"           <!-- 回收策略 -->flushInterval="60000"    <!-- 刷新间隔 -->size="512"               <!-- 缓存大小 -->readOnly="false"         <!-- 只读模式 -->blocking="true"          <!-- 阻塞模式 -->type="org.mybatis.caches.ehcache.EhcacheCache"/> <!-- 缓存实现类 --><!-- 引用其他Mapper的缓存 -->
<cache-ref namespace="com.example.mapper.BaseMapper"/>

语句级别缓存配置

<!-- 查询语句缓存配置 -->
<select id="selectById" parameterType="long" resultType="User" useCache="true"      <!-- 是否使用二级缓存 -->timeout="30000"      <!-- 查询超时时间 -->fetchSize="100"      <!-- 获取记录数 -->statementType="PREPARED"> <!-- 语句类型 -->SELECT id, name, age, email FROM user WHERE id = #{id}
</select><!-- 更新语句缓存配置 -->
<update id="updateById" parameterType="User" flushCache="true"    <!-- 是否清空缓存 -->timeout="30000"      <!-- 执行超时时间 -->statementType="PREPARED">UPDATE user SET name = #{name}, age = #{age} WHERE id = #{id}
</update>

缓存回收策略

  • LRU:最近最少使用,移除最长时间不被使用的对象(默认)
  • FIFO:先进先出,按对象进入缓存的顺序来移除
  • SOFT:软引用,基于垃圾回收器状态和软引用规则移除对象
  • WEAK:弱引用,更积极地基于垃圾收集器状态和弱引用规则移除对象

自定义缓存实现

简要描述:MyBatis支持自定义缓存实现,可以集成Redis、Ehcache等第三方缓存框架。

自定义Redis缓存

// 自定义Redis缓存实现
public class RedisCache implements Cache {private final String id;private RedisTemplate<String, Object> redisTemplate;public RedisCache(String id) {this.id = id;this.redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);}@Overridepublic String getId() {return id;}@Overridepublic void putObject(Object key, Object value) {redisTemplate.opsForValue().set(key.toString(), value, 30, TimeUnit.MINUTES);}@Overridepublic Object getObject(Object key) {return redisTemplate.opsForValue().get(key.toString());}@Overridepublic Object removeObject(Object key) {Object value = getObject(key);redisTemplate.delete(key.toString());return value;}@Overridepublic void clear() {Set<String> keys = redisTemplate.keys(id + "*");if (keys != null && !keys.isEmpty()) {redisTemplate.delete(keys);}}@Overridepublic int getSize() {Set<String> keys = redisTemplate.keys(id + "*");return keys != null ? keys.size() : 0;}
}

使用自定义缓存

<!-- 在Mapper XML中使用自定义缓存 -->
<cache type="com.example.cache.RedisCache"><property name="host" value="localhost"/><property name="port" value="6379"/>
</cache>

Ehcache集成

<!-- 添加Ehcache依赖 -->
<dependency><groupId>org.mybatis.caches</groupId><artifactId>mybatis-ehcache</artifactId><version>1.2.1</version>
</dependency><!-- 使用Ehcache -->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

缓存失效策略

简要描述:了解缓存失效的时机和原因,对于正确使用缓存机制至关重要。

一级缓存失效场景

  1. SqlSession关闭:SqlSession关闭时,一级缓存自动清空
  2. 执行更新操作:执行insert、update、delete操作时清空缓存
  3. 手动清除:调用sqlSession.clearCache()方法
  4. 查询参数不同:不同的查询参数会产生不同的缓存键
public void testFirstLevelCacheInvalidation() {SqlSession sqlSession = sqlSessionFactory.openSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);// 场景1:相同查询,命中缓存User user1 = mapper.selectById(1L);User user2 = mapper.selectById(1L);System.out.println("相同查询:" + (user1 == user2)); // true// 场景2:不同参数,不命中缓存User user3 = mapper.selectById(2L);System.out.println("不同参数:" + (user1 == user3)); // false// 场景3:执行更新,缓存失效mapper.updateById(new User(1L, "New Name", 25, "new@email.com"));User user4 = mapper.selectById(1L);System.out.println("更新后查询:" + (user1 == user4)); // falsesqlSession.close();
}

二级缓存失效场景

  1. 执行更新操作:同一个Mapper中的insert、update、delete操作
  2. 缓存过期:达到flushInterval设置的时间间隔
  3. 缓存满了:达到size限制,根据回收策略移除
  4. 手动清除:调用缓存的clear()方法
public void testSecondLevelCacheInvalidation() {// 第一次查询,建立缓存SqlSession sqlSession1 = sqlSessionFactory.openSession();UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);User user1 = mapper1.selectById(1L);sqlSession1.close();// 执行更新操作,清空二级缓存SqlSession sqlSession2 = sqlSessionFactory.openSession();UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);mapper2.updateById(new User(1L, "Updated", 30, "updated@email.com"));sqlSession2.commit();sqlSession2.close();// 再次查询,重新从数据库获取SqlSession sqlSession3 = sqlSessionFactory.openSession();UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class);User user3 = mapper3.selectById(1L);System.out.println("更新后的用户名:" + user3.getName()); // "Updated"sqlSession3.close();
}

缓存穿透和缓存雪崩

// 缓存穿透:查询不存在的数据
public User selectUserWithCache(Long id) {// 先查缓存User user = (User) cache.get("user:" + id);if (user != null) {return user;}// 查询数据库user = userMapper.selectById(id);// 即使查询结果为null,也缓存一段时间,防止缓存穿透if (user == null) {cache.put("user:" + id, new NullUser(), 60); // 缓存60秒} else {cache.put("user:" + id, user, 1800); // 缓存30分钟}return user;
}// 缓存雪崩:设置随机过期时间
public void setCacheWithRandomExpire(String key, Object value) {int baseExpire = 1800; // 30分钟int randomExpire = new Random().nextInt(300); // 0-5分钟随机cache.put(key, value, baseExpire + randomExpire);
}

最佳实践

  1. 合理使用一级缓存:注意SqlSession的生命周期
  2. 谨慎开启二级缓存:确保实体类实现Serializable
  3. 避免缓存穿透:对空结果也进行短时间缓存
  4. 防止缓存雪崩:设置随机过期时间
  5. 监控缓存效果:定期检查缓存命中率
  6. 及时清理缓存:在数据更新时及时清理相关缓存

Mybatis 核心原理

简要描述:深入理解MyBatis的核心原理,包括SqlSessionFactory的创建过程、SQL执行流程、插件机制等,有助于更好地使用和扩展MyBatis。

SqlSessionFactory创建过程

简要描述:SqlSessionFactory是MyBatis的核心工厂类,负责创建SqlSession实例,其创建过程涉及配置文件解析、环境构建等多个步骤。

核心概念

  • 配置解析:解析mybatis-config.xml和Mapper XML文件
  • 环境构建:构建数据源、事务管理器等环境信息
  • 映射注册:注册Mapper接口和SQL映射
  • 插件加载:加载和配置插件

创建流程

// 1. 通过配置文件创建
public SqlSessionFactory createSqlSessionFactory() throws IOException {// 读取配置文件String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);// 创建SqlSessionFactoryBuilderSqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();// 构建SqlSessionFactorySqlSessionFactory sqlSessionFactory = builder.build(inputStream);return sqlSessionFactory;
}// 2. 通过Configuration对象创建
public SqlSessionFactory createSqlSessionFactoryByConfig() {// 创建Configuration对象Configuration configuration = new Configuration();// 配置数据源DataSource dataSource = createDataSource();TransactionFactory transactionFactory = new JdbcTransactionFactory();Environment environment = new Environment("development", transactionFactory, dataSource);configuration.setEnvironment(environment);// 添加Mapperconfiguration.addMapper(UserMapper.class);// 创建SqlSessionFactorySqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);return sqlSessionFactory;
}

配置解析过程

// XMLConfigBuilder解析配置文件的核心方法
public class XMLConfigBuilder extends BaseBuilder {public Configuration parse() {if (parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");}parsed = true;// 解析configuration节点parseConfiguration(parser.evalNode("/configuration"));return configuration;}private void parseConfiguration(XNode root) {try {// 解析properties节点propertiesElement(root.evalNode("properties"));// 解析settings节点Properties settings = settingsAsProperties(root.evalNode("settings"));loadCustomVfs(settings);loadCustomLogImpl(settings);// 解析typeAliases节点typeAliasesElement(root.evalNode("typeAliases"));// 解析plugins节点pluginElement(root.evalNode("plugins"));// 解析objectFactory节点objectFactoryElement(root.evalNode("objectFactory"));// 解析objectWrapperFactory节点objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));// 解析reflectorFactory节点reflectorFactoryElement(root.evalNode("reflectorFactory"));// 应用settings配置settingsElement(settings);// 解析environments节点environmentsElement(root.evalNode("environments"));// 解析databaseIdProvider节点databaseIdProviderElement(root.evalNode("databaseIdProvider"));// 解析typeHandlers节点typeHandlerElement(root.evalNode("typeHandlers"));// 解析mappers节点mapperElement(root.evalNode("mappers"));} catch (Exception e) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);}}
}

Mapper注册过程

// MapperRegistry负责Mapper接口的注册和获取
public class MapperRegistry {private final Configuration config;private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();public <T> void addMapper(Class<T> type) {if (type.isInterface()) {if (hasMapper(type)) {throw new BindingException("Type " + type + " is already known to the MapperRegistry.");}boolean loadCompleted = false;try {// 创建MapperProxyFactoryknownMappers.put(type, new MapperProxyFactory<>(type));// 解析Mapper注解和XMLMapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);parser.parse();loadCompleted = true;} finally {if (!loadCompleted) {knownMappers.remove(type);}}}}@SuppressWarnings("unchecked")public <T> T getMapper(Class<T> type, SqlSession sqlSession) {final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);if (mapperProxyFactory == null) {throw new BindingException("Type " + type + " is not known to the MapperRegistry.");}try {return mapperProxyFactory.newInstance(sqlSession);} catch (Exception e) {throw new BindingException("Error getting mapper instance. Cause: " + e, e);}}
}

SqlSession生命周期

简要描述:SqlSession是MyBatis的核心接口,代表一次数据库会话,了解其生命周期对于正确使用MyBatis至关重要。

核心概念

  • 会话范围:SqlSession是线程不安全的,不能在多线程间共享
  • 生命周期:从创建到关闭的完整过程
  • 资源管理:需要及时关闭以释放数据库连接
  • 事务管理:控制事务的提交和回滚

SqlSession创建过程

// DefaultSqlSessionFactory创建SqlSession
public class DefaultSqlSessionFactory implements SqlSessionFactory {@Overridepublic SqlSession openSession() {return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);}@Overridepublic SqlSession openSession(boolean autoCommit) {return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit);}@Overridepublic SqlSession openSession(ExecutorType execType) {return openSessionFromDataSource(execType, null, false);}private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {Transaction tx = null;try {final Environment environment = configuration.getEnvironment();final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);// 创建事务tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);// 创建执行器final Executor executor = configuration.newExecutor(tx, execType);// 创建DefaultSqlSessionreturn new DefaultSqlSession(configuration, executor, autoCommit);} catch (Exception e) {closeTransaction(tx);throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}}
}

SqlSession使用模式

// 1. 手动管理模式
public void manualSessionManagement() {SqlSession sqlSession = null;try {sqlSession = sqlSessionFactory.openSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);// 执行数据库操作User user = mapper.selectById(1L);// 手动提交事务sqlSession.commit();} catch (Exception e) {if (sqlSession != null) {sqlSession.rollback();}throw e;} finally {if (sqlSession != null) {sqlSession.close();}}
}// 2. try-with-resources模式
public void autoSessionManagement() {try (SqlSession sqlSession = sqlSessionFactory.openSession()) {UserMapper mapper = sqlSession.getMapper(UserMapper.class);// 执行数据库操作User user = mapper.selectById(1L);sqlSession.commit();} catch (Exception e) {// 异常处理throw e;}
}// 3. Spring集成模式(自动管理)
@Service
public class UserService {@Autowiredprivate UserMapper userMapper;@Transactionalpublic User getUserById(Long id) {// Spring自动管理SqlSession生命周期return userMapper.selectById(id);}
}

SqlSession状态管理

// DefaultSqlSession的状态管理
public class DefaultSqlSession implements SqlSession {private final Configuration configuration;private final Executor executor;private final boolean autoCommit;private boolean dirty;  // 是否有未提交的更改private List<Cursor<?>> cursorList;  // 游标列表@Overridepublic void commit() {commit(false);}@Overridepublic void commit(boolean force) {try {executor.commit(isCommitOrRollbackRequired(force));dirty = false;} catch (Exception e) {throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}}@Overridepublic void rollback() {rollback(false);}@Overridepublic void rollback(boolean force) {try {executor.rollback(isCommitOrRollbackRequired(force));dirty = false;} catch (Exception e) {throw ExceptionFactory.wrapException("Error rolling back transaction.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}}@Overridepublic void close() {try {executor.close(isCommitOrRollbackRequired(false));closeCursors();dirty = false;} finally {ErrorContext.instance().reset();}}private boolean isCommitOrRollbackRequired(boolean force) {return (!autoCommit && dirty) || force;}
}

Executor执行器详解

简要描述:Executor是MyBatis的核心执行器,负责SQL语句的实际执行,包括缓存管理、事务处理等功能。

核心概念

  • 执行器类型:Simple、Reuse、Batch三种类型
  • 缓存管理:一级缓存的实现和管理
  • 事务处理:事务的提交、回滚和关闭
  • 延迟加载:关联对象的延迟加载处理

执行器类型

// 1. SimpleExecutor:简单执行器(默认)
public class SimpleExecutor extends BaseExecutor {@Overridepublic <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {Statement stmt = null;try {Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);// 每次都创建新的Statementstmt = prepareStatement(handler, ms.getStatementLog());return handler.query(stmt, resultHandler);} finally {closeStatement(stmt);}}@Overridepublic int doUpdate(MappedStatement ms, Object parameter) throws SQLException {Statement stmt = null;try {Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);stmt = prepareStatement(handler, ms.getStatementLog());return handler.update(stmt);} finally {closeStatement(stmt);}}
}// 2. ReuseExecutor:重用执行器
public class ReuseExecutor extends BaseExecutor {private final Map<String, Statement> statementMap = new HashMap<>();@Overridepublic <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);// 重用已有的StatementStatement stmt = prepareStatement(handler, ms.getStatementLog());return handler.query(stmt, resultHandler);}@Overridepublic int doUpdate(MappedStatement ms, Object parameter) throws SQLException {Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);Statement stmt = prepareStatement(handler, ms.getStatementLog());return handler.update(stmt);}private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {Statement stmt;BoundSql boundSql = handler.getBoundSql();String sql = boundSql.getSql();// 检查是否已有可重用的Statementif (hasStatementFor(sql)) {stmt = getStatement(sql);applyTransactionTimeout(stmt);} else {Connection connection = getConnection(statementLog);stmt = handler.prepare(connection, transaction.getTimeout());putStatement(sql, stmt);}handler.parameterize(stmt);return stmt;}
}// 3. BatchExecutor:批处理执行器
public class BatchExecutor extends BaseExecutor {public static final int BATCH_UPDATE_RETURN_VALUE = Integer.MIN_VALUE + 1002;private final List<Statement> statementList = new ArrayList<>();private final List<BatchResult> batchResultList = new ArrayList<>();private String currentSql;private MappedStatement currentStatement;@Overridepublic int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {final Configuration configuration = ms.getConfiguration();final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);final BoundSql boundSql = handler.getBoundSql();final String sql = boundSql.getSql();final Statement stmt;// 检查是否可以添加到当前批次if (sql.equals(currentSql) && ms.equals(currentStatement)) {int last = statementList.size() - 1;stmt = statementList.get(last);applyTransactionTimeout(stmt);handler.parameterize(stmt);handler.batch(stmt);} else {Connection connection = getConnection(ms.getStatementLog());stmt = handler.prepare(connection, transaction.getTimeout());handler.parameterize(stmt);handler.batch(stmt);currentSql = sql;currentStatement = ms;statementList.add(stmt);batchResultList.add(new BatchResult(ms, sql, parameterObject));}return BATCH_UPDATE_RETURN_VALUE;}@Overridepublic List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {try {List<BatchResult> results = new ArrayList<>();if (isRollback) {return Collections.emptyList();}for (int i = 0, n = statementList.size(); i < n; i++) {Statement stmt = statementList.get(i);applyTransactionTimeout(stmt);BatchResult batchResult = batchResultList.get(i);try {// 执行批处理batchResult.setUpdateCounts(stmt.executeBatch());MappedStatement ms = batchResult.getMappedStatement();List<Object> parameterObjects = batchResult.getParameterObjects();KeyGenerator keyGenerator = ms.getKeyGenerator();if (Jdbc3KeyGenerator.class.equals(keyGenerator.getClass())) {Jdbc3KeyGenerator jdbc3KeyGenerator = (Jdbc3KeyGenerator) keyGenerator;jdbc3KeyGenerator.processBatch(ms, stmt, parameterObjects);} else if (!NoKeyGenerator.class.equals(keyGenerator.getClass())) {for (Object parameter : parameterObjects) {keyGenerator.processAfter(this, ms, stmt, parameter);}}closeStatement(stmt);} catch (BatchUpdateException e) {StringBuilder message = new StringBuilder();message.append(batchResult.getMappedStatement().getId()).append(" (batch index #").append(i + 1).append(")").append(" failed.");if (i > 0) {message.append(" ").append(i).append(" prior sub executor(s) completed successfully, but will be rolled back.");}throw new BatchExecutorException(message.toString(), e, results, batchResult);}results.add(batchResult);}return results;} finally {for (Statement stmt : statementList) {closeStatement(stmt);}currentSql = null;statementList.clear();batchResultList.clear();}}
}

StatementHandler语句处理

简要描述:StatementHandler负责处理JDBC Statement的创建、参数设置和SQL执行,是MyBatis与JDBC之间的桥梁。

核心概念

  • 语句类型:STATEMENT、PREPARED、CALLABLE三种类型
  • 参数处理:SQL参数的设置和处理
  • 结果处理:查询结果的获取和处理
  • 批处理:批量操作的支持

StatementHandler类型

// 1. SimpleStatementHandler:处理静态SQL
public class SimpleStatementHandler extends BaseStatementHandler {@Overridepublic <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {String sql = boundSql.getSql();statement.execute(sql);return resultSetHandler.handleResultSets(statement);}@Overridepublic int update(Statement statement) throws SQLException {String sql = boundSql.getSql();Object parameterObject = boundSql.getParameterObject();KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();int rows;if (keyGenerator instanceof Jdbc3KeyGenerator) {statement.execute(sql, Statement.RETURN_GENERATED_KEYS);rows = statement.getUpdateCount();keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);} else if (keyGenerator instanceof SelectKeyGenerator) {statement.execute(sql);rows = statement.getUpdateCount();keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);} else {statement.execute(sql);rows = statement.getUpdateCount();}return rows;}@Overrideprotected Statement instantiateStatement(Connection connection) throws SQLException {if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {return connection.createStatement();} else {return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);}}@Overridepublic void parameterize(Statement statement) {// 静态SQL无需参数设置}
}// 2. PreparedStatementHandler:处理预编译SQL
public class PreparedStatementHandler extends BaseStatementHandler {@Overridepublic <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;ps.execute();return resultSetHandler.handleResultSets(ps);}@Overridepublic int update(Statement statement) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;ps.execute();int rows = ps.getUpdateCount();Object parameterObject = boundSql.getParameterObject();KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);return rows;}@Overridepublic void batch(Statement statement) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;ps.addBatch();}@Overrideprotected Statement instantiateStatement(Connection connection) throws SQLException {String sql = boundSql.getSql();if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {String[] keyColumnNames = mappedStatement.getKeyColumns();if (keyColumnNames == null) {return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);} else {return connection.prepareStatement(sql, keyColumnNames);}} else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {return connection.prepareStatement(sql);} else {return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);}}@Overridepublic void parameterize(Statement statement) throws SQLException {parameterHandler.setParameters((PreparedStatement) statement);}
}// 3. CallableStatementHandler:处理存储过程
public class CallableStatementHandler extends BaseStatementHandler {@Overridepublic <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {CallableStatement cs = (CallableStatement) statement;cs.execute();List<E> resultList = resultSetHandler.handleResultSets(cs);resultSetHandler.handleOutputParameters(cs);return resultList;}@Overridepublic int update(Statement statement) throws SQLException {CallableStatement cs = (CallableStatement) statement;cs.execute();int rows = cs.getUpdateCount();resultSetHandler.handleOutputParameters(cs);return rows;}@Overrideprotected Statement instantiateStatement(Connection connection) throws SQLException {String sql = boundSql.getSql();if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {return connection.prepareCall(sql);} else {return connection.prepareCall(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);}}@Overridepublic void parameterize(Statement statement) throws SQLException {registerOutputParameters((CallableStatement) statement);parameterHandler.setParameters((CallableStatement) statement);}private void registerOutputParameters(CallableStatement cs) throws SQLException {List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();for (int i = 0, n = parameterMappings.size(); i < n; i++) {ParameterMapping parameterMapping = parameterMappings.get(i);if (parameterMapping.getMode() == ParameterMode.OUT || parameterMapping.getMode() == ParameterMode.INOUT) {if (null == parameterMapping.getJdbcType()) {throw new ExecutorException("The JDBC Type must be specified for output parameter.  Parameter: " + parameterMapping.getProperty());} else {if (parameterMapping.getNumericScale() != null && (parameterMapping.getJdbcType() == JdbcType.NUMERIC || parameterMapping.getJdbcType() == JdbcType.DECIMAL)) {cs.registerOutParameter(i + 1, parameterMapping.getJdbcType().TYPE_CODE, parameterMapping.getNumericScale());} else {if (parameterMapping.getJdbcTypeName() == null) {cs.registerOutParameter(i + 1, parameterMapping.getJdbcType().TYPE_CODE);} else {cs.registerOutParameter(i + 1, parameterMapping.getJdbcType().TYPE_CODE, parameterMapping.getJdbcTypeName());}}}}}}
}

ParameterHandler参数处理

简要描述:ParameterHandler负责将Java对象参数设置到PreparedStatement中,处理参数类型转换和空值处理。

核心概念

  • 参数映射:Java参数到JDBC参数的映射
  • 类型转换:使用TypeHandler进行类型转换
  • 空值处理:NULL值的特殊处理
  • 参数顺序:确保参数设置的正确顺序

DefaultParameterHandler实现

public class DefaultParameterHandler implements ParameterHandler {private final TypeHandlerRegistry typeHandlerRegistry;private final MappedStatement mappedStatement;private final Object parameterObject;private final BoundSql boundSql;private final Configuration configuration;@Overridepublic Object getParameterObject() {return parameterObject;}@Overridepublic 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 = configuration.newMetaObject(parameterObject);value = metaObject.getValue(propertyName);}// 获取类型处理器TypeHandler typeHandler = parameterMapping.getTypeHandler();JdbcType jdbcType = parameterMapping.getJdbcType();if (value == null && jdbcType == null) {jdbcType = configuration.getJdbcTypeForNull();}try {// 设置参数typeHandler.setParameter(ps, i + 1, value, jdbcType);} catch (TypeException | SQLException e) {throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);}}}}}
}

参数处理示例

// 参数处理的不同场景
public class ParameterHandlingExample {// 1. 单个基本类型参数public void handleSingleParameter() {// SQL: SELECT * FROM user WHERE id = ?// 参数: Long id = 1L// 处理: 直接使用LongTypeHandler设置参数}// 2. 多个基本类型参数public void handleMultipleParameters() {// SQL: SELECT * FROM user WHERE age > ? AND status = ?// 参数: @Param("minAge") Integer minAge, @Param("status") String status// 处理: 创建Map<String, Object>,按参数名映射}// 3. 对象参数public void handleObjectParameter() {// SQL: INSERT INTO user (name, age, email) VALUES (#{name}, #{age}, #{email})// 参数: User user// 处理: 使用MetaObject获取对象属性值}// 4. Map参数public void handleMapParameter() {// SQL: SELECT * FROM user WHERE name = #{name} AND age = #{age}// 参数: Map<String, Object> params// 处理: 直接从Map中获取值}// 5. 集合参数public void handleCollectionParameter() {// SQL: SELECT * FROM user WHERE id IN <foreach collection="ids" item="id" open="(" close=")" separator=",">#{id}</foreach>// 参数: List<Long> ids// 处理: 展开集合,为每个元素设置参数}
}

ResultSetHandler结果处理

简要描述:ResultSetHandler负责将JDBC ResultSet转换为Java对象,处理结果映射、类型转换和关联查询。

核心概念

  • 结果映射:ResultSet到Java对象的映射
  • 类型转换:使用TypeHandler进行类型转换
  • 关联处理:一对一、一对多关联的处理
  • 延迟加载:关联对象的延迟加载

DefaultResultSetHandler实现

public class DefaultResultSetHandler implements ResultSetHandler {@Overridepublic List<Object> handleResultSets(Statement stmt) throws SQLException {ErrorContext.instance().activity("handling results").object(mappedStatement.getId());final List<Object> multipleResults = new ArrayList<>();int resultSetCount = 0;ResultSetWrapper rsw = getFirstResultSet(stmt);List<ResultMap> resultMaps = mappedStatement.getResultMaps();int resultMapCount = resultMaps.size();validateResultMapsCount(rsw, resultMapCount);while (rsw != null && resultMapCount > resultSetCount) {ResultMap resultMap = resultMaps.get(resultSetCount);// 处理结果集handleResultSet(rsw, resultMap, multipleResults, null);rsw = getNextResultSet(stmt);cleanUpAfterHandlingResultSet();resultSetCount++;}String[] resultSets = mappedStatement.getResultSets();if (resultSets != null) {while (rsw != null && resultSetCount < resultSets.length) {ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);if (parentMapping != null) {String nestedResultMapId = parentMapping.getNestedResultMapId();ResultMap resultMap = configuration.getResultMap(nestedResultMapId);handleResultSet(rsw, resultMap, null, parentMapping);}rsw = getNextResultSet(stmt);cleanUpAfterHandlingResultSet();resultSetCount++;}}return collapseSingleResultList(multipleResults);}private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {try {if (parentMapping != null) {handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);} else {if (resultHandler == null) {DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);multipleResults.add(defaultResultHandler.getResultList());} else {handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);}}} finally {closeResultSet(rsw.getResultSet());}}public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {if (resultMap.hasNestedResultMaps()) {ensureNoRowBounds();checkResultHandler();handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);} else {handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);}}private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {DefaultResultContext<Object> resultContext = new DefaultResultContext<>();ResultSet resultSet = rsw.getResultSet();skipRows(resultSet, rowBounds);while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);Object rowValue = getRowValue(rsw, discriminatedResultMap, null);storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);}}private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {final ResultLoaderMap lazyLoader = new ResultLoaderMap();Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {final MetaObject metaObject = configuration.newMetaObject(rowValue);boolean foundValues = this.useConstructorMappings;if (shouldApplyAutomaticMappings(resultMap, false)) {foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;}foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;foundValues = lazyLoader.size() > 0 || foundValues;rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;}return rowValue;}
}

插件机制与拦截器

简要描述:MyBatis的插件机制基于JDK动态代理和责任链模式,允许在SQL执行的关键节点进行拦截和扩展。

核心概念

  • 拦截器:实现Interceptor接口的插件类
  • 拦截点:可以拦截的目标对象和方法
  • 插件链:多个插件按顺序组成的责任链
  • 代理对象:被插件包装后的目标对象

可拦截的对象

  1. Executor:执行器,拦截SQL执行
  2. StatementHandler:语句处理器,拦截SQL预编译和参数设置
  3. ParameterHandler:参数处理器,拦截参数设置
  4. ResultSetHandler:结果集处理器,拦截结果映射

插件开发示例

// 1. 性能监控插件
@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class PerformanceInterceptor implements Interceptor {private long slowSqlThreshold = 1000; // 慢SQL阈值(毫秒)@Overridepublic Object intercept(Invocation invocation) throws Throwable {long startTime = System.currentTimeMillis();try {// 执行原方法Object result = invocation.proceed();long endTime = System.currentTimeMillis();long duration = endTime - startTime;// 记录慢SQLif (duration > slowSqlThreshold) {MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];Object parameter = invocation.getArgs()[1];BoundSql boundSql = mappedStatement.getBoundSql(parameter);String sql = boundSql.getSql();System.out.println("慢SQL警告:");System.out.println("执行时间: " + duration + "ms");System.out.println("SQL语句: " + sql);System.out.println("参数: " + parameter);}return result;} catch (Throwable throwable) {throw throwable;}}@Overridepublic Object plugin(Object target) {// 只拦截Executorif (target instanceof Executor) {return Plugin.wrap(target, this);}return target;}@Overridepublic void setProperties(Properties properties) {String threshold = properties.getProperty("slowSqlThreshold");if (threshold != null) {this.slowSqlThreshold = Long.parseLong(threshold);}}
}

插件配置

<!-- 在mybatis-config.xml中配置插件 -->
<plugins><!-- 性能监控插件 --><plugin interceptor="com.example.plugin.PerformanceInterceptor"><property name="slowSqlThreshold" value="2000"/></plugin><!-- 分页插件 --><plugin interceptor="com.github.pagehelper.PageInterceptor"><property name="helperDialect" value="mysql"/><property name="reasonable" value="true"/></plugin>
</plugins>
http://www.dtcms.com/a/331238.html

相关文章:

  • Python学习-----3.基础语法(2)
  • Linux面试题及详细答案 120道(1-15)-- 基础概念
  • Linux下的软件编程——framebuffer(文件操作的应用)
  • 初识CNN01——认识CNN
  • 计算机组成原理20250814
  • 网络通信---Axios
  • 在线进销存系统高效管理网站源码搭建可二开
  • 考研408《计算机组成原理》复习笔记,第三章(7)——虚拟存储器
  • 考公VS考研,拼哪个性价比高?
  • 什么是域名抢注?域名抢注常见问题汇总
  • 图书商城小程序怎么做?实体书店如何在微信小程序上卖书?
  • 使用vllm运行智谱GLM-4.5V视觉语言模型推理服务
  • 如何使用 AI 大语言模型解决生活中的实际小事情?
  • 数据结构——线性表(链表,力扣简单篇)
  • vscode的wsl环境,ESP32驱动0.96寸oled屏幕
  • 失败存储:查看未成功的内容
  • vscode使用keil5出现变量跳转不了
  • 如何让手机访问本地服务器部署的网页?无公网IP内网主机应用,自定义外网地址,给任意网设备访问
  • 利用 Java 爬虫按图搜索 1688 商品(拍立淘)实战指南
  • 第一章 java基础
  • 手写MyBatis第17弹:ResultSetMetaData揭秘:数据库字段到Java属性的桥梁
  • 《C++》哈希表解析与实现
  • 能源行业数字化转型:边缘计算网关在油田场景的深度应用
  • Python机器学习与深度学习;Transformer模型/注意力机制/目标检测/语义分割/图神经网络/强化学习/生成式模型/自监督学习/物理信息神经网络等
  • 基于51单片机倒计时器秒表定时器数码管显示设计
  • vue+后端
  • 微服务、分布式概念-以及集群部署 vs 分布式部署
  • 容器运行时支持GPU,并使用1panel安装ollama
  • 将 pdf 转为高清 jpg
  • 数巅中标中建科技AI知识库项目,开启建筑业数智化新篇章