Mybatis簡介:
MyBatis 是一款優秀的持久層框架,它支持定製化 SQL、存儲過程以及高級映射。MyBatis 避免了幾乎所有的 JDBC 代碼和手動設置參數以及獲取結果集。MyBatis 可以使用簡單的 XML 或註解來配置和映射原生信息,將接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java對象)映射成數據庫中的記錄。本文將通過debug的方式來了解其工作原理。
Mybatis核心類:
SqlSessionFactory:每個基於 MyBatis 的應用都是以一個 SqlSessionFactory 的實例爲中心的。SqlSessionFactory 的實例可以通過 SqlSessionFactoryBuilder 獲得。而 SqlSessionFactoryBuilder 則可以從 XML 配置文件或通過Java的方式構建出 SqlSessionFactory 的實例。SqlSessionFactory 一旦被創建就應該在應用的運行期間一直存在,建議使用單例模式或者靜態單例模式。一個SqlSessionFactory對應配置文件中的一個環境(environment),如果你要使用多個數據庫就配置多個環境分別對應一個SqlSessionFactory。
SqlSession:SqlSession是一個接口,它有2個實現類,分別是DefaultSqlSession(默認使用)以及SqlSessionManager。SqlSession通過內部存放的執行器(Executor)來對數據進行CRUD。此外SqlSession不是線程安全的,因爲每一次操作完數據庫後都要調用close對其進行關閉,官方建議通過try-finally來保證總是關閉SqlSession。
Executor:Executor(執行器)接口有兩個實現類,其中BaseExecutor有三個繼承類分別是BatchExecutor(重用語句並執行批量更新),ReuseExecutor(重用預處理語句prepared statements),SimpleExecutor(普通的執行器)。以上三個就是主要的Executor。通過下圖可以看到Mybatis在Executor的設計上面使用了裝飾者模式,我們可以用CachingExecutor來裝飾前面的三個執行器目的就是用來實現緩存。
MappedStatement:MappedStatement就是用來存放我們SQL映射文件中的信息包括sql語句,輸入參數,輸出參數等等。一個SQL節點對應一個MappedStatement對象。
Mybatis工作流程:
閱讀全文有驚喜哦!!!
下面將通過debug方式對Mybatis進行一步步解析。首先貼出我的mybatis-config.xml文件以及Mapper.xml文件。
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
select * from user
User where id = #{id}
insert into User (username,birthday,sex,address)
values (#{name},#{birthday},#{sex},#{address})
update User set username = #{username},birthday = #{birthday},
sex = #{sex},address = #{address} where id = #{id}
delete from User where id = #{id}
select * from User where sex = #{param1}
and username like #{param2}
and address = #{parma3}
select count(*) from user where username like #{username}
username like #{pattern}
and sex = #{sex}
and address = #{address}
where id in
#{id}
第一步通過SqlSessionFactoryBuilder創建SqlSessionFactory:
首先在SqlSessionFactoryBuilder的build()方法中可以看到MyBatis內部定義了一個類XMLConfigBuilder用來解析配置文件mybatis-config.xml。針對配置文件中的每一個節點進行解析並將數據存放到Configuration這個對象中,緊接着使用帶有Configuration的構造方法發返回一個DefautSqlSessionFactory。
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//解析mybatis-config.xml
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
//返回SqlSessionFactory,默認使用的是實現類DefaultSqlSessionFactory
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//獲取根節點configuration
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
//開始解析mybatis-config.xml,並把解析後的數據存放到configuration中
private void parseConfiguration(XNode root) {
try {
//保存mybatis-config.xml中的標籤setting,本例中開啓全局緩存cacheEnabled,設置默認執行器defaultExecutorType=REUSE
Properties settings = settingsAsPropertiess(root.evalNode("settings"));
//issue #117 read properties first
//解析是否配置了外部properties,例如本例中配置的jdbc.propertis
propertiesElement(root.evalNode("properties"));
//查看是否配置了VFS,默認沒有,本例也沒有使用
loadCustomVfs(settings);
//查看是否用了類型別名,減少完全限定名的冗餘,本例中使用了別名User代替了com.ctc.Model.User
typeAliasesElement(root.evalNode("typeAliases"));
//查看是否配置插件來攔截映射語句的執行,例如攔截Executor的Update方法,本例沒有使用
pluginElement(root.evalNode("plugins"))
//查看是否配置了ObjectFactory,默認情況下使用對象的無參構造方法或者是帶有參數的構造方法,本例沒有使用
objectFactoryElement(root.evalNode("objectFactory"));
//查看是否配置了objectWrapperFatory,這個用來或者ObjectWapper,可以訪問:對象,Collection,Map屬性。本例沒有使用
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//查看是否配置了reflectorFactory,mybatis的反射工具,提供了很多反射方法。本例沒有使用
reflectorFactoryElement(root.evalNode("reflectorFactory"));
//放入參數到configuration對象中
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
//查看數據庫環境配置
environmentsElement(root.evalNode("environments"));
//查看是否使用多種數據庫,本例沒有使用
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//查看是否配置了新的類型處理器,如果跟處理的類型跟默認的一致就會覆蓋。本例沒有使用
typeHandlerElement(root.evalNode("typeHandlers"));
//查看是否配置SQL映射文件,有四種配置方式,resource,url,class以及自動掃包package。本例使用package
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
第二步通過SqlSessionFactory創建SqlSession:
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
//拿到前文從mybatis中解析到的數據庫環境配置
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
//拿到jdbc的事務管理器,有兩種一種是jbc,一種的managed。本例使用的是JdbcTransaction
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//從mybatis配置文件可以看到本例使用了REUSE,因此返回的是ReuseExecutor並把事務傳入對象中
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();
}
}
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
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);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
//返回一個SqlSession,默認使用DefaultSqlSession
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
第三步通過SqlSession拿到Mapper對象的代理:
@Override
public T getMapper(Class type) {
return configuration.getMapper(type, this);
}
public T getMapper(Class type, SqlSession sqlSession) {
//前文解析Mybatis-config.xml的時候,在解析標籤mapper就是用configuration對象的mapperRegistry存放數據
return mapperRegistry.getMapper(type, sqlSession);
}
@SuppressWarnings("unchecked")
public T getMapper(Class type, SqlSession sqlSession) {
//knownMapper是一個HashMap在存放mapperRegistry的過程中,以每個Mapper對象的類型爲Key, MapperProxyFactory 爲value保存。
//例如本例中保存的就是Key:com.ctc.mapper.UserMapper,value就是保存了key的MapperProxyFactory對象
final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) 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);
}
}
public T newInstance(SqlSession sqlSession) {
//生成一個mapperProxy對象,這個對象實現了InvocationHandler, Serializable。就是JDK動態代理中的方法調用處理器
final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy mapperProxy) {
//通過JDK動態代理生成一個Mapper的代理,在本例中的就是UserMapper的代理類,它實現了UserMapper接口
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
第四步通過MapperProxy調用Maper中相應的方法:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//判斷當前調用的method是不是Object中聲明的方法,如果是的話直接執行。
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
//把當前請求放入一個HashMap中,一旦下次還是同樣的方法進來直接返回。
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
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 {
//本次案例會執行selectOne
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
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;
}
@Override
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.<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;
}
}
@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();
}
}
//這邊調用的是CachingExecutor類的query,還記得前文解析mybatis-config.xml的時候我們指定了REUSE但是因爲在配置文件中開啓了緩存
//所以ReuseExecutor被CachingExecotur裝飾,新增了緩存的判斷,最後還是會調用ReuseExecutor
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, 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);
}
這裏給大家推薦一個java架構師的學習路線(技術塊)
在互聯網寒冬季,更需要加強學習,防止被離職!
依次:開源框架解析-架構師築基-高性能架構-微服務架構-團隊協作開發-B2C商城實戰
腦圖和相關資料獲取方式
可以直接加羣960439918獲取免費架構資料
一、開源框架解析:
閱讀、分析源碼是程序員最基本的碼代碼能力也是碼農的根本所在,學習經典源碼中所用到的經典設計思想及常用設計模式,能夠幫你瞭解大牛是如何寫代碼的,從而吸收大牛的代碼功力。在阿里面試中,MyBatis,Spring等框架的底層原理是經常會被問到的。
二、架構師築基:
百丈高樓平地起,基礎也是非常重要的,基礎不牢,自然不用談架構。
三、高性能架構
性能一直是讓程序員比較頭疼的問題。當系統架構變得複雜而龐大之後,性能方面就會下降,特別是阿里巴巴這樣的一線互聯網公司最爲注重,因此想進入阿里,性能優化一定是要去深入學習與理解的一環
四、微服務架構
關於微服務架構的取捨
微服務是現在互聯網架構技術中最火熱的話題之一,也是我目前正在學習研究的方向。在面試過程中,面試官很少會問到關於微服務相關的問題。但作爲一名開發者,一名有技術夢想的程序員微服務架構是現在必須要去了解的主流技術:
五、團隊協作:
開發工具工程化
通過一小段描述信息來管理項目的構建,報告和文檔的軟件項目管理工具。程序員的戰鬥,往往不是一個人的戰鬥,我們如何在一個平臺下高效的去重,進行代碼review,對功能進行調整,debug,做到在統一的規劃下步步爲營,混亂的堆代碼的過程中找到自己的記錄。這一切都依賴於有效的工具。
六、B2C項目實戰
項目實戰
要想立足於互聯網公司,且能在互聯網浪潮中不被淹沒,對於項目的開發實戰演練是不必可少的技能,也是對自身能力的一個衡量,有多少的量對等於獲得多少的回報。看似簡單的一個項目需求圖譜,其中的底層原理,實現原理又能知道多少?你搭建一個完整的B2C項目平臺到底需要多少知識?這一切都是需要我們考量的。
以上是小編自己目前在互聯網公司用到的java核心技術總結出知識體系思維導。學習是一個複雜的過程,當你擁有了學習的方向和學習的方法時,你缺的只是時間,時間是自己積累出來的,而不是我想學習時說“好像沒空”這些藉口。不要讓今天的藉口變成了明天的迷茫!
針對上面的六大技術知識模塊我總結一些架構資料和麪試題免費分享給大家,希望能幫助到那些工作了的朋友在學習能提供一些幫助。有需要這些免費架構資料和麪試題資料的可以加羣:960439918獲取哦!點擊鏈接加入羣聊【java高級架構交流羣】:https://jq.qq.com/?_wv=1027&k=5fozFzF