<kbd id="5sdj3"></kbd>
<th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>

    Mybatis攔截器實現(xiàn)讀寫分離的思路和實踐

    共 5940字,需瀏覽 12分鐘

     ·

    2021-08-19 13:27

    上次文章我們采用原生的方式做了mybatis的多數(shù)據(jù)源,多數(shù)據(jù)源勢必要決定采用那種數(shù)據(jù)源了,當(dāng)然這里說的是數(shù)據(jù)一樣的數(shù)據(jù)源了,也就是主從或者主備之類的,為啥要做讀寫分離就是因為業(yè)務(wù)讀寫壓力比較大,放到同一臺機(jī)器上會影響效率,所以我們可以讓讀和寫分開,這樣就降低了計算機(jī)的壓力,相當(dāng)于分流了。雖然讀寫分離優(yōu)點多多,但是也不能無腦讀寫分離,對于寫入立馬回查的業(yè)務(wù)讀寫分離的模式大概率就要涼涼。所以這塊在代碼層面上要靈活的決定數(shù)據(jù)源采用寫庫還是讀庫就成為一個比較重要的問題。這塊我們再思考一個問題,現(xiàn)在市面上有很多數(shù)據(jù)庫中間件,比如mycat之類的貌似也解決不了主從之間的時間延遲問題,所以這塊最為靈活的方式是在代碼中決定數(shù)據(jù)源。當(dāng)然在代碼中決定采用數(shù)據(jù)源之后,再用mycat等中間件就有點搞笑了。所以個人總結(jié)來說代碼中的靈活決定數(shù)據(jù)源比采用數(shù)據(jù)庫中間件的優(yōu)勢更加明顯。

    說到這里,我們可以再思考一個問題,一般數(shù)據(jù)庫都是主從,也就是很多都是一主多從,對應(yīng)的就是一寫多讀,意思是寫的時候?qū)懙街鲙欤x的時候可以從從庫的任意中讀取。因此我們的插件要有寫庫的數(shù)據(jù)源已經(jīng)多個讀庫的數(shù)據(jù)源。

    上邊說了那么多,如果沒有讀寫識別的信號,那說的再多也沒有價值。這塊我們就需要解析SQL或者解析方法上邊的特定注解了。前者為一般模式,后者是靈活配置。當(dāng)然這塊要注意的就是事務(wù)了,事務(wù)肯定要操作單庫,也必然是主庫,道理說了挺多哈。我們試著研究一下怎么做吧。

    1.首先就是Mybatis插件了,記得之前我們說mybatis有4個階段(Executor、ParameterHandler、ResultSetHandler 以及 StatementHandler),每個階段的各個方法都可以被攔截,當(dāng)然這塊攔截器的攔截原理責(zé)任鏈模式,過程還是比較難的。然后通過jdk代理的方式植入到mybatis執(zhí)行過程中。這塊的筆記已經(jīng)忘的差不多了。再此貼個筆記。

    Mybatis學(xué)習(xí)筆記(三)- Mybatis插件原理

    Mybatis學(xué)習(xí)筆記(二)- Sql的執(zhí)行過程

    2.因為這塊我們肯定要在sql執(zhí)行執(zhí)行前決定要選的數(shù)據(jù)庫,所以看一下mybatis的執(zhí)行流程(圖片來自網(wǎng)絡(luò))。

    我們要決定采用那種數(shù)據(jù)源要在最開始的地方做,因為后邊的參數(shù)呀或者結(jié)果都不是我們關(guān)心的地方。statementHandler是執(zhí)性的最早的,而且統(tǒng)領(lǐng)全局。而且最開始的方法是prepare,因此可以在這里就行一些操作。


    基于上述思路,我們大概的寫一個mybatis攔截器就是這樣的。


    @Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class,Integer.class})})public class MybatisLanjieqi implements Interceptor {
    @Override public Object intercept(Invocation invocation) throws Throwable { StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); System.out.println(statementHandler.toString()); return invocation.proceed(); }
    @Override public Object plugin(Object target) { System.out.println("enter the plugin"); if (target instanceof StatementHandler) { return Plugin.wrap(target, this); } else { return target; } }}


    插件寫好之后,我們需要將我們的插件注入到SqlSessionFactoryBean中。我們繼續(xù)在上篇文章中的demo中做演示。

    考慮到我們要在代碼中靈活決定采用那種類型的數(shù)據(jù)源,因此我們需要需要將現(xiàn)場的一些東西傳過來,比如調(diào)用類的信息。當(dāng)然還有sql的類型什么的。這塊我們debug一下??纯次覀兊腟tatementHandler中有什么值。

    我們發(fā)現(xiàn)這塊的mappedstatement中的id就是我們要找的一個標(biāo)志,這個id就是我們mybatis映射的接口。所以我們可以針對這個id做反射處理,拿到class類方法信息,當(dāng)然這塊我們要用的就是解析注解了。我們可以通過注解靈活決定采用寫庫還是讀庫。在確定了讀寫庫之后,我們?nèi)绾螌⑦@個標(biāo)志信息傳遞到getConnectio層面?考慮線程安全,我們可以采用threadLocal去做。意思就是說這塊我們可以解析mappedstatement的id,然后反射確定類信息,然后解析注解,通過threadlocal緩存通過注解方式確定的讀寫庫信息。這塊就不演示了,必然是可行的。
    除此之外,對于一般模式的sql,我們記得sql都有類型的標(biāo)志,這塊也是mybatis做的。就是判斷sql的類型,比如select,update這種。如果沒有注解標(biāo)志我們就要通過sql的類型來判斷采用何種庫類型。
    我們繼續(xù)debug,發(fā)現(xiàn)這個mappedstatement包含的內(nèi)容還真是多,看到?jīng)],有個sqlCommendType,所以這塊我們可以先解析注解,如果沒有注解我們可以通過這個sqlCommedType來確定讀寫庫,沒有疑問我們的標(biāo)志勢必還是要存到threadLocal中的。

    以上過程,我們大概的寫了一下我們要在代碼中靈活的做到讀寫分離一些比較細(xì)節(jié)的思路。但是我們還沒有說我們?nèi)绾潍@取讀庫和寫庫的問題。首先必然要從threadlocal中獲取具體的讀寫標(biāo)志位了。然后我們的多數(shù)據(jù)源初始化的時候也必然要按照讀和寫的方式進(jìn)行保存。顯然在原生的框架代碼層面我們是改不了了,所以這塊我們必然要重寫獲取采用代理模式,這塊為啥能在最后獲取原因是數(shù)據(jù)庫連接是懶加載的。也就是getConnection的時候才走得,因此我們只需要在getConnnet之前加一層代理即可。這塊我們采用繼承做demo。

    public class MyDataSource extends DruidDataSource {

    private static DruidDataSource write;

    private static Listreader;
    //模擬多庫初始化.... public static DruidDataSource getDruidDataSource(String url,String userName,String password) throws SQLException { DruidDataSource ds = new DruidDataSource(); ds.setUrl(url); ds.setUsername(userName); ds.setPassword(password); try { ds.setFilters("stat,mergeStat,slf4j"); } catch (Exception var18) { } ds.setMaxActive(50); ds.setInitialSize(1); ds.setMinIdle(1); ds.setMaxWait(60000); ds.setTimeBetweenEvictionRunsMillis(120000); ds.setMinEvictableIdleTimeMillis(300000); ds.setValidationQuery("SELECT 'x'"); ds.setPoolPreparedStatements(true); ds.setMaxPoolPreparedStatementPerConnectionSize(30); ds.setTestWhileIdle(true); ds.setTestOnReturn(false); ds.setTestOnBorrow(false); ds.init(); return ds;    }
    static { //初始化write... try { write=getDruidDataSource("jdbc:mysql://127.0.0.1:3306/tianjl?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true","root","tianjingle"); } catch (SQLException throwables) { throwables.printStackTrace(); } //初始化讀庫// reader.add(write);// reader.add(write);// reader.add(write); }
    public DruidPooledConnection getConnection() throws SQLException { //這塊可以寫具體得庫選擇邏輯,讀庫隨機(jī)可以從用random方法。 return write.getConnection();    }}
    相應(yīng)的數(shù)據(jù)源改成我們新的數(shù)據(jù)源類,如下MyDataSource所示:
    @Bean(name = "dataO")    public SqlSessionFactoryBean getSqlSessionFactoryOne1() throws Exception {        //xml和實體的映射        SqlSessionFactoryBean sqlSessionFactoryBean=new SqlSessionFactoryBean();        sqlSessionFactoryBean.setDataSource(new MyDataSource());        sqlSessionFactoryBean.setTypeAliasesPackage("com.example.demo.one");        Resource[] resources = new Resource[]{new ClassPathResource("tian/one/OneMapper.xml")};        sqlSessionFactoryBean.setMapperLocations(resources);        sqlSessionFactoryBean.setPlugins(new MybatisLanjieqi());        return sqlSessionFactoryBean;    }
    @Bean(name = "dataTwo") public MapperFactoryBean getSqlSessionFactoryTwo() throws Exception { //xml和實體的映射 SqlSessionFactoryBean sqlSessionFactoryBean=new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(new MyDataSource()); sqlSessionFactoryBean.setTypeAliasesPackage("com.example.demo.two"); sqlSessionFactoryBean.setMapperLocations(new ClassPathResource("tian/two/TwoMapper.xml")); //單個數(shù)據(jù)源所有的數(shù)據(jù)庫映射 MapperFactoryBean mapperFactoryBean=new MapperFactoryBean(); //設(shè)置sqlSessionTemplate,zhuru yong de mapperFactoryBean.setMapperInterface(TwoMapper.class); mapperFactoryBean.setSqlSessionFactory(sqlSessionFactoryBean.getObject()); return mapperFactoryBean; }
    通過以上分析,我們大概對代碼層面讀寫分離有了一些認(rèn)識,估計讓寫個讀寫分離就容易多了,當(dāng)然注解之前講過了,所以也沒啥難度?;臼忻嫔系淖x寫分離也都是這么搞的吧。今天就寫到這吧。

    晚安~


    瀏覽 64
    點贊
    評論
    收藏
    分享

    手機(jī)掃一掃分享

    分享
    舉報
    評論
    圖片
    表情
    推薦
    點贊
    評論
    收藏
    分享

    手機(jī)掃一掃分享

    分享
    舉報

    <kbd id="5sdj3"></kbd>
    <th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>
    69av福利导航 | 欧美三级理论片 | 亚洲国产成人无码a在线播放 | 黑人爱爱视频动态图 | 91爱日韩 |