Java併發編程學習-日記12、Redis 和 Java

Redis的配置項清單如下:

(1)port:端口配置項,查看和設置Redis監聽端口,默認端口爲6379。

(2)bind:主機地址配置項,查看和綁定的主機地址,默認地址的值爲127.0.0.1。

(3)timeout:連接空閒多長要關閉連接,表示客戶端閒置一段時間後,要關閉連接。如果指定爲0,表示時長不限制。這個選項的默認值爲0,表示默認不限制連接的空閒時長。

(4)dbfilename:指定保存緩存數據庫的本地文件名,默認值爲dump.rdb。

(5)dir:指定保存緩存數據的本地文件所存放的目錄,默認值爲安裝目錄。

(6)rdbcompression:指定存儲至本地數據庫時是否壓縮數據,默認爲yes,Redis採用LZF壓縮,如果爲了節省CPU時間,可以關閉該選項,但會導致數據庫文件變得巨大。

(7)save:指定在多長時間內,有多少次Key-Value更新操作,就將數據同步到本地數據庫文件。save配置項的格式爲save<seconds><changes>:seconds表示時間段的長度,changes表示變化的次數。

(8)requirepass:設置Redis連接密碼,如果配置了連接密碼,客戶端在連接Redis時需要通過AUTH<password>命令提供密碼,默認這個選項是關閉"

(9)slaveof:在主從複製的模式下,設置當前節點爲slave(從)節點時,設置master(主)節點的IP地址及端口,在Redis啓動時,它會自動從master(主)節點進行數據同步。如果已經是slave(從)服務器,則會丟掉舊數據集,從新的master主服務器同步緩存數據。

設置爲slave節點命令的格式爲:slaveof<masterip><masterport>

(10)masterauth:在主從複製的模式下,當master(主)服務器節點設置了密碼保護時,slave(從)服務器連接master(主)服務器的密碼。

 master服務器節點設置密碼的格式爲:masterauth<master-password>

(11)databases:設置緩存數據庫的數量,默認數據庫數量爲16個。databases配置選項,可以設置多個緩存數據庫,不同的數據庫存放不同應用的緩存數據。當清除緩存數據時,使用flushdb命令,只會清除當前數據庫中的數據,而不會影響到其他數據庫;而flushall命令,則會清除這個Redis實例所有數據庫(從0-15)的數據。

在Java編程中,配置連接Redis的uri連接字符串時,可以指定到具體的數據庫,格式爲: redis://用戶名:密碼@host:port/Redis庫。

通過安裝目錄下的redis-cli命令客戶端,可以連接到Redis本地服務。如果需要在遠程Redis服務上執行命令,我們使用的也是redis-cli命令。Windows/Linux命令的格式爲: redis-cli -h host -p port -a password。

Redis中有5種數據類型:String(字符串類型)、Hash(哈希類型)、List(列表類型)、Set(集合類型)、Zset(有序集合類型)。

Redis,建議使用冒號作爲superkey和subkey直接的分隔符,如下: superkey:subkey:subsubkey:subsubsubkey:……。

Key的命名規範使用冒號分割,大致的優勢如下:

(1)方便分層展示。Redis的很多客戶端可視化管理工具,如Redis Desktop Manager,是以冒號作爲分類展示的,方便快速查到要查閱的Redis Key對應的Value值。

(2)方便刪除與維護。可以對於某一層次下面的Key,使用通配符進行批量查詢和批量刪除。

Jedis開源庫提供了一個負責管理Jedis連接對象的池,名爲JedisPool類,位於redis.clients.jedis包中。

  • JedisPoolConfig配置類重要參數:

(1)maxTotal:資源池中最大的連接數,默認值爲8。

(2)maxIdle:資源池允許最大空閒的連接數,默認值爲8。

(3)minIdle:資源池確保最少空閒的連接數,默認值爲0。如果JedisPool開啓了空閒連接的有效性檢測,如果空閒連接無效,就銷燬。銷燬連接後,連接數量就少了,如果小於minIdle數量,就新建連接,維護數量不少於minIdle的數量。minIdle確保了線程池中有最小的空閒Jedis實例的數量。

