MyBatis的SqlSession理解

SqlSession是Mybatis最重要的构建之一,可以认为Mybatis一系列的配置目的是生成类似JDBC生成的Connection对象的statement对象,这样才能与数据库开启“沟通”,通过SqlSession可以实现增删改查(当然现在更加推荐是使用Mapper接口形式)

1 .sqlsession的创建:

SqlSessionFactoryBuilder创建SqlSessionFactory openSession,sqlSession 执行增删改查
用了注解是通过org.mybatis.spring.SqlSessionFactoryBean该类创建sqlsession的,而mapper里面的每一个方法称为statement。

public void deleteUserTest() throws IOException {
        // mybatis配置文件
        String resource = "SqlMapConfig.xml";
        // 得到配置文件流
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 创建会话工厂,传入mybatis的配置文件信息
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
                .build(inputStream);
        // 通过工厂得到SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 传入id删除 用户
        sqlSession.delete("test.deleteUser", 39);        //更新        sqlSession.update("test.updateUser", user);        //插入        sqlSession.insert("test.insertUser", user);        //查询        List<User> list = sqlSession.selectOne("test.findUserByName", "小明");
        // 提交事务 增删改 需要commit,查询无需commit
        sqlSession.commit();
        // 关闭会话
        sqlSession.close();

    }

2.SqlSession原理

SqlSession提供select/insert/update/delete方法,在旧版本中使用使用SqlSession接口的这些方法,但是新版的Mybatis中就会建议使用Mapper接口的方法。

映射器其实就是一个动态代理对象,进入到MapperMethod的execute方法就能简单找到SqlSession的删除、更新、查询、选择方法,从底层实现来说:通过动态代理技术,让接口跑起来,之后采用命令模式,最后还是采用了SqlSession的接口方法(getMapper()方法等到Mapper)执行SQL查询(也就是说Mapper接口方法的实现底层还是采用SqlSession接口方法实现的)。

3.SqlSession重要的四个对象

1)Execute:调度执行StatementHandler、ParmmeterHandler、ResultHandler执行相应的SQL语句;

2)StatementHandler:使用数据库中Statement(PrepareStatement)执行操作,即底层是封装好了的prepareStatement;

3)ParammeterHandler:处理SQL参数;

4)ResultHandler:结果集ResultSet封装处理返回。
  1. Execute执行器
    execute接口有以下方法
public interface Executor {
    ResultHandler NO_RESULT_HANDLER = null;

    int update(MappedStatement var1, Object var2) throws SQLException;

    <E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4, CacheKey var5, BoundSql var6) throws SQLException;

    <E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4) throws SQLException;

    <E> Cursor<E> queryCursor(MappedStatement var1, Object var2, RowBounds var3) throws SQLException;

    List<BatchResult> flushStatements() throws SQLException;

    void commit(boolean var1) throws SQLException;

    void rollback(boolean var1) throws SQLException;

    CacheKey createCacheKey(MappedStatement var1, Object var2, RowBounds var3, BoundSql var4);

    boolean isCached(MappedStatement var1, CacheKey var2);

    void clearLocalCache();

    void deferLoad(MappedStatement var1, MetaObject var2, String var3, CacheKey var4, Class<?> var5);

    Transaction getTransaction();

    void close(boolean var1);

    boolean isClosed();

    void setExecutorWrapper(Executor var1);
}

执行器起到至关重要的作用,它是真正执行Java与数据库交互的东西,参与了整个SQL查询执行过程中。

1)主要有三种执行器:简易执行器SIMPLE(不配置就是默认执行器)、REUSE是一种重用预处理语句、BATCH批量更新、批量专用处理器

2)执行器作用:Executor会先调用StatementHandler的prepare()方法预编译SQL语句,同时设置一些基本的运行参数,然后调用StatementHandler的parameterize()方法(实际上是启用了ParameterHandler设置参数)设置参数,resultHandler再组装查询结果返回调用者完成一次查询完成预编译,简单总结起来就是即先预编译SQL语句,之后设置参数(跟JDBC的prepareStatement过程类似)最后如果有查询结果就会组装返回。

