一窥摄、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)了幻梯。