(4)blockWhenExhausted:當資源池用盡後,調用者是否需要等待,默認true。當爲true時,maxWaitMillis纔會生效。

(5)maxWaitMillis:資源池連接用盡後最大等待時間,默認值-1,表示永遠不超時,不建議使用默認值。

(6)testOnBorrow:向資源池借用連接時,是否做有效性檢測(ping命令),如果是無效連接,會被移除,默認值爲false,表示不做檢測。如果爲true,則得到的Jedis實例均是可用的。在業務量小的應用場景,建議設置爲true,確保連接可用;在業務量很大的應用場景,建議設置爲false(默認值),少一次ping命令的開銷,有助於提升性能。

(7)testOnReturn:向資源池歸還連接時,是否做有效性檢測(ping命令),如果是無效連接,會被移除,默認值爲false,表示不做檢測。同樣,在業務量很大的應用場景,建議設置爲false(默認值),少一次ping命令的開銷。

(8)testWhileIdle:如果爲true,表示用一個專門的線程對空閒的連接進行有效性的檢測掃描,如果有效性檢測失敗,即表示無效連接,會從資源池中移除。

(9)timeBetweenEvictionRunsMillis:表示兩次空閒連接掃描的活動之間,要睡眠的毫秒數,默認爲30000毫秒,也就是30秒鐘。

(10)minEvictableIdleTimeMillis:表示一個Jedis連接至少停留在空閒狀態的最短時間,然後才能被空閒連接掃描線程進行有效性檢測,默認值爲60000毫秒,即60秒。

(11)numTestsPerEvictionRun:表示空閒檢測線程每次最多掃描的Jedis連接數,默認值爲-1,表示掃描全部的空閒連接。

(12)jmxEnabled:是否開啓jmx監控,默認值爲true,建議開啓。

RedisPool實例:

public class JredisPoolBuilder {

    private static JedisPool pool = null;

    static {

        buildPool();  //創建連接池

        hotPool();//預熱連接池

    }

    //創建連接池

    private static JedisPool buildPool() {

        if (pool == null) {

            long start = System.currentTimeMillis();

            JedisPoolConfig config = new JedisPoolConfig();

            config.setMaxTotal(50);

            config.setMaxIdle(50);

            config.setMaxWaitMillis(1000 * 10);

            // 在borrow一個jedis實例時,是否提前進行validate操作;

            // 如果爲true,則得到的jedis實例均是可用的;

            config.setTestOnBorrow(true);

            //new JedisPool(config, ADDR, PORT, TIMEOUT, AUTH);

            pool = new JedisPool(config, "127.0.0.1", 6379, 10000);

            long end = System.currentTimeMillis();

            Logger.info("buildPool  毫秒數:", end - start);

        }

        return pool;

    }

    //獲取連接

    public synchronized static Jedis getJedis() {

        return pool.getResource();

    }

    //連接池的預熱

    public static void hotPool() {

        long start = System.currentTimeMillis();

        List<Jedis> minIdleJedisList = new ArrayList<Jedis>(MAX_IDLE);

        Jedis jedis = null;

        for (int i = 0; i < MAX_IDLE; i++) {

            try {

                jedis = pool.getResource();

                minIdleJedisList.add(jedis);

                jedis.ping();

            } catch (Exception e) {

                Logger.error(e.getMessage());

            } finally {

            }

        }

        for (int i = 0; i < MAX_IDLE; i++) {

            try {

                jedis = minIdleJedisList.get(i);

                jedis.close();

            } catch (Exception e) {

                Logger.error(e.getMessage());

            } finally {

            }

        }

        long end = System.currentTimeMillis();

        Logger.info("hotPool  毫秒數:", end - start);

    }

}

  • 一般的Java開發,都會使用了Spring框架,可以使用spring-data-redis開源庫來簡化Redis操作的代碼邏輯,做到最大程度的業務聚焦。

1)使用spring-data-redis庫的第一步是,要在Maven的pom文件中加上spring-data-redis庫的依賴。

