MyBatis體系結構
MyBatis的工作流程
- 在MyBatis啓動的時候我們要去解析配置文件,包括全局配置文件和映射器配置文件,我們會把它們解析成一個Configuration對象,裏面會包含各種配置文件的參數信息
- 創建一個包含Configuration會話工廠SqlSessionFactory ,通過它來創建SqlSession對象,SqlSession是我們操作數據庫的接口,代表跟數據庫之間的一次連接
- 執行具體的SQL或接口方法 實際底層是通過SqlSession實現類裏的Executor封裝了對數據庫的操作
MyBatis的主要工作流程裏面,不同的功能是由很多不同的類協作完成的,可以看下Mybatis jar包的結構
大體可以把相關的類按照功能劃分爲如下幾個層次
MyBatis源碼解讀和工作原理
我們可以從MyBatis最基本的使用着手看源碼
@Test
public void Test() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//1.通過SqlSessionFactoryBuilder解析配置文件創建工廠類SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2.通過SqlSessionFactory創建SqlSession
SqlSession session = sqlSessionFactory.openSession();
try {
//3.獲得一個Mapper對象
BlogMapper mapper = session.getMapper(BlogMapper.class);
//4.執行對應的方法
Blog blog = mapper.selectBlogById(1);
System.out.println(blog);
} finally {
session.close();
}
}
配置解析
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
SqlSessionFactory var5;
try {
//MyBatis有各種各樣的XMLXXXBuilder,ConfigBuilder用來解析mybatis-config.xml,還有XMLMapperBuilder,XMLStatementBuilder等
//它們都繼承自抽象父類BaseBuilder
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
var5 = this.build(parser.parse());
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException var13) {
}
}
return var5;
}
public Configuration parse() {
//如果解析過配置文件就不會報錯,MyBatis的配置文件只在啓動的時候解析一次就夠了
if (this.parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
} else {
this.parsed = true;
//真正解析配置文件的方法,從根節點configuration開始解析mybatis-config.xml
this.parseConfiguration(this.parser.evalNode("/configuration"));
return this.configuration;
}
}
//解析mybatis-config.xml裏的各種標籤
private void parseConfiguration(XNode root) {
try {
this.propertiesElement(root.evalNode("properties"));
Properties settings = this.settingsAsProperties(root.evalNode("settings"));
this.loadCustomVfs(settings);
this.loadCustomLogImpl(settings);
this.typeAliasesElement(root.evalNode("typeAliases"));
this.pluginElement(root.evalNode("plugins"));
this.objectFactoryElement(root.evalNode("objectFactory"));
this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
this.settingsElement(settings);
this.environmentsElement(root.evalNode("environments"));
this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//解析TypeHandler,最後存儲在Map<Type, Map<JdbcType, TypeHandler<?>>> 的嵌套Map裏
this.typeHandlerElement(root.evalNode("typeHandlers"));
this.mapperElement(root.evalNode("mappers"));
} catch (Exception var3) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
}
}
private void propertiesElement(XNode context) throws Exception {
//解析Properties標籤的時候會獲取子標籤和屬性字段
if (context != null) {
Properties defaults = context.getChildrenAsProperties();
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
if (resource != null && url != null) {
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = this.configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
//把解析得到的Properties對象defaults賦值給XPathParser和configuration對象
this.parser.setVariables(defaults);
this.configuration.setVariables(defaults);
}
}
private void typeAliasesElement(XNode parent) {
if (parent != null) {
Iterator var2 = parent.getChildren().iterator();
while(var2.hasNext()) {
XNode child = (XNode)var2.next();
String alias;
if ("package".equals(child.getName())) {
alias = child.getStringAttribute("name");
this.configuration.getTypeAliasRegistry().registerAliases(alias);
} else {
alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
Class<?> clazz = Resources.classForName(type);
if (alias == null) {
this.typeAliasRegistry.registerAlias(clazz);
} else {
//會把配置的別名和類型註冊到typeAliasRegistry裏
this.typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException var7) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + var7, var7);
}
}
}
}
//解析對應的plugins標籤,保存到InterceptorChain的Interceptor集合裏
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
Iterator var2 = parent.getChildren().iterator();
while(var2.hasNext()) {
XNode child = (XNode)var2.next();
String interceptor = child.getStringAttribute("interceptor");
Properties properties = child.getChildrenAsProperties();
Interceptor interceptorInstance = (Interceptor)this.resolveClass(interceptor).newInstance();
interceptorInstance.setProperties(properties);
this.configuration.addInterceptor(interceptorInstance);
}
}
}
public void addInterceptor(Interceptor interceptor) {
this.interceptorChain.addInterceptor(interceptor);
}
//對於settings標籤的子標籤的處理
//二級標籤裏面有很多的配置,比如二級緩存,延遲加載等。之前提到的所有的默認值,都是在這裏賦值的
private void settingsElement(Properties props) {
this.configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
this.configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
this.configuration.setCacheEnabled(this.booleanValueOf(props.getProperty("cacheEnabled"), true));
this.configuration.setProxyFactory((ProxyFactory)this.createInstance(props.getProperty("proxyFactory")));
this.configuration.setLazyLoadingEnabled(this.booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
this.configuration.setAggressiveLazyLoading(this.booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
this.configuration.setMultipleResultSetsEnabled(this.booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
this.configuration.setUseColumnLabel(this.booleanValueOf(props.getProperty("useColumnLabel"), true));
this.configuration.setUseGeneratedKeys(this.booleanValueOf(props.getProperty("useGeneratedKeys"), false));
this.configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
this.configuration.setDefaultStatementTimeout(this.integerValueOf(props.getProperty("defaultStatementTimeout"), (Integer)null));
this.configuration.setDefaultFetchSize(this.integerValueOf(props.getProperty("defaultFetchSize"), (Integer)null));
this.configuration.setMapUnderscoreToCamelCase(this.booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
this.configuration.setSafeRowBoundsEnabled(this.booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
this.configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
this.configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
this.configuration.setLazyLoadTriggerMethods(this.stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
this.configuration.setSafeResultHandlerEnabled(this.booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
this.configuration.setDefaultScriptingLanguage(this.resolveClass(props.getProperty("defaultScriptingLanguage")));
this.configuration.setDefaultEnumTypeHandler(this.resolveClass(props.getProperty("defaultEnumTypeHandler")));
this.configuration.setCallSettersOnNulls(this.booleanValueOf(props.getProperty("callSettersOnNulls"), false));
this.configuration.setUseActualParamName(this.booleanValueOf(props.getProperty("useActualParamName"), true));
this.configuration.setReturnInstanceForEmptyRow(this.booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
this.configuration.setLogPrefix(props.getProperty("logPrefix"));
this.configuration.setConfigurationFactory(this.resolveClass(props.getProperty("configurationFactory")));
}
...對於其他的標籤就不一一解釋了
再分析下關於Mapper的解析
//如果Mapper引入的時候使用的Mapper的class,會調用addMapper註冊到MapperRegistry裏,保存在Map<Class<?>, MapperProxyFactory<?>>容器裏
//如果引入使用的mapper.xml,會通過XMLMapperBuilder來解析mapper配置文件
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
Iterator var2 = parent.getChildren().iterator();
while(true) {
while(var2.hasNext()) {
XNode child = (XNode)var2.next();
String resource;
//子節點有兩種情況,package:把包下的所有的mapper解析爲映射器,mapper:針對指定的mapper文件解析成映射器
if ("package".equals(child.getName())) {
resource = child.getStringAttribute("name");
this.configuration.addMappers(resource);
} else {
resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
//根據resource,url,class三個屬性的值選擇不同的解析方法
XMLMapperBuilder mapperParser;
InputStream inputStream;
if (resource != null && url == null && mapperClass == null) {
//通過XMLMapperBuilder來解析Mapper
ErrorContext.instance().resource(resource);
inputStream = Resources.getResourceAsStream(resource);
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
inputStream = Resources.getUrlAsStream(url);
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
mapperParser.parse();
} else {
if (resource != null || url != null || mapperClass == null) {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
Class<?> mapperInterface = Resources.classForName(mapperClass);
this.configuration.addMapper(mapperInterface);
}
}
}
return;
}
}
}
//XMLMapperBuilder的解析過程
public void parse() {
if (!this.configuration.isResourceLoaded(this.resource)) {
//解析mappr.xml裏的所有標籤,其中buildStatementFromContext()方法最終會將MappedStatement對象添加到configuration
this.configurationElement(this.parser.evalNode("/mapper"));
this.configuration.addLoadedResource(this.resource);
//通過namespace綁定Mapper類,如果是configurationElement裏沒有註冊到mapperRegistry的會調用Configuration的addMapper方法註冊進去
this.bindMapperForNamespace();
}
this.parsePendingResultMaps();
this.parsePendingCacheRefs();
this.parsePendingStatements();
}
如果<mappers>標籤裏引入的是mapper.xml,那麼會解析對應的xml文件,組裝成MappedStatement對象,然後添加到Configuration的 Map<String, MappedStatement> mappedStatements 裏
------------MapperRegistry 類
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (this.hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
//把Mapper的class和對應的MapperProxyFactory添加到knownMappers map裏
this.knownMappers.put(type, new MapperProxyFactory(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
//通過MapperAnnotationBuilder解析Mapper
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
this.knownMappers.remove(type);
}
}
}
}
----- MapperAnnotationBuilder 的parse()方法
public void parse() {
String resource = this.type.toString();
if (!this.configuration.isResourceLoaded(resource)) {
this.loadXmlResource();
this.configuration.addLoadedResource(resource);
this.assistant.setCurrentNamespace(this.type.getName());
//對@CacheNamespace 和@CacheNamespaceRef註解進行處理
this.parseCache();
this.parseCacheRef();
Method[] methods = this.type.getMethods();
Method[] var3 = methods;
int var4 = methods.length;
for(int var5 = 0; var5 < var4; ++var5) {
Method method = var3[var5];
try {
if (!method.isBridge()) {
//遍歷Mapper的所有方法,解析得到MappedStatement添加到configuration裏,以namespace + statement id爲key,MappedStatement爲value
this.parseStatement(method);
}
} catch (IncompleteElementException var8) {
this.configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
this.parsePendingMethods();
}
在addMapper裏,會創建一個MapperAnnotationBuilder對象來對註解進行處理
parseCache() 和 parseCacheRef() 方 法 其 實 是 對 @CacheNamespace 和@CacheNamespaceRef這兩個註解的處理。
parseStatement()方法裏也都是對註解的解析,比如@Options,@SelectKey,@ResultMap等。
最後同樣會解析成 MappedStatement 對象
解析完成後的build方法會使用Configuration創建一個DefaultSqlSessionFactory對象
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
SqlSessionFactory var5;
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
var5 = this.build(parser.parse());
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException var13) {
}
}
return var5;
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
會話創建過程
//這裏創建DefaultSqlSession會先通過configuration裏的environment創建一個事務工廠
//再通過事務和定義的執行器類型創建對應的Executor,這是真正執行SQL的核心類
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
DefaultSqlSession var8;
try {
Environment environment = this.configuration.getEnvironment();
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
Executor executor = this.configuration.newExecutor(tx, execType);
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception var12) {
this.closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
} finally {
ErrorContext.instance().reset();
}
return var8;
}
//根據不同的executorType創建對應的Executor,默認是Simple
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? this.defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Object executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
//如果配置了開啓二級緩存(默認cacheEnabled = true),會使用CachingExecutor裝飾executor---裝飾器模式
if (this.cacheEnabled) {
executor = new CachingExecutor((Executor)executor);
}
//這裏通過配置的plugin插件對executor進行處理包裝 具體的原理後面說明
Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
return executor;
}
三種不同的Executor:
SimpleExecutor:每執行一次update或select,就開啓一個Statement對象,用完立刻關閉Statement對象。
ReuseExecutor:會重複使用Statement對象。使用完之後,不關閉 Statement 對象,而是存儲在 Map 內,供下一次使用
BatchExecutor:支持批處理的Executor 。
獲得Mapper對象
我們通過SqlSession調用getMapper接口最終會通過mapperRegistry根據type和當前會話獲取Mapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
-------MapperProxyFactory
protected T newInstance(MapperProxy<T> mapperProxy) {
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
從上面的源碼不難看出,我們獲取到的Mapper實際是根據解析配置時註冊的MapperProxyFactory通過動態代理生成的代理對象,這樣就解釋了爲什麼我們可以不需要創建Mapper的實現類就可以直接調用方法,因爲MyBatis幫我們返回了代理對象。而我們使用Mapper只是爲了根據接口類型+方法的名稱,找到對應namespace下的Statement ID,所以不需要實現類,在 MapperProxy 裏面直接執行SQL就可以。
執行SQL
由於所有的 Mapper 都是使用 MapperProxy 增強的代理對象,所以執行任何方法其實真實執行的都是MapperProxy的invoke()方
法。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//Object本身的方法和Java 8中接口的默認方法不需要去執行SQL,直接執行原來的方法即可
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
//判斷是否是默認方法(Java 8 接口中新增了默認方法)
if (this.isDefaultMethod(method)) {
return this.invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
//獲取方法緩存,這是爲了提升效率,MyBatis一旦啓動,方法對應的SQL也就確定了
MapperMethod mapperMethod = this.cachedMapperMethod(method);
//mapperMethod的execute是真正執行SQL的方法
return mapperMethod.execute(this.sqlSession, args);
}
------MapperMethod 對於不同類型的SQL執行不同的方法
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
Object param;
switch(this.command.getType()) {
case INSERT:
//將方法的參數轉化成SQL的參數
param = this.method.convertArgsToSqlCommandParam(args);
//調用sqlSession的方法來執行SQL
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
break;
case UPDATE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
break;
case DELETE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
break;
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;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + this.command.getName());
}
if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
} else {
return result;
}
}
以select舉例來說,之後都會執行到DefaultSqlSession的selectList方法
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
List var5;
try {
//先通過對應的Statement ID 即command name從configuration中獲得MappedStatement
//MappedStatement包含SQL上所有的參數id,resultMap,parameterMap等
MappedStatement ms = this.configuration.getMappedStatement(statement);
//使用sqlSession的executor執行query方法
var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception var9) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + var9, var9);
} finally {
ErrorContext.instance().reset();
}
return var5;
}
執行query之後會先調用BaseExecutor的query()方法,最後會執行具體的執行器的doQuery方法(模板模式)
創建StatementHandler
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
List var9;
try {
Configuration configuration = ms.getConfiguration();
//先創建對應的StatementHandler
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//通過handler創建對應的Statement
stmt = this.prepareStatement(handler, ms.getStatementLog());
var9 = handler.query(stmt, resultHandler);
} finally {
this.closeStatement(stmt);
}
return var9;
}
----------RoutingStatementHandler
在創建StatementHandler的時候會根據 MappedStatement 裏面的 statementType 決定
StatementHandler的類型默認是PREPARED,包含ParameterHandler和ResultHandler,這裏是在BaseStatementHandler(StatementHandler實現類的抽象父類)初始化的時候創建的
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch(ms.getStatementType()) {
case STATEMENT:
this.delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
this.delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
this.delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
通過StatementHandler創建Statement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Connection connection = this.getConnection(statementLog);
Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
//對statement進行預編譯,處理參數
handler.parameterize(stmt);
return stmt;
}
public void parameterize(Statement statement) throws SQLException {
this.parameterHandler.setParameters((PreparedStatement)statement);
}
最後一步:執行SQL
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement)statement;
ps.execute();
return this.resultSetHandler.handleResultSets(ps);
}
畫了下時序圖,這裏的SQL執行畫的是update: