git地址:https://github.com/dp33221/my_orm
上次分析完了Mybatis啟動(dòng)以及執(zhí)行流程后摘仅,本次根據(jù)上次的思路實(shí)現(xiàn)一個(gè)簡(jiǎn)單的mybatis框架洋机。
首先需要分析一下什么場(chǎng)景下出現(xiàn)了ORM框架棵磷,最早的時(shí)候我們使用數(shù)據(jù)庫(kù)鏈接步驟如下
可以看到每次都需要注冊(cè)驅(qū)動(dòng),建立連接乔妈,執(zhí)行查詢揭朝,獲取結(jié)果猪半,關(guān)閉連接。顯然這存在高耦合以及硬編碼問(wèn)題在旱。所以我們一步一步的解決問(wèn)題摇零。
第一四個(gè)問(wèn)題:注冊(cè)驅(qū)動(dòng)以及建立數(shù)據(jù)庫(kù)連接
? ? 這邊我們可以封裝一個(gè)連接池,這樣就可以解決耦合問(wèn)題桶蝎,然后數(shù)據(jù)庫(kù)連接配置的話我們需要使用方提供驻仅,這邊借鑒Mybatis的方式,使用xml進(jìn)行配置俊嗽。所以需要一個(gè)configuration對(duì)象雾家。
第二三個(gè)問(wèn)題:執(zhí)行查詢
? ? 為了能夠解耦以及可配置性,這邊也是想到的使用配置文件的方式進(jìn)行配置绍豁,參考Mybatis的Mapper.xml文件芯咧,這邊需要一個(gè)mapperStatement對(duì)象存儲(chǔ)對(duì)應(yīng)的xml信息,分析一下這個(gè)對(duì)象需要哪些屬性竹揍,首先肯定需要記錄sql敬飒,然后要記錄入?yún)arameter,然后就是返回結(jié)果result芬位,還有執(zhí)行的類型也就是executorType无拗,以及對(duì)應(yīng)的id這個(gè)id默認(rèn)就是標(biāo)簽中的id。
xml中每個(gè)標(biāo)簽都會(huì)被解析成一個(gè)mappedstatement對(duì)象昧碉,所以我們需要有一個(gè)映射關(guān)系來(lái)保存英染,最容易想到的就是Map也就是鍵值對(duì),鍵就取xml中的namespace+id被饿,值就是對(duì)應(yīng)的MappedStatement對(duì)象四康。存儲(chǔ)在configuration對(duì)象中。所以configuration對(duì)象要存一個(gè)連接池狭握,以及解析的xml的數(shù)據(jù)闪金。
首先解決前兩個(gè)問(wèn)題,第一步需要配置配置文件sqlMapConfig.xml,其中我設(shè)置一個(gè)根標(biāo)簽<configuration />,以及子標(biāo)簽property來(lái)配置數(shù)據(jù)庫(kù)連接屬性解決硬編碼問(wèn)題哎垦。在解析configuration文件時(shí)也可以同時(shí)解析mapper.xml所以在設(shè)置一個(gè)mapper標(biāo)簽來(lái)設(shè)置要解析的mapper路徑囱嫩。
這就是基礎(chǔ)的configuration配置,其中property就是數(shù)據(jù)庫(kù)連接的基本配置漏设,mapper就是要解析的mapper.xml
mapper.xml文件配置了一些sql信息包括namespace墨闲、id、paramType愿题、resultType损俭、sql這些信息。
解析配置文件
? ? 在解析配置文件之前潘酗,要理清楚一個(gè)大體的流程杆兵。首先需要?jiǎng)?chuàng)建一個(gè)SqlSession對(duì)象的工廠用于生成SqlSession,同時(shí)解析配置文件仔夺,然后生成sqlsession對(duì)象琐脏,之后在進(jìn)行excutor操作。
第一步
1.開(kāi)始解析xml配置文件缸兔,我借助dom4j以及jaxen來(lái)實(shí)現(xiàn)日裙。首先第一步要將xml配置文件讀成輸入流的形式,借助classloader的getResourcesAsStream方法實(shí)現(xiàn)惰蜜。
2.將字節(jié)輸入流傳遞進(jìn)來(lái)開(kāi)始進(jìn)行xml文件解析昂拂,執(zhí)行XmlConfigBuild的buildConfiguration方法
3.首先獲取字節(jié)流,然后獲取xml跟標(biāo)簽抛猖, 通過(guò)xpath表達(dá)式得到節(jié)點(diǎn)信息格侯,然后存儲(chǔ)到properties中,創(chuàng)建連接池财著,然后設(shè)置數(shù)據(jù)源联四,存儲(chǔ)到configuration對(duì)象中。然后獲取所有的mapper標(biāo)簽撑教,并遍歷解析朝墩。調(diào)用的xmlMapperBuild的build方法。
在新建xmlmapperbuild對(duì)象時(shí)伟姐,要將已經(jīng)設(shè)置數(shù)據(jù)源的configuration傳入收苏。解析Mapper的步驟與sqlMapConfig差不多,這邊主要講一下解析各個(gè)標(biāo)簽的步驟愤兵,首先就是獲取到對(duì)應(yīng)的id然后返回類型以及參數(shù)類型倒戏,再獲取到sql語(yǔ)句,生成statementid(namespace+id)以及MappedStatement恐似,存入configuration對(duì)象中的Map集合中。到這一步就執(zhí)行完文件解析的步驟了傍念。
第二步
接著就是生成sqlsession對(duì)象矫夷,sqlsession主要就是sql對(duì)話對(duì)象葛闷,執(zhí)行sql操作
首先定義一個(gè)接口 需要實(shí)現(xiàn)哪些方法,然后生成一個(gè)默認(rèn)的實(shí)現(xiàn)類來(lái)實(shí)現(xiàn)這些方法双藕,主要就是一個(gè)查詢select方法以及一個(gè)更新update方法淑趾。其中提供了getMapper方法來(lái)實(shí)現(xiàn)Mapper接口的代理對(duì)象創(chuàng)建(jdk動(dòng)態(tài)代理),具體步驟就是通過(guò)全限定類名+方法名去獲取到MappedStatement對(duì)象(注意:因此namespace需要是類的全限定路徑忧陪,id是類的方法扣泊,同時(shí)這也導(dǎo)致了方法無(wú)法重載)。然后通過(guò)返回值類型以及操作類型判斷是走查詢還是走更新操作嘶摊,如果走更新操作則會(huì)記錄方法的參數(shù)數(shù)組延蟹。sqlsession是一個(gè)對(duì)外的會(huì)話對(duì)象,其中執(zhí)行sql語(yǔ)句還是通過(guò)Executor對(duì)象來(lái)執(zhí)行的叶堆。
第三步
執(zhí)行sql操作
和sqlsession一樣先創(chuàng)建一個(gè)接口定義規(guī)則阱飘,然后創(chuàng)建一個(gè)defaultExecutor進(jìn)行默認(rèn)實(shí)現(xiàn)。其中有一個(gè)查詢功能一個(gè)更新功能虱颗,我這邊注釋寫(xiě)的比較全面沥匈,就不贅述了。其中有在處理請(qǐng)求參數(shù)時(shí)我借用了mybatis中的處理類忘渔。并進(jìn)行了簡(jiǎn)化
參數(shù)處理:下面是代碼
GenericTokenParser
```
public class GenericTokenParser {
private final StringopenToken;
private final StringcloseToken;
private final TokenHandlerhandler;
public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
this.openToken = openToken;
this.closeToken = closeToken;
this.handler = handler;
}
public String parse(String text) {
if (text ==null || text.isEmpty()) {
return "";
}
// search open token
? ? int start = text.indexOf(openToken);
if (start == -1) {
return text;
}
char[] src = text.toCharArray();
int offset =0;
final StringBuilder builder =new StringBuilder();
StringBuilder expression =null;
while (start > -1) {
if (start >0 && src[start -1] =='\\') {
// this open token is escaped. remove the backslash and continue.
? ? ? ? builder.append(src, offset, start - offset -1).append(openToken);
offset = start +openToken.length();
}else {
// found open token. let's search close token.
? ? ? ? if (expression ==null) {
expression =new StringBuilder();
}else {
expression.setLength(0);
}
builder.append(src, offset, start - offset);
offset = start +openToken.length();
int end = text.indexOf(closeToken, offset);
while (end > -1) {
if (end > offset && src[end -1] =='\\') {
// this close token is escaped. remove the backslash and continue.
? ? ? ? ? ? expression.append(src, offset, end - offset -1).append(closeToken);
offset = end +closeToken.length();
end = text.indexOf(closeToken, offset);
}else {
expression.append(src, offset, end - offset);
break;
}
}
if (end == -1) {
// close token was not found.
? ? ? ? ? builder.append(src, start, src.length - start);
offset = src.length;
}else {
builder.append(handler.handleToken(expression.toString()));
offset = end +closeToken.length();
}
}
start = text.indexOf(openToken, offset);
}
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
}
```
ParameterMapping
```
public class ParameterMapping {
private Stringname;
}
```
ParameterMappingTokenHandler
```
public class ParameterMappingTokenHandlerimplements TokenHandler {
private ListparameterMappings =new ArrayList<>();
public List getParameterMappings() {
return parameterMappings;
}
@Override
? ? public String handleToken(String content) {
parameterMappings.add(buildParameterMapping(content));
return "?";
}
private ParameterMapping buildParameterMapping(String content) {
return new ParameterMapping(content);
}
}
```
TokenHandler
public interface TokenHandler {
String handleToken(String content);
}
如圖就是自定義ORM框架的查詢結(jié)果高帖,如果要查看增刪改方法則調(diào)用update/delete/insert方法