#0 系列目錄#java
#1 SqlSessionFactoryBuilder# 上篇例子中,咱們以 SqlSessionFactoryBuilder 去建立 SqlSessionFactory, 那麼,咱們就先從SqlSessionFactoryBuilder入手, 我們先看看源碼是怎麼實現的。mysql
SqlSessionFactoryBuilder源碼片斷:sql
public class SqlSessionFactoryBuilder { //Reader讀取mybatis配置文件,傳入構造方法 //除了Reader外,其實還有對應的inputStream做爲參數的構造方法, //這也體現了mybatis配置的靈活性 public SqlSessionFactory build(Reader reader) { return build(reader, null, null); } public SqlSessionFactory build(Reader reader, String environment) { return build(reader, environment, null); } //mybatis配置文件 + properties, 此時mybatis配置文件中能夠不配置properties,也能使用${}形式 public SqlSessionFactory build(Reader reader, Properties properties) { return build(reader, null, properties); } //經過XMLConfigBuilder解析mybatis配置,而後建立SqlSessionFactory對象 public SqlSessionFactory build(Reader reader, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); //下面看看這個方法的源碼 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. } } } public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); } }
經過源碼,咱們能夠看到SqlSessionFactoryBuilder 經過XMLConfigBuilder 去解析咱們傳入的mybatis的配置文件, 下面就接着看看 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); } private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { super(new Configuration()); ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); this.parsed = false; this.environment = environment; this.parser = parser; } //外部調用此方法對mybatis配置文件進行解析 public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; //從根節點configuration parseConfiguration(parser.evalNode("/configuration")); return configuration; } //此方法就是解析configuration節點下的子節點 //由此也可看出,咱們在configuration下面能配置的節點爲如下10個節點 private void parseConfiguration(XNode root) { try { propertiesElement(root.evalNode("properties")); //issue #117 read properties first typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); settingsElement(root.evalNode("settings")); environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631 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); } } }
經過以上源碼,咱們就能看出,在mybatis的配置文件中:緩存
#2 配置文件元素## ##2.1 元素1:properties##tomcat
<configuration> <!-- 方法一: 從外部指定properties配置文件, 除了使用resource屬性指定外,還可經過url屬性指定url <properties resource="dbConfig.properties"></properties> --> <!-- 方法二: 直接配置爲xml --> <properties> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/test1"/> <property name="username" value="root"/> <property name="password" value="root"/> </properties>
那麼,我要是 兩種方法都同時用了,那麼哪一種方法優先?當以上兩種方法都xml配置優先, 外部指定properties配置其次
。至於爲何,接下來的源碼分析會提到,請留意一下。服務器
##2.2 元素2:envirements##mybatis
<environments default="development"> <environment id="development"> <!-- JDBC–這個配置直接簡單使用了JDBC的提交和回滾設置。它依賴於從數據源獲得的鏈接來管理事務範圍。 MANAGED–這個配置幾乎沒作什麼。它歷來不提交或回滾一個鏈接。而它會讓容器來管理事務的整個生命週期(好比Spring或JEE應用服務器的上下文)。 --> <transactionManager type="JDBC"/> <!-- UNPOOLED–這個數據源的實現是每次被請求時簡單打開和關閉鏈接 POOLED–mybatis實現的簡單的數據庫鏈接池類型,它使得數據庫鏈接可被複用,沒必要在每次請求時都去建立一個物理的鏈接。 JNDI – 經過jndi從tomcat之類的容器裏獲取數據源。 --> <dataSource type="POOLED"> <!-- 若是上面沒有指定數據庫配置的properties文件,那麼此處能夠這樣直接配置 <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/test1"/> <property name="username" value="root"/> <property name="password" value="root"/> --> <!-- 上面指定了數據庫配置文件, 配置文件裏面也是對應的這四個屬性 --> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> <!-- 我再指定一個environment --> <environment id="test"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <!-- 與上面的url不同 --> <property name="url" value="jdbc:mysql://localhost:3306/demo"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments>
environments元素節點能夠配置多個environment子節點, 怎麼理解呢?app
假如咱們系統的開發環境和正式環境所用的數據庫不同(這是確定的), 那麼能夠設置兩個environment, 兩個id分別對應開發環境(dev)和正式環境(final),那麼經過配置environments的default屬性就能選擇對應的environment了
, 例如,我將environments的deault屬性的值配置爲dev, 那麼就會選擇dev的environment。 至於這個是怎麼實現的,下面源碼就會講。ide
##2.3 解析方法:propertiesElement、environmentsElement## 好啦,上面簡單給你們介紹了一下properties 和 environments 的配置, 接下來就正式開始看源碼了: 上次咱們說過mybatis 是經過XMLConfigBuilder這個類在解析mybatis配置文件的,那麼本次就接着看看XMLConfigBuilder對於properties和environments的解析:
public class XMLConfigBuilder extends BaseBuilder { private boolean parsed; // xml解析器 private XPathParser parser; private String environment; // 上次說到這個方法是在解析mybatis配置文件中能配置的元素節點 // 今天首先要看的就是properties節點和environments節點 private void parseConfiguration(XNode root) { try { // 解析properties元素 propertiesElement(root.evalNode("properties")); //issue #117 read properties first typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); settingsElement(root.evalNode("settings")); // 解析environments元素 environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631 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); } } //下面就看看解析properties的具體方法 private void propertiesElement(XNode context) throws Exception { if (context != null) { // 將子節點的 name 以及value屬性set進properties對象 // 這兒能夠注意一下順序,xml配置優先, 外部指定properties配置其次 Properties defaults = context.getChildrenAsProperties(); // 獲取properties節點上 resource屬性的值 String resource = context.getStringAttribute("resource"); // 獲取properties節點上 url屬性的值, resource和url不能同時配置 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."); } // 把解析出的properties文件set進Properties對象 if (resource != null) { defaults.putAll(Resources.getResourceAsProperties(resource)); } else if (url != null) { defaults.putAll(Resources.getUrlAsProperties(url)); } // 將configuration對象中已配置的Properties屬性與剛剛解析的融合 // configuration這個對象會裝載所解析mybatis配置文件的全部節點元素,之後也會頻頻提到這個對象 // 既然configuration對象用有一系列的get/set方法, 那是否就標誌着咱們可使用java代碼直接配置? // 答案是確定的, 不過使用配置文件進行配置,優點不言而喻 Properties vars = configuration.getVariables(); if (vars != null) { defaults.putAll(vars); } // 把裝有解析配置propertis對象set進解析器, 由於後面可能會用到 parser.setVariables(defaults); // set進configuration對象 configuration.setVariables(defaults); } } //下面再看看解析enviroments元素節點的方法 private void environmentsElement(XNode context) throws Exception { if (context != null) { if (environment == null) { //解析environments節點的default屬性的值 //例如: <environments default="development"> environment = context.getStringAttribute("default"); } //遞歸解析environments子節點 for (XNode child : context.getChildren()) { //<environment id="development">, 只有enviroment節點有id屬性,那麼這個屬性有何做用? //environments 節點下能夠擁有多個 environment子節點 //相似於這樣: <environments default="development"><environment id="development">...</environment><environment id="test">...</environments> //意思就是咱們能夠對應多個環境,好比開發環境,測試環境等, 由environments的default屬性去選擇對應的enviroment String id = child.getStringAttribute("id"); //isSpecial就是根據由environments的default屬性去選擇對應的enviroment if (isSpecifiedEnvironment(id)) { //事務, mybatis有兩種:JDBC 和 MANAGED, 配置爲JDBC則直接使用JDBC的事務,配置爲MANAGED則是將事務託管給容器, TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager")); //enviroment節點下面就是dataSource節點了,解析dataSource節點(下面會貼出解析dataSource的具體方法) DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")); DataSource dataSource = dsFactory.getDataSource(); Environment.Builder environmentBuilder = new Environment.Builder(id) .transactionFactory(txFactory) .dataSource(dataSource); //老規矩,會將dataSource設置進configuration對象 configuration.setEnvironment(environmentBuilder.build()); } } } } //下面看看dataSource的解析方法 private DataSourceFactory dataSourceElement(XNode context) throws Exception { if (context != null) { //dataSource的鏈接池 String type = context.getStringAttribute("type"); //子節點 name, value屬性set進一個properties對象 Properties props = context.getChildrenAsProperties(); //建立dataSourceFactory DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance(); factory.setProperties(props); return factory; } throw new BuilderException("Environment declaration requires a DataSourceFactory."); } }
經過以上對mybatis源碼的解讀,相信你們對mybatis的配置又有了一個深刻的認識。還有一個問題, 上面咱們看到,在配置dataSource的時候使用了 ${driver} 這種表達式, 這種形式是怎麼解析的?其實,是經過PropertyParser這個類解析:
/** * 這個類解析${}這種形式的表達式 */ public class PropertyParser { public static String parse(String string, Properties variables) { VariableTokenHandler handler = new VariableTokenHandler(variables); GenericTokenParser parser = new GenericTokenParser("${", "}", handler); return parser.parse(string); } private static class VariableTokenHandler implements TokenHandler { private Properties variables; public VariableTokenHandler(Properties variables) { this.variables = variables; } public String handleToken(String content) { if (variables != null && variables.containsKey(content)) { return variables.getProperty(content); } return "${" + content + "}"; } } }
以上就是對於properties 和 environments元素節點的分析,比較重要的都在對於源碼的註釋中標出。
##2.4 元素3:typeAliases## typeAliases節點主要用來設置別名,其實這是挺好用的一個功能, 經過配置別名,咱們不用再指定完整的包名,而且還能取別名。
例如: 咱們在使用 com.demo.entity. UserEntity 的時候,咱們能夠直接配置一個別名user, 這樣之後在配置文件中要使用到com.demo.entity.UserEntity的時候,直接使用User便可。
就以上例爲例,咱們來實現一下,看看typeAliases的配置方法:
<configuration> <typeAliases> <!-- 經過package, 能夠直接指定package的名字, mybatis會自動掃描你指定包下面的javabean, 而且默認設置一個別名,默認的名字爲: javabean 的首字母小寫的非限定類名來做爲它的別名。 也可在javabean 加上註解@Alias 來自定義別名, 例如: @Alias(user) <package name="com.dy.entity"/> --> <typeAlias alias="UserEntity" type="com.dy.entity.User"/> </typeAliases> ...... </configuration>
再寫一段測試代碼,看看有沒生效:(我只寫一段僞代碼)
Configuration con = sqlSessionFactory.getConfiguration(); Map<String, Class<?>> typeMap = con.getTypeAliasRegistry().getTypeAliases(); for(Entry<String, Class<?>> entry: typeMap.entrySet()) { System.out.println(entry.getKey() + " ================> " + entry.getValue().getSimpleName()); }
##2.5 解析方法:typeAliasesElement##
/** * 解析typeAliases節點 */ private void typeAliasesElement(XNode parent) { if (parent != null) { for (XNode child : parent.getChildren()) { //若是子節點是package, 那麼就獲取package節點的name屬性, mybatis會掃描指定的package if ("package".equals(child.getName())) { String typeAliasPackage = child.getStringAttribute("name"); //TypeAliasRegistry 負責管理別名, 這兒就是經過TypeAliasRegistry 進行別名註冊, 下面就會看看TypeAliasRegistry源碼 configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage); } else { //若是子節點是typeAlias節點,那麼就獲取alias屬性和type的屬性值 String alias = child.getStringAttribute("alias"); String type = child.getStringAttribute("type"); try { Class<?> clazz = Resources.classForName(type); if (alias == null) { typeAliasRegistry.registerAlias(clazz); } else { typeAliasRegistry.registerAlias(alias, clazz); } } catch (ClassNotFoundException e) { throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e); } } } } }
重要的源碼在這兒:TypeAliasRegistry.java
public class TypeAliasRegistry { //這就是核心所在啊, 原來別名就僅僅經過一個HashMap來實現, key爲別名, value就是別名對應的類型(class對象) private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>(); /** * 如下就是mybatis默認爲咱們註冊的別名 */ public TypeAliasRegistry() { 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); 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); registerAlias("_byte", byte.class); registerAlias("_long", long.class); registerAlias("_short", short.class); registerAlias("_int", int.class); registerAlias("_integer", int.class); registerAlias("_double", double.class); registerAlias("_float", float.class); registerAlias("_boolean", boolean.class); registerAlias("_byte[]", byte[].class); registerAlias("_long[]", long[].class); registerAlias("_short[]", short[].class); registerAlias("_int[]", int[].class); registerAlias("_integer[]", int[].class); registerAlias("_double[]", double[].class); registerAlias("_float[]", float[].class); registerAlias("_boolean[]", boolean[].class); registerAlias("date", Date.class); registerAlias("decimal", BigDecimal.class); registerAlias("bigdecimal", BigDecimal.class); registerAlias("biginteger", BigInteger.class); registerAlias("object", Object.class); registerAlias("date[]", Date[].class); registerAlias("decimal[]", BigDecimal[].class); registerAlias("bigdecimal[]", BigDecimal[].class); registerAlias("biginteger[]", BigInteger[].class); registerAlias("object[]", Object[].class); registerAlias("map", Map.class); registerAlias("hashmap", HashMap.class); registerAlias("list", List.class); registerAlias("arraylist", ArrayList.class); registerAlias("collection", Collection.class); registerAlias("iterator", Iterator.class); registerAlias("ResultSet", ResultSet.class); } /** * 處理別名, 直接從保存有別名的hashMap中取出便可 */ @SuppressWarnings("unchecked") public <T> Class<T> resolveAlias(String string) { try { if (string == null) return null; String key = string.toLowerCase(Locale.ENGLISH); // issue #748 Class<T> value; if (TYPE_ALIASES.containsKey(key)) { value = (Class<T>) TYPE_ALIASES.get(key); } else { value = (Class<T>) Resources.classForName(string); } return value; } catch (ClassNotFoundException e) { throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e); } } /** * 配置文件中配置爲package的時候, 會調用此方法,根據配置的報名去掃描javabean ,而後自動註冊別名 * 默認會使用 Bean 的首字母小寫的非限定類名來做爲它的別名 * 也可在javabean 加上註解@Alias 來自定義別名, 例如: @Alias(user) */ public void registerAliases(String packageName){ registerAliases(packageName, Object.class); } public void registerAliases(String packageName, Class<?> superType){ ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>(); resolverUtil.find(new ResolverUtil.IsA(superType), packageName); Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses(); for(Class<?> type : typeSet){ // Ignore inner classes and interfaces (including package-info.java) // Skip also inner classes. See issue #6 if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) { registerAlias(type); } } } public void registerAlias(Class<?> type) { String alias = type.getSimpleName(); Alias aliasAnnotation = type.getAnnotation(Alias.class); if (aliasAnnotation != null) { alias = aliasAnnotation.value(); } registerAlias(alias, type); } //這就是註冊別名的本質方法, 其實就是向保存別名的hashMap新增值而已, 呵呵, 別名的實現太簡單了,對吧 public void registerAlias(String alias, Class<?> value) { if (alias == null) throw new TypeException("The parameter alias cannot be null"); String key = alias.toLowerCase(Locale.ENGLISH); // issue #748 if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) { throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'."); } TYPE_ALIASES.put(key, value); } public void registerAlias(String alias, String value) { try { registerAlias(alias, Resources.classForName(value)); } catch (ClassNotFoundException e) { throw new TypeException("Error registering type alias "+alias+" for "+value+". Cause: " + e, e); } } /** * 獲取保存別名的HashMap, Configuration對象持有對TypeAliasRegistry的引用,所以,若是須要,咱們能夠經過Configuration對象獲取 */ public Map<String, Class<?>> getTypeAliases() { return Collections.unmodifiableMap(TYPE_ALIASES); } }
由源碼可見,設置別名的原理就這麼簡單,Mybatis默認給咱們設置了很多別名,在上面代碼中均可以見到。
##2.6 元素4:TypeHandler## Mybatis中的TypeHandler是什麼?
不管是 MyBatis 在預處理語句(PreparedStatement)中設置一個參數時,仍是從結果集中取出一個值時,都會用類型處理器將獲取的值以合適的方式轉換成 Java 類型
。Mybatis默認爲咱們實現了許多TypeHandler, 當咱們沒有配置指定TypeHandler時,Mybatis會根據參數或者返回結果的不一樣,默認爲咱們選擇合適的TypeHandler處理
。
那麼,Mybatis爲咱們實現了哪些TypeHandler呢? 咱們怎麼自定義實現一個TypeHandler ? 這些都會在接下來的mybatis的源碼中看到。在看源碼以前,仍是像以前同樣,先看看怎麼配置吧?
<configuration> <typeHandlers> <!-- 當配置package的時候,mybatis會去配置的package掃描TypeHandler <package name="com.dy.demo"/> --> <!-- handler屬性直接配置咱們要指定的TypeHandler --> <typeHandler handler=""/> <!-- javaType 配置java類型,例如String, 若是配上javaType, 那麼指定的typeHandler就只做用於指定的類型 --> <typeHandler javaType="" handler=""/> <!-- jdbcType 配置數據庫基本數據類型,例如varchar, 若是配上jdbcType, 那麼指定的typeHandler就只做用於指定的類型 --> <typeHandler jdbcType="" handler=""/> <!-- 也可二者都配置 --> <typeHandler javaType="" jdbcType="" handler=""/> </typeHandlers> ...... </configuration>
##2.7 解析方法:typeHandlerElement## 上面簡單介紹了一下TypeHandler, 下面就看看mybatis中TypeHandler的源碼了。老規矩,先從對xml的解析講起:
/** * 解析typeHandlers節點 */ private void typeHandlerElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { //子節點爲package時,獲取其name屬性的值,而後自動掃描package下的自定義typeHandler if ("package".equals(child.getName())) { String typeHandlerPackage = child.getStringAttribute("name"); typeHandlerRegistry.register(typeHandlerPackage); } else { //子節點爲typeHandler時, 能夠指定javaType屬性, 也能夠指定jdbcType, 也可二者都指定 //javaType 是指定java類型 //jdbcType 是指定jdbc類型(數據庫類型: 如varchar) String javaTypeName = child.getStringAttribute("javaType"); String jdbcTypeName = child.getStringAttribute("jdbcType"); //handler就是咱們配置的typeHandler String handlerTypeName = child.getStringAttribute("handler"); //resolveClass方法就是咱們上篇文章所講的TypeAliasRegistry裏面處理別名的方法 Class<?> javaTypeClass = resolveClass(javaTypeName); //JdbcType是一個枚舉類型,resolveJdbcType方法是在獲取枚舉類型的值 JdbcType jdbcType = resolveJdbcType(jdbcTypeName); Class<?> typeHandlerClass = resolveClass(handlerTypeName); //註冊typeHandler, typeHandler經過TypeHandlerRegistry這個類管理 if (javaTypeClass != null) { if (jdbcType == null) { typeHandlerRegistry.register(javaTypeClass, typeHandlerClass); } else { typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass); } } else { typeHandlerRegistry.register(typeHandlerClass); } } } } }
接下來看看TypeHandler的管理註冊類:TypeHandlerRegistry.java
/** * typeHandler註冊管理類 */ public final class TypeHandlerRegistry { //源碼一上來,二話不說,幾個大大的HashMap就出現,這不又跟上次講的typeAliases的註冊相似麼 //基本數據類型與其包裝類 private static final Map<Class<?>, Class<?>> reversePrimitiveMap = new HashMap<Class<?>, Class<?>>() { private static final long serialVersionUID = 1L; { put(Byte.class, byte.class); put(Short.class, short.class); put(Integer.class, int.class); put(Long.class, long.class); put(Float.class, float.class); put(Double.class, double.class); put(Boolean.class, boolean.class); put(Character.class, char.class); } }; //這幾個MAP不用說就知道存的是什麼東西吧,命名的好處 private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class); private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new HashMap<Type, Map<JdbcType, TypeHandler<?>>>(); private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this); private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>(); //就像上篇文章講的typeAliases同樣,mybatis也默認給咱們註冊了很多的typeHandler //具體以下 public TypeHandlerRegistry() { register(Boolean.class, new BooleanTypeHandler()); register(boolean.class, new BooleanTypeHandler()); register(JdbcType.BOOLEAN, new BooleanTypeHandler()); register(JdbcType.BIT, new BooleanTypeHandler()); register(Byte.class, new ByteTypeHandler()); register(byte.class, new ByteTypeHandler()); register(JdbcType.TINYINT, new ByteTypeHandler()); register(Short.class, new ShortTypeHandler()); register(short.class, new ShortTypeHandler()); register(JdbcType.SMALLINT, new ShortTypeHandler()); register(Integer.class, new IntegerTypeHandler()); register(int.class, new IntegerTypeHandler()); register(JdbcType.INTEGER, new IntegerTypeHandler()); register(Long.class, new LongTypeHandler()); register(long.class, new LongTypeHandler()); register(Float.class, new FloatTypeHandler()); register(float.class, new FloatTypeHandler()); register(JdbcType.FLOAT, new FloatTypeHandler()); register(Double.class, new DoubleTypeHandler()); register(double.class, new DoubleTypeHandler()); 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()); register(JdbcType.NVARCHAR, new NStringTypeHandler()); register(JdbcType.NCHAR, new NStringTypeHandler()); register(JdbcType.NCLOB, new NClobTypeHandler()); register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler()); register(JdbcType.ARRAY, new ArrayTypeHandler()); register(BigInteger.class, new BigIntegerTypeHandler()); register(JdbcType.BIGINT, new LongTypeHandler()); register(BigDecimal.class, new BigDecimalTypeHandler()); register(JdbcType.REAL, new BigDecimalTypeHandler()); register(JdbcType.DECIMAL, new BigDecimalTypeHandler()); register(JdbcType.NUMERIC, new BigDecimalTypeHandler()); register(Byte[].class, new ByteObjectArrayTypeHandler()); register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler()); register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler()); register(byte[].class, new ByteArrayTypeHandler()); register(byte[].class, JdbcType.BLOB, new BlobTypeHandler()); register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler()); register(JdbcType.LONGVARBINARY, new BlobTypeHandler()); register(JdbcType.BLOB, new BlobTypeHandler()); register(Object.class, UNKNOWN_TYPE_HANDLER); register(Object.class, JdbcType.OTHER, UNKNOWN_TYPE_HANDLER); register(JdbcType.OTHER, UNKNOWN_TYPE_HANDLER); register(Date.class, new DateTypeHandler()); register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler()); register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler()); register(JdbcType.TIMESTAMP, new DateTypeHandler()); register(JdbcType.DATE, new DateOnlyTypeHandler()); register(JdbcType.TIME, new TimeOnlyTypeHandler()); register(java.sql.Date.class, new SqlDateTypeHandler()); register(java.sql.Time.class, new SqlTimeTypeHandler()); register(java.sql.Timestamp.class, new SqlTimestampTypeHandler()); // issue #273 register(Character.class, new CharacterTypeHandler()); register(char.class, new CharacterTypeHandler()); } public boolean hasTypeHandler(Class<?> javaType) { return hasTypeHandler(javaType, null); } public boolean hasTypeHandler(TypeReference<?> javaTypeReference) { return hasTypeHandler(javaTypeReference, null); } public boolean hasTypeHandler(Class<?> javaType, JdbcType jdbcType) { return javaType != null && getTypeHandler((Type) javaType, jdbcType) != null; } public boolean hasTypeHandler(TypeReference<?> javaTypeReference, JdbcType jdbcType) { return javaTypeReference != null && getTypeHandler(javaTypeReference, jdbcType) != null; } public TypeHandler<?> getMappingTypeHandler(Class<? extends TypeHandler<?>> handlerType) { return ALL_TYPE_HANDLERS_MAP.get(handlerType); } public <T> TypeHandler<T> getTypeHandler(Class<T> type) { return getTypeHandler((Type) type, null); } public <T> TypeHandler<T> getTypeHandler(TypeReference<T> javaTypeReference) { return getTypeHandler(javaTypeReference, null); } public TypeHandler<?> getTypeHandler(JdbcType jdbcType) { return JDBC_TYPE_HANDLER_MAP.get(jdbcType); } public <T> TypeHandler<T> getTypeHandler(Class<T> type, JdbcType jdbcType) { return getTypeHandler((Type) type, jdbcType); } public <T> TypeHandler<T> getTypeHandler(TypeReference<T> javaTypeReference, JdbcType jdbcType) { return getTypeHandler(javaTypeReference.getRawType(), jdbcType); } private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) { Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = TYPE_HANDLER_MAP.get(type); TypeHandler<?> handler = null; if (jdbcHandlerMap != null) { handler = jdbcHandlerMap.get(jdbcType); if (handler == null) { handler = jdbcHandlerMap.get(null); } } if (handler == null && type != null && type instanceof Class && Enum.class.isAssignableFrom((Class<?>) type)) { handler = new EnumTypeHandler((Class<?>) type); } @SuppressWarnings("unchecked") // type drives generics here TypeHandler<T> returned = (TypeHandler<T>) handler; return returned; } public TypeHandler<Object> getUnknownTypeHandler() { return UNKNOWN_TYPE_HANDLER; } public void register(JdbcType jdbcType, TypeHandler<?> handler) { JDBC_TYPE_HANDLER_MAP.put(jdbcType, handler); } // // REGISTER INSTANCE // /** * 只配置了typeHandler, 沒有配置jdbcType 或者javaType */ @SuppressWarnings("unchecked") public <T> void register(TypeHandler<T> typeHandler) { boolean mappedTypeFound = false; //在自定義typeHandler的時候,能夠加上註解MappedTypes 去指定關聯的javaType //所以,此處須要掃描MappedTypes註解 MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class); if (mappedTypes != null) { for (Class<?> handledType : mappedTypes.value()) { register(handledType, typeHandler); mappedTypeFound = true; } } // @since 3.1.0 - try to auto-discover the mapped type if (!mappedTypeFound && typeHandler instanceof TypeReference) { try { TypeReference<T> typeReference = (TypeReference<T>) typeHandler; register(typeReference.getRawType(), typeHandler); mappedTypeFound = true; } catch (Throwable t) { // maybe users define the TypeReference with a different type and are not assignable, so just ignore it } } if (!mappedTypeFound) { register((Class<T>) null, typeHandler); } } /** * 配置了typeHandlerhe和javaType */ public <T> void register(Class<T> javaType, TypeHandler<? extends T> typeHandler) { register((Type) javaType, typeHandler); } private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) { //掃描註解MappedJdbcTypes MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class); if (mappedJdbcTypes != null) { for (JdbcType handledJdbcType : mappedJdbcTypes.value()) { register(javaType, handledJdbcType, typeHandler); } if (mappedJdbcTypes.includeNullJdbcType()) { register(javaType, null, typeHandler); } } else { register(javaType, null, typeHandler); } } public <T> void register(TypeReference<T> javaTypeReference, TypeHandler<? extends T> handler) { register(javaTypeReference.getRawType(), handler); } /** * typeHandlerhe、javaType、jdbcType都配置了 */ public <T> void register(Class<T> type, JdbcType jdbcType, TypeHandler<? extends T> handler) { register((Type) type, jdbcType, handler); } /** * 註冊typeHandler的核心方法 * 就是向Map新增數據而已 */ private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) { if (javaType != null) { Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType); if (map == null) { map = new HashMap<JdbcType, TypeHandler<?>>(); TYPE_HANDLER_MAP.put(javaType, map); } map.put(jdbcType, handler); if (reversePrimitiveMap.containsKey(javaType)) { register(reversePrimitiveMap.get(javaType), jdbcType, handler); } } ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler); } // // REGISTER CLASS // // Only handler type public void register(Class<?> typeHandlerClass) { boolean mappedTypeFound = false; MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class); if (mappedTypes != null) { for (Class<?> javaTypeClass : mappedTypes.value()) { register(javaTypeClass, typeHandlerClass); mappedTypeFound = true; } } if (!mappedTypeFound) { register(getInstance(null, typeHandlerClass)); } } // java type + handler type public void register(Class<?> javaTypeClass, Class<?> typeHandlerClass) { register(javaTypeClass, getInstance(javaTypeClass, typeHandlerClass)); } // java type + jdbc type + handler type public void register(Class<?> javaTypeClass, JdbcType jdbcType, Class<?> typeHandlerClass) { register(javaTypeClass, jdbcType, getInstance(javaTypeClass, typeHandlerClass)); } // Construct a handler (used also from Builders) @SuppressWarnings("unchecked") public <T> TypeHandler<T> getInstance(Class<?> javaTypeClass, Class<?> typeHandlerClass) { if (javaTypeClass != null) { try { Constructor<?> c = typeHandlerClass.getConstructor(Class.class); return (TypeHandler<T>) c.newInstance(javaTypeClass); } catch (NoSuchMethodException ignored) { // ignored } catch (Exception e) { throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e); } } try { Constructor<?> c = typeHandlerClass.getConstructor(); return (TypeHandler<T>) c.newInstance(); } catch (Exception e) { throw new TypeException("Unable to find a usable constructor for " + typeHandlerClass, e); } } /** * 根據指定的pacakge去掃描自定義的typeHander,而後註冊 */ public void register(String packageName) { ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>(); resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName); Set<Class<? extends Class<?>>> handlerSet = resolverUtil.getClasses(); for (Class<?> type : handlerSet) { //Ignore inner classes and interfaces (including package-info.java) and abstract classes if (!type.isAnonymousClass() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) { register(type); } } } // get information /** * 經過configuration對象能夠獲取已註冊的全部typeHandler */ public Collection<TypeHandler<?>> getTypeHandlers() { return Collections.unmodifiableCollection(ALL_TYPE_HANDLERS_MAP.values()); } }
由源碼能夠看到, mybatis爲咱們實現了那麼多TypeHandler, 隨便打開一個TypeHandler,看其源碼,均可以看到,它繼承自一個抽象類:BaseTypeHandler, 那麼咱們是否是也能經過繼承BaseTypeHandler,從而實現自定義的TypeHandler
? 答案是確定的, 那麼如今下面就爲你們演示一下自定義TypeHandler。
@MappedJdbcTypes(JdbcType.VARCHAR) //此處若是不用註解指定jdbcType, 那麼,就能夠在配置文件中經過"jdbcType"屬性指定, 同理, javaType 也可經過 @MappedTypes指定 public class ExampleTypeHandler extends BaseTypeHandler<String> { @Override public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException { ps.setString(i, parameter); } @Override public String getNullableResult(ResultSet rs, String columnName) throws SQLException { return rs.getString(columnName); } @Override public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException { return rs.getString(columnIndex); } @Override public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { return cs.getString(columnIndex); } }
而後,就該配置咱們的自定義TypeHandler了:
<configuration> <typeHandlers> <!-- 因爲自定義的TypeHandler在定義時已經經過註解指定了jdbcType, 因此此處不用再配置jdbcType --> <typeHandler handler="ExampleTypeHandler"/> </typeHandlers> ...... </configuration>
也就是說,咱們在自定義TypeHandler的時候,能夠在TypeHandler經過@MappedJdbcTypes指定jdbcType, 經過 @MappedTypes 指定javaType, 若是沒有使用註解指定,那麼咱們就須要在配置文件中配置
。詳細使用,請參見Mybatis類型轉換介紹。
##2.8 元素5:objectFactory## objectFactory是幹什麼的? 須要配置嗎?
MyBatis 每次建立結果對象的新實例時,它都會使用一個對象工廠(ObjectFactory)實例來完成
。默認的對象工廠須要作的僅僅是實例化目標類,要麼經過默認構造方法,要麼在參數映射存在的時候經過參數構造方法來實例化
。默認狀況下,咱們不須要配置,mybatis會調用默認實現的objectFactory。 除非咱們要自定義ObjectFactory的實現, 那麼咱們才須要去手動配置。
那麼怎麼自定義實現ObjectFactory? 怎麼配置呢?自定義ObjectFactory只須要去繼承DefaultObjectFactory(是ObjectFactory接口的實現類),並重寫其方法便可
。具體的,本處很少說,後面再具體講解。
寫好了ObjectFactory, 僅需作以下配置:
<configuration> ...... <objectFactory type="org.mybatis.example.ExampleObjectFactory"> <property name="someProperty" value="100"/> </objectFactory> ...... </configuration>
##2.9 元素6:plugins## plugin有何做用? 須要配置嗎?
plugins 是一個可選配置。mybatis中的plugin其實就是個interceptor
, 它能夠攔截Executor 、ParameterHandler 、ResultSetHandler 、StatementHandler 的部分方法,處理咱們本身的邏輯。Executor就是真正執行sql語句的東西
, ParameterHandler 是處理咱們傳入參數的
,還記得前面講TypeHandler的時候提到過,mybatis默認幫咱們實現了很多的typeHandler, 當咱們不顯示配置typeHandler的時候,mybatis會根據參數類型自動選擇合適的typeHandler執行,其實就是ParameterHandler 在選擇
。ResultSetHandler 就是處理返回結果的
。
怎麼自定義plugin ? 怎麼配置?要自定義一個plugin, 須要去實現Interceptor接口
,這兒不細說,後面實戰部分會詳細講解。定義好以後,配置以下:
<configuration> ...... <plugins> <plugin interceptor="org.mybatis.example.ExamplePlugin"> <property name="someProperty" value="100"/> </plugin> </plugins> ...... </configuration>
##2.10 元素7:mappers## mappers, 這下引出mybatis的核心之一了,mappers做用 ? 須要配置嗎?
mappers 節點下,配置咱們的mapper映射文件, 所謂的mapper映射文件,就是讓mybatis 用來創建數據表和javabean映射的一個橋樑
。在咱們實際開發中,一般一個mapper文件對應一個dao接口, 這個mapper能夠看作是dao的實現
。因此,mappers必須配置。
<configuration> ...... <mappers> <!-- 第一種方式:經過resource指定 --> <mapper resource="com/dy/dao/userDao.xml"/> <!-- 第二種方式, 經過class指定接口,進而將接口與對應的xml文件造成映射關係 不過,使用這種方式必須保證 接口與mapper文件同名(不區分大小寫), 我這兒接口是UserDao,那麼意味着mapper文件爲UserDao.xml <mapper class="com.dy.dao.UserDao"/> --> <!-- 第三種方式,直接指定包,自動掃描,與方法二同理 <package name="com.dy.dao"/> --> <!-- 第四種方式:經過url指定mapper文件位置 <mapper url="file://........"/> --> </mappers> ...... </configuration>
本篇僅做簡單介紹,更高級的使用以及其實現原理,會在後面的實戰部分進行詳細講解。
##2.11 解析方法:objectFactoryElement、pluginElement、mapperElement##
以上幾個節點的解析源碼,與以前提到的那些節點的解析相似,故此處再也不講。 我將源碼摺疊, 須要的能夠打開看看。
/** * objectFactory 節點解析 */ private void objectFactoryElement(XNode context) throws Exception { if (context != null) { //讀取type屬性的值, 接下來進行實例化ObjectFactory, 並set進 configuration //到此,簡單講一下configuration這個對象,其實它裏面主要保存的都是mybatis的配置 String type = context.getStringAttribute("type"); //讀取propertie的值, 根據須要能夠配置, mybatis默認實現的objectFactory沒有使用properties Properties properties = context.getChildrenAsProperties(); ObjectFactory factory = (ObjectFactory) resolveClass(type).newInstance(); factory.setProperties(properties); configuration.setObjectFactory(factory); } } /** * plugins 節點解析 */ private void pluginElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { String interceptor = child.getStringAttribute("interceptor"); Properties properties = child.getChildrenAsProperties(); //因而可知,咱們在定義一個interceptor的時候,須要去實現Interceptor, 這兒先不具體講,之後會詳細講解 Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance(); interceptorInstance.setProperties(properties); configuration.addInterceptor(interceptorInstance); } } } /** * mappers 節點解析 * 這是mybatis的核心之一,這兒先簡單介紹,在接下來的文章會對它進行分析 */ private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) { //若是mappers節點的子節點是package, 那麼就掃描package下的文件, 注入進configuration String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else { String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); //resource, url, class 三選一 if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); //mapper映射文件都是經過XMLMapperBuilder解析 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null) { ErrorContext.instance().resource(url); InputStream inputStream = Resources.getUrlAsStream(url); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url == null && mapperClass != null) { Class<?> mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); } else { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } } } } }
##2.12 元素8:settings##
<settings> <setting name="cacheEnabled" value="true"/> <setting name="lazyLoadingEnabled" value="true"/> <setting name="multipleResultSetsEnabled" value="true"/> <setting name="useColumnLabel" value="true"/> <setting name="useGeneratedKeys" value="false"/> <setting name="enhancementEnabled" value="false"/> <setting name="defaultExecutorType" value="SIMPLE"/> <setting name="defaultStatementTimeout" value="25000"/> </settings>
setting節點裏配置的值會直接改寫Configuration對應的變量值,這些變量描述的是Mybatis的全局運行方式
,若是對這些屬性的含義不熟悉的話建議不要配置,使用默認值便可。下面這個表格描述了各個配置項的含義和默認值:
##2.13 解析方法:settingsElement##
private void settingsElement(XNode context) throws Exception { if (context != null) { Properties props = context.getChildrenAsProperties(); // Check that all settings are known to the configuration class MetaClass metaConfig = MetaClass.forClass(Configuration.class); for (Object key : props.keySet()) { if (!metaConfig.hasSetter(String.valueOf(key))) { throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive)."); } } configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL"))); configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true)); configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory"))); configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false)); configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), true)); configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true)); configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true)); configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false)); configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE"))); configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null)); configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false)); configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false)); configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION"))); configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER"))); configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString")); configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true)); configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage"))); configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false)); configuration.setLogPrefix(props.getProperty("logPrefix")); configuration.setLogImpl(resolveClass(props.getProperty("logImpl"))); configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory"))); } }