4.Mapper
Mybatis官方手册建议通过mapper对象访问mybatis,因为使用mapper看起来更优雅,就像下面这样:

session = sqlSessionFactory.openSession();
UserDao userDao= session.getMapper(UserDao.class);
UserDto user =new UserDto();
user.setUsername("iMbatis");
user.setPassword("iMbatis");
userDao.insertUser(user);

那么这个mapper到底是什么呢,它是如何创建的呢,它又是怎么与sqlsession等关联起来的呢?下面为你一一解答。

1、创建

表面上看mapper是在sqlsession里创建的,但实际创建它的地方是MapperRegistry:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    if (!knownMappers.contains(type))
        throw new BindingException("Type " + type + " isnot known to the MapperRegistry.");
    try {
        return MapperProxy.newMapperProxy(type, sqlSession);
    } catch (Exceptione) {
        throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}

可以看到,mapper是一个代理对象,它实现的接口就是传入的type,这就是为什么mapper对象可以通过接口直接访问。同时还可以看到,创建mapper代理对象时传入了sqlsession对象,这样就把sqlsession也关联起来了。我们进一步看看MapperProxy.newMapperProxy(type,sqlSession); 背后发生了什么事情:

public static <T> T newMapperProxy(Class<T> mapperInterface, SqlSession sqlSession) {
    ClassLoader classLoader = mapperInterface.getClassLoader();
    Class<?>[] interfaces = new Class[]{mapperInterface};
    MapperProxy proxy = new MapperProxy(sqlSession);
    return (T) Proxy.newProxyInstance(classLoader,interfaces, proxy);
}

看起来没什么特别的,和其他代理类的创建一样,我们重点关注一下MapperProxy的invoke方法:
2、MapperProxy 的 invoke

我们知道对被代理对象的方法的访问都会落实到代理者的invoke上来,MapperProxy的invoke如下:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
    if (method.getDeclaringClass()== Object.class) {
        return method.invoke(this, args);
    }
 
    final Class<?> declaringInterface = findDeclaringInterface(proxy, method);
    final MapperMethod mapperMethod = new MapperMethod(declaringInterface, method, sqlSession);
    final Object result = mapperMethod.execute(args);
 
    if (result ==null && method.getReturnType().isPrimitive()&& !method.getReturnType().equals(Void.TYPE)) {
        throw new BindingException("Mapper method '" + method.getName() + "'(" + method.getDeclaringClass()
                + ") attempted toreturn null from a method with a primitive return type ("
               + method.getReturnType() + ").");
    }
    return result;
}

可以看到invoke把执行权转交给了MapperMethod,我们来看看MapperMethod里又是怎么运作的:

public Object execute(Object[] args) {
    Object result = null;
    if(SqlCommandType.INSERT == type) {
        Object param = getParam(args);
        result = sqlSession.insert(commandName, param);
    } else if(SqlCommandType.UPDATE == type) {
        Object param = getParam(args);
        result = sqlSession.update(commandName, param);
    } else if(SqlCommandType.DELETE == type) {
        Object param = getParam(args);
        result = sqlSession.delete(commandName, param);
    } else if(SqlCommandType.SELECT == type) {
        if (returnsVoid && resultHandlerIndex != null) {
           executeWithResultHandler(args);
        } else if (returnsList) {
           result = executeForList(args);
        } else if (returnsMap) {
           result = executeForMap(args);
        } else {
           Object param = getParam(args);
           result = sqlSession.selectOne(commandName, param);
        }
    } else {
        throw new BindingException("Unknown execution method for: " + commandName);
    }
    return result;
 
}

可以看到,MapperMethod就像是一个分发者,他根据参数和返回值类型选择不同的sqlsession方法来执行。这样mapper对象与sqlsession就真正的关联起来了。

5.Executor
前面提到过,sqlsession只是一个门面,真正发挥作用的是executor,对sqlsession方法的访问最终都会落到executor的相应方法上去。Executor分成两大类,一类是CacheExecutor,另一类是普通Executor。Executor的创建前面已经介绍了,下面介绍下他们的功能:

CacheExecutor

