Mybatis源碼探索

一窥摄、mybatis的Mapper接口實(shí)例化的Bean源碼分析

這里有幾個(gè)問(wèn)題需要了解下:
1、Mapper是接口,它為啥會(huì)被Spring認(rèn)為是Bean畦娄,進(jìn)而去解析它的BeanDefiniton呢谐算?
2熟尉、Mapper既然是接口,Spring為什么能實(shí)例化一個(gè)接口呢洲脂?

答案就是:
1斤儿、Mybatis實(shí)現(xiàn)了Spring的BeanFactoryPostProcessor的拓展點(diǎn)剧包,可以修改bean的定義。
2往果、Spring確實(shí)不會(huì)實(shí)例化一個(gè)接口疆液,因?yàn)檫@是java語(yǔ)言的規(guī)范,無(wú)法突破陕贮。Mybatis通過(guò)JDK動(dòng)態(tài)代理堕油,為接口實(shí)現(xiàn)一個(gè)代理類。所以肮之,Spring實(shí)例化的mapper bean類型就不在是接口類型了掉缺。

下面就是看源碼了。

1.1 我的工程戈擒,引入mybatis的方式如下:

@SpringBootApplication(scanBasePackages = {"com.xxx.xxx"})
@ImportResource({"classpath*:spring/spring-*.xml"})
@EnableTransactionManagement
@EnableAspectJAutoProxy(exposeProxy = true)
public class WebApplication {
  public static void main(String[] args) {
    SpringApplication.run(WebApplication.class, args);
  }
}

我的mybatis文件叫:spring-database.xml眶明。所以mybatis的xml中的bean肯定是通過(guò)@ImportResource,委托給Spring來(lái)加載的峦甩。預(yù)知加載詳情赘来,請(qǐng)繼續(xù)向下面看。

我們來(lái)看下mybatis的xml文件

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
    xmlns:c="http://www.springframework.org/schema/c"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"
        p:dataSource-ref="xxxDataSource" p:mapperLocations="classpath*:mapper/*.xml">
        <property name="configuration">
            <bean class="org.apache.ibatis.session.Configuration">
                <property name="mapUnderscoreToCamelCase" value="true" />
            </bean>
        </property>
    </bean>

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.xxx.xxx.mapper" />
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
    </bean>
</beans>

看到只需要有兩個(gè)bean托管給Spring凯傲,就能做到把你配置的basePackage下所有的mapper接口實(shí)例化為Bean犬辰。是不是很神奇,mapper下全是接口冰单,不用寫一行代碼幌缝,也不用寫任何配置,就神奇的把接口給實(shí)例化了诫欠。sqlSessionFactory我們暫且按下不表涵卵,它是sql執(zhí)行的時(shí)候用到。下面會(huì)詳細(xì)解析下MapperScannerConfigurer類荒叼,我們猜測(cè)它肯定是實(shí)現(xiàn)了某一個(gè)spring拓展點(diǎn)轿偎,spring在加載的時(shí)候會(huì)執(zhí)行這些拓展點(diǎn)。它重寫了某個(gè)方法被廓,把接口這種特殊類成功委托給spring坏晦。下面我們就進(jìn)去MapperScannerConfigurer源碼一探究竟。

1.1 Mybatis實(shí)現(xiàn)的拓展點(diǎn)是哪個(gè)類嫁乘?MapperScannerConfigurer類昆婿。

@Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }

    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    scanner.setAddToConfig(this.addToConfig);
    scanner.setAnnotationClass(this.annotationClass);
    scanner.setMarkerInterface(this.markerInterface);
    scanner.setSqlSessionFactory(this.sqlSessionFactory);
    scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
    scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
    scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
    scanner.setResourceLoader(this.applicationContext);
    scanner.setBeanNameGenerator(this.nameGenerator);
    scanner.registerFilters();
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

這個(gè)方法就是向參數(shù)registry注冊(cè)我自己scan出來(lái)的類。我們看下scan方法:

public int scan(String... basePackages) {
        int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

        doScan(basePackages);

        // Register annotation config processors, if necessary.
        if (this.includeAnnotationConfig) {
            AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
        }

        return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
    }

繼續(xù)看doSanc方法:

@Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

    if (beanDefinitions.isEmpty()) {
      logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
    } else {
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }

