摘要:直接通過獲取在中我們可以通過配置文件生成或者獲得了就可以拿他進行數據庫操作了。這樣類通過這個方法獲得進行數據庫操作。扒開外衣,還原本質其實三種實現數據庫操作的方式最終都是通過來操作數據庫的。
前言
在構建一個系統的過程中難免需要對數據存儲,而存儲一般會有緩存(內存)、數據庫(硬盤)兩種存儲介質。
本篇文章我們主要來介紹下在我們通過spring構建應用的過程中如何進行數據庫連接、以及數據庫連接的幾種方式進行簡單介紹。
spring中連接數據庫有如下幾種方式:
直接通過驅動連接數據庫的方式
spring提供的JdbcTemplate
spring集成Mybatis,通過Mybatis的方式進行數據庫連接
原始JDBC方式一般初學者在學到jdbc這個階段都會動手寫下下面這樣的鏈接數據庫的代碼,只需三步就可以從數據庫總拿到數據,這個時候是不是在竊喜終于按照教程把數據拿出來了。見下面代碼:
Class.forName("org.apache.phoenix.jdbc.PhoenixDriver"); Connection connection = DriverManager.getConnection("jdbc:phoenix:10.1.168.1:2181/hbase"); ResultSet rs = connection.createStatement().executeQuery("select * from table limit 10 ");
針對上面的鏈接數據的代碼來深入挖下為什么這樣就能連接上數據庫:
org.apache.phoenix.jdbc.PhoenixDriver這個類在在加載(也就是執行Class.forName(driver))的過程中會執行其靜態代碼塊DriverManager.registerDriver(INSTANCE);
執行registerDriver方法后會往靜態registeredDrivers list中添加PhoenixDriver類。
public static synchronized void registerDriver(java.sql.Driver driver, DriverAction da) throws SQLException { /* Register the driver if it has not already been added to our list */ if(driver != null) { registeredDrivers.addIfAbsent(new DriverInfo(driver, da)); } else { // This is for compatibility with the original DriverManager throw new NullPointerException(); } println("registerDriver: " + driver); }
后面就是進行連接操作DriverManager.getConnection(url)方法源代碼如下:
// Worker method called by the public getConnection() methods. private static Connection getConnection( String url, java.util.Properties info, Class> caller) throws SQLException { /* * When callerCl is null, we should check the application"s * (which is invoking this class indirectly) * classloader, so that the JDBC driver class outside rt.jar * can be loaded from here. */ ClassLoader callerCL = caller != null ? caller.getClassLoader() : null; synchronized(DriverManager.class) { // synchronize loading of the correct classloader. if (callerCL == null) { callerCL = Thread.currentThread().getContextClassLoader(); } } if(url == null) { throw new SQLException("The url cannot be null", "08001"); } println("DriverManager.getConnection("" + url + "")"); // Walk through the loaded registeredDrivers attempting to make a connection. // Remember the first exception that gets raised so we can reraise it. SQLException reason = null; for(DriverInfo aDriver : registeredDrivers) { // If the caller does not have permission to load the driver then // skip it. if(isDriverAllowed(aDriver.driver, callerCL)) { try { println(" trying " + aDriver.driver.getClass().getName()); Connection con = aDriver.driver.connect(url, info); if (con != null) { // Success! println("getConnection returning " + aDriver.driver.getClass().getName()); return (con); } } catch (SQLException ex) { if (reason == null) { reason = ex; } } } else { println(" skipping: " + aDriver.getClass().getName()); } } // if we got here nobody could connect. if (reason != null) { println("getConnection failed: " + reason); throw reason; } println("getConnection: no suitable driver found for "+ url); throw new SQLException("No suitable driver found for "+ url, "08001"); } }
for(DriverInfo aDriver : registeredDrivers)去遍歷所有的Drivers然后創建連接,獲得連接了后面就可以開始進行數據庫操作了。
JdbcTemplate方式這種方式是spring針對原始的JDBC的方式進行了一層封裝將所有的操作都托管給了DataSource。
1、直接通過JdbcTemplate在spring中我們可以通過配置文件生成JdbcTemplate:
或者
@Bean public JdbcTemplate getJdbcTemplate() { DruidDataSource druidDataSource = new DruidDataSource(); return new JdbcTemplate(druidDataSource); }
獲得了JdbcTemplate就可以拿他進行數據庫操作了。
SqlRowSet rowSet = jdbcTemplate.queryForRowSet(sql);
通過上面的范式就可以獲取數據庫中的數據了。這個可以換成queryForXXX(sql)更多查詢方式
2、間接通過JdbcTemplate的方式對于一個普通的類通過繼承org.springframework.jdbc.core.support.JdbcDaoSupport這個類,然后向類中注入DataSource就可以實現JDBC的功能了。
這樣類exampleDao 通過org.springframework.jdbc.core.support.JdbcDaoSupport#getJdbcTemplate這個方法獲得JdbcTemplate進行數據庫操作。
下面看下為什么會這樣?
其實原理比較簡單JdbcDaoSupport這個類組合了JdbcTemplate在進行DataSource注入的時候會去創建一個JdbcTemplate,后面就可以通過JdbcDaoSupport#getJdbcTemplate方法拿到創建好的實例操作數據庫了。
Mybatis的方式 1、SqlSessionTemplate的方式通過上面的方式獲得操作數據的句柄(sqlSessionTemplate),示例通過句柄操作數據獲得數據。
sqlSessionTemplate.selectList()這里換成selectxxx()等,具體可以參見org.mybatis.spring.SqlSessionTemplate中的方法。
使用上面的方式會生成一個MapperFactoryBean在Spring中獲取userMapper對象的時候會自動通過MapperFactoryBean創建出來,這樣就可以直接使用userMapper中的接口方法去查詢數據庫。
注意1:mapperInterface接口中的接口名稱必須和mapper.xml配置中id一致,這樣才能匹配到具體的sql語句。
注意2:同時如果接口中參數名稱和sql語句中參數不一致可以通過在接口中加入注解@Param("code")來進行參數匹配List
注意3:Mybatis從數據庫中拿到數據會自動進行a_b => aB的匹配,所以代碼中用駝峰數據庫中用下劃線的方式,Mybatis能夠進行自動匹配
3、MapperScannerConfigurer自動生成mapper這種方式原理和上面那種方式很相似都是通過MapperFactoryBean來生成mapper。MapperScannerConfigurer在spring啟動的過程中會去掃描basePackage下面所有的接口動態生成MapperFactoryBean。
注:sqlSessionFactoryBeanName這個參數不是必須得,如果spring容器中有多個sqlSessionFactory才需要明確指出來
為什么要使用這種方式?
這種方式主要是解決第二種方式針對每個mapper接口都要進行一次匹配操作,而導致配置拖沓。
扒開外衣,還原本質(Mybatis)其實Mybatis三種實現數據庫操作的方式最終都是通過sqlSessionTemplate來操作數據庫的。為什么這么說,下面來一層層剖析。
org.mybatis.spring.mapper.MapperScannerConfigurer#postProcessBeanDefinitionRegistry這個方法在spring容器啟動的過程中會被調用,函數體:
@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)); }
定義了一個scanner(ClassPathMapperScanner),最后會調用scanner.scan進行掃描basePackage,跟蹤調用層次關系最后會調用到org.mybatis.spring.mapper.ClassPathMapperScanner#processBeanDefinitions方法
private void processBeanDefinitions(SetbeanDefinitions) { 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); } } }
實例化一個GenericBeanDefinition放到容器,從方法體可以看到definition.setBeanClass(this.mapperFactoryBean.getClass());
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);很明顯了這就是在實例化一個MapperFactoryBean對象。
下面轉戰MapperFactoryBean類,發現進行實例化的時候,設置SqlSessionFactory對象的時候進行了SqlSessionTemplate的實例化。
if (!this.externalSqlSession) { this.sqlSession = new SqlSessionTemplate(sqlSessionFactory); } }
這里就生成了一個SqlSessionTemplate 來進行數據庫操作。
再來徹底點轉戰SqlSessionTemplate的實現,new SqlSessionTemplate(sqlSessionFactory)
最后會調用到
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sqlSessionFactory, "Property "sqlSessionFactory" is required"); notNull(executorType, "Property "executorType" is required"); this.sqlSessionFactory = sqlSessionFactory; this.executorType = executorType; this.exceptionTranslator = exceptionTranslator; this.sqlSessionProxy = (SqlSession) newProxyInstance( SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSession.class }, new SqlSessionInterceptor()); }
這個方法體重點是this.sqlSessionProxy這個屬性,由JDK提供的動態代理來動態實例惡化SqlSession.class這里SqlSessionInterceptor通過openSession創建SqlSession。
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { final Environment environment = configuration.getEnvironment(); final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); final Executor executor = configuration.newExecutor(tx, execType); return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
主要是實例化DefaultSqlSession執行器executor。
調用sqlSessionTemplate.selectList方法,最終調用
@Override publicList selectList(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
調用鏈繼續,后面調用:
@Override publicList doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { flushStatements(); Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql); Connection connection = getConnection(ms.getStatementLog()); stmt = handler.prepare(connection); handler.parameterize(stmt); return handler. query(stmt, resultHandler); } finally { closeStatement(stmt); } }
看到了什么?
Connection、StatementHandler,又回到了我們最開始討論的純JDBC方式從數據庫中獲取數據。再往下異步就可以看到
@Override publicList query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); return resultSetHandler. handleResultSets(ps); }
獲取結果的步驟了。
總結好吧!寫了這么多,感覺也有點亂了,各位看官能看到這里也說明足夠有耐性了。JdbcTemplate的深挖就不繼續了,比起Mybatis這種封裝方式輕量級太多,往下扒兩層就出來了。
堅持深挖源碼的習慣,保持好的學習方式。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/67444.html
摘要:本文主要介紹如何使用這個可以直接顯示完整的日志框架,希望對大家能有所幫助。當設置為或時,意味關閉記錄。 在常規項目的開發中可能最容易出問題的地方就在于對數據庫的處理了,在大部分的環境下,我們對數據庫的操作都是使用流行的框架,比如 Hibernate 、 MyBatis 等。由于各種原因,我們有時會想知道在這些框架下實際執行的 SQL 究竟是什么。 雖然 Hibernate 可以在配置...
摘要:本文主要介紹如何使用這個可以直接顯示完整的日志框架,希望對大家能有所幫助。當設置為或時,意味關閉記錄。 在常規項目的開發中可能最容易出問題的地方就在于對數據庫的處理了,在大部分的環境下,我們對數據庫的操作都是使用流行的框架,比如 Hibernate 、 MyBatis 等。由于各種原因,我們有時會想知道在這些框架下實際執行的 SQL 究竟是什么。 雖然 Hibernate 可以在配置...
摘要:常用的方法創建向數據庫發送的對象。創建執行存儲過程的對象設置事務自動提交提交事務回滾事務對象對象用于向數據庫發送語句,對數據庫的增刪改查都可以通過此對象發送語句完成。 1.什么是JDBC JDBC全稱為:Java Data Base Connectivity,它是可以執行SQL語句的Java API 2.為什么我們要用JDBC 市面上有非常多的數據庫,本來我們是需要根據不同的數據庫學...
摘要:實現數據庫分庫分表可以自己實現,也可以使用和實現。分布式數據庫的自增不是自增的。分布式數據庫分頁查詢需要使用插入時間實現。包含分庫分片和讀寫分離功能。 Sharding-Jdbc實現mysql分庫分表 簡單介紹 數據庫分庫分表和讀寫分離區別,分庫分表是在多個庫建相同的表和同一個庫建不同的表,根據隨機或者哈希等方式查找實現。讀寫分離是為了解決數據庫的讀寫性能不足,使用主庫master進行...
閱讀 1000·2021-11-22 13:52
閱讀 1441·2021-11-19 09:40
閱讀 3122·2021-11-16 11:44
閱讀 1263·2021-11-15 11:39
閱讀 3893·2021-10-08 10:04
閱讀 5333·2021-09-22 14:57
閱讀 3096·2021-09-10 10:50
閱讀 3177·2021-08-17 10:13