<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                [TOC] # db-spring-boot-starter 前面項目中,咱們使用了 [01.db-core模塊](01.db-core%E6%A8%A1%E5%9D%97.md)為整個項目提供通用的數據庫處理,現在我們將采用springboot 標準starter的做法,重構項目基礎組件,利用org.springframework.boot.autoconfigure,完成對象的基本裝配。同時他具有以下功能: * 集成druid數據源 * 集成mybatis-plus * 動態數據源切換 * pagehelper分頁處理 * Guava ## db-spring-boot-starter代碼分析 * 工具類 ![](https://img.kancloud.cn/66/39/663991e94fed078fe0728f7a522e2a0a_1283x581.png) * AOP切換數據源類 ![](https://img.kancloud.cn/c5/f3/c5f3ac49fb355d8406a42407111f708b_1530x611.png) * 動態數據源定義core log ![](https://img.kancloud.cn/77/7e/777ede181c9cf389d069dc5636086b1b_1626x640.png) * 多數據源自動裝配定義 ![](https://img.kancloud.cn/07/87/078711f35a963869ee0b8eeb4816b632_1834x645.png) ### druid配置 ``` initial-size: 1 max-active: 20 min-idle: 1 # 配置獲取連接等待超時的時間 max-wait: 60000 #打開PSCache,并且指定每個連接上PSCache的大小 pool-prepared-statements: true max-pool-prepared-statement-per-connection-size: 20 validation-query: SELECT 'x' test-on-borrow: false test-on-return: false test-while-idle: true #配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接,單位是毫秒 time-between-eviction-runs-millis: 60000 #配置一個連接在池中最小生存的時間,單位是毫秒 min-evictable-idle-time-millis: 300000 ``` validationQuery和testWhileIdle這兩個參數一起用,用來不間斷檢測是否有失效的鏈接,避免高并發的出現失效鏈接; 數據庫連接池在初始化的時候會創建initialSize個連接,當有數據庫操作時,會從池中取出一個連接。如果當前池中正在使用的連接數等于maxActive,則會等待一段時間,等待其他操作釋放掉某一個連接,如果這個等待時間超過了maxWait,則會報錯;如果當前正在使用的連接數沒有達到maxActive,則判斷當前是否空閑連接,如果有則直接使用空閑連接,如果沒有則新建立一個連接。在連接使用完畢后,不是將其物理連接關閉,而是將其放入池中等待其他操作復用。 同時連接池內部有機制判斷,如果當前的總的連接數少于miniIdle,則會建立新的空閑連接,以保證連接數得到miniIdle。如果當前連接池中某個連接在空閑了timeBetweenEvictionRunsMillis時間后仍然沒有使用,則被物理性的關閉掉。有些數據庫連接的時候有超時限制(mysql連接在8小時后斷開),或者由于網絡中斷等原因,連接池的連接會出現失效的情況,這時候設置一個testWhileIdle參數為true,可以保證連接池內部定時檢測連接的可用性,不可用的連接會被拋棄或者重建,最大情況的保證從連接池中得到的Connection對象是可用的。當然,為了保證絕對的可用性,你也可以使用testOnBorrow為true(即在獲取Connection對象時檢測其可用性),不過這樣會影響性能。 ### 動態數據源詳解 * [15.動態數據源配置](18.%E5%8A%A8%E6%80%81%E6%95%B0%E6%8D%AE%E6%BA%90%E9%85%8D%E7%BD%AE.md) ## db-spring-boot-starter 如何使用 > user-center代碼 * user-center pom文件使用 ![](https://img.kancloud.cn/3f/6f/3f6f3f642f446d680a5af9300efc405e_1530x400.png) * user-center application.yml ![](https://img.kancloud.cn/40/a5/40a57e25f8c3cd6521c05798abf79b77_1846x617.png) * 編寫dao xml代碼 ![](https://img.kancloud.cn/50/2c/502c2953cf4547194c91a530b38cd903_1850x738.png) ## spring事務 ![](https://img.kancloud.cn/91/4b/914b4231d49e92b47243d3e629dacaef_2178x277.png) spring事務抽象 * PlatformTransactionManager 事務管理接口,事務的開啟,提交,回滾 * TransactionDefinition 事務的屬性,傳播屬性等 * TransactionStatus 事務的運行狀態 ``` @Test // select @@GLOBAL.tx_isolation ,@@tx_isolation @Transactional( propagation = Propagation.REQUIRED ,isolation=Isolation.DEFAULT) public void testSaveException2() { Map dmo = Maps.newHashMap(); dmo.put("id", 3); dmo.put("name", "3"); testDao.save(dmo); Throwables.throwIfUnchecked(new RuntimeException("模擬業務出錯")); } @Test public void testSaveException3() { DefaultTransactionDefinition definition = new DefaultTransactionDefinition(); definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); TransactionStatus status = txManager.getTransaction(definition) ; try{ Map dmo = Maps.newHashMap(); dmo.put("id", 5); dmo.put("name", "5"); testDao.save(dmo); Throwables.throwIfUnchecked(new RuntimeException("模擬業務出錯")); txManager.commit(status); }catch (Exception e) { txManager.rollback(status); } } ``` ### aop與傳播機制問題 ![](https://img.kancloud.cn/74/2b/742bfad2ab8772ccc979d001877254d8_1565x785.png) ### @Transactional try catch 后手動回退事務 ``` TransactionAspectSupport.currentTransactionStatus().setRollbackOnly() ``` ### transactionTemplate 異常后回退事務 ``` transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION\_RE QUIRED); transactionTemplate.execute(item->{ try { compareService.insert2(); } catch (Exception e) { item.setRollbackOnly(); } return Boolean.FALSE; }); return "hello" ; } ``` ### 多線程與事務 ``` @Transactional( rollbackFor = Exception.class) public String delete(List<String> strList){ try{ ConnectionHolder connHolder1 = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource) ; // com.mysql.cj.jdbc.ConnectionImpl@214f85e6 IntStream.range(0, (strList.size() + 100 - 1)/100) .mapToObj(i -> strList.subList(i * 100, Math.min(strList.size(), (i+1) * 100))).parallel() .forEach(batch -> { ConnectionHolder connHolder2 = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource) ; if(connHolder1 == connHolder2) { System.out.println(1); } tableMapper.update("1"); } ); System.out.println(1/0); } catch (Exception e){ TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } return "OK"; } ``` ### CompletableFuture.runAsync 與事務問題 ![](https://img.kancloud.cn/a5/f6/a5f6456fb6bfad7a902c38964e75e9a0_1023x641.png) 需要下沉到另外一個類 ![](https://img.kancloud.cn/ab/75/ab75e8f0c13966e55365c9cb82b27fab_1070x682.png) ### 源碼解析 ~~~ public static Connection doGetConnection(DataSource dataSource) throws SQLException { Assert.notNull(dataSource, "No DataSource specified"); //TransactionSynchronizationManager重點!!!有沒有很熟悉的感覺?? //還記得我們前面Spring事務源碼的分析嗎?@Transaction會創建Connection,并放入ThreadLocal中 //這里從ThreadLocal中獲取ConnectionHolder ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource); if (conHolder == null || !conHolder.hasConnection() && !conHolder.isSynchronizedWithTransaction()) { logger.debug("Fetching JDBC Connection from DataSource"); //如果沒有使用@Transaction,那調用Mapper接口方法時,也是通過Spring的方法獲取Connection Connection con = fetchConnection(dataSource); if (TransactionSynchronizationManager.isSynchronizationActive()) { logger.debug("Registering transaction synchronization for JDBC Connection"); ConnectionHolder holderToUse = conHolder; if (conHolder == null) { holderToUse = new ConnectionHolder(con); } else { conHolder.setConnection(con); } holderToUse.requested(); TransactionSynchronizationManager.registerSynchronization(new DataSourceUtils.ConnectionSynchronization(holderToUse, dataSource)); holderToUse.setSynchronizedWithTransaction(true); if (holderToUse != conHolder) { //將獲取到的ConnectionHolder放入ThreadLocal中,那么當前線程調用下一個接口,下一個接口使用了Spring事務,那Spring事務也可以直接取到Mybatis創建的Connection //通過ThreadLocal保證了同一線程中Spring事務使用的Connection和Mapper代理類使用的Connection是同一個 TransactionSynchronizationManager.bindResource(dataSource, holderToUse); } } return con; } else { conHolder.requested(); if (!conHolder.hasConnection()) { logger.debug("Fetching resumed JDBC Connection from DataSource"); conHolder.setConnection(fetchConnection(dataSource)); } //所以如果我們業務代碼使用了@Transaction注解,在Spring中就已經通過dataSource創建了一個Connection并放入ThreadLocal中 //那么當Mapper代理對象調用方法時,通過SqlSession的SpringManagedTransaction獲取連接時,就直接獲取到了當前線程中Spring事務創建的Connection并返回 return conHolder.getConnection(); } } 我們看到直接從ThreadLocal中取出來的conn,而spring自己的事務也是操作的這個ThreadLocal中的conn來進行事務的開啟和回滾,由此我們知道了在同一線程中Spring事務中的Connection和Mybaits中Mapper代理對象中操作數據庫的Connection是同一個,當取出來的conn為空時候,調用org.springframework.jdbc.datasource.DataSourceUtils#fetchConnection獲取,然后把從數據源取出來的連接返回 private static Connection fetchConnection(DataSource dataSource) throws SQLException { //從數據源取出來conn Connection con = dataSource.getConnection(); if (con == null) { throw new IllegalStateException("DataSource returned null from getConnection(): " + dataSource); } return con; } ~~~ ## mybatis-plus 介紹 * MyBatis 是一款優秀的持久層框架,其目的是想當做互聯網的籬笆墻,圍繞著數據庫提供持久化服務的一個框架,支持自定義 SQL、存儲過程及高級映射。 * MyBatis 免除了幾乎所有的 JDBC 代碼以及設置參數和獲取結果集的工作,還可以通過簡單的 XML 或注解來配置和映射原始類型、接口和 Java POJO(Plain Ordinary Java Object,普通 Java 對象)為數據庫中的記錄。 * [MyBatis-Plus](https://github.com/baomidou/mybatis-plus)(簡稱 MP)是一個[MyBatis](http://www.mybatis.org/mybatis-3/)的增強工具,在 MyBatis 的基礎上只做增強不做改變,為簡化開發、提高效率而生。 ### 使用mybatis編寫Dao ![](https://img.kancloud.cn/50/2c/502c2953cf4547194c91a530b38cd903_1850x738.png) ## db-spring-boot-starter自動裝配原理解析 咱們想想,在不同項目中,咱們的項目是如何裝配這些對象的嗎?下面咱們需要揭密。 * db-spring-boot-starter 中定義了spring.factories文件 ![](https://img.kancloud.cn/7b/65/7b65c4f503da46d6cf96d56d516f9684_1554x443.png) * DataSourceAutoConfig 中@Import(DataSourceAOP.class) ![](https://img.kancloud.cn/c4/88/c48802a1d13011b1baf97f7c7b698fb7_1055x298.png) 那么這些文件是如何完成加載到spring容器的呢? 此時,咱們必須回到user-center,閱讀源碼 * @SpringBootApplication ![](https://img.kancloud.cn/9b/cf/9bcf14c2f60672903bd0f633c4f868a2_1545x621.png) * @EnableAutoConfiguration ![](https://img.kancloud.cn/3a/27/3a2745529f91f89fccc11b06c74879b4_1088x677.png) * AutoConfigurationImportSelector ![](https://img.kancloud.cn/08/b7/08b727562ad33adfd71fdf4bed7810f8_1007x485.png) 閱讀到這里,我們了解到,user-center在啟動時,由于@SpringBootApplication是復合注解,包含@EnableAutoConfiguration,這個類中@import了核心處理類AutoConfigurationImportSelector,這個類的核心就是將classpath中搜索所有META-INF/spring.factories配置文件,并且將其中org.springframework.boot.autoconfigure.EnableAutoConfiguration key對應的配置項加載到spring容器 ## SPI機制的使用 ``` SPI的全名為Service Provider Interface,簡單的總結下java spi機制的思想。我們系統里抽象的各個模塊,往往有很多不同的實現方案,比如日志模塊的方案,xml解析模塊、jdbc模塊的方案等。面向的對象的設計里,我們一般推薦模塊之間基于接口編程,模塊之間不對實現類進行硬編碼。一旦代碼里涉及具體的實現類,就違反了可拔插的原則,如果需要替換一種實現,就需要修改代碼。為了實現在模塊裝配的時候能不在程序里動態指明,這就需要一種服務發現機制。 java spi就是提供這樣的一個機制:為某個接口尋找服務實現的機制 ``` ### JDK SPI 在 JDBC 中的應用 JDK 中只定義了一個 java.sql.Driver 接口,具體的實現是由不同數據庫廠商來提供的。這里以 MySQL 提供的 JDBC 實現包為例進行分析。 在 mysql-connector-java-*.jar 包中的 META-INF/services 目錄下,有一個 java.sql.Driver 文件中只有一行內容,如下所示: ``` com.mysql.cj.jdbc.Driver ``` 在使用 mysql-connector-java-*.jar 包連接 MySQL 數據庫的時候,我們會用到如下語句創建數據庫連接: ``` String?url?=?"jdbc:mysql://59.110.164.254:3306/user-center?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false "; Connection?conn?=?DriverManager.getConnection(url,?username,?pwd); ``` DriverManager 是 JDK 提供的數據庫驅動管理器,其中的代碼片段,如下所示: ``` static?{ ????loadInitialDrivers(); ????println("JDBC?DriverManager?initialized"); } ``` 在調用 getConnection() 方法的時候,DriverManager 類會被 Java 虛擬機加載、解析并觸發 static 代碼塊的執行,在 loadInitialDrivers() 方法中通過 JDK SPI 掃描 Classpath 下 ?java.sql.Driver 接口實現類并實例化,核心實現如下所示: ``` private?static?void?loadInitialDrivers()?{ ????String?drivers?=?System.getProperty("jdbc.drivers") ????//?使用?JDK?SPI機制加載所有?java.sql.Driver實現類 ????ServiceLoader<Driver>?loadedDrivers?=? ???????????ServiceLoader.load(Driver.class); ????Iterator<Driver>?driversIterator?=?loadedDrivers.iterator(); ????while(driversIterator.hasNext())?{ ????????driversIterator.next(); ????} ????String[]?driversList?=?drivers.split(":"); ????for?(String?aDriver?:?driversList)?{?//?初始化Driver實現類 ????????Class.forName(aDriver,?true, ????????????ClassLoader.getSystemClassLoader()); ????} } ``` 在 MySQL 提供的 com.mysql.cj.jdbc.Driver 實現類中,同樣有一段 static 靜態代碼塊,這段代碼會創建一個 com.mysql.cj.jdbc.Driver 對象并注冊到 DriverManager.registeredDrivers 集合中( CopyOnWriteArrayList 類型),如下所示: ``` static?{ ???java.sql.DriverManager.registerDriver(new?Driver()); } ``` 在 getConnection() 方法中,DriverManager 從該 registeredDrivers 集合中獲取對應的 Driver 對象創建 Connection,核心實現如下所示: ``` private?static?Connection?getConnection(String?url,?java.util.Properties?info,?Class<?>?caller)?throws?SQLException?{ ????//?省略?try/catch代碼塊以及權限處理邏輯 ????for(DriverInfo?aDriver?:?registeredDrivers)?{ ????????Connection?con?=?aDriver.driver.connect(url,?info); ????????return?con; ????} } ``` ### SpringBoot中的類SPI擴展機制 在springboot的自動裝配過程中,最終會加載META-INF/spring.factories文件,而加載的過程是由SpringFactoriesLoader加載的。從CLASSPATH下的每個Jar包中搜尋所有META-INF/spring.factories配置文件,然后將解析properties文件,找到指定名稱的配置后返回。需要注意的是,其實這里不僅僅是會去ClassPath路徑下查找,會掃描所有路徑下的Jar包,只不過這個文件只會在Classpath下的jar包中。 ## Guava Guava 還提供了很多實用工具,如 Lists、Maps、Sets,接下來我們分別來看下這些常用工具的使用和原理。 * List<泛型> list = Lists.newArrayList(); * Map<String,String> hashMap = Maps.newHashMap(); 這種寫法其實就是一種簡單的工廠模式 ``` // 可以預估 list 的大小為 20 List<String> list = Lists.newArrayListWithCapacity(20); List<String> list = Lists.newArrayListWithExpectedSize(20); Map<String,String> hashMap = Maps.newHashMap(); Map<String,String> linkedHashMap = Maps.newLinkedHashMap(); Map<String,String> withExpectedSizeHashMap = Maps.newHashMapWithExpectedSize(20); ``` Guava 還提供了提供了一些異常處理的靜態方法 ``` Throwables.throwIfUnchecked(new RuntimeException("模擬業務出錯")); ``` ## 總結回顧 db-spring-boot-starter構建原理 * 1.ImportSelector 該接口的方法的返回值都會被納入到spring容器管理中 * 2.SpringFactoriesLoader 該類可以從classpath中搜索所有META-INF/spring.factories配置文件,并讀取配置 db-spring-boot-starter如何使用 * 1.使用mybatis構建dao文件 * 2.配置數據源 * 3.配置xml路徑
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看