科普文:深入理解Mybatis
概叙
(1) JDBC
JDBC(Java Data Base Connection,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成.JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。
·优点:运行期:快捷、高效
·缺点:编辑期:代码量大、繁琐异常处理、不支持数据库跨平台
(2) DBUtils(相当于C#中的DBHelper类)
DBUtils是Java编程中的数据库操作实用工具,小巧简单实用。DBUtils封装了对JDBC的操作,简化了JDBC操作,可以少写代码。
DBUtils三个核心功能介绍
- QueryRunner中提供对sql语句操作的API
- ResultSetHandler接口,用于定义select操作后,怎样封装结果集
- DBUtils类,它就是一个工具类,定义了关闭资源与事务处理的方法
(3)Hibernate
Hibernate 是由 Gavin King 于 2001 年创建的开放源代码的对象关系框架。它强大且高效的构建具有关系对象持久性和查询服 务的 Java 应用程序。
Hibernate 将 Java 类映射到数据库表中,从 Java 数据类型中映射到 SQL 数据类型中,并把开发人员从 95% 的公共数据持续 性编程工作中解放出来。
Hibernate 是传统 Java 对象和数据库服务器之间的桥梁,用来处理基于 O/R 映射机制和模式的那些对象。
Hibernate 优势
- Hibernate 使用 XML 文件来处理映射 Java 类别到数据库表格中,并且不用编写任何代码
- 为在数据库中直接储存和检索 Java 对象提供简单的 APIs。
- 如果在数据库中或任何其它表格中出现变化,那么仅需要改变 XML 文件属性。
- 抽象不熟悉的 SQL 类型,并为我们提供工作中所熟悉的 Java 对象。
- Hibernate 不需要应用程序服务器来操作。
- 操控你数据库中对象复杂的关联。
- 最小化与访问数据库的智能提取策略。
- 提供简单的数据询问。
Hibernate劣势
- hibernate的完全封装导致无法使用数据的一些功能。
- Hibernate的缓存问题。
- Hibernate对于代码的耦合度太高。
- Hibernate寻找bug困难。
- Hibernate批量数据操作需要大量的内存空间而且执行过程中需要的对象太多
(4) JDBCTemplate
JdbcTemplate针对数据查询提供了多个重载的模板方法,你可以根据需要选用不同的模板方法.如果你的查询很简单,仅仅是传入相应SQL或者相关参数,然后取得一个单一的结果,那么你可以选择如下一组便利的模板方法。
优点:运行期:高效、内嵌Spring框架中、支持基于AOP的声明式事务
缺点:必须于Spring框架结合在一起使用、不支持数据库跨平台、默认没有缓存
(5) MyBatis
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。
1、Mybatis是一个半ORM(对象关系映射)框架,底层封装了JDBC,是程序员在开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。使得程序员可以花更多的精力放到业务开发中。另外,程序员直接编写原生态sql,严格控制sql执行性能,灵活度高。
2、MyBatis 可以使用简单的 XML文件 或注解方式来配置和映射原生信息,将 POJO映射成数据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
3、通过xml 文件或注解的方式将要执行的各种 statement 配置起来,并通过java对象和 statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。(从执行sql到返回result的过程)。
Mybaits的优点:
1、基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用。
2、与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接;
3、很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持)。
4、能够与Spring很好的集成;
5、提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护。
MyBatis框架的缺点:
1、SQL语句编写工作量大,熟练度要高:SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求。
2、SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。
MyBatis框架适用场合:
1、MyBatis专注于SQL本身,是一个足够灵活的DAO层解决方案。
2、对性能的要求很高,或者需求变化较多的项目,如互联网项目,MyBatis将是不错的选择。
MyBatis与Hibernate有哪些不同?
1、Mybatis是一个半自动的ORM框架,在查询关联对象或关联集合对象时,需要手动编写sql语句来完成;Hibernate是全自动ORM映射工具,查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,不需要编写sql.
2、Mybatis直接编写原生态sql,可以严格控制sql执行性能,灵活度高,非常适合对性能要求高,需求变化频繁的项目;但是如果涉及到较多的字段或者关联多表时,sql语句编写量大且对开发人的sql语句编写功底要求高。
3、Hibernate对象/关系映射能力强,数据库无关性好,适合需求变化不大的项目,使用hibernate开发可以节省很多代码,提高效率。
MyBatis 的核心组件
1、SqlSessionFactoryBuilder:
-
SqlSessionFactoryBuilder 负责解析配置文件并构建 SqlSessionFactory。
-
它通常使用 XML 配置文件(mybatis-config.xml)作为输入。
-
在解析过程中,它会创建 Configuration 对象,该对象包含 MyBatis 的所有配置信息。
-
解析完成后,它会调用 SqlSessionFactoryBuilder 的 build 方法来创建 SqlSessionFactory 实例。
SqlSessionFactoryBuilder
是 MyBatis 中用于构建 SqlSessionFactory
的类。它主要负责解析 MyBatis 的配置文件,并基于配置信息构建 SqlSessionFactory
。由于 MyBatis 的源代码文件通常较长,V哥尽量简化并只列出与 SqlSessionFactoryBuilder
相关的关键代码段,并加上注释。
以下是 SqlSessionFactoryBuilder
的代码简化版本:
import org.apache.ibatis.builder.xml.XMLConfigBuilder;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder; import java.io.InputStream;
import java.io.Reader; public class SqlSessionFactoryBuilder { // 使用XML配置文件构建SqlSessionFactory public SqlSessionFactory build(Reader reader) { return build(reader, null, null); } // 使用XML配置文件构建SqlSessionFactory,并允许传入Environment和Properties public SqlSessionFactory build(Reader reader, String environment, Properties properties) { try { // 使用XML配置构建器创建Configuration对象 XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); // 解析配置文件,返回Configuration对象 Configuration configuration = parser.parse(); // 基于Configuration对象创建SqlSessionFactory return new SqlSessionFactoryBuilder.SqlSessionFactoryImpl(configuration); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { // 关闭读取器 ErrorContext.instance().reset(); try { reader.close(); } catch (IOException e) { // 忽略关闭读取器时可能抛出的异常 } } } // 使用InputStream构建SqlSessionFactory public SqlSessionFactory build(InputStream inputStream) { return build(inputStream, null, null); } // 使用InputStream构建SqlSessionFactory,并允许传入Environment和Properties public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { // 使用XML配置构建器创建Configuration对象 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); // 解析配置文件,返回Configuration对象 Configuration configuration = parser.parse(); // 基于Configuration对象创建SqlSessionFactory return new SqlSessionFactoryBuilder.SqlSessionFactoryImpl(configuration); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { // 关闭输入流 ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException e) { // 忽略关闭输入流时可能抛出的异常 } } } // SqlSessionFactoryImpl是SqlSessionFactory的默认实现 private static class SqlSessionFactoryImpl implements SqlSessionFactory { private final Configuration configuration; private SqlSessionFactoryImpl(Configuration configuration) { this.configuration = configuration; } // ... 其他方法实现,例如openSession等 }
}
解释:
1、SqlSessionFactoryBuilder
类提供了几个重载的 build
方法,这些方法接收不同的参数(如 Reader
或 InputStream
),用于读取 MyBatis
的配置文件。
2、在每个 build
方法中,首先创建了一个 XMLConfigBuilder
对象,这个对象负责解析 MyBatis 的 XML 配置文件。
3、XMLConfigBuilder
的 parse
方法被调用,它会读取配置文件并构建 Configuration
对象,该对象包含了 MyBatis 的所有配置信息。
4、构建完 Configuration
对象后,使用它创建 SqlSessionFactory
的默认实现 SqlSessionFactoryImpl
的实例。
5、如果在解析配置文件或创建 SqlSessionFactory
的过程中发生异常,会捕获异常并包装为 MyBatis 自定义的异常类型。
6、在方法执行完毕后,无论是否发生异常,都会尝试关闭 Reader
或 InputStream
以释放资源。
7SqlSessionFactoryImpl
是 SqlSessionFactory
接口的一个默认实现,它内部持有 Configuration
对象,并提供了如 openSession
等方法用于创建 SqlSession
。
2、SqlSessionFactory:
-
SqlSessionFactory
是创建SqlSession
的工厂类。 -
它内部持有一个
Configuration
对象,该对象包含了 MyBatis 的所有配置信息。 -
当调用
openSession
方法时,它会根据配置信息创建一个新的SqlSession
实例。
SqlSessionFactory
在 MyBatis 中是一个核心接口,用于生产 SqlSession
对象。通常情况下,我们不会直接实现这个接口,而是使用 SqlSessionFactoryBuilder
来构建它的一个实现类实例。但是,为了解释 SqlSessionFactory
的作用,V哥先展示一个简化的 SqlSessionFactory
接口和其可能的一个实现类的代码。
首先是 SqlSessionFactory
接口的简化版本:
import org.apache.ibatis.session.SqlSession; public interface SqlSessionFactory { /** * 打开一个新的SqlSession。 * * @return 新的SqlSession实例 * @throws Exception 如果打开SqlSession时出错 */ SqlSession openSession(); /** * 打开一个新的SqlSession,并允许传入执行器类型。 * * @param executorType 执行器类型 * @return 新的SqlSession实例 * @throws Exception 如果打开SqlSession时出错 */ SqlSession openSession(ExecutorType executorType); /** * 打开一个新的SqlSession,并允许传入执行器类型和自动提交参数。 * * @param executorType 执行器类型 * @param autoCommit 是否自动提交 * @return 新的SqlSession实例 * @throws Exception 如果打开SqlSession时出错 */ SqlSession openSession(ExecutorType executorType, boolean autoCommit); /** * 打开一个新的SqlSession,并允许传入配置属性。 * * @param properties 配置属性 * @return 新的SqlSession实例 * @throws Exception 如果打开SqlSession时出错 */ SqlSession openSession(Properties properties); /** * 打开一个新的SqlSession,并允许传入执行器类型、自动提交参数和配置属性。 * * @param executorType 执行器类型 * @param autoCommit 是否自动提交 * @param properties 配置属性 * @return 新的SqlSession实例 * @throws Exception 如果打开SqlSession时出错 */ SqlSession openSession(ExecutorType executorType, boolean autoCommit, Properties properties); // ... 可能还有其他方法,如关闭SqlSessionFactory等
}
接下来是一个可能的 SqlSessionFactory
实现类的简化版本(注意:MyBatis 并没有直接提供一个名为 SqlSessionFactoryImpl
的类, V 哥这里只是为了演示):
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.ExecutorType;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory; import java.util.Properties; public class SqlSessionFactoryImpl implements SqlSessionFactory { private final Configuration configuration; public SqlSessionFactoryImpl(Configuration configuration) { this.configuration = configuration; } @Override public SqlSession openSession() { return openSession(ExecutorType.SIMPLE); } @Override public SqlSession openSession(ExecutorType executorType) { return openSession(executorType, false); } @Override public SqlSession openSession(ExecutorType executorType, boolean autoCommit) { return openSession(executorType, autoCommit, null); } @Override public SqlSession openSession(Properties properties) { return openSession(ExecutorType.SIMPLE, properties); } @Override public SqlSession openSession(ExecutorType executorType, boolean autoCommit, Properties properties) { // 创建Executor实例 Executor executor = configuration.newExecutor(executorType, autoCommit); // 使用Configuration和Executor创建SqlSession return new DefaultSqlSession(configuration, executor); } // ... 其他方法实现,如关闭SqlSessionFactory等
}
解释:
1、SqlSessionFactory
接口定义了如何打开一个或多个 SqlSession
。SqlSession
是 MyBatis 的核心接口,它提供了执行 SQL 语句和获取映射结果的方法。
2、SqlSessionFactoryImpl
类是 SqlSessionFactory
接口的一个可能实现。在实际应用中,MyBatis 使用了不同的实现类,但原理类似。
3、SqlSessionFactoryImpl
的构造函数接收一个 Configuration
对象,该对象包含了 MyBatis 的所有配置信息,如环境设置、类型别名、映射文件等。
4、openSession
方法有多个重载版本,允许用户指定执行器类型、是否自动提交事务以及配置属性来打开 SqlSession
。这些重载方法最终都会调用一个或多个带有所有参数的 openSession
方法,以便在打开 SqlSession
时应用所有必要的配置。
5、在 openSession
方法中,根据传入的执行器类型 (ExecutorType
) 和是否自动提交 (autoCommit
) 的参数,调用 Configuration
对象的 newExecutor
方法来创建一个新的执行器 (Executor
) 实例。执行器负责管理和执行 SQL
语句。
6、使用 Configuration
和 Executor
实例来创建一个新的 SqlSession
实例。这个 SqlSession
实例会用于执行 SQL
语句、获取映射结果以及管理数据库事务。
7、在实际应用中,SqlSessionFactory
通常通过 SqlSessionFactoryBuilder
构建。SqlSessionFactoryBuilder
会读取 MyBatis 的配置文件(通常是 XML
格式),解析配置信息,并创建一个 Configuration
对象。然后,使用这个 Configuration
对象来创建一个 SqlSessionFactory
实例。
8、SqlSessionFactory
是线程安全的,一旦创建,就可以在整个应用程序中重用。通常,每个应用程序只需要一个 SqlSessionFactory
实例。
9、SqlSession
则是非线程安全的,因此不应该在多个线程之间共享。每个线程应该有自己的 SqlSession
实例。使用完 SqlSession
后,应该调用其 close 方法来释放资源。
10、SqlSessionFactory
和 SqlSession
的设计符合了工厂模式和单例模式的思想。SqlSessionFactory
负责生产 SqlSession
,而 SqlSession
则负责执行具体的数据库操作。
上面的代码示例是一个简化的版本,用于解释 SqlSessionFactory 和其实现类的基本概念和工作原理。
3、SqlSession:
-
SqlSession
是执行SQL
的核心接口。 -
它通过
Executor
来执行SQL
语句。 -
当调用
selectOne
、selectList
、insert
、update
、delete
等方法时,实际上会调用Executor
的相应方法。 -
SqlSession
也负责事务的管理,例如提交或回滚事务。
当涉及到 SqlSession
的源代码时,实际上 MyBatis 框架的源代码包含了多个与 SqlSession
相关的类,例如 DefaultSqlSession
,这是 SqlSession
接口的一个常见实现。以下是一个简化的 DefaultSqlSession
类的示例,V 哥会在代码中加入中文注释来解释它的作用和功能:
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession; import java.util.List;
import java.util.Map; public class DefaultSqlSession implements SqlSession { private final Configuration configuration; private final Executor executor; public DefaultSqlSession(Configuration configuration, Executor executor) { this.configuration = configuration; this.executor = executor; } @Override public <T> T selectOne(String statement, Object parameter) { // 根据statement和parameter获取MappedStatement MappedStatement ms = configuration.getMappedStatement(statement); // 创建StatementHandler StatementHandler statementHandler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null); // 使用Executor执行查询,并返回结果 return executor.query(ms, statementHandler); } @Override public <E> List<E> selectList(String statement, Object parameter) { // 类似selectOne,但返回结果是List MappedStatement ms = configuration.getMappedStatement(statement); StatementHandler statementHandler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null); return executor.query(ms, statementHandler, RowBounds.DEFAULT, ResultHandler.DEFAULT_RESULT_HANDLER); } @Override public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { // 与上一个selectList方法类似,但允许传入RowBounds以进行分页查询 MappedStatement ms = configuration.getMappedStatement(statement); StatementHandler statementHandler = configuration.newStatementHandler(this, ms, parameter, rowBounds, null, null); return executor.query(ms, statementHandler, rowBounds, ResultHandler.DEFAULT_RESULT_HANDLER); } @Override public void select(String statement, Object parameter, ResultHandler resultHandler) { // 执行查询,并将结果传递给ResultHandler进行处理 MappedStatement ms = configuration.getMappedStatement(statement); StatementHandler statementHandler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null); executor.query(ms, statementHandler, RowBounds.DEFAULT, resultHandler); } @Override public int insert(String statement, Object parameter) { // 执行插入操作,并返回影响的记录数 MappedStatement ms = configuration.getMappedStatement(statement); StatementHandler statementHandler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null); return executor.update(ms, statementHandler); } @Override public int update(String statement, Object parameter) { // 执行更新操作,并返回影响的记录数 MappedStatement ms = configuration.getMappedStatement(statement); StatementHandler statementHandler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null); return executor.update(ms, statementHandler); } @Override public int delete(String statement, Object parameter) { // 执行删除操作,并返回影响的记录数 MappedStatement ms = configuration.getMappedStatement(statement); StatementHandler statementHandler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null); return executor.update(ms, statementHandler); } @Override public <T> T getMapper(Class<T> type) { // 获取Mapper接口的代理实现 return configuration.getMapper(type, this); } // ... 可能还有其他方法,如提交事务、回滚事务、关闭SqlSession等 @Override public void close() { // 清理资源,// 关闭SqlSession}// ... 省略其他可能的方法和细节
上面的代码片段是 DefaultSqlSession
的简化版本,用于解释 SqlSession
的一些基本操作。下面 V 哥将对关键部分进行解释:
-
构造器:
-
DefaultSqlSession
的构造器接受一个Configuration
对象和一个Executor
对象。Configuration
对象包含了 MyBatis 的所有配置信息,而Executor
对象则负责执行 SQL 语句。
-
-
查询方法:
-
selectOne
,selectList
,select
等方法用于执行查询操作。它们首先从Configuration
中获取与提供的 SQL 语句标识符对应的MappedStatement
,然后创建一个StatementHandler
来处理 SQL 语句的生成和参数绑定。最后,它们使用Executor
来执行查询并返回结果。
-
-
增删改方法:
-
insert
,update
,delete
等方法用于执行插入、更新和删除操作。它们与查询方法类似,但返回的是受影响的记录数。
-
-
获取Mapper:
-
getMapper
方法用于获取一个 Mapper 接口的代理实现。这允许你直接使用接口调用方法而无需手动创建和配置代理。
-
-
关闭SqlSession:
-
close
方法用于关闭SqlSession
,释放相关资源。
-
需要注意的是,SqlSession
是线程不安全的,因此通常每个线程都应该有自己的 SqlSession
实例。同时,SqlSession
的使用通常遵循“打开-执行-关闭”的模式,以确保资源的正确释放。
在实际应用中,你通常不会直接创建 DefaultSqlSession
的实例,而是使用 SqlSessionFactory
来创建 SqlSession
。SqlSessionFactory
负责根据配置创建 SqlSession
实例,并管理相关的资源。
希望这些注释和解释能够帮助你理解 SqlSession
的作用和工作原理。如果需要更深入的理解,建议阅读 MyBatis 的官方文档和源代码。
4、Mapper 接口及其实现:
-
Mapper
接口是开发者定义的,用于描述数据库操作。 -
MyBatis
使用JDK
动态代理为Mapper
接口创建代理对象。 -
当调用
Mapper
接口的方法时,代理对象会拦截调用,并转换为SQL
语句的执行。 -
这个转换过程涉及
MapperStatement
的查找和解析,以及参数和结果的映射。
在 MyBatis 中,Mapper
接口通常没有直接的实现类,而是通过 MyBatis 的动态代理机制自动生成代理对象。Mapper
接口定义了与数据库操作相关的方法,而 MyBatis 会根据这些方法自动生成相应的 SQL 语句并执行。
下面是一个简单的 Mapper
接口示例及注释:
// 定义一个 Mapper 接口,用于映射数据库操作
public interface UserMapper { // 根据 ID 查询用户信息 // @Select 注解用于指定查询的 SQL 语句 // #{id} 是参数占位符,表示方法参数 @Select("SELECT * FROM user WHERE id = #{id}") User selectUserById(int id); // 插入用户信息 // @Insert 注解用于指定插入的 SQL 语句 // 使用 @Options 注解可以配置插入操作的一些选项,比如是否使用生成的键等 @Insert("INSERT INTO user (name, age) VALUES (#{name}, #{age})") @Options(useGeneratedKeys = true, keyProperty = "id") int insertUser(User user); // 更新用户信息 // @Update 注解用于指定更新的 SQL 语句 @Update("UPDATE user SET name = #{name}, age = #{age} WHERE id = #{id}") int updateUser(User user); // 删除用户信息 // @Delete 注解用于指定删除的 SQL 语句 @Delete("DELETE FROM user WHERE id = #{id}") int deleteUser(int id); // 查询所有用户信息 // @Select 注解指定查询所有用户的 SQL 语句 @Select("SELECT * FROM user") List<User> selectAllUsers(); // 其他的数据库操作方法...
}
解释:
接口定义: UserMapper
是一个接口,它定义了与 user
表相关的数据库操作。 注解: MyBatis 提供了注解(如 @Select
、@Insert
、@Update
、@Delete
)来简化 SQL
语句的编写。这些注解允许你在接口方法上直接指定 SQL 语句。
参数占位符: 在 SQL
语句中,#{id}、#{name}、#{age}
等是参数占位符,它们会在运行时被方法参数的实际值替换。
自动映射: MyBatis 会自动将查询结果映射到 User
类型的对象上,前提是你的 User
类的属性名称和数据库表的列名能够对应上。
动态代理: 当你在 MyBatis 的 SqlSession
中调用 getMapper(UserMapper.class)
时,MyBatis 会根据 UserMapper
接口动态生成一个代理对象。这个代理对象会在运行时拦截方法调用,并自动执行相应的 SQL
语句。
选项配置: @Options
注解用于配置 SQL
语句执行的一些选项。例如,在插入操作中,useGeneratedKeys = true
表示使用数据库自动生成的主键,keyProperty = "id"
指定将生成的主键设置到 User
对象的 id
属性上。
返回类型: 方法的返回类型通常与 SQL
语句的执行结果相对应。例如,查询单个用户返回 User
对象,查询多个用户返回 List<User>
。
在实际应用中,你通常不需要手动编写 Mapper
接口的实现类。你只需要定义接口,并在 XML
映射文件(如果不使用注解)或注解中编写 SQL
语句。MyBatis 会负责接口的动态代理实现和 SQL
语句的执行。这大大简化了数据库操作的开发过程。
5、MappedStatement:
-
MappedStatement 是 MyBatis 内部表示一个 SQL 映射语句的对象。
-
它包含 SQL 语句、参数类型、结果映射等信息。
-
当 MyBatis 解析 Mapper XML 文件时,会为每个 SQL 语句创建一个 MappedStatement 对象,并存储在 Configuration 对象中。
-
执行 SQL 时,MyBatis 会根据方法签名或 ID 查找对应的 MappedStatement。
MappedStatement
是 MyBatis 中的一个核心类,它代表了一个映射语句,即一个 SQL
语句及其相关的配置信息。在 MyBatis 中,MappedStatement
对象是由 MyBatis 在解析 XML
映射文件或注解时创建的,并存储在 Configuration
对象中。
由于 MappedStatement
是 MyBatis 内部使用的核心类,其实现细节和源代码通常较为复杂,不适合在这里完整地列出。不过,我可以为你提供一个简化版的 MappedStatement
类结构,并添加必要的注释来解释其主要组成部分。
请注意,以下代码仅用于解释目的,帮助你更好的理解:
// MappedStatement 类简化版,用于解释其主要组成部分
public class MappedStatement { // 映射语句的唯一标识符 private String id; // 映射语句对应的 SQL 语句 private String sql; // 映射语句的类型(SELECT, INSERT, UPDATE, DELETE) private SqlCommandType sqlCommandType; // 参数类型,即传递给 SQL 语句的参数的类型 private Class<?> parameterType; // 结果类型,即 SQL 语句执行后返回的结果的类型 private Class<?> resultType; // 语句的结果映射配置 private ResultMap resultMap; // 语句使用的数据库 ID(用于分库分表等情况) private String databaseId; // 语句使用的参数处理器类型 private Class<? extends ParameterHandler> parameterHandlerType; // 语句使用的结果处理器类型 private Class<? extends ResultHandler> resultHandlerType; // 语句使用的 SQL 语句解析器类型 private Class<? extends StatementHandler> statementHandlerType; // 语句使用的绑定器类型 private Class<? extends TypeHandler> boundSqlTypeHandler; // 语句的插件列表 private List<Interceptor> interceptors; // ... 可能还有其他字段和方法 // 构造函数(通常不是直接创建的,而是通过 MyBatis 的内部机制) public MappedStatement(String id, String sql, SqlCommandType sqlCommandType, Class<?> parameterType, Class<?> resultType, ResultMap resultMap, String databaseId, Class<? extends ParameterHandler> parameterHandlerType, Class<? extends ResultHandler> resultHandlerType, Class<? extends StatementHandler> statementHandlerType, Class<? extends TypeHandler> boundSqlTypeHandler, List<Interceptor> interceptors) { this.id = id; this.sql = sql; this.sqlCommandType = sqlCommandType; this.parameterType = parameterType; this.resultType = resultType; this.resultMap = resultMap; this.databaseId = databaseId; this.parameterHandlerType = parameterHandlerType; this.resultHandlerType = resultHandlerType; this.statementHandlerType = statementHandlerType; this.boundSqlTypeHandler = boundSqlTypeHandler; this.interceptors = interceptors; } // Getter 和 Setter 方法省略... // ... 可能还有其他方法,如执行 SQL 语句、获取绑定参数等
}
解释:
1、标识符 id
: 每个 MappedStatement
对象都有一个唯一的标识符,它通常对应于 Mapper
接口中的一个方法名。
2、SQL
语句 sql
: 存储了具体的 SQL
语句字符串。
3、语句类型 sqlCommandType
: 表示这个映射语句是查询、插入、更新还是删除操作。
4、参数类型 parameterType
和结果类型 resultType
: 分别表示传递给 SQL
语句的参数类型和 SQL
语句执行后返回的结果类型。
5、结果映射 resultMap
: 用于复杂结果集的映射配置。
6、数据库 ID
databaseId
: 用于分库分表等高级功能。
7、处理器类型: 包括参数处理器 parameterHandlerType
、结果处理器 resultHandlerType
、语句处理器 statementHandlerType
和绑定器 boundSqlTypeHandler
,它们都是用于处理 SQL
语句执行过程中不同阶段的任务的类型。
8、插件列表 interceptors
: 存储了应用于这个映射语句的插件列表,插件可以用于拦截和修改 SQL
语句的执行过程。
在实际应用中,MappedStatement
对象是由 MyBatis 在启动时解析 XML
映射文件或注解时创建的,并存储在 Configuration
对象中。当执行数据库操作时,MyBatis 会根据 Mapper
接口方法的名称查找对应的 MappedStatement
对象,并使用其中的信息来构建和执行 SQL
语句。
由于 MappedStatement
是 MyBatis内部实现的一部分,它的具体细节可能会随着 MyBatis
的版本更新而有所变化。然而,其核心功能和设计原则通常保持一致:为 SQL
映射语句提供元数据信息和运行时环境。
在实际的 MyBatis 实现中,MappedStatement
类通常包含更多的字段和方法,用于处理更复杂的场景,比如动态 SQL
、缓存配置、结果集映射、条件分支等等。它通常还与 MyBatis 的其他关键组件如 SqlSession
、Executor
、StatementHandler
等紧密协作,以完成 SQL
语句的执行和结果处理。
当你使用 MyBatis 时,你通常不需要直接创建或操作 MappedStatement
对象。相反,你会通过定义 Mapper
接口和 XML
映射文件来声明你的 SQL
映射语句,然后 MyBatis 会自动为你处理 MappedStatement
的创建和管理。
6、Executor:
-
Executor 是 SQL 语句执行的核心。
-
它有三个实现类:SimpleExecutor、ReuseExecutor 和 BatchExecutor,分别对应不同的执行策略。
-
Executor 负责与 JDBC 交互,包括创建 PreparedStatement、设置参数、执行 SQL、处理结果等。
-
它会使用 TypeHandler 来处理参数和结果集的转换。
由于 Executor
类是 MyBatis 框架中的核心组件,其源代码相对较长且涉及多个内部类和复杂逻辑。在这里,V 哥将为你提供一个简化版的 Executor
类及其部分实现,来解释其主要功能。
// Executor接口,定义了执行SQL语句的方法
public interface Executor { // 执行更新操作(插入、更新、删除) int update(MappedStatement ms, Object parameter); // 执行查询操作,返回结果列表 <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql); // 执行查询操作,返回单个结果 <E> E query(MappedStatement ms, Object parameter, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql); // 执行查询操作,返回结果集游标 <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql); // 刷新缓存 void flushStatements(); // 关闭Executor,释放资源 void close(boolean forceClose); // 是否已关闭 boolean isClosed(); // 获取事务对象 Transaction getTransaction(); // 延迟加载是否开启 boolean isLazyLoadEnabled(); // 设置延迟加载是否开启 void setLazyLoadEnabled(boolean lazyLoadEnabled);
} // BaseExecutor类,Executor接口的一个基础实现类
public abstract class BaseExecutor implements Executor { protected final Configuration configuration; protected final Transaction transaction; protected ErrorContext errorContext; public BaseExecutor(Configuration configuration, Transaction transaction) { this.configuration = configuration; this.transaction = transaction; this.errorContext = new ErrorContext(); } // 省略其他方法... // 更新操作实现 @Override public int update(MappedStatement ms, Object parameter) { // ... 更新操作的实现逻辑,包括预处理语句、设置参数、执行更新等 } // 查询操作实现(返回结果列表) @Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) { // ... 查询操作的实现逻辑,包括预处理语句、设置参数、执行查询、处理结果集等 } // ... 其他方法的实现...
} // SimpleExecutor类,BaseExecutor的一个简单实现,用于执行SQL语句
public class SimpleExecutor extends BaseExecutor { public SimpleExecutor(Configuration configuration, Transaction transaction) { super(configuration, transaction); } // 更新操作实现(继承自BaseExecutor) @Override public int update(MappedStatement ms, Object parameter) { // 这里可以添加SimpleExecutor特有的逻辑,或者直接调用父类的实现 return super.update(ms, parameter); } // 查询操作实现(继承自BaseExecutor) @Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) { // 这里可以添加SimpleExecutor特有的逻辑,或者直接调用父类的实现 return super.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql); } // ... 其他方法的实现...
}
解释:
1、Executor
接口: 定义了执行 SQL
语句所需的方法,包括更新、查询等。它是 MyBatis 中执行器模式的核心部分,允许不同的执行策略(如批处理、重用预处理语句等)通过不同的实现类来实现。
2、BaseExecutor
类: 是 Executor
接口的一个基础实现类,提供了执行器的一些通用逻辑。它通常包含配置信息、事务对象和错误上下文等成员变量。BaseExecutor
提供了对 SQL
语句执行的基础支持,但具体的执行逻辑可能由其子类实现。
3、SimpleExecutor
类: 是 BaseExecutor
的一个具体实现,它可能不包含复杂的逻辑或优化,但提供了基本的 SQL
执行功能。在实际应用中,MyBatis 可能提供了更多的执行器实现类,比如 ReuseExecutor
用于重用预处理语句,BatchExecutor
用于批量执行等。
在 MyBatis 的实际实现中,Executor
类及其实现通常包含更多的成员变量、方法和复杂的逻辑,以处理SQL
语句的解析、参数绑定、结果映射以及缓存等高级功能。此外,Executor
类通常还会与其他组件如 StatementHandler
、ParameterHandler
、ResultSetHandler
和 TypeHandler
等紧密合作,以构建和执行完整的SQL
执行流程。
下面,V 哥将进一步解释 Executor
及其实现类在 MyBatis 中的一些核心功能:
1、SQL解析与绑定:
-
Executor
接收MappedStatement
作为输入,该对象包含了SQL
语句的元数据信息。 -
使用
ParameterHandler
处理参数绑定,将用户提供的参数转换为JDBC
可以理解的格式,并设置到预处理语句中。
2、执行SQL语句:
-
调用
JDBC
的Statement
或PreparedStatement
执行SQL
语句。 -
Executor
可能管理自己的预处理语句缓存,以提高性能。
3、结果处理:
-
使用
ResultSetHandler
将JDBC
的ResultSet
转换为Java
对象列表。 -
涉及类型转换和结果映射,使用
TypeHandler
来处理字段类型和Java
类型之间的转换。
4、事务管理:
-
Executor
通常与事务管理对象(如Transaction
)一起工作,以确保SQL操作在事务的上下文中执行。 -
负责提交或回滚事务,以处理成功或失败的
SQL
操作。
5、缓存管理:
-
MyBatis 提供了一级缓存和二级缓存机制,
Executor
负责管理这些缓存。 -
在执行查询时,首先检查缓存中是否有结果,如果有则直接返回,避免重复执行
SQL
。
6、延迟加载:
-
MyBatis 支持延迟加载,即当需要时才加载关联数据。
-
Executor
需要在适当的时候触发延迟加载的执行。
MyBatis 提供了多种 Executor
实现类,它们之间的主要差异在于执行策略和资源管理:
SIMPLE
: 最基本的实现,每次执行都创建一个新的预处理语句。
REUSE
: 重用预处理语句,以减少JDBC对象的创建和销毁开销。
BATCH
: 批量执行SQL语句,适用于大量数据的插入、更新或删除操作。
每种实现都有其特定的使用场景和性能特点,用户可以根据应用的需求选择合适的实现。
Executor
是 MyBatis 框架中的核心组件之一,它负责执行SQL
语句并处理结果。通过不同的实现类,MyBatis 提供了灵活的执行策略,以满足不同应用场景的性能需求。在实际应用中,用户通常不需要直接创建或管理 Executor
对象,而是通过配置和使用 MyBatis 的 API
来间接使用它。
7、TypeHandler:
-
TypeHandler
是 Java 类型和JDBC
类型之间的桥梁。 -
MyBatis 提供了一系列内置的
TypeHandler
,如StringTypeHandler
、IntegerTypeHandler
等。 -
当需要自定义类型转换时,开发者可以实现自己的
TypeHandler
。 -
TypeHandler
负责将 Java 对象转换为JDBC
参数,以及将 JDBC 结果集转换为 Java 对象。
TypeHandler
是 MyBatis 中一个非常核心的组件,它负责 Java 类型和 JDBC
类型之间的转换。TypeHandler
定义了类型转换的接口,并提供了一些基础实现。以下是一个简化版的 TypeHandler
接口及其一个实现类的示例。
// TypeHandler接口,定义了类型转换的方法
public interface TypeHandler<T> { // 设置参数值 void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException; // 从结果集中获取值 T getResult(ResultSet rs, String columnName) throws SQLException; // 从结果集中获取值(使用列索引) T getResult(ResultSet rs, int columnIndex) throws SQLException; // 从CallableStatement中获取值 T getResult(CallableStatement cs, int columnIndex) throws SQLException;
} // BaseTypeHandler类,TypeHandler的一个基础实现类,提供了默认的类型转换逻辑
public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> { // 设置参数值(默认实现,子类可覆盖) @Override public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException { if (parameter == null) { if (jdbcType == null) { throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters."); } ps.setNull(i, jdbcType.TYPE_CODE); } else { setNonNullParameter(ps, i, parameter, jdbcType); } } // 设置非空参数值(子类需要实现这个方法) protected abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException; // 从结果集中获取值(默认实现,子类可覆盖) @Override public T getResult(ResultSet rs, String columnName) throws SQLException { return getResult(rs, rs.findColumn(columnName)); } // 从结果集中获取值(默认实现,子类需要实现这个方法) @Override public T getResult(ResultSet rs, int columnIndex) throws SQLException { return getNullableResult(rs, columnIndex); } // 从CallableStatement中获取值(默认实现,子类需要实现这个方法) @Override public T getResult(CallableStatement cs, int columnIndex) throws SQLException { return getNullableResult(cs, columnIndex); } // 获取非空结果(子类需要实现这个方法) protected abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException; // 获取非空结果(子类需要实现这个方法) protected abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException; // 获取非空结果(子类需要实现这个方法) protected abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;
} // IntegerTypeHandler类,TypeHandler的一个具体实现,用于处理Integer类型的转换
public class IntegerTypeHandler extends BaseTypeHandler<Integer> { // 设置非空参数值 @Override protected void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType) throws SQLException { ps.setInt(i, parameter); } // 从结果集中获取非空Integer值 @Override protected Integer getNullableResult(ResultSet rs, String columnName) throws SQLException { return rs.getInt(columnName); } // 从结果集中获取非空Integer值(使用列索引) @Override protected Integer getNullableResult(ResultSet rs, int columnIndex) throws SQLException { return rs.getInt(columnIndex); } // 从CallableStatement中获取非空Integer值 @Override protected Integer getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { return cs.getInt(columnIndex); }
}
解释:
1、TypeHandler 接口:
-
setParameter
方法:将 Java 类型的参数设置到PreparedStatement
对象中,以便执行SQL
语句。 -
getResult
方法:从ResultSet
或CallableStatement
对象中获取指定列的结果,并将其转换为 Java 类型。
2、BaseTypeHandler 抽象类:
-
setParameter
方法提供了默认实现,用于处理null
值和JDBC
类型的设置。 -
setNonNullParameter
是一个抽象方法,子类需要实现,用于处理非空值的设置。 -
getResult
方法也提供了默认实现,它们通常调用getNullableResult
抽象方法,子类需要实现具体的转换逻辑。
3、IntegerTypeHandler 类:
-
继承自
BaseTypeHandler<Integer>
,专门用于处理Integer
类型的转换。 -
实现了
setNonNullParameter
方法,用于将Integer
类型的参数设置到PreparedStatement
中。 -
实现了
getNullableResult
方法的三个重载版本,用于从ResultSet
或CallableStatement
中获取Integer
类型的结果。
使用场景:
当 MyBatis 执行 SQL
语句时,它需要根据 Java 类型的参数和 SQL
查询的结果来设置参数和获取结果。这时,MyBatis 会查找合适的 TypeHandler
来执行这些类型转换。如果 MyBatis 提供了现成的 TypeHandler
(如 IntegerTypeHandler
),它可以直接使用。如果没有现成的 TypeHandler
,用户也可以自定义 TypeHandler
来处理特殊的类型转换逻辑。
TypeHandler
接口及其实现类在 MyBatis 中扮演了非常重要的角色,它们负责在 Java 类型和 JDBC 类型之间进行转换,使得 MyBatis 能够灵活地处理各种类型的参数和结果集。通过自定义 TypeHandler
,用户可以扩展 MyBatis 的类型转换能力,以满足不同的业务需求。
8、Plugin:
-
Plugin
是 MyBatis 的插件机制,允许开发者在核心流程中插入自定义逻辑。 -
插件通过实现
Interceptor
接口并覆盖intercept
方法来定义自己的拦截逻辑。 -
插件在 MyBatis 初始化时通过
Plugin
类进行包装,并插入到目标对象的代理链中。 -
当目标对象的方法被调用时,插件的拦截逻辑会先被执行。
Plugin
类在 MyBatis 中通常用于拦截和修改 MyBatis 的核心行为。它允许用户在不修改 MyBatis 核心代码的情况下,对 SQL
语句的生成、参数设置、结果集处理等过程进行自定义处理。以下是一个简化版的 Plugin
类及其实现。
// Plugin接口,定义插件需要实现的方法
public interface Plugin { // 包裹目标对象,返回一个被拦截对象 Object wrap(Object target); // 获取插件的属性 Class<?> getType(); // 获取插件的处理程序 Interceptor getInterceptor(); // 插件是否可以被用于目标对象 boolean isTarget(Object target); // 静态方法,用于生成插件实例 static Object wrap(Object target, Interceptor interceptor, Class<?> type) { // 创建Plugin对象 Plugin plugin = new Plugin(target, interceptor, type); // 返回被拦截的目标对象 return plugin.wrap(target); } // Plugin类的私有构造器,防止外部直接实例化 private Plugin(Object target, Interceptor interceptor, Class<?> type) { // 初始化成员变量 this.target = target; this.interceptor = interceptor; this.type = type; } // 成员变量 private Object target; private Interceptor interceptor; private Class<?> type;
} // Interceptor接口,定义插件需要实现的拦截方法
public interface Interceptor { // 插件在MyBatis初始化时调用 void intercept(Invocation invocation) throws Throwable; // 插件的ID,用于唯一标识插件 Object plugin(Object target); // 插件的属性集合 void setProperties(Properties properties);
} // 假设我们有一个实现Interceptor接口的自定义插件
public class MyCustomPlugin implements Interceptor { // 插件的属性 private String someProperty; @Override public Object intercept(Invocation invocation) throws Throwable { // 在这里编写拦截逻辑 // 例如,可以修改SQL语句、参数等 System.out.println("Intercepted method: " + invocation.getMethod().getName()); // 继续执行原始逻辑 return invocation.proceed(); } @Override public Object plugin(Object target) { // 在这里可以对目标对象进行包装或处理 return Plugin.wrap(target, this, MyCustomPlugin.class); } @Override public void setProperties(Properties properties) { // 设置插件的属性 this.someProperty = properties.getProperty("someProperty"); }
}
解释:
1、Plugin 接口:
-
wrap(Object target)
: 这是一个用于包装目标对象的方法,通常会在插件初始化时被调用,返回被包装后的对象,这个对象会代理目标对象的行为,并在必要时插入拦截逻辑。 -
getType()
: 返回插件的类类型。 -
getInterceptor()
: 返回插件的拦截器实现。 -
isTarget(Object target)
: 判断插件是否适用于目标对象。 -
wrap(Object target, Interceptor interceptor, Class<?> type)
: 这是一个静态方法,用于创建并返回 Plugin 实例,同时完成目标对象的包装。
2、Interceptor 接口:
-
intercept(Invocation invocation)
: 这是插件的核心方法,当目标对象的方法被调用时,这个方法会被执行。在这里,你可以编写自定义的拦截逻辑。 -
plugin(Object target)
: 这是一个用于包装目标对象的方法,返回包装后的对象。在 MyBatis 中,这个方法通常与Plugin
接口的wrap
方法结合使用,以创建代理对象。 -
setProperties(Properties properties)
: 这是一个设置插件属性的方法,MyBatis 在配置插件时会调用此方法。
3、MyCustomPlugin 类:
-
这个类实现了
Interceptor
接口,是自定义插件的具体实现。 -
在
intercept
方法中,你可以编写拦截目标对象方法执行的代码,例如修改SQL
语句、修改参数等。 -
plugin
方法返回包装后的目标对象,通常直接调用Plugin.wrap
方法。 -
setProperties
方法用于设置插件的配置属性。
使用场景:
当你在 MyBatis 中需要修改 SQL 语句、参数设置或结果集处理时,你可以编写一个自定义的 Interceptor
实现,并使用 Plugin
接口来包装目标对象,从而在不修改 MyBatis 核心代码的情况下扩展其功能。在 MyBatis 的配置文件中配置插件后,MyBatis 会在启动时加载插件。
Plugin 类的使用:
在 MyBatis 中,Plugin 类的使用通常涉及到以下步骤:
1、编写自定义插件:
-
创建一个类实现
Interceptor
接口,实现其中的intercept
、plugin
和setProperties
方法。 -
在
intercept
方法中编写拦截逻辑,比如修改SQL
语句、参数或处理结果集。 -
在
plugin
方法中调用Plugin.wrap
方法包装目标对象。 -
在
setProperties
方法中处理插件配置属性。
2、配置插件:
-
在 MyBatis 的配置文件(通常是
mybatis-config.xml
)中,使用<plugins>
元素配置插件。 -
在
<plugin>
子元素中指定插件的interceptor
实现类,以及可能的属性。
3、启动 MyBatis:
-
当 MyBatis 启动时,它会加载并初始化配置的插件。
-
插件的
intercept
方法会在相应的方法调用时被触发。
示例配置:
在 mybatis-config.xml
配置文件中配置自定义插件:
<configuration> <!-- 其他配置 --> <plugins> <plugin interceptor="com.example.MyCustomPlugin"> <property name="someProperty" value="someValue"/> </plugin> </plugins> <!-- 其他配置 -->
</configuration>
Plugin 类的实现细节:
在 Plugin
类的实现中,通常会使用动态代理技术来包装目标对象。当目标对象的方法被调用时,动态代理会拦截调用,并首先执行插件的拦截逻辑,然后再调用原始方法。
Plugin 类中的 wrap
方法通常利用 Java 的反射 API
和动态代理(例如 JDK
动态代理或 CGLIB
)来创建目标对象的代理。代理对象会实现目标对象的接口,并在调用方法时执行拦截逻辑。
注意:
-
插件的
intercept
方法必须谨慎处理,避免引入性能问题或破坏 MyBatis 的行为。 -
插件的
plugin
方法必须正确处理目标对象,确保返回的是正确的代理对象。 -
插件的
setProperties
方法应该能够处理所有必要的配置属性,并在需要时验证它们的值。
Plugin
类在 MyBatis 中是一个非常重要的机制,它允许用户在不修改 MyBatis 核心代码的情况下扩展其功能。通过编写自定义的 Interceptor
实现,并正确配置插件,用户可以拦截和修改 MyBatis 的行为,以满足特定的业务需求。在实际应用中,需要深入理解 MyBatis 的内部机制和动态代理技术,才能有效地使用 Plugin
类来扩展 MyBatis 的功能。
MyBatis整体架构图
MyBatis 分为三层架构,分别是基础支撑层、核心处理层和接口层,如上两图所示。
1. 基础支撑层
1.1 类型转换模块
<typeAliase> 标签的别名机制,由基础支撑层中的类型转换模块实现的;
JDBC 类型与 Java 类型之间的相互转换,绑定实参、映射 ResultSet 场景中都有所体现:
- 在 SQL 模板绑定用户传入实参的场景中,类型转换模块会将 Java 类型数据转换成 JDBC 类型数据;
- 在将 ResultSet 映射成结果对象的时候,类型转换模块会将 JDBC 类型数据转换成 Java 类型数据。
1.2 日志模块
MyBatis 提供了日志模块来集成 Java 生态中的第三方日志框架,该模块目前可以集成 Log4j、Log4j2、slf4j 等优秀的日志框架。
1.3 反射工具模块
MyBatis 的反射工具箱是在 Java 反射的基础之上进行的一层封装,为上层使用方提供更加灵活、方便的 API 接口,同时缓存 Java 的原生反射相关的元数据,提升了反射代码执行的效率,优化了反射操作的性能。
1.4 Binding 模块
通过 SqlSession 获取 Mapper 接口的代理,然后通过这个代理执行关联 Mapper.xml 文件中的数据库操作。通过这种方式,可以将一些错误提前到编译期,该功能就是通过 Binding 模块完成的。
1.5 数据源模块
持久层框架核心组件之一就是数据源,MyBatis 自身提供了一套不错的数据源实现,也是 MyBatis 的默认实现。MyBatis 的数据源模块中也提供了与第三方数据源集成的相关接口,这也为用户提供了更多的选择空间,提升了数据源切换的灵活性。
1.6缓存模块
数据库是实践生成中非常核心的存储,数据库性能的优劣直接影响了上层业务系统的优劣。
很多线上业务都是读多写少的场景,在数据库遇到瓶颈时,缓存是最有效、最常用的手段之一(如下图所示),正确使用缓存可以将一部分数据库请求拦截在缓存这一层,这就能够减少一部分数据库的压力,提高系统性能。
MyBatis 就提供了一级缓存和二级缓存,具体实现位于基础支撑层的缓存模块中。
1.7 解析器模块
mybatis-config.xml 配置文件和 Mapper.xml 配置文件的解析。
1.8 事务管理模块
持久层框架一般都会提供一套事务管理机制实现数据库的事务控制,MyBatis 对数据库中的事务进行了一层简单的抽象,提供了简单易用的事务接口和实现。一般情况下,Java 项目都会集成 Spring,并由 Spring 框架管理事务。
2. 核心处理层
核心处理层是 MyBatis 核心实现所在,其中涉及 MyBatis 的初始化以及执行一条 SQL 语句的全流程。
2.1 配置解析
MyBatis 有三处可以添加配置信息的地方,分别是:mybatis-config.xml 配置文件、Mapper.xml 配置文件以及 Mapper 接口中的注解信息。在 MyBatis 初始化过程中,会加载这些配置信息,并将解析之后得到的配置对象保存到 Configuration 对象中。
2.2 SQL 解析与 scripting 模块
MyBatis 的最大亮点应该要数其动态 SQL 功能了,只需要通过 MyBatis 提供的标签即可根据实际的运行条件动态生成实际执行的 SQL 语句。MyBatis 提供的动态 SQL 标签非常丰富,包括 <where> 标签、<if> 标签、<foreach> 标签、<set> 标签等。
MyBatis 中的 scripting 模块就是负责动态生成 SQL 的核心模块。它会根据运行时用户传入的实参,解析动态 SQL 中的标签,并形成 SQL 模板,然后处理 SQL 模板中的占位符,用运行时的实参填充占位符,得到数据库真正可执行的 SQL 语句。
2.3 SQL 执行
要执行一条 SQL 语句,会涉及非常多的组件,比较核心的有:Executor、StatementHandler、ParameterHandler 和 ResultSetHandler。
其中,Executor 会调用事务管理模块实现事务的相关控制,同时会通过缓存模块管理一级缓存和二级缓存。SQL 语句的真正执行将会由 StatementHandler 实现。StatementHandler 会先依赖 ParameterHandler 进行 SQL 模板的实参绑定,然后由 java.sql.Statement 对象将 SQL 语句以及绑定好的实参传到数据库执行,从数据库中拿到 ResultSet,最后,由 ResultSetHandler 将 ResultSet 映射成 Java 对象返回给调用方,这就是 SQL 执行模块的核心。
2.4 插件
很多成熟的开源框架,都会以各种方式提供扩展能力。当框架原生能力不能满足某些场景的时候,就可以针对这些场景实现一些插件来满足需求,这样的框架才能有足够的生命力。这也是 MyBatis 插件接口存在的意义。
3. 接口层
接口层是 MyBatis 暴露给调用的接口集合,这些接口都是使用 MyBatis 时最常用的一些接口,例如,SqlSession 接口、SqlSessionFactory 接口等。其中,最核心的是 SqlSession 接口,你可以通过它实现很多功能,例如,获取 Mapper 代理、执行 SQL 语句、控制事务开关等。
架构流程图
执行流程
(1) MyBatis配置文件config.xml:配置了全局配置文件,配置了MyBatis的运行环境等信息。mapper,xml:sql的映射文件,配置了操作数据库的sql语句,此文件需在config.xml中加载。
(2)SqlSessionFactory:通过MyBatis环境等配置信息构造SqlSessionFactory(会话工厂)。
(3)SqlSession:通过会话工厂创建SqlSession(会话),对数据库进行增删改查操作。
(4)Exector执行器:MyBatis底层自定义了Exector执行器接口来具体操作数据库,Exector接口有两个实现,一个基本执行器(默认),一个是缓存执行器,SqlSession底层是通过Exector接口操作数据库。
(5)MappedStatement:MyBatis的一个底层封装对象,它包装了MyBatis配置信息与sql映射信息等。mapper.xml中的insert/select/update/delete标签对应一个MappedStatement对象。标签的id就是MappedStatement的id。
MappedStatement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo、Executor通过MappedStatement在执行sql前将输入的Java对象映射至sql中,输入参数映射就是JDBC编程对preparedStatement设置参数。MappedStatement对sql执行输出结果进行定义,包括HashMap、基本类型、pojo,Executor通过MappedStatement在执行sql后将输出结果映射至Java对象中,输出结果映射就是JDBC编程对结果的解析处理过程。
调用流程图
Mapper代理执行原理
Mapper代理开发方式使用的是JDK的动态代理(针对有接口的类进行动态代理)。
Springboot整合Mybatis的流程
1.查询前
也就是springboot启动时做的工作
实例化SqlSessionFactory
1.构建一个DefaultSqlSessionFactory,主要作用就是维护Configuration和查询时获取DefaultSqlSession,然后通过DefaultSqlSession执行查询操作。
2.实例化的过程中会解析mapper.xml中的各种标签封装成xxxsqlSource,保存在Configuration的mappedstatelment的sqlSource属性中。
3.解析mapper.xml中的各种标签的过程中会对已经解析过的xml对应的mapper进行保存,保存在Configuration的mapperRegistry的konwnMappers中,key是接口全限定名,value是接口对应的MapperProxyFactory类型,保存是为了实例化mapper接口时能获取到mapper及对应的MapperProxyFactory(作用是实例化时创建mapper接口的代理类MapperProxy)
4.mybatis-plus这种,不写xml的,会解析baseMapper中的方法,根据实体类信息等,生成sql。
实例化SqlSessionTemplate
1.构建一个SqlSessionTemplate,用来在实例化mapper接口时获取mapper以及在执行查询时获取sqlsession。SqlSessionTemplate里维护DefaultSqlSessionFactory,比如获取Configuration就会通过SqlSessionTemplate获取DefaultSqlSessionFactory然后在获取Configuration,查询时获取和数据库关联的sqlsession,也是通过SqlSessionTemplate维护的DefaultSqlSessionFactory的opensession方法获取到的,类型是DefaultSqlSession。
实例化mapper
扫描mapper文件变成BeanDefinition(@Mapper和@MapperScan),变成BeanDefinition后会把BeanDefinition中的BeanClass属性设置为MapperFactoryBean类型,以便在spring容器实例化对象时,对mapper接口也进行实例化,也就是生成对应的代理类MapperProxy,用以执行mapper的增删改查方法。
实例化完这三个对象,springboot就可以等待前端调用接口然后执行mapper方法进行增删改查了。
2.查询时
也就是前端调接口,然后调service,然后调mapper的方法时做的工作
解析传参
当通过servcie调用mapper接口的方法时,会调用代理对象MapperProxy的invoke 方法。然后会调用MapperMethod的invoke 方法。在MapperMethod的invoke 方法会调用MapperMethod的execute方法。在这个方法中会调用SqlSessionTemplate的对应方法执行查询,在调用之前会进行方法参数解析,最终方法是ParamNameResolver类的getNamedParams,得到一个map,key是参数名,value是参数值。
获取最终的sql
mapper.xml中的sql会在MybatisAutoConfiguration中构建SqlSessionFactory时得到解析,如果有where if之类的标签会被解析成DynamicSqlSource,如果是普通的查询语句(select * from departments where department_id=#{depId})则会被解析成RawSqlSource,这个属性会被存在configuration的mappedstatements属性中,属性名称为sqlSource。然后执行查询时,会从sqlSource中拿到对应的原始sql,然后再进行解析,也就是把方法调用时的传参拼接到sql中以及拼接where if这种动态标签,最终得到完整的sql。方法就是对应的SqlSource类的getBoundsql方法。这里DynamicSqlSource类的getBoundsql方法也会调用RawSqlSource的getBoundSql方法。
3.查询后
也就是查询出结果后做的工作
- 解析返回值:关键类DefaultResultSetHandler,基本逻辑都是在这个类实现的。关键类ResultSetWrapper,保存要映射的字段集合和查询出的数值的字节数组
- DefaultResultSetHandler类的handleResultSets方法,先拿到需要映射的字段集合,封装在ResultSetWrapper中,然后再获取一个resultmap类型的集合,每个resultmap保存需要映射的类型,如果有resultmap标签则会封装到resultmappings属性中。
- DefaultResultSetHandler类的handleResultSet方法,调用handleRowValues方法处理结果集放到multipleResults中。
- DefaultResultSetHandler类的handleRowValues方法,分别处理嵌套映射和非嵌套映射。
- 非嵌套映射,handleRowValuesForSimpleResultMap方法,遍历映射每行数据,调用getRowValue方法。没加resultmap或者resultmap中没做映射的字段调用applyAutomaticMappings方法,resultmap中映射的字段调用applyPropertyMappings方法。具体方法就是调用对应字段类型的typeHandler从字节数组中拿到数据进行转换。所有对应字段值的字节数组在ResultSetWrapper的resultset属性中。
- 嵌套映射,handleRowValuesForNestedResultMap方法,遍历映射每行数据,调用重载的getRowValue方法,没加resultmap或者resultmap中没做映射的字段调用applyAutomaticMappings方法,resultmap中映射的字段调用applyPropertyMappings方法,嵌套映射的字段调用applyNestedResultMappings方法。而applyNestedResultMappings会再次调用getRowValue方法解析每行数据,逻辑和非嵌套映射相同。
相关文章:
科普文:深入理解Mybatis
概叙 (1) JDBC JDBC(Java Data Base Connection,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成.JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。 优点…...
称重传感器有哪些种类
有关称重传感器的知识,称重传感器是众多传感器产品中的一种,也是很常用的传感器之一,那么称重传感器有哪些种类,称重传感器的分类方式是什么样的,一起来了解下。 称重传感器的分类 主要有六种称重传感器类型…...
程序员鱼皮的保姆级写简历指南第四弹,优秀简历参考
大家好,我是程序员鱼皮。做知识分享这些年来,我看过太多简历、也帮忙修改过很多的简历,发现很多同学是完全不会写简历的、会犯很多常见的问题,不能把自己的优势充分展示出来,导致措施了很多面试机会,实在是…...
UML建模案例分析-时序图和类图的对应关系
概念 简单地说,类图定义了系统中的对象,时序图定义了对象之间的交互。 例子 一个电子商务系统,会员可通过电子商务系统购买零件。具体功能需求如下: 会员请求结账时,系统验证会员的账户是否处于登录状态࿱…...
Java版Flink使用指南——从RabbitMQ中队列中接入消息流
大纲 创建RabbitMQ队列新建工程新增依赖编码设置数据源配置读取、处理数据完整代码 打包、上传和运行任务测试 工程代码 在《Java版Flink使用指南——安装Flink和使用IntelliJ制作任务包》一文中,我们完成了第一个小型Demo的编写。例子中的数据是代码预先指定的。而…...
Python酷库之旅-第三方库Pandas(013)
目录 一、用法精讲 31、pandas.read_feather函数 31-1、语法 31-2、参数 31-3、功能 31-4、返回值 31-5、说明 31-6、用法 31-6-1、数据准备 31-6-2、代码示例 31-6-3、结果输出 32、pandas.DataFrame.to_feather函数 32-1、语法 32-2、参数 32-3、功能 32-4、…...
Linux 高级 Shell 脚本编程:掌握 Shell 脚本精髓,提升工作效率
【Linux】 高级 Shell 脚本编程:掌握 Shell 脚本精髓,提升工作效率 Shell 脚本编程是 Linux 系统管理员和开发人员的必备技能。通过学习高级 Shell 脚本编程,你可以编写更高效、更灵活和更易于维护的脚本。本文将介绍 Shell 脚本编程中的函数…...
【ARMv8/v9 GIC 系列 1.5 -- Enabling the distribution of interrupts】
请阅读【ARM GICv3/v4 实战学习 】 文章目录 Enabling the distribution of interruptsGIC Distributor 中断组分发控制CPU Interface 中断组分发控制Physical LPIs 的启用Summary Enabling the distribution of interrupts 在ARM GICv3和GICv4体系结构中,中断分发…...
《mysql篇》--索引事务
索引 索引的介绍 索引是帮助MySQL高效获取数据的数据结构,是一种特殊的文件,包含着对数据表里所有记录的引用指针,因为索引本身也比较大,所以索引一般是存储在磁盘上的,索引的种类有很多,不过如果没有特殊…...
科研绘图系列:R语言STAMP图(STAMP Plot)
介绍 STAMP图(STAMP plot)并非一个广泛认知的、具有特定名称的图表类型,而是可能指在STAMP(Statistical Analysis of Metagenomic Profiles:“STAMP: statistical analysis of taxonomic and functional profiles”)软件使用过程中生成的各种统计和可视化图表的总称。ST…...
运维团队如何应对动环监控与IT监控分离的挑战
IT与机房动环监控的一体化是当下及未来的必然趋势,这一模式显著节省了运维过程中的时间与成本。一体化平台不仅消除了频繁切换系统的繁琐,更在一个统一界面上实现了多元化的管理运维功能,极大地提升了工作效率。 在机房升级或新建项目中&…...
深入解析大数据核心概念:数据平台、数据中台、数据湖与数据仓库的异同与应用
大数据领域内的诸多概念常常让人困惑,其中数据平台、数据中台、数据湖和数据仓库是最为关键的几个。 1. 数据平台 定义: 数据平台是一个综合性的技术框架,旨在支持整个数据生命周期的管理和使用。它包含数据采集、存储、处理、分析和可视化…...
开发指南040-业务操作日志
平台所有业务操作都存储在核心库,以便统一分析处理。各业务微服务通过feign调用核心日志服务。底层提供了API: <dependency><groupId>org.qlm</groupId><artifactId>qlm-api</artifactId><version>1.0-SNAPSHOT<…...
如何构建数据驱动的企业?爬虫管理平台是关键桥梁吗?
一、数据驱动时代:为何选择爬虫管理平台? 在信息爆炸的今天,数据驱动已成为企业发展的核心战略之一。爬虫管理平台,作为数据采集的第一站,它的重要性不言而喻。这类平台通过自动化手段,从互联网的各个角落…...
多线程Thread
线程Thread简介 任务、线程、金城、多线程 多任务:短时间切换不同得任务 多线程:通过同一条道路,增加道多条道路,提高使用率,解决堵塞问题 普通方法调多线程只有主线一台执行路径是主线程调run()方法,方…...
计算机网络之WPAN 和 WLAN
上一篇文章内容:无线局域网 1.WPAN(无线个人区域网) WPAN 是以个人为中心来使用的无线个人区域网,它实际上就是一个低功率、小范围、低速率和低价格的电缆替代技术。 (1) 蓝牙系统(Bluetooth) &#…...
TikTok海外运营,云手机多种变现方法
从现阶段来看,TikTok 的用户基数不断增长,已然成为全球创业者和品牌的全新竞争舞台。其用户数量近乎 20 亿,年轻用户占据主导,市场渗透率也逐年提高。不管是大型企业、著名品牌,还是个体创业者,都绝不能小觑…...
kubekey在ubuntu24实现kubernetes快速安装
基于Ubunut24.04安装 设置主机名 hostnamectl set-hostname kkmain hostnamectl set-hostname kknode1 hostnamectl set-hostname kknode2关闭swap sudo swapoff -a sudo sed -i s/.*swap.*/#&/ /etc/fstab安装kubekey export KKZONEcn curl -sfL https://get-kk.kubes…...
根据关键词query获取google_img(api方式)
文章目录 说明代码第一部分:链接保存为Json第二部分:链接转换为img 说明 根据关键词query获取google_img USERNAME “xxx” PASSWORD “xxx” 官网申请。 代码 首先获取图片链接,保存为json之后下载。 第一部分:链接保存为…...
西安明德理工学院师生莅临泰迪智能科技开展参观见习活动
为进一步深化校企合作,落实高校应用型人才培养。7月8日,西安明德理工学院与广东泰迪智能科技股份有限公司联合开展学生企业见习活动。西安明德理工学院金融产业学院副院长刘敏、金融学专业负责人张莉萍、金融学专业教师曹艳飞、赵浚妤、泰迪智能科技董事…...
通用机器人里程碑!MIT提出策略组合框架PoCo,解决数据源异构难题,实现机器人多任务灵活执行
18 位人形机器人充当「迎宾」人员,整齐划一向嘉宾挥手,这是 2024 世界人工智能大会上的一个震撼场景,让人们直观感受到了今年机器人的飞速发展。 图源:甲子光年 1954 年,世界上第一台可编程机器人「尤尼梅特」在通用汽…...
基于Java中的SSM框架实现疫情冷链追溯系统项目【项目源码+论文说明】
基于Java中的SSM框架实现疫情冷链追溯系统演示 摘要 近几年随着城镇化发展和居民消费水平的不断提升,人们对健康生活方式的追求意识逐渐加强,生鲜食品逐渐受到大众青睐,诸如盒马鲜生、7-fresh等品牌生鲜超市,一时间如雨后春笋般迅…...
想在vue中预览doxc,excel,pdf文件? vue-office提供包支持
在浩瀚的Vue生态中,vue-office犹如一颗璀璨的星辰,以其独特的魅力照亮了开发者处理多种文件格式的预览之路。这款精心打造的Vue组件库,不仅拥抱了Vue2的经典,也紧密跟随Vue3的步伐,展现了卓越的技术前瞻性和兼容性。它…...
PostgreSQL16安装Mac(brew)
问题 最近需要从MySQL切换到PostgreSQL。我得在本地准备一个PostgreSQL。 步骤 使用brew安装postgresql16: arch -arm64 brew install postgresql16启动postgresql16: brew services start postgresql16配置postgresql环境变量,打开环境变量文件: …...
【语音识别算法】深度学习语音识别算法与传统语音识别算法的区别、对比及联系
深度学习语音识别算法与传统语音识别算法在理论基础、实现方式、性能表现等方面存在显著区别,同时也有一些联系。下面将从几个方面详细比较这两种方法,并给出应用实例和代码示例。 一、理论基础与实现方式 1.传统语音识别算法: 特征提取&a…...
图片批量重命名bat,一个脚本快速搞定图片批量重命名
BAT 批处理 是一种在 Microsoft Windows 操作系统中使用的脚本语言,用于自动执行一系列预定义的命令或任务。这些命令集合通常存储在一个文本文件中,文件扩展名为 .bat 或 .cmd。批处理脚本可以包含简单的命令,如文件复制、移动、删除&#x…...
基于stm32单片机的智能手环的设计
摘 要 随着科技的飞速发展和人们生活水平的提高,健康与科技日益融合,智能可穿戴设备已成为现代人生活中不可或缺的一部分。智能手环,作为一种便携、实用且功能丰富的可穿戴设备,受到越来越多用户的喜爱。它不仅能够实时监测用户的…...
雷池WAF动态防护功能初体验
一、 介绍 大名鼎鼎的雷池WAF最近新上了个名为 动态防护 的功能 所谓动态防护,是在用户浏览到的网页内容不变的情况下,将网页赋予动态特性,即使是静态页面,也会具有动态的随机性。 说白了就是给你网站的 html 和 js 代码加上加密…...
持安科技CEO何艺荣获中国信通院2023-2024年度标准卓越贡献奖
近日,由中国信息通信研究院、中国通信标准化协会承办的“全球数字经济大会—云和软件安全论坛”暨“2024第二届SecGo云和软件安全大会”胜利召开,零信任办公安全技术创新企业持安科技创始人兼CEO何艺获评为2023-2024年度零信任领域标准卓越贡献者。 由中…...
gitee上传和下载idea项目的流程
环境:idea2022 一、上传项目 1、在gitee中新建一个仓库。 2、打开所要上传的项目的文件夹,点击Git Bash,生成.git文件夹。 3、在idea中打开所要上传的项目,在控制台的Terminal菜单中,输入git add . (注意…...
邳州做网站/seo站长之家
最近在学习springboot 2.0 用它搭建一个新项目。 奇怪的是,原本在springboot 1.4下面用的好好的thymeleaf layout 模板,突然间不能用了。页面的内容怎么也嵌不到模板页去。 于是开始查找原因,一开始以为是配置的问题,仔细检查了所…...
网站建设对促进部门工作的益处/福州seo按天收费
Hadoop介绍 内容简介: 1. 编写可扩展的,分布式的,海量数据处理的程序的基础 2. 介绍hadoop与MapREduce 3. 编写一个简单的MapReduce程序 今天,我们被数据所围绕,人们上传视频,手机拍照,给朋友发信息,更新facebook状态,论坛回帖,点击广告,等等.另外,机器本身也…...
怎样做网站个人简介/外贸网站制作
没什么多说的 document.onkeydown function () {var oEvent window.event;if (oEvent.keyCode 13 && oEvent.ctrlKey) {console.log("你按下了ctrlenter");}else if(oEvent.keyCode 13){console.log(2222222222)}}jQuery的实现方法还没找到...
b2b网站怎么注册/全球疫情最新消息
给定一个带有头结点 head 的非空单链表,返回链表的中间结点。 如果有两个中间结点,则返回第二个中间结点。 示例 1: 输入:[1,2,3,4,5] 输出:此列表中的结点 3 (序列化形式:[3,4,5]) 返回的结点值为 3 。 (…...
包头 网站制作/产品推广宣传方案
题目: Design a HashSet without using any built-in hash table libraries. 在不使用任何内置哈希表库的情况下设计HashSet。 To be specific, your design should include these functions: 具体而言,您的设计应包括以下功能: add(value): …...
手机做网站公司有哪些/百度在线识图
计算机二级考试《Java》代码的基本知识Java语言程序设计是计算机二级考试中的科目之一,是一种可以撰写跨平台应用程序的面向对象的程序设计语言。下面百分网小编为大家搜索整理了关于《Java》代码的基本知识,欢迎参考学习,希望对大家有所帮助…...