資料庫緩存mybatis,redis

来源:https://www.cnblogs.com/zhouyun-yx/archive/2019/02/21/10413444.html
-Advertisement-
Play Games

簡介 處理併發問題的重點不在於你的設計是怎樣的,而在於你要評估你的併發,併在併發範圍內處理。你預估你的併發是多少,然後測試r+m是否支持。緩存的目的是為了應對普通對象資料庫的讀寫限制,依托與nosql的優勢進行高速讀寫。 redis本身也有併發瓶頸。所以你要把讀寫和併發區分開來處理。只讀業務是不是可 ...


  • 簡介

    處理併發問題的重點不在於你的設計是怎樣的,而在於你要評估你的併發,併在併發範圍內處理。
    你預估你的併發是多少,然後測試r+m是否支持。緩存的目的是為了應對普通對象資料庫的讀寫限制,依托與nosql的優勢進行高速讀寫。

    redis本身也有併發瓶頸。所以你要把讀寫和併發區分開來處理。只讀業務是不是可以用mysql分佈做只讀庫和只讀表,進行讀寫分離+庫分佈,
    拆庫拆表不能搞定再考慮上多級緩存
    任何設計,你外面套一層,就多一倍的維護成本,緩存不是萬金油。

    這裡多級緩存主要指的是二級緩存技術,也就是依托nosql的高速讀取優勢。

  • mybatis

    如果底層ORM框架用的是mybatis框架,就可以用mybatis自帶的緩存機制,mybatis自帶一級緩存和二級緩存。
    一級緩存指的是sqlsession緩存,是session級別緩存
    二級緩存指的是mapper級別的緩存,同一個namespace公用這一個緩存,所以對SqlSession是共用的,這裡的namespace指的是xml里的命名空間
    mybatis預設開啟一級緩存,二級緩存預設不開啟,需要手動開啟。

    一級緩存
    mybatis的一級緩存是SqlSession級別的緩存,在操作資料庫的時候需要先創建SqlSession會話對象,在對象中有一個HashMap用於存儲緩存數據,
    此HashMap是當前會話對象私有的,別的SqlSession會話對象無法訪問。
    具體流程:

          1.第一次執行select完畢會將查到的數據寫入SqlSession內的HashMap中緩存起來

          2.第二次執行select會從緩存中查數據,如果select相同切傳參數一樣,那麼就能從緩存中返回數據,不用去資料庫了,從而提高了效率

      註意事項:

          1.如果SqlSession執行了DML操作(insert、update、delete),並commit了,那麼mybatis就會清空當前SqlSession緩存中的所有緩存數據,
    這樣可以保證緩存中的存的數據永遠和資料庫中一致,避免出現臟讀

          2.當一個SqlSession結束後那麼他裡面的一級緩存也就不存在了,mybatis預設是開啟一級緩存,不需要配置

          3.mybatis的緩存是基於[namespace:sql語句:參數]來進行緩存的,意思就是,SqlSession的HashMap存儲緩存數據時,
    是使用[namespace:sql:參數]作為key,查詢返回的語句作為value保存的。
    例如:-1242243203:1146242777:winclpt.bean.userMapper.getUser:0:2147483647:select * from user where id=?:19

    二級緩存
    二級緩存是mapper級別的緩存,也就是同一個namespace的mappe.xml,當多個SqlSession使用同一個Mapper操作資料庫的時候,得到的數據會緩存在同一個二級緩存區域

    二級緩存預設是沒有開啟的,需要在xml配置setting全局參數中配置開啟二級緩存

    <settings>
            <setting name="cacheEnabled" value="true"/><!--開啟mybatis緩存 預設是false:關閉二級緩存--><settings>

    然後再具體的xml里配置

    <cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>當前mapper下所有語句開啟二級緩存

    這裡配置了一個LRU緩存,並每隔60秒刷新,最大存儲512個對象,而卻返回的對象是只讀的

    若想禁用當前select語句的二級緩存,添加useCache="false"修改如下:

    <select id="getCountByName" parameterType="java.util.Map" resultType="INTEGER" statementType="CALLABLE" useCache="false">

    具體流程:

          1.當一個sqlseesion執行了一次select後,在關閉此session的時候,會將查詢結果緩存到二級緩存

          2.當另一個sqlsession執行select時,首先會在他自己的一級緩存中找,如果沒找到,就回去二級緩存中找,
    找到了就返回,就不用去資料庫了,從而減少了資料庫壓力提高了性能 

    註意事項:

          1.如果SqlSession執行了DML操作(insert、update、delete),並commit了,那麼mybatis就會清空當前mapper緩存中的所有緩存數據,這樣可以保證緩存中的存的數據永遠和資料庫中一致,避免出現臟讀

          2.mybatis的緩存是基於[namespace:sql語句:參數]來進行緩存的,意思就是,SqlSession的HashMap存儲緩存數據時,是使用[namespace:sql:參數]作為key,查詢返回的語句作為value保存的。例如:-1242243203:1146242777:winclpt.bean.userMapper.getUser:0:2147483647:select * from user where id=?:19

  • redis

    這裡我實現了spring集成Jedis,並通過解析key實現自定義緩存失效時間。官網可以下載linux下的redis,沒有windows下的,但micro開發小組在github上維護了windows環境下的redis,
    https://github.com/MSOpenTech/redis/releases

    pom.xml引入依賴
      <!--redis start-->
            <dependency>
                <groupId>org.springframework.data</groupId>
                <artifactId>spring-data-redis</artifactId>
                <version>1.6.0.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>redis.clients</groupId>
                <artifactId>jedis</artifactId>
                <version>2.7.3</version>
            </dependency>
     <!--redis end-->

    在xml配置文件里配置各種bean

     <!--JedisPool線程池-->
        <!--redis是單線程的,為了滿足java多線程需求,jedis引入線程池的概念-->
        <bean id="jedisPool" class="redis.clients.jedis.JedisPoolConfig">
            <property name="maxIdle" value="100"/>
            <property name="maxTotal" value="700"/>
            <property name="maxWaitMillis" value="1000"/>
            <property name="testOnBorrow" value="false"/>
        </bean>
    
        <!--創建redis連接工廠-->
        <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
            <property name="hostName" value="${redis.hostname}"/>
            <property name="port" value="${redis.port}"/>
            <property name="password" value="${redis.password}"/>
            <property name="database" value="${redis.database}"/>
            <property name="poolConfig" ref="jedisPool"/>
        </bean>
    
        <!--配置redistempLate -->
        <bean id="redistempLate" class="org.springframework.data.redis.core.RedisTemplate">
            <property name="connectionFactory" ref="jedisConnectionFactory"/>
            <!--<property name="defaultSerializer">-->
                <!--<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>-->
            <!--</property>-->
        </bean>
        <!--配置緩存配置信息-->
        <!--這是用的spring的cacheManager,不支持緩存有效期動態配置-->
        <!--<bean id="redisCacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">-->
            <!--&lt;!&ndash;配置redis模板&ndash;&gt;-->
            <!--<constructor-arg name="redisOperations" ref="redistempLate"/>-->
            <!--&lt;!&ndash;預設緩存失效時間&ndash;&gt;-->
            <!--<property name="defaultExpiration" value="120"/>-->
            <!--&lt;!&ndash;是否使用首碼&ndash;&gt;-->
            <!--<property name="usePrefix" value="false"/>-->
    
        <!--</bean>-->
    
        <!--手動實現的redisCacheManager,支持緩存有效期動態配置,可在@Cacheable中的value屬性添加有效時間-->
        <bean id="myRedisCacheManager" class="com.djkj.demo.common.MyRedisCacheManager">
            <!--配置redis模板-->
            <constructor-arg name="redisOperations" ref="redistempLate"/>
            <!--預設緩存失效時間-->
            <property name="defaultExpiration" value="120"/>
            <!--是否使用首碼-->
            <property name="usePrefix" value="false"/>
            <!--緩存名字和有效期的分隔符-->
            <property name="separator" value="#"/>
    
            <!-- 多個緩存有效期,一般的單個工程可以省略此項 -->
            <!--<property name="expires">-->
                <!--<map>-->
                    <!--<entry key="caiya_a" value="1800"/>-->
                <!--</map>-->
            <!--</property>-->
    
        </bean>
    
        <!--配置RedisCacheConfig,redis緩存啟動類-->
        <bean id="redisCacheConfig" class="com.djkj.demo.common.RedisCacheConfig">
            <constructor-arg ref="jedisConnectionFactory"/>
            <constructor-arg ref="redistempLate"/>
            <constructor-arg ref="myRedisCacheManager"/>
        </bean>

    這裡jedisPool的數據源地址根據實際情況設置,reis預設port為6379,如果你的redis設置了校驗密碼則這裡需要否則不用,database設置的0,redis預設生成15個資料庫,0表示採用的是第一個。

    最主要的是redisCacheConfig緩存啟動類和myRedisCacheManager緩存管理類,啟動類里設置了緩存的key的生成策略,管理類主要實現了自定義有效期
    redisCacheConfig
    package com.djkj.demo.common;
    
    import org.springframework.cache.annotation.CachingConfigurerSupport;
    import org.springframework.cache.annotation.EnableCaching;
    import org.springframework.cache.interceptor.KeyGenerator;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.cache.RedisCacheManager;
    import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    
    import java.lang.reflect.Method;
    
    @Configuration
    @EnableCaching
    public class RedisCacheConfig extends CachingConfigurerSupport {
    
        private volatile JedisConnectionFactory jedisConnectionFactory;
        private volatile RedisTemplate<String,String> redisTemplate;
        private volatile RedisCacheManager redisCacheManager;
    
    
        public RedisCacheConfig(){
            super();
        }
    
        public RedisCacheConfig(JedisConnectionFactory jedisConnectionFactory
                ,RedisTemplate<String,String> redisTemplate, RedisCacheManager redisCacheManager){
            this.jedisConnectionFactory = jedisConnectionFactory;
            this.redisTemplate = redisTemplate;
            this.redisCacheManager = redisCacheManager;
        }
    
        public JedisConnectionFactory getJedisConnectionFactory() {
            return jedisConnectionFactory;
        }
    
        public RedisTemplate<String, String> getRedisTemplate() {
            return redisTemplate;
        }
    
        public RedisCacheManager getRedisCacheManager() {
            return redisCacheManager;
        }
      //主鍵生成策略 @Bean
    public KeyGenerator customKeyGenerator(){ return new KeyGenerator() { @Override public Object generate(Object o, Method method, Object... objects) { StringBuilder stringBuilder=new StringBuilder(); stringBuilder.append(o.getClass().getName()); stringBuilder.append(method.getName()); for (Object obj : objects) { stringBuilder.append(obj.toString().hashCode()); } System.out.println(stringBuilder.toString()); return stringBuilder.toString(); } }; } }

    myRedisCacheManager

    package com.djkj.demo.common;
    
    import org.apache.commons.lang3.StringUtils;
    import org.apache.log4j.Logger;
    import org.springframework.cache.Cache;
    import org.springframework.data.redis.cache.RedisCache;
    import org.springframework.data.redis.cache.RedisCacheManager;
    import org.springframework.data.redis.cache.RedisCachePrefix;
    import org.springframework.data.redis.core.RedisOperations;
    
    import javax.script.ScriptEngine;
    import javax.script.ScriptEngineManager;
    import javax.script.ScriptException;
    import java.util.Collection;
    import java.util.Collections;
    import java.util.regex.Pattern;
    
    public class MyRedisCacheManager extends RedisCacheManager {
    
        private static final Logger logger = Logger.getLogger(MyRedisCacheManager.class);
    
        private static final ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName("JavaScript");
    
        private static final Pattern pattern = Pattern.compile("[+\\-*/%]");
    
        private String defaultCacheName;
    
        private String separator = "#";
    
        public MyRedisCacheManager(RedisOperations redisOperations) {
            this(redisOperations, Collections.<String>emptyList());
        }
    
        public MyRedisCacheManager(RedisOperations redisOperations, Collection<String> cacheNames) {
            super(redisOperations, cacheNames);
        }
    
        @Override
        public Cache getCache(String name) {
            String cacheName="";
            String expirationStr="";
            Long expiration=0L;
            String[] params = name.split(getSeparator());
            if(params.length>=1){
                cacheName = params[0];
            }
            if(params.length>=2){
                expirationStr = params[1];
            }
    
            if(StringUtils.isBlank(cacheName)){
                cacheName = defaultCacheName;
            }
            Cache cache = (RedisCache) super.getCache(cacheName);
            if (cache == null) {
                return null;
            }
    
            if(StringUtils.isNotEmpty(expirationStr)){
                try {
                    expiration = Double.valueOf(expirationStr).longValue();
                }catch (Exception e){
                    logger.error("expiration exchange failed!");
                }
            }
    
            if (expiration==null || expiration == 0L) {
                logger.warn("Default expiration time will be used for cache '{}' because cannot parse '{}', cacheName : " + cacheName + ", name : " + name);
                return cache;
            }
    
            return new RedisCache(cacheName, (isUsePrefix() ? getCachePrefix().prefix(cacheName) : null), getRedisOperations(), expiration);
        }
    
    
        public String getSeparator() {
            return separator;
        }
    
        public void setSeparator(String separator) {
            this.separator = separator;
        }
    
        private Long getExpiration(final String name, final int separatorIndex) {
            Long expiration = null;
            String expirationAsString = name.substring(separatorIndex + 1);
            try {
                // calculate expiration, support arithmetic expressions.
                if(pattern.matcher(expirationAsString).find()){
                    expiration = (long) Double.parseDouble(scriptEngine.eval(expirationAsString).toString());
                }else{
                    expiration = Long.parseLong(expirationAsString);
                }
            } catch (NumberFormatException ex) {
                logger.error(String.format("Cannnot separate expiration time from cache: '%s'", name), ex);
            } catch (ScriptException e) {
                logger.error(String.format("Cannnot separate expiration time from cache: '%s'", name), e);
            }
    
            return expiration;
        }
    
        @Override
        public void setUsePrefix(boolean usePrefix) {
            super.setUsePrefix(usePrefix);
        }
    
        @Override
        public void setCachePrefix(RedisCachePrefix cachePrefix) {
            super.setCachePrefix(cachePrefix);
        }
    
        public void setDefaultCacheName(String defaultCacheName) {
            this.defaultCacheName = defaultCacheName;
        }
    }

    根據符號  #  將緩存名切割,前面的作為緩存名,後面的作為有效期


  • 具體應用方法

    在service層通過@Cacheable註解使用緩存
    //設置緩存有效時間和刷新時間
    @Cacheable(value="testPojoCache" ,keyGenerator = "customKeyGenerator")
    @Override
    public List<TestPojo> query(TestPojo bean) {
    List<TestPojo> testList = testPojoMapper.query(bean);
    if(testList.size()>0){
    for(TestPojo pojo:testList){
    pojo.setTime(sdf.format(new Date()));
    pojo.setAttr1(bean.getAttr1());
    }
    }
    return testList;
    }

    testPojoCache表示緩存name,30表示有效期,keyGenerator表示key的生成策略,用的是在配置類里通過@Bean配置的bean。

  • 進階說明

    spring通過集成jedis的方式有別於直接通過獲取Jedis對象的set方法,通過jedis.set(key,value)只會在redis伺服器產生一條數據,而用註解會產生兩條數據,以上面的例子為例,首先會生成一條key為testPojoCache的緩存數據,value內容是一個集合,放的是通過生存策略生成的值,再以這生成的值為key生成一條緩存數據,value為這個service方法返回的對象數據。當你根據不同的條件參數調用service的方法,都會在testPojoCache里存放鍵值,然後再創建緩存。那怎麼調用緩存呢?在調用service方法前,redis會根據生成策略生成的值到testPojoCache里去找,看有沒有。若果有,根據鍵值獲取緩存;如果沒有就查詢資料庫。



您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.ico:一般作為網頁的標題上面的圖標出現,文件 favicon.ico一般存放在網站根目錄 2.jpg:非常適合作為儲存像素色彩豐富的圖片、例如照片等等 3.png:分為 png-8 以及 png-24 兩種格式 png-8 的特性很接近 gif ,支持 256 色以及透明背景的特性 PNG-2 ...
  • 我想刪除一個實際上是背景的部分。 假設這是我添加part的方式 現在我想刪除它 在添加時,加上特定name,然後根據name查找到改part ...
  • 要註意,書寫時遵循先行性,程式之後用的所有,之前必須設置或定義好,否則沒有效果出現 var serviceUrl = "https://services.arcgis.com/V6ZHFr6zdgNZuVG0/ArcGIS/rest/services/NYCDemographics1/Feature ...
  • 今天童心未泯,玩起了大風車,特別好玩,大家一定不要忘記兒時的夢想,吹吹大風車,轉出好心情 CSS代碼是用less生成的,所以有些繁瑣,大家見諒^_^ JS代碼是實現效果的代碼,絕對不能少 ...
  • mockjs 官網:http://mockjs.com/ 之前沒有使用過 mockjs 的同學,請參考官網文檔,數據生成規則和方法的調用都有詳細說明。 一、通過npm安裝依賴包 1. 進入到項目目錄,執行指令: npm install mockjs -D 由於mockjs是用來模擬數據的,只有是開發 ...
  • ElementUI 官網: http://element-cn.eleme.io/#/zh-CN/component/installation 一、通過npm安裝依賴包 1. 進入到項目目錄,執行指令 : npm i element-ui -S 2. 安裝成功後 package.json 中可以看到 ...
  • 實現效果是在要素點的四周不同位置添加標簽。 此節需要註意一個問題,書寫的先後順序可能會影響運行速度。要註意正確的書寫先後。 1、定義涉及到的所有變數 var minScale = 2500000; var serviceUrl = "https://services.arcgis.com/V6ZHF ...
  • js String擴展方法 'asdasdasd'.repalceA()"AsdAsdAsd" ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...