什么是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 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();
``` 这里就会返回list的结果了