上一節內容咱們簡單回顧了Mybatis的總體架構和相關概念知識點,並簡述了本系列所用框架的版本。Mybatis功能強大,花樣繁多。咱們不會太關心全部的技術點,而是重點剖析經常使用的功能點。同Spring相比,Mybatis多以應用爲主。從本節開始,咱們正式開始源碼的分析。node
每一個基於 MyBatis 的應用都是以一個 SqlSessionFactory 的實例爲中心的,SqlSessionFactory 的實例能夠經過 SqlSessionFactoryBuilder 得到。而 SqlSessionFactoryBuilder 則能夠從 XML 配置文件或一個預先定製的 Configuration 的實例構建出 SqlSessionFactory 的實例。例如:算法
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory =
new SqlSessionFactoryBuilder().build(inputStream);
複製代碼
固然,上面這些是Mybatis官方的樣例。不過,咱們平常開發中Mybatis都是與Spring一塊兒使用的,交給Spring去搞定這些豈不更好。spring
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="mapperLocations" value="classpath:com/viewscenes/netsupervisor/mapping/*.xml"></property>
<property name="typeAliasesPackage">
<array>
<value>com.viewscenes.netsupervisor.entity</value>
</array>
</property>
</bean>
複製代碼
咱們來到org.mybatis.spring.SqlSessionFactoryBean
,看到它實現了InitializingBean
接口。這說明,在這個類被實例化以後會調用到afterPropertiesSet()
。它只有一個方法sql
public void afterPropertiesSet() throws Exception {
this.sqlSessionFactory = buildSqlSessionFactory();
}
複製代碼
SqlSessionFactory是一個接口,它裏面其實就兩個方法:openSession、getConfiguration
。數據庫
package org.apache.ibatis.session;
public interface SqlSessionFactory {
SqlSession openSession();
SqlSession openSession(boolean autoCommit);
SqlSession openSession(Connection connection);
SqlSession openSession(TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType);
SqlSession openSession(ExecutorType execType, boolean autoCommit);
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType, Connection connection);
Configuration getConfiguration();
}
複製代碼
咱們知道,能夠經過openSession方法獲取一個SqlSession對象,完成必要數據庫增刪改查功能。可是,SqlSessionFactory屬性也太少了,那些mapper映射文件、SQL參數、返回值類型、緩存等屬性都在哪呢?apache
Configuration,你能夠把它當成一個數據的大管家。MyBatis全部的配置信息都維持在Configuration對象之中,基本每一個對象都會持有它的引用。正是應了那句話我是革命一塊磚,哪裏須要往哪搬
。下面是部分屬性緩存
public class Configuration {
//環境
protected Environment environment;
protected boolean safeRowBoundsEnabled;
protected boolean safeResultHandlerEnabled = true;
protected boolean mapUnderscoreToCamelCase;
protected boolean aggressiveLazyLoading;
protected boolean multipleResultSetsEnabled = true;
protected boolean useGeneratedKeys;
protected boolean useColumnLabel = true;
protected boolean cacheEnabled = true;
protected boolean callSettersOnNulls;
protected boolean useActualParamName = true;
protected boolean returnInstanceForEmptyRow;
//日誌信息的前綴
protected String logPrefix;
//日誌接口
protected Class<? extends Log> logImpl;
//文件系統接口
protected Class<? extends VFS> vfsImpl;
//本地Session範圍
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
//數據庫類型
protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
//延遲加載的方法
protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(
Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
//默認執行語句超時
protected Integer defaultStatementTimeout;
//默認的執行器
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
//數據庫ID
protected String databaseId;
//mapper註冊表
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
//攔截器鏈
protected final InterceptorChain interceptorChain = new InterceptorChain();
//類型處理器
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
//類型別名
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
//語言驅動
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
//mapper_id 和 mapper文件的映射
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>(
"Mapped Statements collection");
//mapper_id和緩存的映射
protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
//mapper_id和返回值的映射
protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
//mapper_id和參數的映射
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");
//資源列表
protected final Set<String> loadedResources = new HashSet<String>();
未完.......
}
複製代碼
在afterPropertiesSet
方法只有一個動做,就是buildSqlSessionFactory。它能夠分爲兩部分來看,先從配置文件的property
屬性中加載各類組件,解析配置到configuration中,而後加載mapper文件,解析SQL語句,封裝成MappedStatement對象,配置到configuration中。bash
這是一個類型的別名,很好用。好比在user_mapper的方法中,resultType和parameterType想使用實體類映射,而不用寫全限定類名。session
//這裏的resultType就是別名
//它對應的是com.viewscenes.netsupervisor.entity.User
<select id="getUserById" resultType="user">
select * from user where uid=#{uid}
</select>
複製代碼
它有兩種配置方式,指定包路徑或者指定類文件的路徑。mybatis
<property name="typeAliasesPackage">
<array>
<value>com.viewscenes.netsupervisor.entity</value>
</array>
</property>
或者
<property name="typeAliases">
<array>
<value>com.viewscenes.netsupervisor.entity.User</value>
</array>
</property>
複製代碼
它的解析很簡單,就是拿到類路徑的反射對象,key爲默認類名小寫,value爲Class對象,註冊到容器中。固然了,你也可使用@Alias註解來設置別名的名稱。
public void registerAlias(Class<?> type) {
String alias = type.getSimpleName();
Alias aliasAnnotation = type.getAnnotation(Alias.class);
if (aliasAnnotation != null) {
alias = aliasAnnotation.value();
}
String key = alias.toLowerCase(Locale.ENGLISH);
TYPE_ALIASES.put(key, type);
}
複製代碼
TYPE_ALIASES容器就是一個HashMap,裏面已經默認添加了不少的類型別名。
registerAlias("string", String.class);
registerAlias("byte", Byte.class);
registerAlias("long", Long.class);
registerAlias("short", Short.class);
registerAlias("int", Integer.class);
registerAlias("integer", Integer.class);
registerAlias("double", Double.class);
registerAlias("float", Float.class);
registerAlias("boolean", Boolean.class);
......未完
複製代碼
它是一個類型的處理器。在數據庫查詢出結果後,應該轉換成Java中的什麼類型?由它來決定。若是Mybatis裏面沒有你想要的,就能夠在這裏自定義一個處理器。 這塊內容,在後續章節我將經過一個實例獨立講解。如今,先來看下它的默認處理器。
register(JdbcType.BOOLEAN, new BooleanTypeHandler());
register(JdbcType.TINYINT, new ByteTypeHandler());
register(JdbcType.SMALLINT, new ShortTypeHandler());
register(Integer.class, new IntegerTypeHandler());
register(JdbcType.INTEGER, new IntegerTypeHandler());
register(JdbcType.FLOAT, new FloatTypeHandler());
register(JdbcType.DOUBLE, new DoubleTypeHandler());
register(String.class, new StringTypeHandler());
register(String.class, JdbcType.CHAR, new StringTypeHandler());
register(String.class, JdbcType.CLOB, new ClobTypeHandler());
register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
register(String.class, JdbcType.LONGVARCHAR, new ClobTypeHandler());
register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
register(String.class, JdbcType.NCLOB, new NClobTypeHandler());
register(JdbcType.CHAR, new StringTypeHandler());
register(JdbcType.VARCHAR, new StringTypeHandler());
register(JdbcType.CLOB, new ClobTypeHandler());
register(JdbcType.LONGVARCHAR, new ClobTypeHandler());
.....未完
複製代碼
能夠配置一個或多個插件。插件功能很強大,在執行SQL以前,在返回結果以後,在插入數據時...均可以讓你有機會插手數據的處理。這個部分最經常使用的是分頁,在後續章節,筆者將經過分頁和數據同步的實例單獨講解。
<property name="plugins">
<array>
<bean class="com.viewscenes.netsupervisor.interceptor.xxxInterceptor"></bean>
<bean class="com.viewscenes.netsupervisor.interceptor.xxxInterceptor"></bean>
</array>
</property>
複製代碼
mapperLocations配置的是應用中mapper文件的路徑,獲取全部的mapper文件。經過解析裏面的select/insert/update/delete
節點,每個節點生成一個MappedStatement對象。最後註冊到Configuration對象的mappedStatements。key爲mapper的namespace+節點id。
先來看一下方法的總體。
public class XMLMapperBuilder extends BaseBuilder {
private void configurationElement(XNode context) {
//命名空間 即mapper接口的路徑
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
//設置當前mapper文件的命名空間
builderAssistant.setCurrentNamespace(namespace);
//引用緩存
cacheRefElement(context.evalNode("cache-ref"));
//是否開啓二級緩存
cacheElement(context.evalNode("cache"));
//參數
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//返回值
resultMapElements(context.evalNodes("/mapper/resultMap"));
//解析sql節點
sqlElement(context.evalNodes("/mapper/sql"));
//SQL語句解析
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
}
}
複製代碼
經過在mapper文件中聲明</cache>
來開啓二級緩存。經過獲取緩存的配置信息來構建Cache的實例,最後註冊到configuration。
private void cacheElement(XNode context) throws Exception {
//獲取緩存的實例類型,在configuration初始化的時候註冊
// typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
//typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
//typeAliasRegistry.registerAlias("LRU", LruCache.class);
String type = context.getStringAttribute("type", "PERPETUAL");
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
//LRU回收算法
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);
//是否阻塞
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
複製代碼
拿到這些屬性後,將緩存設置到configuration。
//二級緩存是和mapper綁定的
//因此,這裏的id就是mapper的命名空間
public void addCache(Cache cache) {
caches.put(cache.getId(), cache);
}
複製代碼
它們表示的是查詢結果集中的列與Java對象中屬性的對應關係。其實,只有在數據庫字段與JavaBean不匹配的狀況下才用到,一般狀況下推薦使用resultType/parameterType,也就是直接利用實體類便可。這種方式很簡便,同時遵循約定大於配置,代碼出錯的可能較小。
中間過程不看了,最後他們也都是註冊到configuration。
public void addParameterMap(ParameterMap pm) {
parameterMaps.put(pm.getId(), pm);
}
public void addResultMap(ResultMap rm) {
resultMaps.put(rm.getId(), rm);
}
複製代碼
SQL標籤可將重複的sql提取出來,使用時用include引用便可,最終達到sql重用的目的。它的解析很簡單,就是把內容放入sqlFragments容器。id爲命名空間+節點ID
動態SQL的解析是Mybatis的核心所在。之因此是動態SQL,源自它不一樣的動態標籤,好比Choose、ForEach、If、Set
等,而Mybatis把它們都封裝成不一樣的類對象,它們共同的接口是SqlNode。
每一種標籤又對應一種處理器。
private void initNodeHandlerMap() {
nodeHandlerMap.put("trim", new TrimHandler());
nodeHandlerMap.put("where", new WhereHandler());
nodeHandlerMap.put("set", new SetHandler());
nodeHandlerMap.put("foreach", new ForEachHandler());
nodeHandlerMap.put("if", new IfHandler());
nodeHandlerMap.put("choose", new ChooseHandler());
nodeHandlerMap.put("when", new IfHandler());
nodeHandlerMap.put("otherwise", new OtherwiseHandler());
nodeHandlerMap.put("bind", new BindHandler());
}
複製代碼
靜態SQL,就是不帶上面那些標籤的節點。它比較簡單,最後就是將SQL內容封裝到MixedSqlNode對象。MixedSqlNode對象裏面有個List,封裝的就是StaticTextSqlNode對象,而StaticTextSqlNode對象只有一個屬性text,即SQL內容。
protected MixedSqlNode parseDynamicTags(XNode node) {
//SQL內容
String data = child.getStringBody("");
//生成TextSqlNode判斷是否爲動態SQL
TextSqlNode textSqlNode = new TextSqlNode(data);
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
} else {
contents.add(new StaticTextSqlNode(data));
}
return new MixedSqlNode(contents);
}
複製代碼
若是是靜態SQL,將SQL語句中的#{}轉爲?,返回StaticSqlSource對象 。
public StaticSqlSource(Configuration configuration, String sql, List<ParameterMapping> parameterMappings) {
this.sql = sql;
this.parameterMappings = parameterMappings;
this.configuration = configuration;
}
複製代碼
一個動態SQL會分爲不一樣的子節點,咱們以一個UPDATE語句爲例,嘗試跟蹤下它的解析過程。好比下面的UPDATE節點會分爲三個子節點。兩個靜態節點和一個SET動態節點,而SET節點又分爲兩個IF動態節點。
<update id="updateUser" parameterType="user">
update user
<set>
<if test="username!=null">
username = #{username},
</if>
<if test="password!=null">
password = #{password},
</if>
</set>
where uid = #{uid}
</update>
[
[#text: update user ],
[set: null],
[#text: where uid = #{uid}]
]
複製代碼
首先,得到當前的節點的內容,即updateUser。調用parseDynamicTags
public class XMLScriptBuilder extends BaseBuilder {
//參數node即爲當前UDATE節點的內容
protected MixedSqlNode parseDynamicTags(XNode node) {
List<SqlNode> contents = new ArrayList<SqlNode>();
//children分爲3個子節點
//2個靜態節點(update user和where id=#{id})
//1個動態節點set
NodeList children = node.getNode().getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
XNode child = node.newXNode(children.item(i));
//若是是靜態節點,將內容封裝成StaticTextSqlNode對象
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
String data = child.getStringBody("");
TextSqlNode textSqlNode = new TextSqlNode(data);
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
} else {
contents.add(new StaticTextSqlNode(data));
}
}
//動態節點
else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
//獲取節點名稱 好比SET/IF
String nodeName = child.getNode().getNodeName();
//獲取節點標籤對應的處理類 好比SetHandler
NodeHandler handler = nodeHandlerMap.get(nodeName);
handler.handleNode(child, contents);
isDynamic = true;
}
}
return new MixedSqlNode(contents);
}
}
複製代碼
第一個是靜態節點update user。根據上面的源碼,它將被封裝成StaticTextSqlNode對象,加入contents集合。
第二個是動態節點SET,他將調用到SetHandler.handleNode()。
private class SetHandler implements NodeHandler {
//nodeToHandle爲SET節點的內容,targetContents爲已解析完成的Node集合
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
//回調parseDynamicTags
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
//最終將SET節點封裝成SetSqlNode,放入Node集合
SetSqlNode set = new SetSqlNode(configuration, mixedSqlNode);
targetContents.add(set);
}
}
複製代碼
在第二次回調到parseDynamicTags
方法時,這時候的參數爲SET節點裏的2個IF子節點。一樣,它們會將當作動態節點解析,調用到IfHandler.handleNode()。
private class IfHandler implements NodeHandler {
//nodeToHandle爲IF節點的內容, targetContents爲已解析完成的Node集合
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
//回調parseDynamicTags
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
//獲取IF標籤的test屬性,即條件表達式
String test = nodeToHandle.getStringAttribute("test");
//將IF節點封裝成IfSqlNode對象,放入Node集合。
IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
targetContents.add(ifSqlNode);
}
}
複製代碼
就這樣,遞歸的調用parseDynamicTags方法,直到傳進來的參數Node爲一個靜態節點,返回StaticTextSqlNode對象,並加入集合中。 第三個是靜態節點where id=#{id},封裝成StaticTextSqlNode對象,加入contents集合。
最後,contents集合就是UPDATE節點對應的各類sqlNode。
若是是動態SQL,返回DynamicSqlSource對象。
public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
this.configuration = configuration;
this.rootSqlNode = rootSqlNode;
}
複製代碼
mapper文件中的每個SELECT/INSERT/UPDATE/DELETE
節點對應一個MappedStatement對象。
public MappedStatement addMappedStatement() {
//全限定類名+方法名
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對象註冊到configuration
//註冊其實就是往Map中添加。mappedStatements.put(ms.getId(), ms);
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}
複製代碼
除了在mapper文件中配置SQL,Mybatis還支持註解方式的SQL。經過@Select,標註在Mapper接口的方法上。
public interface UserMapper {
@Select("select * from user ")
List<User> AnnotationGetUserList();
}
複製代碼
或者你想要的是動態SQL,那麼就加上<script>
。
public interface UserMapper {
@Select("select * from user ")
List<User> AnnotationGetUserList();
@Select("<script>"
+ "select * from user "
+ "<if test='id!=null'>"
+ "where id=#{id}"
+ "</if>"
+ "</script>")
List<User> AnnotationGetUserById(@Param("id")String id);
}
複製代碼
以上這兩種方式都不經常使用,若是你真的不想用mapper.xml文件來定義SQL,那麼如下方式可能適合你。你能夠經過@SelectProvider來聲明一個類的方法,此方法負責返回一個SQL的字符串。
public interface UserMapper {
@SelectProvider(type=SqlProvider.class,method="getUserById")
List<User> AnnotationProviderGetUserById(String id);
}
複製代碼
types指定了類的Class,method就是類的方法。其實這種方式也很不錯,動態SQL的生成不只僅依靠Mybatis的動態標籤,在程序中能夠隨便搞。
public class SqlProvider {
public String getUserById(String id) {
String sql = "select * from user ";
if (id!=null) {
sql += " where id="+id;
}
return sql;
}
}
複製代碼
除了上面的@Select,固然還有對應其它幾種的註解。
sqlAnnotationTypes.add(Select.class);
sqlAnnotationTypes.add(Insert.class);
sqlAnnotationTypes.add(Update.class);
sqlAnnotationTypes.add(Delete.class);
sqlProviderAnnotationTypes.add(SelectProvider.class);
sqlProviderAnnotationTypes.add(InsertProvider.class);
sqlProviderAnnotationTypes.add(UpdateProvider.class);
sqlProviderAnnotationTypes.add(DeleteProvider.class);
複製代碼
上面咱們看完了mapper文件中SQL的解析,下面來看註解是在哪裏被掃描到的呢?
public class MapperRegistry {
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
try {
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
}
}
}
}
複製代碼
type就是當前mapper接口的Class對象。獲取Class對象的全部Method[]。
Method[] methods = type.getMethods();
for (Method method : methods) {
if (!method.isBridge()) {
parseStatement(method);
}
}
複製代碼
parseStatement方法最終就生成MappedStatement對象,註冊到configuration中,這個流程是與XML的方式同樣,不會變的。
void parseStatement(Method method) {
//判斷方法上是否包含那幾種註解
//若是有,就根據註解的內容建立SqlSource對象。建立過程與XML建立過程同樣
SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
if (sqlSource != null) {
//獲取各類屬性,過程略過
......
//建立MappedStatement對象,註冊到configuration
assistant.addMappedStatement();
}
}
複製代碼
因此,咱們看到。getSqlSourceFromAnnotations纔是重點,拿到註解及註解上的值,建立SqlSource對象。
private SqlSource getSqlSourceFromAnnotations(Method method,
Class<?> parameterType, LanguageDriver languageDriver) {
//註解就分爲兩大類,sqlAnnotation和sqlProviderAnnotation
//循環註解列表,判斷Method包含哪種,就返回哪一種類型註解的實例
Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);
Class<? extends Annotation> sqlProviderAnnotationType = getSqlProviderAnnotationType(method);
//不能兩種類型都配置哦
if (sqlAnnotationType != null) {
if (sqlProviderAnnotationType != null) {
throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName());
}
Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);
final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);
return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
}else if (sqlProviderAnnotationType != null) {
Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation, type, method);
}
return null;
}
複製代碼
掃描到註解後,就要根據註解類型的不一樣,建立SqlSource對象。
咱們以AnnotationGetUserList爲例,它的註解是這樣:@Select("select * from user ")
。最後建立SqlSource對象的過程與XML建立過程是同樣的。
public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
//若是是帶script標籤的內容,最終調用到parseDynamicTags方法。
//parseDynamicTags方法會遞歸調用,直到節點屬性爲靜態節點。
if (script.startsWith("<script>")) {
XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
return createSqlSource(configuration, parser.evalNode("/script"), parameterType);
}else {
script = PropertyParser.parse(script, configuration.getVariables());
TextSqlNode textSqlNode = new TextSqlNode(script);
if (textSqlNode.isDynamic()) {
return new DynamicSqlSource(configuration, textSqlNode);
}else {
//把#{}換成?,生成StaticSqlSource對象
return new RawSqlSource(configuration, script, parameterType);
}
}
}
複製代碼
調用到ProviderSqlSource類的構造器,過程比較簡單,就是拿到SqlProvider類上的方法,將方法名、方法參數和參數類型設置一下。
public ProviderSqlSource(Configuration configuration, Object provider, Class<?> mapperType, Method mapperMethod) {
String providerMethodName;
this.configuration = configuration;
this.sqlSourceParser = new SqlSourceBuilder(configuration);
this.providerType = (Class<?>) provider.getClass().getMethod("type").invoke(provider);
providerMethodName = (String) provider.getClass().getMethod("method").invoke(provider);
for (Method m : this.providerType.getMethods()) {
if (providerMethodName.equals(m.getName()) && CharSequence.class.isAssignableFrom(m.getReturnType())) {
this.providerMethod = m;
this.providerMethodArgumentNames = new ParamNameResolver(configuration, m).getNames();
this.providerMethodParameterTypes = m.getParameterTypes();
}
}
}
複製代碼
註冊過程也同XML方式同樣,生成MappedStatement對象,而後設置到configuration中。configuration.addMappedStatement(statement);
本章節主要闡述了Mybatis的啓動過程之一,加載配置信息,解析SQL。最後生成SqlSessionFactory對象。
Mybatis的配置信息較多,但也並不是都須要。經常使用的就是緩存、類型轉換器、類型別名、插件等。
生成SQL的方式大體有mapper.xml和Annotation兩種。Annotation又分爲SqlAnnotation和SqlProviderAnnotation,若是真的想要註解式的SQL,仍是比較推薦SqlProviderAnnotation。