super.doSanc是調(diào)用父類通用方法蜓斧,沒(méi)啥好說(shuō)的仓蛆。我們主要看下mybatis繼承類,特殊的處理邏輯挎春。就在方法processBeanDefinitions看疙。

 private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();

      if (logger.isDebugEnabled()) {
        logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() 
          + "' and '" + definition.getBeanClassName() + "' mapperInterface");
      }

      // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean
      definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
      definition.setBeanClass(this.mapperFactoryBean.getClass());

      definition.getPropertyValues().add("addToConfig", this.addToConfig);

      boolean explicitFactoryUsed = false;
      if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
        definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionFactory != null) {
        definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
        explicitFactoryUsed = true;
      }

      if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
        if (explicitFactoryUsed) {
          logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionTemplate != null) {
        if (explicitFactoryUsed) {
          logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
        explicitFactoryUsed = true;
      }

      if (!explicitFactoryUsed) {
        if (logger.isDebugEnabled()) {
          logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
        }
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
    }
  }

有兩個(gè)非常重要的偷天換日的操作:
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
definition.setBeanClass(this.mapperFactoryBean.getClass());
第一個(gè)就是告訴后面Spring實(shí)例化階段豆拨,你在實(shí)例化這個(gè)bean的時(shí)候,不要調(diào)用它的無(wú)參構(gòu)造方法能庆,要調(diào)用我這里指定的有參構(gòu)造方法辽装。
第二個(gè)就是偷偷換掉Bean的類型,把接口類型替換為 MapperFactoryBean這種普通class類型相味。

后面還有個(gè)非常重要的操作:
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
TODO 有空在解釋下。

總結(jié):
到這里我們不用Spring那一套 @Compent殉挽,@Autowired注入方式丰涉,用Mybatis直接的Scaner解析器解析Bean,然后默默的注入到registry容器斯碌,這樣才能交給Spring實(shí)例化一死。為了能讓spring成功實(shí)例化,我們又用代理解決接口不能實(shí)例化的問(wèn)題傻唾。

代理怎么創(chuàng)建的呢投慈?

上面已經(jīng)分析了mapper接口的beanDefinition,它的class類型已經(jīng)由接口類型被偷天換日冠骄,換為了MapperFactoryBean類型伪煤。那么該mapper接口在后面被實(shí)例化的時(shí)候,必然會(huì)被調(diào)用到MapperFactoryBean的getObject方法凛辣。那么很明顯抱既,這個(gè)getObject方法就是動(dòng)態(tài)代理的地方。下面我們看下源碼:

/**
   * {@inheritDoc}
   */
  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

這個(gè)沒(méi)啥好說(shuō)的扁誓,繼續(xù)往下看getMapper防泵。這里要注意mapperInterface是通過(guò)哪個(gè)構(gòu)造方法傳遞進(jìn)來(lái)的呢?
是通過(guò):

public MapperFactoryBean() {
    //intentionally empty 
  }
  
  public MapperFactoryBean(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

看到第二個(gè)有參構(gòu)造方法沒(méi)蝗敢,上面beanDefinition被修改為有參構(gòu)造方法捷泞,這里就用到了吧。這個(gè)也是它能靈活支持各種Mapper接口的根本原因寿谴。
好了锁右,我們繼續(xù)看getMapper方法。如下:

 /**
   * {@inheritDoc}
   */
  @Override
  public <T> T getMapper(Class<T> type) {
    return getConfiguration().getMapper(type, this);
  }

沒(méi)啥好說(shuō)的拭卿,繼續(xù)getMapper骡湖。如下:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return this.mapperRegistry.getMapper(type, sqlSession);
    }

沒(méi)啥好說(shuō)的,繼續(xù)getMapper峻厚。如下:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        } else {
            try {
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
            }
        }
    }

關(guān)鍵的來(lái)了响蕴,mapperProxyFactory.newInstance(sqlSession)。源碼如下:

protected T newInstance(MapperProxy<T> mapperProxy) {
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }

    public T newInstance(SqlSession sqlSession) {
        MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        return this.newInstance(mapperProxy);
    }

第二個(gè)newInstance方法里面惠桃,有個(gè)MapperProxy代理類浦夷,我們看下這個(gè)類辖试。如下:

public class MapperProxy<T> implements InvocationHandler, Serializable {
    private static final long serialVersionUID = -6424540398559729838L;
    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache;

    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }

            if (this.isDefaultMethod(method)) {
                return this.invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }

        MapperMethod mapperMethod = this.cachedMapperMethod(method);
        return mapperMethod.execute(this.sqlSession, args);
    }

熟悉不?實(shí)現(xiàn)了InvocationHandler接口劈狐,典型的JDK動(dòng)態(tài)代理罐孝。里面有幾個(gè)成員變量,sqlSession用來(lái)執(zhí)行sql的肥缔,mapperInterface用來(lái)指明接口的萨西,methodCache用來(lái)保存method到MapperMethod的映射。到這里mapper的interface哼御,就在這里被實(shí)例化出來(lái)了胖替,是一個(gè)代理類的實(shí)例。

第二節(jié)分析這個(gè)代理對(duì)象的MapperMethod調(diào)用過(guò)程坟岔。

二谒兄、mybatis的mapper方法運(yùn)行過(guò)程中的源碼分析

mapper方法調(diào)用的起點(diǎn),就在上面提到的MapperProxy社付。它實(shí)現(xiàn)了InvocationHandler接口承疲,里面必然實(shí)現(xiàn)了invoke方法。觀察得出鸥咖,調(diào)用的真正起點(diǎn)就在 mapperMethod.execute這個(gè)方法內(nèi)燕鸽。

這里的調(diào)用鏈路非常深,前面的就不在寫出來(lái)了扛或,選擇一個(gè)靠近底層的開(kāi)始绵咱。比如下面的NonRegisteringDriver類的connect方法。Mybatis的代理調(diào)用invoke熙兔,肯定會(huì)調(diào)用到這個(gè)connect方法悲伶,去拿數(shù)據(jù)庫(kù)連接。

1住涉、數(shù)據(jù)庫(kù)真正建立連接的地方在:com.mysql.jdbc.NonRegisteringDriver#connect

public java.sql.Connection connect(String url, Properties info) throws SQLException {
        if (url == null) {
            throw SQLError.createSQLException(Messages.getString("NonRegisteringDriver.1"), SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE, null);
        }

        if (StringUtils.startsWithIgnoreCase(url, LOADBALANCE_URL_PREFIX)) {
            return connectLoadBalanced(url, info);
        } else if (StringUtils.startsWithIgnoreCase(url, REPLICATION_URL_PREFIX)) {
            return connectReplicationConnection(url, info);
        }

        Properties props = null;

        if ((props = parseURL(url, info)) == null) {
            return null;
        }

        if (!"1".equals(props.getProperty(NUM_HOSTS_PROPERTY_KEY))) {
            return connectFailover(url, info);
        }

        try {
            Connection newConn = com.mysql.jdbc.ConnectionImpl.getInstance(host(props), port(props), props, database(props), url);

            return newConn;
        } catch (SQLException sqlEx) {
            // Don't wrap SQLExceptions, throw
            // them un-changed.
            throw sqlEx;
        } catch (Exception ex) {
            SQLException sqlEx = SQLError.createSQLException(
                    Messages.getString("NonRegisteringDriver.17") + ex.toString() + Messages.getString("NonRegisteringDriver.18"),
                    SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE, null);

            sqlEx.initCause(ex);

            throw sqlEx;
        }
    }

里面有段代碼:
Connection newConn = com.mysql.jdbc.ConnectionImpl.getInstance(host(props), port(props), props, database(props), url);

可以看到核心在getInstance方法麸锉,如下:

protected static Connection getInstance(String hostToConnectTo, int portToConnectTo, Properties info, String databaseToConnectTo, String url)
            throws SQLException {
        if (!Util.isJdbc4()) {
            return new ConnectionImpl(hostToConnectTo, portToConnectTo, info, databaseToConnectTo, url);
        }

        return (Connection) Util.handleNewInstance(JDBC_4_CONNECTION_CTOR,
                new Object[] { hostToConnectTo, Integer.valueOf(portToConnectTo), info, databaseToConnectTo, url }, null);
    }

核心方法在handleNewInstance。這里要特別留意下JDBC_4_CONNECTION_CTOR這個(gè)Constructor舆声』ǔ粒看下源碼:

private static final Constructor<?> JDBC_4_CONNECTION_CTOR;

    private static final int DEFAULT_RESULT_SET_TYPE = ResultSet.TYPE_FORWARD_ONLY;

    private static final int DEFAULT_RESULT_SET_CONCURRENCY = ResultSet.CONCUR_READ_ONLY;

    static {
        mapTransIsolationNameToValue = new HashMap<String, Integer>(8);
        mapTransIsolationNameToValue.put("READ-UNCOMMITED", TRANSACTION_READ_UNCOMMITTED);
        mapTransIsolationNameToValue.put("READ-UNCOMMITTED", TRANSACTION_READ_UNCOMMITTED);
        mapTransIsolationNameToValue.put("READ-COMMITTED", TRANSACTION_READ_COMMITTED);
        mapTransIsolationNameToValue.put("REPEATABLE-READ", TRANSACTION_REPEATABLE_READ);
        mapTransIsolationNameToValue.put("SERIALIZABLE", TRANSACTION_SERIALIZABLE);

        if (Util.isJdbc4()) {
            try {
                JDBC_4_CONNECTION_CTOR = Class.forName("com.mysql.jdbc.JDBC4Connection")
                        .getConstructor(new Class[] { String.class, Integer.TYPE, Properties.class, String.class, String.class });
            } catch (SecurityException e) {
                throw new RuntimeException(e);
            } catch (NoSuchMethodException e) {
                throw new RuntimeException(e);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        } else {
            JDBC_4_CONNECTION_CTOR = null;
        }
    }

看到JDBC_4_CONNECTION_CTOR = Class.forName("com.mysql.jdbc.JDBC4Connection")
.getConstructor(new Class[] { String.class, Integer.TYPE, Properties.class, String.class, String.class });
我們?cè)诨仡^看下handleNewInstance方法的實(shí)現(xiàn),如下:

public static final Object handleNewInstance(Constructor<?> ctor, Object[] args, ExceptionInterceptor exceptionInterceptor) throws SQLException {
        try {

            return ctor.newInstance(args);
        } catch (IllegalArgumentException e) {
            throw SQLError.createSQLException("Can't instantiate required class", SQLError.SQL_STATE_GENERAL_ERROR, e, exceptionInterceptor);
        } catch (InstantiationException e) {
            throw SQLError.createSQLException("Can't instantiate required class", SQLError.SQL_STATE_GENERAL_ERROR, e, exceptionInterceptor);
        } catch (IllegalAccessException e) {
            throw SQLError.createSQLException("Can't instantiate required class", SQLError.SQL_STATE_GENERAL_ERROR, e, exceptionInterceptor);
        } catch (InvocationTargetException e) {
            Throwable target = e.getTargetException();

            if (target instanceof SQLException) {
                throw (SQLException) target;
            }

            if (target instanceof ExceptionInInitializerError) {
                target = ((ExceptionInInitializerError) target).getException();
            }

            throw SQLError.createSQLException(target.toString(), SQLError.SQL_STATE_GENERAL_ERROR, target, exceptionInterceptor);
        }
    }

很明顯媳握,在ctor.newInstance這里調(diào)用反射就能得到Connection了碱屁。那么args里面的參數(shù)到底怎么用的呢 ?就需要追溯到上面的com.mysql.jdbc.JDBC4Connection的構(gòu)造方法了蛾找,如下:

public JDBC4Connection(String hostToConnectTo, int portToConnectTo, Properties info, String databaseToConnectTo, String url) throws SQLException {
        super(hostToConnectTo, portToConnectTo, info, databaseToConnectTo, url);
    }

繼續(xù)追溯super娩脾,如下:

public ConnectionImpl(String hostToConnectTo, int portToConnectTo, Properties info, String databaseToConnectTo, String url) throws SQLException {

        this.connectionCreationTimeMillis = System.currentTimeMillis();

        if (databaseToConnectTo == null) {
            databaseToConnectTo = "";
        }

        // Stash away for later, used to clone this connection for Statement.cancel and Statement.setQueryTimeout().
        //

        this.origHostToConnectTo = hostToConnectTo;
        this.origPortToConnectTo = portToConnectTo;
        this.origDatabaseToConnectTo = databaseToConnectTo;

        try {
            Blob.class.getMethod("truncate", new Class[] { Long.TYPE });

            this.isRunningOnJDK13 = false;
        } catch (NoSuchMethodException nsme) {
            this.isRunningOnJDK13 = true;
        }

        this.sessionCalendar = new GregorianCalendar();
        this.utcCalendar = new GregorianCalendar();
        this.utcCalendar.setTimeZone(TimeZone.getTimeZone("GMT"));

        //
        // Normally, this code would be in initializeDriverProperties, but we need to do this as early as possible, so we can start logging to the 'correct'
        // place as early as possible...this.log points to 'NullLogger' for every connection at startup to avoid NPEs and the overhead of checking for NULL at
        // every logging call.
        //
        // We will reset this to the configured logger during properties initialization.
        //
        this.log = LogFactory.getLogger(getLogger(), LOGGER_INSTANCE_NAME, getExceptionInterceptor());

        if (NonRegisteringDriver.isHostPropertiesList(hostToConnectTo)) {
            Properties hostSpecificProps = NonRegisteringDriver.expandHostKeyValues(hostToConnectTo);

            Enumeration<?> propertyNames = hostSpecificProps.propertyNames();

            while (propertyNames.hasMoreElements()) {
                String propertyName = propertyNames.nextElement().toString();
                String propertyValue = hostSpecificProps.getProperty(propertyName);

                info.setProperty(propertyName, propertyValue);
            }
        } else {

            if (hostToConnectTo == null) {
                this.host = "localhost";
                this.hostPortPair = this.host + ":" + portToConnectTo;
            } else {
                this.host = hostToConnectTo;

                if (hostToConnectTo.indexOf(":") == -1) {
                    this.hostPortPair = this.host + ":" + portToConnectTo;
                } else {
                    this.hostPortPair = this.host;
                }
            }
        }

        this.port = portToConnectTo;

        this.database = databaseToConnectTo;
        this.myURL = url;
        this.user = info.getProperty(NonRegisteringDriver.USER_PROPERTY_KEY);
        this.password = info.getProperty(NonRegisteringDriver.PASSWORD_PROPERTY_KEY);

        if ((this.user == null) || this.user.equals("")) {
            this.user = "";
        }

        if (this.password == null) {
            this.password = "";
        }

        this.props = info;

        initializeDriverProperties(info);

        // We store this per-connection, due to static synchronization issues in Java's built-in TimeZone class...
        this.defaultTimeZone = TimeUtil.getDefaultTimeZone(getCacheDefaultTimezone());

        this.isClientTzUTC = !this.defaultTimeZone.useDaylightTime() && this.defaultTimeZone.getRawOffset() == 0;

        if (getUseUsageAdvisor()) {
            this.pointOfOrigin = LogUtils.findCallingClassAndMethod(new Throwable());
        } else {
            this.pointOfOrigin = "";
        }

        try {
            this.dbmd = getMetaData(false, false);
            initializeSafeStatementInterceptors();
            createNewIO(false);
            unSafeStatementInterceptors();
        } catch (SQLException ex) {
            cleanup(ex);

            // don't clobber SQL exceptions
            throw ex;
        } catch (Exception ex) {
            cleanup(ex);

            StringBuilder mesg = new StringBuilder(128);

            if (!getParanoid()) {
                mesg.append("Cannot connect to MySQL server on ");
                mesg.append(this.host);
                mesg.append(":");
                mesg.append(this.port);
                mesg.append(".\n\n");
                mesg.append("Make sure that there is a MySQL server ");
                mesg.append("running on the machine/port you are trying ");
                mesg.append("to connect to and that the machine this software is running on ");
                mesg.append("is able to connect to this host/port (i.e. not firewalled). ");
                mesg.append("Also make sure that the server has not been started with the --skip-networking ");
                mesg.append("flag.\n\n");
            } else {
                mesg.append("Unable to connect to database.");
            }

            SQLException sqlEx = SQLError.createSQLException(mesg.toString(), SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE, getExceptionInterceptor());

            sqlEx.initCause(ex);

            throw sqlEx;
        }

        NonRegisteringDriver.trackConnection(this);
    }

找到有價(jià)值的代碼。createNewIo(false)方法打毛。到這里就不向下追溯了柿赊,如果繼續(xù)向下追溯俩功,那么肯定會(huì)看到網(wǎng)絡(luò)通信相關(guān)的代碼。比如:建立socket碰声,指定ip和端口诡蜓。然后再往下就肯定是socket相關(guān)的系統(tǒng)調(diào)用了,即java的native方法了胰挑。如:native void socketConnect(InetAddress address, int port, int timeout) throws IOException 方法蔓罚。