CacheExecutor有一个重要属性delegate,它保存的是某类普通的Executor,值在构照时传入。执行数据库update操作时,它直接调用delegate的update方法,执行query方法时先尝试从cache中取值,取不到再调用delegate的查询方法,并将查询结果存入cache中。代码如下:

public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    if (ms != null) {
        Cache cache = ms.getCache();
        if (cache != null) {
           flushCacheIfRequired(ms);
           cache.getReadWriteLock().readLock().lock();
           try {
               if (ms.isUseCache() && resultHandler ==null) {
                   CacheKey key = createCacheKey(ms, parameterObject, rowBounds);
                   final List cachedList = (List)cache.getObject(key);
                   if (cachedList != null) {
                        return cachedList;
                   } else {
                       List list = delegate.query(ms,parameterObject, rowBounds, resultHandler);
                       tcm.putObject(cache,key, list);
                       return list;
                   }
               } else {
                   return delegate.query(ms,parameterObject, rowBounds, resultHandler);
               }
            } finally {
               cache.getReadWriteLock().readLock().unlock();
            }
        }
    }
    return delegate.query(ms,parameterObject, rowBounds, resultHandler);
}

普通 Executor

普通Executor有3类,他们都继承于BaseExecutor,BatchExecutor专门用于执行批量sql操作,ReuseExecutor会重用statement执行sql操作,SimpleExecutor只是简单执行sql没有什么特别的。下面以SimpleExecutor为例:

public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    Statementstmt = null;
    try {
       Configuration configuration = ms.getConfiguration();
       StatementHandler handler = configuration.newStatementHandler(this, ms,parameter, rowBounds,resultHandler);
       stmt = prepareStatement(handler);
       return handler.query(stmt, resultHandler);
    } finally {
       closeStatement(stmt);
    }
}

可以看出,Executor本质上也是个甩手掌柜,具体的事情原来是StatementHandler来完成的。

6.StatementHandler
当Executor将指挥棒交给StatementHandler后,接下来的工作就是StatementHandler的事了。我们先看看StatementHandler是如何创建的。

创建

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement,
        Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) {
   StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler);
   statementHandler= (StatementHandler) interceptorChain.pluginAll(statementHandler);
   return statementHandler;
}

可以看到每次创建的StatementHandler都是RoutingStatementHandler,它只是一个分发者,他一个属性delegate用于指定用哪种具体的StatementHandler。可选的StatementHandler有SimpleStatementHandler、PreparedStatementHandler和CallableStatementHandler三种。选用哪种在mapper配置文件的每个statement里指定,默认的是PreparedStatementHandler。同时还要注意到StatementHandler是可以被拦截器拦截的,和Executor一样,被拦截器拦截后的对像是一个代理对象。由于mybatis没有实现数据库的物理分页,众多物理分页的实现都是在这个地方使用拦截器实现的,本文作者也实现了一个分页拦截器,在后续的章节会分享给大家,敬请期待。

初始化

StatementHandler创建后需要执行一些初始操作,比如statement的开启和参数设置、对于PreparedStatement还需要执行参数的设置操作等。代码如下:

private Statement prepareStatement(StatementHandler handler) throwsSQLException {
    Statement stmt;
    Connection connection = transaction.getConnection();
    stmt =handler.prepare(connection);
    handler.parameterize(stmt);
    return stmt;
}
statement的开启和参数设置没什么特别的地方,handler.parameterize倒是可以看看是怎么回事。handler.parameterize通过调用ParameterHandler的setParameters完成参数的设置,ParameterHandler随着StatementHandler的创建而创建,默认的实现是DefaultParameterHandlerpublic ParameterHandler newParameterHandler(MappedStatement mappedStatement, ObjectparameterObject, BoundSql boundSql) {
   ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement,parameterObject,boundSql);
   parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
   return parameterHandler;
}

同Executor和StatementHandler一样,ParameterHandler也是可以被拦截的。

参数设置

DefaultParameterHandler里设置参数的代码如下:

public void setParameters(PreparedStatement ps) throws SQLException {
   ErrorContext.instance().activity("settingparameters").object(mappedStatement.getParameterMap().getId());
   List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if(parameterMappings != null) {
       MetaObject metaObject = parameterObject == null ? null : configuration.newMetaObject(parameterObject);
        for (int i = 0; i< parameterMappings.size(); i++) {
           ParameterMapping parameterMapping = parameterMappings.get(i);
            if(parameterMapping.getMode() != ParameterMode.OUT) {
               Object value;
               String propertyName = parameterMapping.getProperty();
               PropertyTokenizer prop = new PropertyTokenizer(propertyName);
               if (parameterObject == null) {
                   value = null;
               } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())){
                   value = parameterObject;
               } else if (boundSql.hasAdditionalParameter(propertyName)){
                   value = boundSql.getAdditionalParameter(propertyName);
               } else if(propertyName.startsWith(ForEachSqlNode.ITEM_PREFIX)
                        && boundSql.hasAdditionalParameter(prop.getName())){
                   value = boundSql.getAdditionalParameter(prop.getName());
                   if (value != null) {
                        value = configuration.newMetaObject(value).getValue(propertyName.substring(prop.getName().length()));
                   }
               } else {
                   value = metaObject == null ? null :metaObject.getValue(propertyName);
               }
               TypeHandler typeHandler = parameterMapping.getTypeHandler();
               if (typeHandler == null) {
                   throw new ExecutorException("Therewas no TypeHandler found for parameter " + propertyName  + " of statement " + mappedStatement.getId());
                }
               typeHandler.setParameter(ps, i + 1, value,parameterMapping.getJdbcType());
            }
        }
    }
}

这里面最重要的一句其实就是最后一句代码,它的作用是用合适的TypeHandler完成参数的设置。那么什么是合适的TypeHandler呢,它又是如何决断出来的呢?BaseStatementHandler的构造方法里有这么一句:

this.boundSql= mappedStatement.getBoundSql(parameterObject);

它触发了sql 的解析,在解析sql的过程中,TypeHandler也被决断出来了,决断的原则就是根据参数的类型和参数对应的JDBC类型决定使用哪个TypeHandler。比如:参数类型是String的话就用StringTypeHandler,参数类型是整数的话就用IntegerTypeHandler等。

参数设置完毕后,执行数据库操作(update或query)。如果是query最后还有个查询结果的处理过程。

7.ResultSetHandler

结果处理

结果处理使用ResultSetHandler来完成,默认的ResultSetHandler是FastResultSetHandler,它在创建StatementHandler时一起创建,代码如下:

public ResultSetHandler newResultSetHandler(Executor executor, MappedStatementmappedStatement,
RowBoundsrowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSqlboundSql) {
   ResultSetHandler resultSetHandler = mappedStatement.hasNestedResultMaps() ? newNestedResultSetHandler(executor, mappedStatement, parameterHandler,resultHandler, boundSql, rowBounds): new FastResultSetHandler(executor,mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
   resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
   return resultSetHandler;
}

可以看出ResultSetHandler也是可以被拦截的,可以编写自己的拦截器改变ResultSetHandler的默认行为。

// ResultSetHandler内部一条记录一条记录的处理,在处理每条记录的每一列时会调用TypeHandler转换结果,如下:

protected boolean applyAutomaticMappings(ResultSet rs, List<String> unmappedColumnNames,MetaObject metaObject) throws SQLException {
    boolean foundValues = false;
    for (StringcolumnName : unmappedColumnNames) {
        final Stringproperty = metaObject.findProperty(columnName);
        if (property!= null) {
            final ClasspropertyType = metaObject.getSetterType(property);
            if (typeHandlerRegistry.hasTypeHandler(propertyType)) {
               final TypeHandler typeHandler = typeHandlerRegistry.getTypeHandler(propertyType);
               final Object value = typeHandler.getResult(rs,columnName);
               if (value != null) {
                   metaObject.setValue(property, value);
                   foundValues = true;
               }
            }
        }
    }
 
    return foundValues;
}

从代码里可以看到,决断TypeHandler使用的是结果参数的属性类型。因此我们在定义作为结果的对象的属性时一定要考虑与数据库字段类型的兼容性。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

princeAladdin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值