MyBatis3 执行流程
什么是Mybatis
官网介绍 MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。 https://mybatis.org/mybatis-3/zh_CN/index.html
可以看到主要作用是免除了JDBC代码的部分和结果提取的流程。 因为直接使用JDBC的 api话要连接数据库,设置参数,执行Sql,然后再将结果映射到Java 对象上,使用原生api比较繁琐。
可以看个JDBC的例子:
public class JdbcDemo {
public static void main(String[] args) {
JdbcDataSource dataSource = new JdbcDataSource();
dataSource.setURL("jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1");
try {
dataSource.getConnection().createStatement().execute("CREATE TABLE book (isbn varchar,name varchar)");
dataSource.getConnection().createStatement().execute("INSERT INTO book values('1224','harrypotter')");
ResultSet resultSet = dataSource.getConnection().prepareStatement("SELECT * FROM book;").executeQuery();
List<Book> books = new ArrayList<>();
while(resultSet.next()) {
Book book = new Book(resultSet.getString(1),resultSet.getString(2));
books.add(book);
}
System.out.println("query result:"+books);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
对比Mybatis Demo
将sql的执行统一放到Mapper接口类,要执行的sql就定义在mapper类里(通过注解或者xml文件映射方式),业务代码只需要调用mapper接口类的方法,将参数映射到sql和将执行结果映射到list或者java对象则由mybatis负责
public interface BookMapper {
@Insert(“””
INSERT INTO book VALUES(#{isbn},#{name})
“””)
Boolean addBook(Book book);
@Select(“SELECT * FROM book”)
List list();
public class MyBatisDemo {
public static void main(String[] args) {
JdbcDataSource dataSource = new JdbcDataSource();
dataSource.setURL("jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1");
try {
dataSource.getConnection().createStatement().execute("CREATE TABLE book (isbn varchar,name varchar)");
} catch (SQLException e) {
throw new RuntimeException(e);
}
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(BookMapper.class);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
try(SqlSession session = sqlSessionFactory.openSession()) {
BookMapper mapper = session.getMapper(BookMapper.class);
Book book1 = new Book("978","HarryPotter");
mapper.addBook(book1);
List<Book> books = mapper.list();
System.out.println("query database result:"+books);
}
}
}
1.初始化的流程
先初始化一个Configuration类
Configuration configuration = new Configuration(environment); configuration.addMapper(BookMapper.class);
Configuration类保存了注册的Mapper类信息
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
sqlSessionFactor用来打开SqlSession,里面可以获取Mapper来调用Mapper,调用Mapper接口时会执行对应的sql查询,和映射结果成返回值的类型。
2.执行断点流程
mapper接口代理
invoke:83, MapperProxy (org.apache.ibatis.binding)
list:-1, $Proxy7 (jdk.proxy2)
执行mapper.list()时,进入了 public class MapperProxy 类,由于我们定义的BookMapper只是个interface,并没有实现类,这里进入了Mapper的代理类,应该是getMapper时返回了一个接口的代理类
这里如果是Object类的函数就直接执行,如果是其他的,就调用cachedInvoker得到MapperMethodInvoker,调用execute
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
} catch (Throwable var5) {
Throwable t = var5;
throw ExceptionUtil.unwrapThrowable(t);
}
}
得到MapperMethod后,调用Sqlsession来执行
execute:59, MapperMethod (org.apache.ibatis.binding)
invoke:141, MapperProxy$PlainMethodInvoker (org.apache.ibatis.binding)
invoke:86, MapperProxy (org.apache.ibatis.binding)
list:-1, $Proxy7 (jdk.proxy2)
main:37, MyBatisDemo (online.indigo6a.mybatisdemo)
据执行的类型,和返回值类型,选择不同的处理方法,比如void就会不用管结果,list,map哪些等等
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
Object param;
switch (this.command.getType()) {
…
case SELECT:
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else if (this.method.returnsCursor()) {
result = this.executeForCursor(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
因为返回值是列表类型的,所以到了executeForMany
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
Object param = this.method.convertArgsToSqlCommandParam(args);
List result;
if (this.method.hasRowBounds()) {
RowBounds rowBounds = this.method.extractRowBounds(args);
result = sqlSession.selectList(this.command.getName(), param, rowBounds);
} else {
result = sqlSession.selectList(this.command.getName(), param);
}
if (!this.method.getReturnType().isAssignableFrom(result.getClass())) {
return this.method.getReturnType().isArray() ? this.convertToArray(result) : this.convertToDeclaredCollection(sqlSession.getConfiguration(), result);
} else {
return result;
}
}
如果有RowBounds就提取 RowBounds RowBounds 是一个用于实现分页功能的类。它允许你在查询时指定从哪一行开始获取数据以及获取的记录数。这种方式不需要在 SQL 查询中使用分页相关的语句(如 MySQL 的 LIMIT),而是由 MyBatis 在执行查询后对结果集进行裁剪。 性能问题:虽然 RowBounds 方式简单,但在处理大量数据时可能会导致性能问题,因为它会先加载所有数据到内存中,然后再进行裁剪。因此,在处理大数据量时,建议使用数据库原生的分页语句(如 MySQL 的 LIMIT)。
调用Sqlsession的selectList方法
selectList:152, DefaultSqlSession (org.apache.ibatis.session.defaults) selectList:147, DefaultSqlSession (org.apache.ibatis.session.defaults) selectList:142, DefaultSqlSession (org.apache.ibatis.session.defaults) executeForMany:147, MapperMethod (org.apache.ibatis.binding) execute:80, MapperMethod (org.apache.ibatis.binding) invoke:141, MapperProxy$PlainMethodInvoker (org.apache.ibatis.binding) invoke:86, MapperProxy (org.apache.ibatis.binding) list:-1, $Proxy7 (jdk.proxy2) main:37, MyBatisDemo (online.indigo6a.mybatisdemo)
接收一个statement的id,在configuration里找到对应的语句,使用 private final Executor executor;来执行
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
List var6;
try {
MappedStatement ms = this.configuration.getMappedStatement(statement);
this.dirty |= ms.isDirtySelect();
var6 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, handler);
} catch (Exception var10) {
Exception e = var10;
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
return var6;
}
调用CachingExecutor执行
这里用CachingExecutor这个实现来执行,会先生成一个CacheKey,如果缓存已经存在的话就直接返回结果,这里是mybatis的本地缓存
query:88, CachingExecutor (org.apache.ibatis.executor)
selectList:154, DefaultSqlSession (org.apache.ibatis.session.defaults)
selectList:147, DefaultSqlSession (org.apache.ibatis.session.defaults)
selectList:142, DefaultSqlSession (org.apache.ibatis.session.defaults)
executeForMany:147, MapperMethod (org.apache.ibatis.binding)
execute:80, MapperMethod (org.apache.ibatis.binding)
invoke:141, MapperProxy$PlainMethodInvoker (org.apache.ibatis.binding)
invoke:86, MapperProxy (org.apache.ibatis.binding)
list:-1, $Proxy7 (jdk.proxy2)
main:37, MyBatisDemo (online.indigo6a.mybatisdemo)
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
数据库查询
queryFromDatabase:334, BaseExecutor (org.apache.ibatis.executor)
query:158, BaseExecutor (org.apache.ibatis.executor)
query:110, CachingExecutor (org.apache.ibatis.executor)
query:90, CachingExecutor (org.apache.ibatis.executor)
selectList:154, DefaultSqlSession (org.apache.ibatis.session.defaults)
selectList:147, DefaultSqlSession (org.apache.ibatis.session.defaults)
selectList:142, DefaultSqlSession (org.apache.ibatis.session.defaults)
executeForMany:147, MapperMethod (org.apache.ibatis.binding)
execute:80, MapperMethod (org.apache.ibatis.binding)
invoke:141, MapperProxy$PlainMethodInvoker (org.apache.ibatis.binding)
invoke:86, MapperProxy (org.apache.ibatis.binding)
list:-1, $Proxy7 (jdk.proxy2)
main:37, MyBatisDemo (online.indigo6a.mybatisdemo)
public <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);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
调用handler查询和映射结果
query:64, PreparedStatementHandler (org.apache.ibatis.executor.statement)
query:80, RoutingStatementHandler (org.apache.ibatis.executor.statement)
doQuery:65, SimpleExecutor (org.apache.ibatis.executor)
queryFromDatabase:336, BaseExecutor (org.apache.ibatis.executor)
query:158, BaseExecutor (org.apache.ibatis.executor)
query:110, CachingExecutor (org.apache.ibatis.executor)
query:90, CachingExecutor (org.apache.ibatis.executor)
selectList:154, DefaultSqlSession (org.apache.ibatis.session.defaults)
selectList:147, DefaultSqlSession (org.apache.ibatis.session.defaults)
selectList:142, DefaultSqlSession (org.apache.ibatis.session.defaults)
executeForMany:147, MapperMethod (org.apache.ibatis.binding)
execute:80, MapperMethod (org.apache.ibatis.binding)
invoke:141, MapperProxy$PlainMethodInvoker (org.apache.ibatis.binding)
invoke:86, MapperProxy (org.apache.ibatis.binding)
list:-1, $Proxy7 (jdk.proxy2)
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleResultSets(ps);
}
DefaultResultHandler 来处理查询结果 ```java public List
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
``` 这里就会返回list的结果了