之前在spring mvc + mybatis項(xiàng)目中對(duì)mybatis的使用有了一定的掌握,但對(duì)于其內(nèi)部的具體實(shí)現(xiàn)并不了解季二,因此在此開(kāi)啟對(duì)于mybatis更加深入的學(xué)習(xí)檩咱。
一、介紹
定義
MyBatis 是一個(gè)可以自定義SQL胯舷、存儲(chǔ)過(guò)程和高級(jí)映射的持久層框架刻蚯。MyBatis 摒除了大部分的JDBC代碼、手工設(shè)置參數(shù)和結(jié)果集重獲桑嘶。
MyBatis 只使用簡(jiǎn)單的XML 和注解來(lái)配置和映射基本數(shù)據(jù)類(lèi)型炊汹、Map 接口和POJO 到數(shù)據(jù)庫(kù)記錄。相對(duì)Hibernate和Apache OJB等“一站式”O(jiān)RM解決方案而言逃顶,Mybatis 是一種“半自動(dòng)化”的ORM實(shí)現(xiàn)讨便。
核心組件
主要包括:
SqlSessionFactoryBuilder:會(huì)根據(jù)配置信息或代碼來(lái)生成SqlSessionFactory充甚;
SqlSessionFactory:依靠工廠來(lái)生成SqlSession;
SqlSession:是一個(gè)既可以發(fā)送SQL去執(zhí)行并返回結(jié)果器钟,也可以獲取Mapper的接口津坑;
SQL Mapper:是MyBatis新設(shè)計(jì)的組件,由一個(gè)Java接口和XML文件構(gòu)成傲霸,需要給出對(duì)應(yīng)的SQL和映射規(guī)則疆瑰。它負(fù)責(zé)發(fā)送SQL去執(zhí)行,并返回結(jié)果昙啄。
二穆役、架構(gòu)
API接口層:提供給外部使用的接口API,開(kāi)發(fā)人員通過(guò)這些本地API來(lái)操縱數(shù)據(jù)庫(kù)梳凛。接口層一接收到調(diào)用請(qǐng)求就會(huì)調(diào)用數(shù)據(jù)處理層來(lái)完成具體的數(shù)據(jù)處理耿币。
數(shù)據(jù)處理層:負(fù)責(zé)具體的SQL查找、SQL解析韧拒、SQL執(zhí)行和執(zhí)行結(jié)果映射處理等淹接。它主要的目的是根據(jù)調(diào)用的請(qǐng)求完成一次數(shù)據(jù)庫(kù)操作。
框架支撐層:負(fù)責(zé)最基礎(chǔ)的功能支撐叛溢,包括連接管理塑悼、事務(wù)管理、配置加載和緩存處理楷掉,這些都是共用的東西厢蒜,將他們抽取出來(lái)作為最基礎(chǔ)的組件。為上層的數(shù)據(jù)處理層提供最基礎(chǔ)的支撐烹植。
引導(dǎo)層:配置和啟動(dòng)MyBatis配置信息的方法斑鸦。
數(shù)據(jù)處理流程
數(shù)據(jù)處理過(guò)程:
1.根據(jù)SQL的ID查找相應(yīng)的MappedStatement對(duì)象。
2.根據(jù)傳入?yún)?shù)對(duì)象解析MappedStatement對(duì)象草雕,得到最終要執(zhí)行的SQL和執(zhí)行傳入?yún)?shù)巷屿。
3.獲取數(shù)據(jù)庫(kù)連接,根據(jù)得到的最終SQL語(yǔ)句和執(zhí)行傳入?yún)?shù)到數(shù)據(jù)庫(kù)執(zhí)行墩虹,并得到執(zhí)行結(jié)果嘱巾。
4.根據(jù)MappedStatement對(duì)象中的結(jié)果映射對(duì)得到的執(zhí)行結(jié)果進(jìn)行轉(zhuǎn)換處理,并得到最終的處理結(jié)果败晴。
5.釋放連接資源。
三栽渴、源碼剖析
1.Mybatis Demo
以我mybatis入門(mén)的demo為例:
private static SqlSessionFactoryBuilder sqlSessionFactoryBuilder;
private static SqlSessionFactory sqlSessionFactory;
private static void init() throws IOException {
String resource = "mybatis-config.xml";
Reader reader = Resources.getResourceAsReader(resource);
sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
sqlSessionFactory = sqlSessionFactoryBuilder.build(reader);
}
應(yīng)用程序的入口是SqlSessionFactoryBuilder尖坤,作用是通過(guò)XML配置文件創(chuàng)建Configuration對(duì)象,然后通過(guò)build方法創(chuàng)建SqlSessionFactory對(duì)象闲擦。
注:
沒(méi)有必要每次訪問(wèn)Mybatis就創(chuàng)建一次SqlSessionFactoryBuilder慢味,通常的做法是創(chuàng)建一個(gè)全局的對(duì)象
2. 入口類(lèi)SqlSessionFactoryBuilder
public class SqlSessionFactoryBuilder {
//Reader讀取mybatis配置文件场梆,傳入構(gòu)造方法
public SqlSessionFactory build(Reader reader) {
return build(reader, null, null);
}
public SqlSessionFactory build(Reader reader, String environment) {
return build(reader, environment, null);
}
public SqlSessionFactory build(Reader reader, Properties properties) {
return build(reader, null, properties);
}
//通過(guò)XMLConfigBuilder解析mybatis配置,從而創(chuàng)建SqlSessionFactory對(duì)象
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
//構(gòu)建的核心方法
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
}
可以看到構(gòu)建的核心是這一行:
return build(parser.parse());
parser的類(lèi)是XMLConfigBuilder纯路,XMLConfigBuilder 部分源碼如下:
/**
* mybatis 配置文件解析
*/
public class XMLConfigBuilder extends BaseBuilder {
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
//外部調(diào)用此方法對(duì)mybatis配置文件進(jìn)行解析
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//從根節(jié)點(diǎn)configuration
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
/**此方法解析configuration節(jié)點(diǎn)下的子節(jié)點(diǎn)
*在configuration下面能配置的節(jié)點(diǎn)為以下10個(gè)節(jié)點(diǎn)
*/
private void parseConfiguration(XNode root) {
try {
propertiesElement(root.evalNode("properties"));
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
settingsElement(root.evalNode("settings"));
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
}
跟進(jìn)中...
三或油、SQL執(zhí)行流程源碼剖析
我們都是通過(guò)SqlSession去執(zhí)行sql語(yǔ)句,Sqlsession對(duì)應(yīng)著一次數(shù)據(jù)庫(kù)會(huì)話驰唬。由于數(shù)據(jù)庫(kù)會(huì)話不是永久的顶岸,因此Sqlsession的生命周期也不應(yīng)該是永久的,相反叫编,在你每次訪問(wèn)數(shù)據(jù)庫(kù)時(shí)都需要?jiǎng)?chuàng)建辖佣。
獲取SqlSession的步驟:
- 首先,SqlSessionFactoryBuilder去讀取mybatis的配置文件搓逾;
- 然后構(gòu)建一個(gè)DefaultSqlSessionFactory卷谈。
源碼如下:
/**
* 一系列的構(gòu)造方法最終都會(huì)調(diào)用此構(gòu)建方法
*/
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
/**通過(guò)XMLConfigBuilder解析配置文件,
*解析的配置相關(guān)信息都會(huì)被封裝為一個(gè)Configuration對(duì)象
*/
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
//創(chuàng)建DefaultSessionFactory對(duì)象
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
在獲取到SqlSessionFactory之后霞篡,就可以通過(guò)SqlSessionFactory去獲取SqlSession對(duì)象:
/**
* 通常一系列openSession方法最終都會(huì)調(diào)用此方法
*/
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
/**通過(guò)Confuguration對(duì)象去獲取Mybatis相關(guān)配置信息,
*Environment對(duì)象包含了數(shù)據(jù)源和事務(wù)的配置
*/
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//通過(guò)excutor真正執(zhí)行sql世蔗, excutor是對(duì)于Statement的封裝
final Executor executor = configuration.newExecutor(tx, execType);
//創(chuàng)建了一個(gè)DefaultSqlSession對(duì)象
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();
}
}
而方法openSessionFromDataSource才是實(shí)際創(chuàng)建SqlSession的地方:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Connection connection = null;
try {
final Environment environment = configuration.getEnvironment();
final DataSource dataSource = getDataSourceFromEnvironment(environment);
/**MyBatis對(duì)事務(wù)的處理相對(duì)簡(jiǎn)單,TransactionIsolationLevel中定義了幾種隔離級(jí)別朗兵,
*并不支持內(nèi)嵌事務(wù)這樣較復(fù)雜的場(chǎng)景污淋,同時(shí)由于其是持久層的緣故,
*所以真正在應(yīng)用開(kāi)發(fā)中會(huì)委托Spring來(lái)處理事務(wù)實(shí)現(xiàn)真正的與開(kāi)發(fā)者隔離矛市。
*分析事務(wù)的實(shí)現(xiàn)是個(gè)入口芙沥,借此可以了解不少JDBC規(guī)范方面的事情。
*/
TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
connection = dataSource.getConnection();
if (level != null) {
connection.setTransactionIsolation(level.getLevel());
}
connection = wrapConnection(connection);
Transaction tx = transactionFactory.newTransaction(connection,autoCommit);
Executorexecutor = configuration.newExecutor(tx, execType);
return newDefaultSqlSession(configuration, executor, autoCommit);
} catch (Exceptione) {
closeConnection(connection);
throwExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
綜上浊吏,創(chuàng)建sqlsession的主要步驟:
- 從配置中獲取Environment访圃;
- 從Environment中取得DataSource;
- 從Environment中取得TransactionFactory箕昭;
- 從DataSource里獲取數(shù)據(jù)庫(kù)連接對(duì)象Connection叶雹;
- 在取得的數(shù)據(jù)庫(kù)連接上創(chuàng)建事務(wù)對(duì)象Transaction;
- 創(chuàng)建Executor對(duì)象(該對(duì)象非常重要墩衙,事實(shí)上sqlsession的所有操作都是通過(guò)它完成的)务嫡;
- 創(chuàng)建sqlsession對(duì)象。
在mybatis中漆改,通過(guò)MapperProxy動(dòng)態(tài)代理dao心铃, 也就是說(shuō), 當(dāng)執(zhí)行dao中的方法的時(shí)挫剑,其實(shí)是對(duì)應(yīng)的mapperProxy在代理去扣。
那么,接下來(lái)我們來(lái)看看是如何獲取MapperProxy對(duì)象:
首先樊破,通過(guò)SqlSession從Configuration中獲扔淅狻:
/**
* 什么都不做唆铐,直接調(diào)用configuration中的getMapper方法
*/
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
之后,Configuration源碼:
/**
* 直接調(diào)用MapperRegistry的方法
*/
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
MapperRegistry源碼如下:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//MapperProxyFactory動(dòng)態(tài)代理DAO接口
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
//關(guān)鍵方法的實(shí)現(xiàn)
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
MapperProxyFactory源碼:
protected T newInstance(MapperProxy<T> mapperProxy) {
//動(dòng)態(tài)代理dao接口
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
上述解釋了是如何動(dòng)態(tài)代理DAO接口奔滑,接下來(lái)我們繼續(xù)來(lái)看具體是怎么執(zhí)行sql語(yǔ)句的艾岂,Sqlsession對(duì)數(shù)據(jù)庫(kù)的操作都是通過(guò)Executor來(lái)完成的。與Sqlsession一樣朋其,Executor也是動(dòng)態(tài)創(chuàng)建的:
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ?ExecutorType.SIMPLE : executorType;
Executor executor;
/**如果不開(kāi)啟cache的話王浴,
*創(chuàng)建的Executor只是3中基礎(chǔ)類(lèi)型之一
*/
//BatchExecutor專(zhuān)門(mén)用于執(zhí)行批量sql操作
if(ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this,transaction);
}
//ReuseExecutor會(huì)重用statement執(zhí)行sql操作
else if(ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this,transaction);
}
//SimpleExecutor只是簡(jiǎn)單執(zhí)行sql
else {
executor = newSimpleExecutor(this, transaction);
}
/**如果開(kāi)啟cache的話(默認(rèn)開(kāi)啟),
*就會(huì)創(chuàng)建CachingExecutor令宿,它以前面創(chuàng)建的Executor作為唯一參數(shù)
*/
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
上述源碼中叼耙,CachingExecutor在查詢(xún)數(shù)據(jù)庫(kù)前先查找緩存,若沒(méi)找到的話調(diào)用delegate從數(shù)據(jù)庫(kù)查詢(xún)粒没,并將查詢(xún)結(jié)果存入緩存中筛婉。
上述中,每個(gè)MapperProxy對(duì)應(yīng)一個(gè)dao接口癞松, 在使用的時(shí)候爽撒,MapperProxy的具體實(shí)現(xiàn):
/**
* MapperProxy在執(zhí)行時(shí)會(huì)觸發(fā)此方法
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
//MapperMethod執(zhí)行sqlSession
return mapperMethod.execute(sqlSession, args);
}
MapperMethod:
- 根據(jù)參數(shù)和返回值類(lèi)型選擇不同的sqlsession方法來(lái)執(zhí)行。
- 將mapper對(duì)象與sqlsession真正的關(guān)聯(lián)起來(lái)响蓉。
其execute方法源碼:
/**
* 先判斷CRUD類(lèi)型硕勿,
* 然后根據(jù)類(lèi)型去選擇到底執(zhí)行sqlSession中的哪個(gè)方法
*/
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
if (SqlCommandType.INSERT == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
} else if (SqlCommandType.SELECT == command.getType()) {
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 {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} else {
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;
}
對(duì)sqlsession方法的訪問(wèn)最終都會(huì)落到executor的相應(yīng)方法上去。
SqlSession的CRUD方法枫甲,以selectList方法為例:
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
//CRUD實(shí)際上是交給Excecutor去處理
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分成兩大類(lèi)源武,一類(lèi)是CacheExecutor,另一類(lèi)是普通Executor想幻。
普通Executor:
- BatchExecutor專(zhuān)門(mén)用于執(zhí)行批量sql操作粱栖。
- ReuseExecutor會(huì)重用statement執(zhí)行sql操作。
- SimpleExecutor只是簡(jiǎn)單執(zhí)行sql沒(méi)有什么特別的脏毯。
以SimpleExecutor為例:
public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds,ResultHandler resultHandler) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms,parameter, rowBounds,resultHandler);
stmt =prepareStatement(handler);
returnhandler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
通過(guò)一層一層的調(diào)用闹究,最終會(huì)來(lái)到doQuery方法,以SimpleExecutor為例:
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);
stmt = prepareStatement(handler, ms.getStatementLog());
//StatementHandler封裝了Statement, 通過(guò)StatementHandler 去處理
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
四食店、動(dòng)態(tài)SQL
什么是動(dòng)態(tài)SQL? 有什么作用渣淤?
傳統(tǒng)的使用JDBC的方法,在組合復(fù)雜的的SQL語(yǔ)句時(shí)吉嫩,需要拼接价认,容易導(dǎo)致錯(cuò)誤。Mybatis的動(dòng)態(tài)SQL功能正是為了解決這種問(wèn)題應(yīng)用而生自娩, 其通過(guò) if, choose, when, otherwise, trim, where, set, foreach標(biāo)簽用踩,可組合成非常靈活的SQL語(yǔ)句,從而提高開(kāi)發(fā)人員的效率。
if
<select id="findUserById" resultType="user">
select * from user where
<if test="id != null">
id=#{id}
</if>
and deleteFlag=0;
</select>
上面例子: 如果傳入的id 不為空捶箱, 那么才會(huì)SQL才拼接id = #{id}。
但如果傳入的id為null, 那么你這最終的SQL語(yǔ)句:
select * from user where and deleteFlag=0
語(yǔ)句有錯(cuò)动漾,無(wú)法通過(guò)解析丁屎!
此時(shí)需要引入where
where
<select id="findUserById" resultType="user">
select * from user
<where>
<if test="id != null">
id=#{id}
</if>
and deleteFlag=0;
</where>
</select>
mybatis中,當(dāng)where標(biāo)簽遇到AND或OR時(shí)旱眯,會(huì)去除AND或OR晨川。
set
<update id="updateUser" parameterType="com.dy.entity.User">
update user
<set>
<if test="name != null">name = #{name},</if>
<if test="password != null">password = #{password},</if>
<if test="age != null">age = #{age},</if>
</set>
<where>
<if test="id != null">
id = #{id}
</if>
and deleteFlag = 0;
</where>
</update>
foreach
java中有for, 可通過(guò)for循環(huán), 同樣在mybatis中有foreach, 可通過(guò)它實(shí)現(xiàn)循環(huán)删豺,循環(huán)的對(duì)象主要是java容器和數(shù)組共虑。
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
WHERE ID in
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
五、緩存機(jī)制源碼分析
1. 介紹
當(dāng)一條SQL語(yǔ)句被標(biāo)記為“可緩存”后呀页,第一次執(zhí)行時(shí)會(huì)將從數(shù)據(jù)庫(kù)獲取的所有數(shù)據(jù)存儲(chǔ)在一段高速緩存中妈拌,之后執(zhí)行同樣語(yǔ)句時(shí)會(huì)從高速緩存中讀取結(jié)果,而不是再次在數(shù)據(jù)庫(kù)中去命中蓬蝶。
Mybatis提供查詢(xún)緩存尘分,用于減輕數(shù)據(jù)壓力,提高數(shù)據(jù)庫(kù)性能丸氛。
Mybaits提供一級(jí)緩存培愁,和二級(jí)緩存:
一級(jí)緩存的作用域是同一個(gè)SqlSession,在同一個(gè)sqlSession中兩次執(zhí)行相同的sql語(yǔ)句缓窜,第一次執(zhí)行完畢會(huì)將數(shù)據(jù)庫(kù)中查詢(xún)的數(shù)據(jù)寫(xiě)到緩存(內(nèi)存)定续,第二次會(huì)從緩存中獲取數(shù)據(jù)將不再?gòu)臄?shù)據(jù)庫(kù)查詢(xún),從而提高查詢(xún)效率禾锤。當(dāng)一個(gè)sqlSession結(jié)束后該sqlSession中的一級(jí)緩存也就不存在了私股。
Mybatis默認(rèn)開(kāi)啟一級(jí)緩存。二級(jí)緩存是多個(gè)SqlSession共享的时肿,其作用域是mapper的同一個(gè)namespace庇茫,不同的sqlSession兩次執(zhí)行相同namespace下的sql語(yǔ)句且向sql中傳遞參數(shù)也相同即最終執(zhí)行相同的sql語(yǔ)句,第一次執(zhí)行完畢會(huì)將數(shù)據(jù)庫(kù)中查詢(xún)的數(shù)據(jù)寫(xiě)到緩存(內(nèi)存)螃成,第二次會(huì)從緩存中獲取數(shù)據(jù)將不再?gòu)臄?shù)據(jù)庫(kù)查詢(xún)旦签,從而提高查詢(xún)效率。
Mybatis中一級(jí)緩存和二級(jí)緩存的結(jié)構(gòu)如下:
2. 源碼剖析
2.1 一級(jí)緩存
一級(jí)緩存的作用域是SqlSession寸宏,那么我們就先看從SqlSession入手宁炫,類(lèi)DefaultSqlSession是接口SqlSession的實(shí)現(xiàn)類(lèi), 其中方法selectList:
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
return result;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
可以看到SqlSession調(diào)用接口Executor中的方法氮凝。接下來(lái)我們看下DefaultSqlSession中的executor接口屬性是如何得到的:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType, autoCommit);
return new DefaultSqlSession(configuration, executor);
} 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();
}
}
可以看到羔巢,Executor接口的實(shí)現(xiàn)類(lèi)是由Configuration構(gòu)造的:
public Executor newExecutor(Transaction transaction, ExecutorType executorType, boolean autoCommit) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor 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);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor, autoCommit);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
根據(jù)不同的ExecutorType創(chuàng)建Executor:
- 如果屬性cacheEnabled為true的話,那么通過(guò)裝飾器CachingExecutor包裝executor,這個(gè)裝飾器是 竿秆。
- 屬性cacheEnabled是配置文件中節(jié)點(diǎn)settings中子節(jié)點(diǎn)cacheEnabled的值启摄,默認(rèn)為true。
接下來(lái)幽钢,CachingExecutor執(zhí)行sql的操作是什么歉备,類(lèi)CachingExecutor中方法query:
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
//cache是個(gè)二級(jí)緩存
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, parameterObject, boundSql);
if (!dirty) {
cache.getReadWriteLock().readLock().lock();
try {
@SuppressWarnings("unchecked")
List<E> cachedList = (List<E>) cache.getObject(key);
if (cachedList != null) return cachedList;
} finally {
cache.getReadWriteLock().readLock().unlock();
}
}
List<E> list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578. Query must be not synchronized to prevent deadlocks
return list;
}
}
return delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
上述代碼中是類(lèi)SimpleExecutor,由于SimpleExecutor沒(méi)有覆蓋父類(lèi)中方法query匪燕,因此最終執(zhí)行了類(lèi)SimpleExecutor的父類(lèi)BaseExecutor中的方法query蕾羊。
由此可見(jiàn),一級(jí)緩存的核心就是類(lèi)BaseExecutor的方法query帽驯。
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++;
//localCache就是一級(jí)緩存
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();
}
deferredLoads.clear(); // issue #601
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache(); // issue #482
}
}
return list;
}
類(lèi)BaseExecutor中的屬性localCache是類(lèi)PerpetualCache的實(shí)例龟再。類(lèi)PerpetualCache 同樣實(shí)現(xiàn)了Mybatis的Cache緩存接口的實(shí)現(xiàn)類(lèi),內(nèi)部通過(guò)使用Map 類(lèi)型的屬性存儲(chǔ)緩存數(shù)據(jù)尼变。
localCache就是一級(jí)緩存利凑。
在執(zhí)行新增或更新或刪除操作,一級(jí)緩存就會(huì)被清除嫌术,接下來(lái)我們來(lái)看看其原理截碴。首先Mybatis在新增或刪除時(shí),都是通過(guò)調(diào)用方法update蛉威,即日丹,新增或刪除操作在Mybatis中都被視為更新操作。
類(lèi)DefaultSqlSession中方法update:
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
//調(diào)用了CachingExecutor的update方法
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
調(diào)用了CachingExecutor的update方法蚯嫌。
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
//方法flushCacheIfRequired清除的是二級(jí)緩存
flushCacheIfRequired(ms);
return delegate.update(ms, parameterObject);
}
CachingExecutor委托給類(lèi)SimpleExecutor的方法update哲虾,SimpleExecutor沒(méi)有覆蓋父類(lèi)BaseExecutor的方法update。BaseExecutor的方法update:
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) throw new ExecutorException("Executor was closed.");
//清除一級(jí)緩存LocalCache
clearLocalCache();
return doUpdate(ms, parameter);
}
方法clearLocalCache清除一級(jí)緩存LocalCache:
public void clearLocalCache() {
if (!closed) {
localCache.clear();
localOutputParameterCache.clear();
}
}
可以看到:
如果sqlsession沒(méi)有關(guān)閉的話择示,進(jìn)行新增束凑、刪除、修改這類(lèi)更新操作栅盲,那么就清除一級(jí)緩存汪诉,即SqlSession的緩存。
2.2 二級(jí)緩存
二級(jí)緩存的作用域是全局谈秫,即扒寄,二級(jí)緩存已脫離SqlSession的控制,二級(jí)緩存在SqlSession關(guān)閉或提交之后才會(huì)生效拟烫。
二級(jí)緩存的工作機(jī)制:
- 一個(gè)SqlSession對(duì)象會(huì)通過(guò)使用一個(gè)Executor對(duì)象來(lái)完成會(huì)話操作该编,Mybatis的二級(jí)緩存機(jī)制的關(guān)鍵就在于這個(gè)Executor對(duì)象。
- 如果用戶(hù)配置了屬性"cacheEnabled=true"硕淑,那么Mybatis在為SqlSession的對(duì)象創(chuàng)建Executor對(duì)象時(shí)课竣,會(huì)對(duì)Executor對(duì)象加上裝飾器CachingExecutor嘉赎,此時(shí)SqlSession通過(guò)使用CachingExecutor對(duì)象完成操作請(qǐng)求。
- CachingExecutor對(duì)于查詢(xún)請(qǐng)求于樟,首先判斷該查詢(xún)請(qǐng)求在Application級(jí)別的二級(jí)緩存中是否有緩存結(jié)果公条。
3.1如果有查詢(xún)結(jié)果,則直接返回緩存結(jié)果迂曲;
3.2 如果緩存中沒(méi)有赃份,再交給真正的Executor對(duì)象來(lái)完成查詢(xún)操作,之后CachingExecutor會(huì)將真正Executor返回的查詢(xún)結(jié)果放置到緩存中奢米,最后再返回給用戶(hù)。
下圖是二級(jí)緩存工作模式:
緩存配置操作:
- mybatis全局配置文件中的setting中的cacheEnabled需為true纠永。
- mapper配置文件中需要加入<cache>節(jié)點(diǎn)鬓长。
- mapper配置文件中的select節(jié)點(diǎn)需要加上屬性u(píng)seCache需要為true。
類(lèi)XMLMappedBuilder用來(lái)解析每個(gè)mapper配置文件的解析類(lèi)尝江,每一個(gè)mapper配置都會(huì)實(shí)例化一個(gè)XMLMapperBuilder類(lèi)涉波,其中的解析方法:
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
//解析緩存cache方法
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"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}
方法cacheElement解析緩存cache:
private void cacheElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type", "PERPETUAL");
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
String eviction = context.getStringAttribute("eviction", "LRU");
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
Long flushInterval = context.getLongAttribute("flushInterval");
Integer size = context.getIntAttribute("size");
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
Properties props = context.getChildrenAsProperties();
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, props);
}
}
解析完cache標(biāo)簽之后會(huì)使用類(lèi)builderAssistant的userNewCache方法:
public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
Properties props) {
typeClass = valueOrDefault(typeClass, PerpetualCache.class);
evictionClass = valueOrDefault(evictionClass, LruCache.class);
Cache cache = new CacheBuilder(currentNamespace)
.implementation(typeClass)
.addDecorator(evictionClass)
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.properties(props)
.build();
configuration.addCache(cache);
currentCache = cache;
return cache;
}
目前,mapper配置文件中的cache節(jié)點(diǎn)被解析到了XMLMapperBuilder實(shí)例中的builderAssistant屬性中的currentCache值里炭序。
接下來(lái)類(lèi)XMLMapperBuilder會(huì)解析節(jié)點(diǎn)select啤覆,通過(guò)使用XMLStatementBuilder進(jìn)行解析(也包括其他節(jié)點(diǎn)insert,update惭聂,delete):
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) return;
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
Class<?> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
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);
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// 解析selectKey
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// 解析SQL
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
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))
? new Jdbc3KeyGenerator() : new NoKeyGenerator();
}
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
上述源碼前半部分都在解析一些標(biāo)簽的屬性窗声,可以看到最后一行使用builderAssistant添加MappedStatement,其中builderAssistant屬性是構(gòu)造XMLStatementBuilder的時(shí)候通過(guò)XMLMappedBuilder傳入的辜纲,接下來(lái)笨觅,我們看如何設(shè)置二級(jí)緩存:
private void setStatementCache(
boolean isSelect,
boolean flushCache,
boolean useCache,
Cache cache,
MappedStatement.Builder statementBuilder) {
flushCache = valueOrDefault(flushCache, !isSelect);
useCache = valueOrDefault(useCache, isSelect);
statementBuilder.flushCacheRequired(flushCache);
statementBuilder.useCache(useCache);
statementBuilder.cache(cache);
}
最終mapper配置文件中的<cache/>被設(shè)置到了類(lèi)XMLMapperBuilder的屬性builderAssistant中,XMLMapperBuilder中使用XMLStatementBuilder遍歷CRUD節(jié)點(diǎn)耕腾,遍歷CRUD節(jié)點(diǎn)的時(shí)候?qū)⑦@個(gè)cache節(jié)點(diǎn)設(shè)置到這些CRUD節(jié)點(diǎn)中见剩,這個(gè)cache就是所謂的二級(jí)緩存。
在使用二級(jí)緩存之后:查詢(xún)數(shù)據(jù)的話扫俺,先從二級(jí)緩存中拿數(shù)據(jù)苍苞,如果沒(méi)有的話,去一級(jí)緩存中拿狼纬,一級(jí)緩存也沒(méi)有的話再查詢(xún)數(shù)據(jù)庫(kù)羹呵。有了數(shù)據(jù)之后在丟到TransactionalCache這個(gè)對(duì)象的entriesToAddOnCommit屬性中。
接下來(lái)我們來(lái)驗(yàn)證為什么SqlSession commit或close之后疗琉,二級(jí)緩存才會(huì)生效:
類(lèi)DefaultSqlSession的方法commit:
public void commit(boolean force) {
try {
executor.commit(isCommitOrRollbackRequired(force));
dirty = false;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
類(lèi)CachingExecutor的方法commit:
public void commit(boolean required) throws SQLException {
delegate.commit(required);
tcm.commit();
dirty = false;
}
類(lèi)TransactionalCacheManager的方法commit:
public void commit() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.commit();
}
}
類(lèi)TransactionalCache的方法commit:
public void commit() {
delegate.getReadWriteLock().writeLock().lock();
try {
if (clearOnCommit) {
delegate.clear();
} else {
for (RemoveEntry entry : entriesToRemoveOnCommit.values()) {
entry.commit();
}
}
for (AddEntry entry : entriesToAddOnCommit.values()) {
entry.commit();
}
reset();
} finally {
delegate.getReadWriteLock().writeLock().unlock();
}
}
可以看到調(diào)用了AddEntry的方法commit:
public void commit() {
cache.putObject(key, value);
}
原來(lái)方法AddEntry中的commit方法會(huì)把數(shù)據(jù)丟到cache中担巩,也就是丟到二級(jí)緩存中。
而之所以為何調(diào)用close方法后没炒,二級(jí)緩存才會(huì)生效涛癌,是因?yàn)閏lose方法內(nèi)部會(huì)調(diào)用commit方法犯戏。
四、JDBC演變到Mybatis過(guò)程
JDBC實(shí)現(xiàn)查詢(xún)所需步驟:
加載JDBC驅(qū)動(dòng)拳话;
建立并獲取數(shù)據(jù)庫(kù)連接先匪;
創(chuàng)建 JDBC Statements 對(duì)象;
設(shè)置SQL語(yǔ)句的傳入?yún)?shù)弃衍;
執(zhí)行SQL語(yǔ)句并獲得查詢(xún)結(jié)果呀非;
對(duì)查詢(xún)結(jié)果進(jìn)行轉(zhuǎn)換處理并將處理結(jié)果返回;
釋放相關(guān)資源(關(guān)閉Connection镜盯,關(guān)閉Statement岸裙,關(guān)閉ResultSet);
1. 連接獲取和釋放
問(wèn)題描述:
數(shù)據(jù)庫(kù)連接頻繁的開(kāi)啟和關(guān)閉本身就造成了資源的浪費(fèi)速缆,影響系統(tǒng)的性能降允。
優(yōu)化方案:
數(shù)據(jù)庫(kù)連接的獲取和關(guān)閉我們可以使用數(shù)據(jù)庫(kù)連接池來(lái)解決資源浪費(fèi)的問(wèn)題。通過(guò)連接池就可以反復(fù)利用已經(jīng)建立的連接去訪問(wèn)數(shù)據(jù)庫(kù)了艺糜。減少連接的開(kāi)啟和關(guān)閉的時(shí)間剧董。
2. SQL統(tǒng)一存取
問(wèn)題描述:
使用JDBC進(jìn)行操作數(shù)據(jù)庫(kù)時(shí),SQL語(yǔ)句基本都散落在各個(gè)JAVA類(lèi)中破停,這樣有三個(gè)不足之處:
- 可讀性很差翅楼,不利于維護(hù)以及做性能調(diào)優(yōu)。
- 改動(dòng)Java代碼需要重新編譯真慢、打包部署毅臊。
- 不利于取出SQL在數(shù)據(jù)庫(kù)客戶(hù)端執(zhí)行。
優(yōu)化方案:
可以考慮不把SQL語(yǔ)句寫(xiě)到Java代碼中黑界,那么把SQL語(yǔ)句放到哪里呢褂微?首先需要有一個(gè)統(tǒng)一存放的地方,我們可以將這些SQL語(yǔ)句統(tǒng)一集中放到配置文件或者數(shù)據(jù)庫(kù)里面(以key-value的格式存放)园爷。然后通過(guò)SQL語(yǔ)句的key值去獲取對(duì)應(yīng)的SQL語(yǔ)句宠蚂。
3. 傳入?yún)?shù)映射和動(dòng)態(tài)SQL
問(wèn)題描述:
既然我們已經(jīng)把SQL語(yǔ)句統(tǒng)一存放在配置文件或者數(shù)據(jù)庫(kù)中了,怎么做到能夠根據(jù)前臺(tái)傳入?yún)?shù)的不同童社,動(dòng)態(tài)生成對(duì)應(yīng)的SQL語(yǔ)句呢求厕?
優(yōu)化方案:
需要使用一種有別于SQL的語(yǔ)法來(lái)嵌入變量(比如使用#變量名#)。這樣扰楼,SQL語(yǔ)句經(jīng)過(guò)解析后就可以動(dòng)態(tài)的生成符合上下文的SQL語(yǔ)句呀癣。可以使用#變量名#表示占位符變量弦赖,使用表示非占位符變量项栏。
4. 結(jié)果映射和結(jié)果緩存
問(wèn)題描述:
執(zhí)行SQL語(yǔ)句、獲取執(zhí)行結(jié)果蹬竖、對(duì)執(zhí)行結(jié)果進(jìn)行轉(zhuǎn)換處理沼沈、釋放相關(guān)資源是一整套下來(lái)的流酬。假如是執(zhí)行查詢(xún)語(yǔ)句,那么執(zhí)行SQL語(yǔ)句后列另,返回的是一個(gè)ResultSet結(jié)果集芽腾,這個(gè)時(shí)候我們就需要將ResultSet對(duì)象的數(shù)據(jù)取出來(lái),不然等到釋放資源時(shí)就取不到這些結(jié)果信息了页衙。
優(yōu)化方案:
必須告訴SQL處理器兩點(diǎn):第一摊滔,需要返回什么類(lèi)型的對(duì)象;第二店乐,需要返回的對(duì)象的數(shù)據(jù)結(jié)構(gòu)怎么跟執(zhí)行的結(jié)果映射艰躺,這樣才能將具體的值copy到對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)上。
5. 解決重復(fù)SQL語(yǔ)句問(wèn)題
問(wèn)題描述:
由于我們將所有SQL語(yǔ)句都放到配置文件中眨八,這個(gè)時(shí)候會(huì)遇到一個(gè)SQL重復(fù)的問(wèn)題腺兴,幾個(gè)功能的SQL語(yǔ)句其實(shí)都差不多,有些可能是SELECT后面那段不同踪古、有些可能是WHERE語(yǔ)句不同。有時(shí)候表結(jié)構(gòu)改了券腔,那么我們就需要改多個(gè)地方伏穆,不利于維護(hù)。
優(yōu)化方案:
當(dāng)我們的代碼程序出現(xiàn)重復(fù)代碼時(shí)怎么辦纷纫?將重復(fù)的代碼抽離出來(lái)成為獨(dú)立的一個(gè)類(lèi)枕扫,然后在各個(gè)需要使用的地方進(jìn)行引用。對(duì)于SQL重復(fù)的問(wèn)題辱魁,我們也可以采用這種方式烟瞧,通過(guò)將SQL片段模塊化,將重復(fù)的SQL片段獨(dú)立成一個(gè)SQL塊染簇,然后在各個(gè)SQL語(yǔ)句引用重復(fù)的SQL塊参滴,這樣需要修改時(shí)只需要修改一處即可。