标签:遍历 本地缓存 ppi rem news HERE protected red 回忆一下
这一步的主要目的就是通过SqlSession执行SQL语句。
Payment payment = sqlSession.selectOne("com.gzy.mybatistest.mapper.PaymentMapper.getPaymentById", 1);
直接进入DefaultSqlSession类中selectOne方法,我们发现它调用了selectList方法,其实查询一个或者多个都是调用selectList方法,我们再进入selectList方法中。
selectList方法从Configuration对象的mappedStatements集合中取出对应的MappedStatement对象,我们调用selectOne方法传入的参数是SQL的全限定方法名和SQL参数,在这行代码中第一个参数值是“com.gzy.mybatistest.mapper.PaymentMapper.getPaymentById” ,第二个参数值是要查询的Id值。
我们回忆一下,MyBatis初始化的时候是不是把每个SQL标签解析成一个个的MapperStatement对象,并且把这些MapperStatement对象装进Configuration对象维护的一个Map集合中,这个Map集合的Key值就是SQL标签的全限定方法名,Value是对应的MapperStatement对象,我们之前说装进集合中备用就是在这里用的,这里就通过要调用的方法的全限定名从Map集合中取出对应的MapperStatement对象。
比如我们现在selectOne方法调用的的是getPaymentById这个方法,所以现在通过它的全限定名“com.gzy.mybatistest.mapper.PaymentMapper.getPaymentById” 这个Key值从Configuration维护的Map集合中取出对应的MapperStatement对象。为什么要取出这个对象呢?因为MyBatis把每个SQL标签的所有数据都封装在了MapperStatement对象中,比如输入值类型、输入值、输出值类型、输出值以及SQL语句等。
public <T> T selectOne(String statement, Object parameter) {
// 调用下面的selectList方法
List<T> list = this.selectList(statement, parameter);
// 处理结果集
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
public <E> List<E> selectList(String statement, Object parameter) {
// 调用重载方法
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
// 最终调用
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 通过要调用方法的全限定名获取对应的MappedStatement对象
MappedStatement ms = configuration.getMappedStatement(statement);
// 执行器调用父类BaseExecutor的query方法
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
Executor接口先走的是CachingExcutor缓存执行器,我们研究一下代码,我们看第二段代码他一开始从MapperStatement中获取BoundSql这个对象,因为真正的SQL语句封装在这个对象中,而且这个对象也负责把SQL中的占位符替换成我们传的参数,只是MapperStatement维护了BoundSql的引用而已。
然后我们继续看createCacheKey方法,这个方法的意思就是根据这些参数生成一个缓存Key,当我们调用同一个SQL并且传的参数是一样的时候,生成的缓存Key是相同的。
然后我们看到第二个query重载方法,它一开始就是获取二级缓存,因为没有开启二级缓存,所以这里为null,它查询缓存为null,就会走最后一句代码,调用代理Executor(即SimpleExecutor类)的query方法。
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
// 调用下面的query重载方法
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// 获取缓存
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
随后Executor接口调用父类BaseExecutor的query方法来查询数据库,MapperStatement被当做参数传入query方法,这个query方法是执行器调用的,我们知道执行器的作用是SQL的生成执行和查询缓存等操作,在这个query方法中我们会查询缓存和执行SQL语句,我们进入query方法。
在第二个query重载方法一开始就声明了一个List集合,因为resultHandler参数传入的是null,所以会通过我们之前创建的缓存Key去本地缓存localCache中查询是否有缓存。如果list结果集不是null就处理一下缓存数据直接返回list结果集,如果没有缓存,他会从数据库中查询,看他这名字起的一看就知道是什么意思queryFromDatabase。
// BaseExecutor类
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 通过MappedStatement类中的SqlSource对象获取存有SQL语句以及参数的BoundSql对象
BoundSql boundSql = ms.getBoundSql(parameter);
// 获取缓存的Key值
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
// 先从缓存中获取数据并赋值给结果集
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 当缓存中没有结果时,就去数据库查询
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
假设目前是第一次查询还没有缓存,会进入queryFromDatabase方法。
最后进入到SimpleExecutor类的doQuery方法,doQuery方法一开始就通过Configuration对象创建了一个会话处理器StatementHandler,会话处理器我们上面的组件介绍提到过,作用是封装了JDBC Statement的操作并且负责对JDBC Statement的操作,如设置参数等。
// BaseExecutor类
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
// 缓存先占个位
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// 然后再调用子类中的doQuery查询方法
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
// 将查询结果存入缓存
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
到了这一步,我们终于接触到原生JDBC API了!
StatementHandler有三个常用子类:
那我们现在复习一下Jdbc操作数据库的步骤:
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();
// 通过Configuration对象创建了一个会话处理器StatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
// 其实本质还是调用的Statement或PreparedStatement的查询方法
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
我们进入prepareStatement方法中,可以看到全都是熟悉的JDBC步骤!下面我们逐步分析这三行代码。
// SimpleExecutor类
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 获取连接
Connection connection = getConnection(statementLog);
// 获取会话
stmt = handler.prepare(connection, transaction.getTimeout());
// 设置参数
handler.parameterize(stmt);
return stmt;
}
// 获取连接
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
一开始就是获取数据库连接,然后执行BaseStatementHandler类(PreparedStatementHandler父类)中的prepare方法,这个方法的作用就是根据Connection创建Statement(或PreparedStatement)对象,也就是上面JDBC操作中的第三步。
// BaseStatementHandler类(PreparedStatementHandler父类)
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
// 生成Statement对象
statement = instantiateStatement(connection);
setStatementTimeout(statement, transactionTimeout);
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
MyBatis默认StatementHandler接口实现类是PreparedStatementHandler,所以MyBatis默认就可以防止SQL注入攻击。我们进入PreparedStatement类的instantiateStatement方法,里面均是调用的原生JDBC方法。先获取到SQL语句,然后通过Connection对象调用prepareStatement方法来获取PreparedStatement对象。
// 实例化Statement接口
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
// 调用原生JDBC的prepareStatement方法生成PreparedStatement对象
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);
}
}
最后会调用PreparedStatementHandler类中的parameterize方法,它的作用就是为PreparedStatement对象设置参数。它又调用了DefaultparameterHandler类的setParameters方法,这里通过BoundSql对象获取所有的参数,得到一个ParameterMapping的集合,然后遍历该集合,获取每个参数的类型处理器(即TypeHandler),再根据参数类型的不同调用各自的设置方法,例如Integer类型参数的TypeHandler就是IntegerTypeHandler,因此就是调用原生JDBC的setInt方法来赋值。
// PreparedStatementHandler类
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
// DefaultparameterHandler类
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
// 通过BoundSql对象获取到所有参数
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
// 遍历所有参数来为PreparedStatemnet对象赋值
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) { // 当该参数为null时
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { // 当MyBatis含有该参数的类型处理器时
value = parameterObject;
} else { // 当MyBatis未含有该参数的类型处理器时
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
// 获取参数的类型处理器,不同的类型处理器对应了不同的参数设置方法
TypeHandler typeHandler = parameterMapping.getTypeHandler();
// 获取所指定的JdbcType
JdbcType jdbcType = parameterMapping.getJdbcType();
// 如果要设置null值但是JdbcType又未指定,则设置为默认的Other
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
// 开始为PreparedStatement对象设置参数
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);
}
}
}
}
}
注意一下,每个参数的JdbcType对象被传入了setParameter方法,我们进入到该方法。这里就能看见两个非常经典的异常信息,比如当我们为Oracle数据库设置null值但是又没指定JdbcType时就会抛出该异常。而且JdbcType这个参数只当参数为null时才有用,JdbcType如果没有指定,默认值是“Other”。
// BaseTypeHandler类
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.");
}
try {
ps.setNull(i, jdbcType.TYPE_CODE);
} catch (SQLException e) {
throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . "
+ "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. "
+ "Cause: " + e, e);
}
} else {
try {
// 调用具体实现类的方法
setNonNullParameter(ps, i, parameter, jdbcType);
} catch (Exception e) {
throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . "
+ "Try setting a different JdbcType for this parameter or a different configuration property. "
+ "Cause: " + e, e);
}
}
}
// IntegerTypeHandler类
public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)
throws SQLException {
// 可以看出如果是设置非空参数,根本没有用到JdbcType
ps.setInt(i, parameter);
}
到这里,我们已经获取了数据库连接,又获得了会话对象,参数也设置好了,然后就该执行该SQL了,我们再返回到开头的doQuery方法。
进入到PreparedStatementHandler类的query方法中,看到其实MyBatis还是通过原生JDBC Statement(或PreparedStatement、CalledStatement)来操作数据库的,这里的步骤就很熟悉了,execute方法执行PreparedStatement预编译的SQL语句。最后交由Resulthandler的实现类DefaultResultSetHandler来处理结果集,并返回一个存有结果集的List泛型集合。
// PreparedStatementHandler类
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 执行查询SQL语句
ps.execute();
// 处理返回的结果集
return resultSetHandler.handleResultSets(ps);
}
// DefaultResultSetHandler类
public 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);
}
这时候已经把查询出来的一条数据放在缓存中了,再次调用第二条查询语句的话,就不会操作数据库了,而是直接从缓存中拿这条数据。
至此,MyBatis执行一条SQL的大部分步骤已经全部讲解完毕!
关于其他添加、修改和删除的执行步骤都大同小异,这三种都是调用的SimpleExecutor类中的doUpdate方法(即都看作是对数据库的修改操作)。
public int delete(String statement, Object parameter) {
return update(statement, parameter);
}
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
标签:遍历 本地缓存 ppi rem news HERE protected red 回忆一下
原文地址:https://www.cnblogs.com/SunnyGao/p/14136767.html