一、executor执行前参数处理
Blog blog = mapper.selectBlog(101);
以上述为示例,开始执行select方法,源码如下:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
//根据mapper解析的基础信息,确定sql执行类型 switch (command.getType()) {
case INSERT: { //插入sql类型 Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: { //修改sql类型
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: { //删除sql类型 Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT: //查询sql类型 if (method.returnsVoid() && method.hasResultHandler()) { //如果方法没有返回值并存在返回解析器 executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) { //方法返回值是集合 result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) { //方法返回值是map类型 result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) { //方法返回值是cursor,查了下好像是awt相关的 我就忽略了 result = executeForCursor(sqlSession, args);
} else { //如果都不是,则执行查询单条sql Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH: //预处理sql类型 result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type
}
return result;
}
(1)selectOne
首先执行method.convertArgsToSqlCommandParam获取传参。
public Object getNamedParams(Object[] args) {
//... //根据上述例子,参数只有一个 因此,直接返回的是传参 else if (!hasParamAnnotation && paramCount == 1) { return args[names.firstKey()];
//...
}
开始进行selectOne操作。
result = sqlSession.selectOne(command.getName(), param);
@Override
public <T> T selectOne(String statement, Object parameter) {
//底层调用的是selectList方法
List<T> list = this.<T>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;
}
}
在selectOne方法中,这里的statement参数其实为sql标识即mapper路径名+方法名,因此在mapper方法中是不允许重载的,param参数则为传参101。另外selectOne的方法的执行实际上底层是调用了selectList方法。
(2)selectList
List<Blog> list = mapper.selectList(101,"name");
上述执行的含义为查询type为101,name为name的多条数据。在mapper接口方法中需要使用@Param指定参数进行解析。
List<Blog> selectList(@Param("type") Integer type, @Param("name") String name);
开始执行selectList,首先会根据加载后的returnManys判断返回结果是集合类型,则指向executeForMany。然后执行method.convertArgsToSqlCommandParam获取传参,这里观察下多参数的情况与单个参数参数结果有何不同。
public Object getNamedParams(Object[] args) {
//...
final Map<String, Object> param = new ParamMap<Object>();
int i = 0; //对sortedMap遍历 for (Map.Entry<Integer, String> entry : names.entrySet()) { //放入到map中 key-参数名 如type,value-值,如101 param.put(entry.getValue(), args[entry.getKey()]);
final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
//将自定义生成的paramName放入到map中 key-param+遍历次数 如params1, //value-值,如101 if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
selectList的方法参数首先已经在加载时被解析成了sortedMap结构{0-type,1-name}。然后会对该sortedMap进行遍历,将参数名作为key,值作为value放到map中,并且也会往map中放入自定义生成的参数名作为key-即param+遍历次数,value为值。
将请求参数解析成map后返回,最后返回的结构为{type-101,param1-101,name-name,param2-name}。
开始执行selectList方法。
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try { //根据sql标识,去取mapper映射信息 MappedStatement ms = configuration.getMappedStatement(statement);
//执行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.query方法之前,会从configuration的mappedStatementsMap中根据sql标识即mapper路径名+方法取出mapper映射信息,MappedStatement中主要存放的是mapper.xml的全路径信息、jdbc资源信息、resultMap信息、sql执行类型(SELECT)、statementType(PREPARED)等基础信息。
(3)insert
首先也会执行method.convertArgsToSqlCommandParam获取传参。然后开始执行insert,底层实际上是调用的update方法。
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}
(4)delete
和insert一样,首先也会执行method.convertArgsToSqlCommandParam获取传参。然后开始执行delete,底层实际上是调用的也是update方法。
public int delete(String statement, Object parameter) {
return update(statement, parameter);
}
(5)update
@Override
public int update(String statement, Object parameter) {
try {
dirty = true; //根据sql标识从map中获取Mapped映射 MappedStatement ms = configuration.getMappedStatement(statement); //通过executor执行。 return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
二、executor.query
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//获取boundSql BoundSql boundSql = ms.getBoundSql(parameter);
//获取一级缓存的key CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
//执行query return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
第一步,从mapped映射器中获取boundSql信息,里面存放的是被替换为占位符的sql语句、参数映射、参数map等与sql相关的基础信息。这里的sql由#{type}、#{name}被替换为?,也是发生在mybatis加载的时候(org.apache.ibatis.builder.SqlSourceBuilder#parse)。
第二步,获取一级缓存的key = sql标识id+sql语句+参数类型+参数值+额外参数。一级缓存是默认开启的。
第三步,开始执行query。
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {//mapper映射中是否存在缓存
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, parameterObject, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}//查询数据库return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
先查询mapper映射中是否存在缓存,如果存在缓存,则直接取缓存中的数据,否则查询数据库。
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
//...
List<E> list;
try { //在一个事物中或一个sqlSession中,sql执行次数+1 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 { //sql执行完毕,sql执行次数-1
queryStack--;
} //事物中或者sqlSession的sql全部执行完毕需要清除一级缓存
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache();
}
}
return list;
}private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list; //先根据key存放一级缓存占位符 localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
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;
}
一级缓存默认开启,并且生命周期只存在于一个sqlSession中,sqlSession执行完毕,一级缓存也就被清空。
· doQuery核心方法
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try { //获取configuration Configuration configuration = ms.getConfiguration();
//封装Handler执行器 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//准备statement执行器 stmt = prepareStatement(handler, ms.getStatementLog());
//执行query方法 return handler.<E>query(stmt, resultHandler);
} finally { //关闭资源 closeStatement(stmt);
}
}
这一步主要是在建立数据连接、准备PreparedStatement,然后进行数据库操作。最后关闭连接资源。
三、executor.update
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
//清除缓存 flushCacheIfRequired(ms);
return delegate.update(ms, parameterObject);
}
public int update(MappedStatement ms, Object parameter) throws SQLException {
//清除一级缓存
clearLocalCache();
return doUpdate(ms, parameter);
}
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
//执行update方法 return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
过程大致与query的相同,不过在操作之前会先清空当前sqlSession所有的一级缓存,防止出现脏读情况。然后开始建立连接、准备PreparedStatement,进行数据库操作。最后关闭资源。
四、总结
execute中只存在query和update,并且我们定义的selectOne和selectLsit都是调用execute的query,而insert、update、delete都是调用的update。即query代表对数据库的查询操作,而insert、update、delete代表对数据库的修改操作,因此都调用了update。
在源码中我们也看到了一级缓存的应用,一级缓存只会在相同sqlSession作用域情况才会被使用到,并且key为sql标识id+sql语句+参数类型+参数值+额外参数,一级缓存在我们的mybaits中也是默认开启的。为了不出现脏读的情况,在相同sqlSession作用域中如果出现了update操作则会清空一级缓存。
问题一:一级缓存、二级缓存的理解和区别。
首先一级缓存的作用域是一个SqlSession范围内的。只有当这个sqlSesion执行两次相同的sql时才会命中缓存。从缓存中获取数据,不再去数据库查询,提高查询效率。并且当sqlSession执行完毕或者出现了update操作,则会清空一级缓存避免出现脏读现象。一级缓存默认开启。
二级缓存的作用域比一级缓存大,是mapper级别的(即nameSpace),因此不同的sqlSession执行相同的sql语句时可以命中缓存,不再去数据库查询。二级缓存需要手动开启。但是二级缓存在多表查询情况下不推荐使用,虽然二级缓存在单表进行update操作时会清空二级缓存,但是在连表的情况下,如:A.xml,b.xml,A.xml中存在连b表的查询,当b中进行了update操作时,只会把b.xml中nameSpace关联的二级缓存清空,其他nameSpace并不会清空,在这样的情况下,会导致其他表的数据读取充满着风险。在最新的mybatis也已经取消掉了二级缓存。
问题二:PreparedStatement和Statement的区别
PreparedStatement继承自Statement。但是PreparedStatement会在启动时会将占位符#{}替换为?,采用预编译的方式来处理sql和替换参数,比Statement拥有着更好的性能。并且有了预编译机制,sql编写的扩展性也提高了很多,可以写很多动态性的sql。
而Statement不推荐使用的原因在于,没有预编译机制可能会导致sql注入攻击,比如select * from user where name = +(),这个sql如果传入了“test” or 1=1 ======>(select * from user where name = ‘test’ or ‘1’ = ‘1’),导致sql注入。