2)使用spring-data-redis庫的第二步,即配置spring-data-redis庫的連接池實例和RedisTemplate模板實例。spring-data-redis庫在JedisPool提供連接池的基礎上封裝了自己的連接池——RedisConnectionFactory連接工廠;並且spring-data-redis封裝了一個短期、非線程安全的連接類,名爲RedisConnection連接類。

  1. RedisConnection的API命令操作的對象都是字節級別的Key鍵和Value值。爲了更進一步地減少開發的工作,spring-data-redis庫在RedisConnection連接類的基礎上,針對不同的緩存類型,設計了五大數據類型的命令API集合,用於完成不同類型的數據緩存操作,並封裝在RedisTemplate模板類。
  2. RedisTemplate模板類位於核心包org.springframework.data.redis.core中,它封裝了五大數據類型的命令API集合:

(1)ValueOperations字符串類型操作API集合。

(2)ListOperations列表類型操作API集合。

(3)SetOperations集合類型操作API集合。

(4)ZSetOperations有序集合類型API集合。

(5)HashOperations哈希類型操作API集合。

  1. RedisConnection連接類更加底層,它負責二進制層面的Redis操作,Key、Value都是二進制字節數組。而RedisTemplate模板類,在RedisConnection的基礎上,使用在spring-redis.xml中配置的序列化、反序列化的工具類,完成上層類型(如String、Object、POJO類等)的Redis操作。
  2. Spring的Redis緩存註解

(1)@CachePut作用是設置緩存。先執行方法,並將執行結果緩存起來。Redis緩存中的Key鍵即爲@CachePut註解配置的key屬性值,一般是一個字符串,或者是結果爲字符串的一個SpEL(SpringEL)。一般來說,可以給@CachePut設置三個屬性,Value、Key和Condition。

(2)@CacheEvict的作用是刪除緩存。在執行方法前,刪除緩存。使用@CacheEvict註解清除緩存時,可以通過合理配置清除指定Cache名稱下的所有Key。

(3)@Cacheable的作用更多是查詢緩存。首先檢查註解中的Key鍵是否在緩存中,如果是,則返回Key的緩存值,不再執行方法;否則,執行方法並將方法結果緩存。

(4)@Caching註解,是一個緩存處理的組合註解。通過@Caching,可以一次指定多個Spring Cache註解的組合。@Caching註解擁有三個屬性:cacheable、put和evict。

Redis緩存的一些操作實例:

public class CacheOperationService {

    private RedisTemplate redisTemplate;

    public void setRedisTemplate(RedisTemplate redisTemplate) {

        this.redisTemplate = redisTemplate;

    }

    // --------------RedisTemplate 基礎操作  --------------------

    /**

     * 取得指定格式的所有的key

     * @param patens 匹配的表達式

     * @return key 的集合

     */

    public Set getKeys(Object patens) {

        try {

            return redisTemplate.keys(patens);

        } catch (Exception e) {

            e.printStackTrace();

            return null;

        }

    }

 

    /**

     * 指定緩存失效時間

     *

     * @param key  鍵

     * @param time 時間(秒)

     * @return

     */

