<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 5940字,需瀏覽 12分鐘

           ·

          2021-08-19 13:27

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

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

          上邊說了那么多,如果沒有讀寫識(shí)別的信號(hào),那說的再多也沒有價(jià)值。這塊我們就需要解析SQL或者解析方法上邊的特定注解了。前者為一般模式,后者是靈活配置。當(dāng)然這塊要注意的就是事務(wù)了,事務(wù)肯定要操作單庫(kù),也必然是主庫(kù),道理說了挺多哈。我們?cè)囍芯恳幌略趺醋霭伞?/span>

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

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

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

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

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


          基于上述思路,我們大概的寫一個(gè)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)場(chǎng)的一些東西傳過來,比如調(diào)用類的信息。當(dāng)然還有sql的類型什么的。這塊我們debug一下??纯次覀兊腟tatementHandler中有什么值。

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

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

          public class MyDataSource extends DruidDataSource {

          private static DruidDataSource write;

          private static Listreader;
          //模擬多庫(kù)初始化.... 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(); } //初始化讀庫(kù)// reader.add(write);// reader.add(write);// reader.add(write); }
          public DruidPooledConnection getConnection() throws SQLException { //這塊可以寫具體得庫(kù)選擇邏輯,讀庫(kù)隨機(jī)可以從用random方法。 return write.getConnection();    }}
          相應(yīng)的數(shù)據(jù)源改成我們新的數(shù)據(jù)源類,如下MyDataSource所示:
          @Bean(name = "dataO")    public SqlSessionFactoryBean getSqlSessionFactoryOne1() throws Exception {        //xml和實(shí)體的映射        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和實(shí)體的映射 SqlSessionFactoryBean sqlSessionFactoryBean=new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(new MyDataSource()); sqlSessionFactoryBean.setTypeAliasesPackage("com.example.demo.two"); sqlSessionFactoryBean.setMapperLocations(new ClassPathResource("tian/two/TwoMapper.xml")); //單個(gè)數(shù)據(jù)源所有的數(shù)據(jù)庫(kù)映射 MapperFactoryBean mapperFactoryBean=new MapperFactoryBean(); //設(shè)置sqlSessionTemplate,zhuru yong de mapperFactoryBean.setMapperInterface(TwoMapper.class); mapperFactoryBean.setSqlSessionFactory(sqlSessionFactoryBean.getObject()); return mapperFactoryBean; }
          通過以上分析,我們大概對(duì)代碼層面讀寫分離有了一些認(rèn)識(shí),估計(jì)讓寫個(gè)讀寫分離就容易多了,當(dāng)然注解之前講過了,所以也沒啥難度?;臼忻嫔系淖x寫分離也都是這么搞的吧。今天就寫到這吧。

          晚安~


          瀏覽 64
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产18女人水真多免费看 | 欧美一级毛片免费高清老鸭窝 | www8050午夜 | 一区二区三区四区视频在线 | 免费无码婬片A片AAA毛片96 |