只要知道這里我們終于和數(shù)據(jù)庫(kù)服務(wù)器建立起了TCP連接,那么就表示真正創(chuàng)建了Connection瞻颂。有了Sockect的Connection脚粟,才是真正的連接。至于這個(gè)連接用什么管理蘸朋,無(wú)所謂,很多框架可以管理扣唱。比如:Druid藕坯,c3p0等等連接池框架。都可以幫忙hold組連接噪沙,并提供非常方便炼彪,高可用,高性能的連接池管理正歼。

連接池有人管理了辐马,那么這個(gè)Connection接口上的功能有誰(shuí)來(lái)實(shí)現(xiàn)呢?java只提供了java.sql.*接口類局义,并沒(méi)有提供實(shí)現(xiàn)類喜爷。這種功能就由數(shù)據(jù)庫(kù)廠商來(lái)實(shí)現(xiàn),java語(yǔ)言只是約定一個(gè)標(biāo)準(zhǔn)萄唇,或者一個(gè)規(guī)范檩帐。

Connection的語(yǔ)義(實(shí)現(xiàn))有人來(lái)做了,那么剩下的事情就是如何來(lái)操作Connection連接另萤。這個(gè)功能就交給ORM框架湃密,也就是Mybatis了。到現(xiàn)在mybatis終于排上用場(chǎng)了四敞。

總結(jié):
mybatis可以很方便的把sql語(yǔ)句泛源,bind到statement語(yǔ)義。statement語(yǔ)義由數(shù)據(jù)庫(kù)廠商實(shí)現(xiàn)(mysql)忿危,通過(guò)Connection來(lái)操作數(shù)據(jù)庫(kù)达箍。為了高效的操作數(shù)據(jù)庫(kù),使用了Druid數(shù)據(jù)庫(kù)連接池來(lái)管理癌蚁。到此整個(gè)過(guò)程就串起來(lái)了幻梯。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末兜畸,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子碘梢,更是在濱河造成了極大的恐慌咬摇,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,589評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件煞躬,死亡現(xiàn)場(chǎng)離奇詭異肛鹏,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)恩沛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門在扰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人雷客,你說(shuō)我怎么就攤上這事芒珠。” “怎么了搅裙?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,933評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵皱卓,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我部逮,道長(zhǎng)娜汁,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,976評(píng)論 1 295
  • 正文 為了忘掉前任兄朋,我火速辦了婚禮掐禁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘颅和。我一直安慰自己傅事,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布峡扩。 她就那樣靜靜地躺著享完,像睡著了一般。 火紅的嫁衣襯著肌膚如雪有额。 梳的紋絲不亂的頭發(fā)上般又,一...
    開(kāi)封第一講書(shū)人閱讀 51,775評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音巍佑,去河邊找鬼茴迁。 笑死,一個(gè)胖子當(dāng)著我的面吹牛萤衰,可吹牛的內(nèi)容都是我干的堕义。 我是一名探鬼主播,決...
    沈念sama閱讀 40,474評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼倦卖!你這毒婦竟也來(lái)了洒擦?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,359評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤怕膛,失蹤者是張志新(化名)和其女友劉穎熟嫩,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體褐捻,經(jīng)...
    沈念sama閱讀 45,854評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡掸茅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了柠逞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片昧狮。...
    茶點(diǎn)故事閱讀 40,146評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖板壮,靈堂內(nèi)的尸體忽然破棺而出逗鸣,到底是詐尸還是另有隱情,我是刑警寧澤绰精,帶...
    沈念sama閱讀 35,826評(píng)論 5 346
  • 正文 年R本政府宣布慕购,位于F島的核電站,受9級(jí)特大地震影響茬底,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜获洲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評(píng)論 3 331
  • 文/蒙蒙 一阱表、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧贡珊,春花似錦最爬、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,029評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至寒随,卻和暖如春糠悯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背妻往。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,153評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工互艾, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人讯泣。 一個(gè)月前我還...
    沈念sama閱讀 48,420評(píng)論 3 373
  • 正文 我出身青樓纫普,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親好渠。 傳聞我的和親對(duì)象是個(gè)殘疾皇子昨稼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評(píng)論 2 356