    public boolean expire(String key, long time) {

        try {

            if (time > 0) {

                redisTemplate.expire(key, time, TimeUnit.SECONDS);

            }

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

 

    /**

     * 根據key 獲取過期時間

     *

     * @param key 鍵 不能爲null

     * @return 時間(秒) 返回0代表爲永久有效

     */

    public long getExpire(String key) {

        return redisTemplate.getExpire(key, TimeUnit.SECONDS);

    }

 

    /**

     * 判斷key是否存在

     *

     * @param key 鍵

     * @return true 存在 false不存在

     */

    public boolean hasKey(String key) {

        try {

            return redisTemplate.hasKey(key);

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

 

    /**

     * 刪除緩存

     *

     * @param key 可以傳一個值 或多個

     * @return 刪除的個數

     */

    public void del(String... key) {

        if (key != null && key.length > 0) {

            if (key.length == 1) {

                redisTemplate.delete(key[0]);

            } else {

                redisTemplate.delete(CollectionUtils.arrayToList(key));

            }

        }

    }

    // --------------RedisTemplate 操作 String --------------------

    /**

     * 普通緩存獲取

     *

     * @param key 鍵

     * @return 值

     */

    public Object get(String key) {

        return key == null ? null : redisTemplate.opsForValue().get(key);

    }

 

    /**

     * 普通緩存放入

     *

     * @param key   鍵

     * @param value 值

     * @return true成功 false失敗

     */

    public boolean set(String key, Object value) {

        try {

            redisTemplate.opsForValue().set(key, value);

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

 

    }

 

    /**

     * 普通緩存放入並設置時間

     *

     * @param key   鍵

     * @param value 值

     * @param time  時間(秒) time要大於0 如果time小於等於0 將設置無限期

     * @return true成功 false 失敗

     */

    public boolean set(String key, Object value, long time) {

        try {

            if (time > 0) {

                redisTemplate.opsForValue()

                        .set(key, value, time, TimeUnit.SECONDS);

            } else {

                set(key, value);

            }

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

 

    /**

     * 遞增

     *

     * @param key   鍵

     * @param delta 自增因子,要增加幾(大於0)

     * @return 自增的結果

     */

    public long incr(String key, long delta) {

        if (delta < 0) {

            throw new RuntimeException("自增因子必須大於0");

        }

        return redisTemplate.opsForValue().increment(key, delta);

    }

 

    /**

     * 自減

     *

     * @param key   鍵

     * @param delta 自減,  要減少幾(小於0)

     * @return

     */

    public long decr(String key, long delta) {

        if (delta < 0) {

            throw new RuntimeException("自減因子必須大於0");

        }

        return redisTemplate

                .opsForValue().increment(key, -delta);

    }

 

    // --------------RedisTemplate 操作 Map --------------------

 

    /**

     * HashGet

     *

     * @param key   鍵 不能爲null

     * @param field 項 不能爲null

     * @return 值

     */

    public Object hget(String key, String field) {

        return redisTemplate.opsForHash().get(key, field);

    }

 

    /**

     * 獲取hashKey對應的所有鍵值

     *

     * @param key 鍵

     * @return 對應的多個鍵值

     */

    public Map<Object, Object> hmget(String key) {

        return redisTemplate.opsForHash().entries(key);

    }

 

    /**

     * HashSet

     *

     * @param key 鍵

     * @param map 對應多個鍵值

     * @return true 成功 false 失敗

     */

    public boolean hmset(String key, Map<String, Object> map) {

        try {

            redisTemplate.opsForHash().putAll(key, map);

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

 

    /**

     * HashSet 並設置時間

     *

     * @param key  鍵

     * @param map  對應多個鍵值

     * @param time 時間(秒)

     * @return true成功 false失敗

     */

    public boolean hmset(String key, Map<String, Object> map, long time) {

        try {

            redisTemplate.opsForHash().putAll(key, map);

            if (time > 0) {

                expire(key, time);

            }

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

 

    /**

     * 向一張hash表中放入數據,如果不存在將創建

     *

     * @param key   鍵

     * @param field 項

     * @param value 值

     * @return true 成功 false失敗

     */

    public boolean hset(String key, String field, Object value) {

        try {

            redisTemplate.opsForHash().put(key, field, value);

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

 

    /**

     * 向一張hash表中放入數據,如果不存在將創建

     *

     * @param key   鍵

     * @param field 項

     * @param value 值

     * @param time  時間(秒) 注意:如果已存在的hash表有時間,這裏將會替換原有的時間

     * @return true 成功 false失敗

     */

    public boolean hset(String key, String field, Object value, long time) {

        try {

            redisTemplate.opsForHash().put(key, field, value);

            if (time > 0) {

                expire(key, time);

            }

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

 

    /**

     * 刪除hash表中的值

     *

     * @param key   鍵 不能爲null

     * @param field 項 可以使多個 不能爲null

     */

    public void hdel(String key, Object... field) {

        redisTemplate.opsForHash().delete(key, field);

    }

 

    /**

     * 判斷hash表中是否有該項的值

     *

     * @param key   鍵 不能爲null

     * @param field 項 不能爲null

     * @return true 存在 false不存在

     */

    public boolean hHasKey(String key, String field) {

        return redisTemplate.opsForHash().hasKey(key, field);

    }

 

    /**

     * hash自增 如果不存在,就會創建一個 並把新增後的值返回

     *

     * @param key   鍵

     * @param field 項

     * @param by    要增加幾(大於0)

     * @return

     */

    public double hincr(String key, String field, double by) {

        return redisTemplate.opsForHash().increment(key, field, by);

    }

 

    /**

     * hash自減

     *

     * @param key   鍵

     * @param field 項

     * @param by    要減少記(小於0)

     * @return

     */

    public double hdecr(String key, String field, double by) {

        return redisTemplate.opsForHash().increment(key, field, -by);

    }

 

    // --------------RedisTemplate 操作 Set --------------------

 

    /**

     * 根據key獲取Set中的所有值

     *

     * @param key 鍵

     * @return

     */

    public Set<Object> sGet(String key) {

        try {

            return redisTemplate.opsForSet().members(key);

        } catch (Exception e) {

            e.printStackTrace();

            return null;

        }

    }

 

    /**

     * 根據value從一個set中查詢,是否存在

     *

     * @param key   鍵

     * @param value 值

     * @return true 存在 false不存在

     */

    public boolean sHasKey(String key, Object value) {

        try {

            return redisTemplate.opsForSet().isMember(key, value);

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

 

    /**

     * 將數據放入set緩存

     *

     * @param key    鍵

     * @param values 值 可以是多個

     * @return 成功個數

     */

    public long sSet(String key, Object... values) {

        try {

            return redisTemplate.opsForSet().add(key, values);

        } catch (Exception e) {

            e.printStackTrace();

            return 0;

        }

    }

 

    /**

     * 將set數據放入緩存

     *

     * @param key    鍵

     * @param time   時間(秒)

     * @param values 值 可以是多個

     * @return 成功個數

     */

    public long sSetAndTime(String key, long time, Object... values) {

        try {

            Long count = redisTemplate.opsForSet().add(key, values);

            if (time > 0)

                expire(key, time);

            return count;

        } catch (Exception e) {

            e.printStackTrace();

            return 0;

        }

    }

 

    /**

     * 獲取set緩存的長度

     *

     * @param key 鍵

     * @return

     */

    public long sGetSetSize(String key) {

        try {

            return redisTemplate.opsForSet().size(key);

        } catch (Exception e) {

            e.printStackTrace();

            return 0;

        }

    }

 

    /**

     * 移除值爲value的

     *

     * @param key    鍵

     * @param values 值 可以是多個

     * @return 移除的個數

     */

    public long setRemove(String key, Object... values) {

        try {

            Long count = redisTemplate.opsForSet().remove(key, values);

            return count;

        } catch (Exception e) {

            e.printStackTrace();

            return 0;

        }

    }

    // --------------RedisTemplate 操作list --------------------

 

    /**

     * 獲取list緩存的內容

     *

     * @param key   鍵

     * @param start 開始

     * @param end   結束 0 到 -1代表所有值

     * @return

     */

    public List<Object> lGet(String key, long start, long end) {

        try {

            return redisTemplate.opsForList().range(key, start, end);

        } catch (Exception e) {

            e.printStackTrace();

            return null;

        }

    }

 

    /**

     * 獲取list緩存的長度

     *

     * @param key 鍵

     * @return

     */

    public long lGetListSize(String key) {

        try {

            return redisTemplate.opsForList().size(key);

        } catch (Exception e) {

            e.printStackTrace();

            return 0;

        }

    }

 

    /**

     * 通過索引 獲取list中的值

     *

     * @param key   鍵

     * @param index 索引 index>=0時, 0 表頭,1 第二個元素,依次類推;index<0時,-1,表尾,-2倒數第二個元素,依次類推

     * @return

     */

    public Object lGetIndex(String key, long index) {

        try {

            return redisTemplate.opsForList().index(key, index);

        } catch (Exception e) {

            e.printStackTrace();

            return null;

        }

    }

 

    /**

     * 將list放入緩存

     *

     * @param key   鍵

     * @param value 值

     * @return

     */

    public boolean lSet(String key, Object value) {

        try {

            redisTemplate.opsForList().rightPush(key, value);

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

 

    /**

     * 將list放入緩存

     *

     * @param key   鍵

     * @param value 值

     * @param time  時間(秒)

     * @return

     */

    public boolean lSet(String key, Object value, long time) {

        try {

            redisTemplate.opsForList().rightPush(key, value);

            if (time > 0)

                expire(key, time);

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

 

    /**

     * 將list放入緩存

     *

     * @param key   鍵

     * @param value 值

     * @return

     */

    public boolean lSet(String key, List<Object> value) {

        try {

            redisTemplate.opsForList().rightPushAll(key, value);

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

 

    /**

     * 將list放入緩存

     *

     * @param key   鍵

     * @param value 值

     * @param time  時間(秒)

     * @return

     */

    public boolean lSet(String key, List<Object> value, long time) {

        try {

            redisTemplate.opsForList().rightPushAll(key, value);

            if (time > 0)

                expire(key, time);

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

 

    /**

     * 根據索引修改list中的某條數據

     *

     * @param key   鍵

     * @param index 索引

     * @param value 值

     * @return

     */

    public boolean lUpdateIndex(String key, long index, Object value) {

        try {

            redisTemplate.opsForList().set(key, index, value);

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

 

    /**

     * 移除N個值爲value

     *

     * @param key   鍵

     * @param count 移除多少個

     * @param value 值

     * @return 移除的個數

     */

    public long lRemove(String key, long count, Object value) {

        try {

            Long remove = redisTemplate.opsForList().remove(key, count, value);

            return remove;

        } catch (Exception e) {

            e.printStackTrace();

            return 0;

        }

    }

}

Redis緩存Spring註解的一些操作實例:

 

@Service

@CacheConfig(cacheNames = "userCache")

public class UserServiceImplWithAnno implements UserService {

 

    public static final String USER_UID_PREFIX = "'userCache:'+";

 

    /**

     * CRUD 之  新增/更新

     *

     * @param user 用戶

     */

    @CachePut(key = USER_UID_PREFIX + "T(String).valueOf(#user.uid)")

    @Override

    public User saveUser(final User user) {

        //保存到數據庫

        //返回值,將保存到緩存

        Logger.info("user : save to redis");

        return user;

    }

 

    /**

     * 帶條件緩存

     *

     * @param user 用戶

     * @return 用戶

     */

    @CachePut(key = "T(String).valueOf(#user.uid)", condition = "#user.uid>1000")

    public User cacheUserWithCondition(final User user) {

        //保存到數據庫

        //返回值,將保存到緩存

        Logger.info("user : save to redis");

        return user;

    }

 

 

    /**

     * CRUD 之   查詢

     *

     * @param id id

     * @return 用戶

     */

    @Cacheable(key = USER_UID_PREFIX + "T(String).valueOf(#id)")

    @Override

    public User getUser(final long id) {

        //如果緩存沒有,則從數據庫中加載

        Logger.info("user : is null");

        return null;

    }

 

    /**

     * CRUD 之 刪除

     *

     * @param id id

     */

 

    @CacheEvict(key = USER_UID_PREFIX + "T(String).valueOf(#id)")

    @Override

    public void deleteUser(long id) {

 

        //從數據庫中刪除

        Logger.info("delete  User:", id);

    }

 

    /**

     * 刪除userCache中的全部緩存

     */

    @CacheEvict(value = "userCache", allEntries = true)

    public void deleteAll() {

 

    }

    /**

     * 一個方法上,加上三類cache處理

     */

    @Caching(cacheable = @Cacheable(key = "'userCache:'+ #uid"),

            put = @CachePut(key = "'userCache:'+ #uid"),

            evict = {

                    @CacheEvict(key = "'userCache:'+ #uid"),

                    @CacheEvict(key = "'addressCache:'+ #uid"),

                    @CacheEvict(key = "'messageCache:'+ #uid")

            }

    )

    public User updateRef(String uid) {

        //....業務邏輯

        return null;

    }

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章