Mybatis解析
select * from user where name =? and pwd=?
-
statement:select * from user where name = and pwd=(采用直接拼接的方式,有sql注入的风险)
-
preparestatement: **~.setString(1,‘name’) ~.setString(2,‘pwd’)** select * from user where name =‘ n a m e ’ a n d p w d = ′ ‘ {name}’ and pwd='‘ name’andpwd=′‘{pwd}’' (采用String拼接的方式,没有sql注入的风险)
Mybatis的执行流程图
mybatis的四大对象:
流程如下:
Exeutor
发起sql执行任务
1、先调用statementHandler
中的prepare()
进行SQL的编译
2、然后调用statementHandler
中的parameterize()
设置参数
2.1、这里其实真正设置参数的是`ParameterHandler`中的`setparameters()`方法,该方法与`typeHandler`进行参数类型的转换
3、然后执行query/update方法,这里使用ResultSetHandler
进行结果的组装工作
3.1、这里`ResultSetHandler`又与`typeHandler`、`ObjectFactory`配合工作共同完成结果的组装工作
每一条sql语句都会绑定对应的对象:
MappedStatement:sql的ID、缓存信息、resultType、ParameterType、resultMap等信息
(mapper.xml文件就是被解析成这个对象);
Sqlsource:是MappedStatement的一个属性,是一个接口,主要提供BoundSql;
BoudSql:是建立SQL和参数的地方,有三个主要属性,ParameterMappings、ParameterObject和sql,这个对象比较重要,我们通常使用插件(实现接口Interceptor
+注解@Intercepts+mybatiscong.xml的plugin标签配置)就是对它进行拦截;
解析config.xml、mapper.xml:
@Before
public static void initFactory() {
try {
SqlSession session = null;
String resource = "configuration.xml";
// 使用io流读取配置
InputStream inputStream;
inputStream = Resources.getResourceAsStream(resource);
//这里是解析配置文件
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 得到了一个会话,有了这个会话,你就可以对数据进行增,删,改,查的操作
session = sqlSessionFactory.openSession();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
=======》new SqlSessionFactoryBuilder().build(inputStream);
return build(inputStream, null, null);
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));//解析节点
//全局的配置文件就会被解析成一个org.apache.ibatis.session.Configuration
return configuration;
====parser.parse()调用========》parseConfiguration(parser.evalNode("/configuration"));//解析节点
private void parseConfiguration(XNode root) {
try {
//标签配置顺序:
//properties,settings,typeAliases,typeHandlers,objectFactory,objectWrapperFactory, //plugins,environments,databaseIdProvider,mappers
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));//解析mapper节点
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
/**
*mapper接口的方法同时写xml配置和注解配置的话会报错
*xml配置和注解配置都会被mybatis翻译成MappedStatement对象(两个mappedStatement的id相同(就是方法名),用的缓存容器是Hashmap(继承了hashmap的一个StateMap,重写了put())所以会报错)
*<mappers>
//如何只找到mapper文件的(mapper接口+*mapper.xml)
<package name="mapper" />
<!-- <mapper class="" resource="" url=""/> -->
</mappers>
*/
=============》 mapperElement(root.evalNode("mappers"));//解析mapper节点
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//解析mapper/package节点
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);//各种各样的mapper文件
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
//解析你写的<mapper>注解的这三个属性(看源码可以看出这三个属性你只能配置一个属性)
/**
*resource/url是资源定位符(就是路径)
* mapperClass你直接指定了一个接口
*/
if (resource != null && url == null && mapperClass == null) {
//配置了resource
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
//配置了url
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);//拿到mapper.xml文件的输入流
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();//解析
} else if (resource == null && url == null && mapperClass != null) {
//配置了mapperClass(直接拿接口了)
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
===============》configuration.addMappers(mapperPackage);//各种各样的mapper文件
public void addMappers(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);//干活的地方
}
}
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {//拿接口
if (hasMapper(type)) {//验证你是不是有一个加载的mapper接口了
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
//解析接口(注解还是xml写的sql语句)
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
============》parser.parse();//解析接口的注解
public void parse() {
String resource = type.toString();//先拿接口的名字<mapper class="" url="" resource=""/>
if (!configuration.isResourceLoaded(resource)) {
loadXmlResource();//加载xml文件
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();//解析注解
}
==============》//parsePendingMethods();//解析注解
private void parsePendingMethods() {
Collection<MethodResolver> incompleteMethods = configuration.getIncompleteMethods();
synchronized (incompleteMethods) {
Iterator<MethodResolver> iter = incompleteMethods.iterator();
while (iter.hasNext()) {
try {
iter.next().resolve();
iter.remove();
} catch (IncompleteElementException e) {
// This method is still missing a resource
}
}
}
}
========================》mapperx.xml文件的解析
public void parse() {
if (!configuration.isResourceLoaded(resource)) {//判断是否解析过你这个文件了
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
==========================》解析mapper的里面的节点,拿到里面的配置项,最终封装成一个MapperedStatement对象
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));//解析你写的sql语句了,解析到一个list容器,
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
===========================》//buildStatementFromContext(context.evalNodes("select|insert|update|delete"));//解析你写的sql语句了,解析到一个list容器,
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());//那你配置的那个指定的databaseid
}
buildStatementFromContext(list, null);//开始循环遍历
}
===========================》//buildStatementFromContext(list, null);//开始循环遍历
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
//list表示Usermapper.xml的select|insert|update|delete节点
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
//具体解析
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
//你写的sql语句有问题
configuration.addIncompleteStatement(statementParser);
}
}
}
================================》//这个方法解析select|~|~|~节点的
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
//刷新缓存:一级缓存二级缓存
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
//解析sql 根据sql文本来判断是否需要动态解析(就是看你写了动态sql标签没<where>...) 如果没有动态sql语句且只有#{}表达式时 直接使用?静态解析占位 当有${}时不进行解析
/**
科普:<select lang="就是自己写的一个语言解析器"></select>
mybatis支持自己写自定义的标签,但是在解析标签时你必须指定语言相对应的语言解析器
*/
/**
${}---->动态的sql语句(dynamicSqlSource)---解析配置文件的时候不会动这个sql语句,执行时才会去除${}符号(sql注入问题风险)
#{}---->静态的sql语句(staticSqlSource)---解析的时候会把他改成?占位符(会对自动传入的数据加一个""双引号),mybatis官网有解释
模糊查询的sql写法:1.自己拼接%% 2.'%${arg0}%' 3.使用mysql自己的函数concat==》concat('%',#{arg0},'%')
*/
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String resultType = context.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
String resultMap = context.getStringAttribute("resultMap");
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
//构造一个MappedStatement
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
======================》//构造一个MappedStatement builderAssistant.addMappedStatement
public MappedStatement addMappedStatement(
String id,
SqlSource sqlSource,
StatementType statementType,
SqlCommandType sqlCommandType,
Integer fetchSize,
Integer timeout,
String parameterMap,
Class<?> parameterType,
String resultMap,
Class<?> resultType,
ResultSetType resultSetType,
boolean flushCache,
boolean useCache,
boolean resultOrdered,
KeyGenerator keyGenerator,
String keyProperty,
String keyColumn,
String databaseId,
LanguageDriver lang,
String resultSets) {
if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
//全是你设置的配置项
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);//将构建好的mapperedstatement文件添加到一个全局的配置文件中
return statement;
}
解析sqlsessionFactory:
session = sqlSessionFactory.openSession();
==========》//默认实现类org.apache.ibatis.session.defaults.DefaultSqlSessionFactory 然后调用openSessionFromDataSource
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
//通过解析到的configuration来获得environment标签
final Environment environment = configuration.getEnvironment();
//事物
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
mapper代理对象的执行流程:
CustomerMapper mapper = session.getMapper(CustomerMapper.class);该方法开始
科普1:接口参数不合法时(即mapper接口方法的的参数名与mapper.xml参数名不一致时)解决方法:
#{arg0}…#{argn}、#{param1}…#{paramn}、接口方法参数打@Param(“参数名”)注解
为什么这么设计(为什么有arg0):
JDK8之前,你拿参数名是拿不到的拿到的字符串是arg0;
JDK8之后,你映射参数时才能使用具体的方法名
为什么这么设计(为什么有param1):
mybatis框架设计时,需要思考我拿到用户参数时,写什么参数名,为了你一定有一个参数可以使用,param1就产生了
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);//拿到代理的实例
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
===============》//return mapperProxyFactory.newInstance(sqlSession);//拿到代理的实例
//MapperProxy实现了 InvocationHandler接口
//invoke方法就是sql具体的执行流程了
//我这是mybatis3.5.6的
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
}
}
===============》MapperMethed
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
//这行代码非常重要
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
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 (" + method.getReturnType() + ").");
}
return result;
}
============》//method.convertArgsToSqlCommandParam(args);单一非特殊参数返回没有名字。 多个参数用的命名规则命名(就是这里提供了param1这种参数写法)主要针对sql语句的参数映射
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
Object value = args[names.firstKey()];
return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
} else {
final Map<String, Object> param = new ParamMap<>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
// ensure not to overwrite parameter named with @Param
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
============》//select查询具体调用方法流程
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.selectList(statement, parameter);//查询都是调用的selectList
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;
}
}
===============》//调用selectList
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
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();
}
}
==========》//开始查询
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//sql语句就包装在这里面(走完这行代码:静态的sql语句没有赋值但是#{}改成了?,动态的sql语句已经赋值了)
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
=========》query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
@Override
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;
}
===========》//去数据库查询
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 {
//根据你指定的执行器去调用不同的方法(默认是simpleExecutor)
/**
三大执行器:
SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。
ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map<String, Statement>内,供下一次使用。简言之,就是重复使用Statement对象。
BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。(使用时出现一种情况:我无法在他这个处理器的缓存中拿到我希望得到的id)
四大处理器:
StatementHandler:处理对象PrepareStatement
ParameterHandler:处理参数
ResultSetHandler:处理结果集
TypeHandler:类型转换
*/
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;
}
================》//simpleExecutor的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 = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//这个就是JDBC使用preparestatement去查询数据了
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
===========》//jdbc
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
//这里实现~.setString的()设置参数(调用的是RoutingStatementHandle)
handler.parameterize(stmt);
return stmt;
}
==========》//调用prepareStatement的parameterize方法
@Override
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
==========》//调用defaultpreparestatementHandler的setParameters((PreparedStatement) statement);参数都存在parameterObject这个对象里(给问号赋值)----走完这个方法就是preparestatement已经完成赋值操作了
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
//迭代参数对比参数名字和参数类型和参数顺序分别赋值
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)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
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);
}
}
}
}
}
mybatis的逻辑分页
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds)//这个就是加入了分页
=====================》//分页类
public class RowBounds {
public static final int NO_ROW_OFFSET = 0;
public static final int NO_ROW_LIMIT = Integer.MAX_VALUE;
public static final RowBounds DEFAULT = new RowBounds();
private final int offset;
private final int limit;
public RowBounds() {
this.offset = NO_ROW_OFFSET;
this.limit = NO_ROW_LIMIT;
}
public RowBounds(int offset, int limit) {
this.offset = offset;
this.limit = limit;
}
public int getOffset() {
return offset;
}
public int getLimit() {
return limit;
}
}
在DefaultResultSetHandler中,逻辑分页会将所有的结果都查询到,然后根据RowBounds中提供的offset和limit值来获取最后的结果,DefaultResultSetHandler实现如下:
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
//跳过RowBounds设置的offset值
skipRows(rsw.getResultSet(), rowBounds);
//判断数据是否小于limit,如果小于limit的话就不断的循环取值
while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
Object rowValue = getRowValue(rsw, discriminatedResultMap);
storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
}
}
private boolean shouldProcessMoreRows(ResultContext<?> context, RowBounds rowBounds) throws SQLException {
//判断数据是否小于limit,小于返回true
return !context.isStopped() && context.getResultCount() < rowBounds.getLimit();
}
//跳过不需要的行,应该就是rowbounds设置的limit和offset
private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {
if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) {
rs.absolute(rowBounds.getOffset());
}
} else {
//跳过RowBounds中设置的offset条数据
for (int i = 0; i < rowBounds.getOffset(); i++) {
rs.next();
}
}
}
JDBC的桥接模式
通过反射com.mysql.jdbc.Driver类,实例化该类时,会调用该类的静态代码块,该代码块会去DriverManager类中注册自己,DriverManager管理所有已注册的驱动类,当调用DriverManager.getConnection()方法时会遍历所有驱动类,并尝试连接数据库,只要有一个连接成功,就返回Connection对象,否则报异常.