最新mybatis配置
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
如:
<version>6.0.6</version>
#數據庫配置
#spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false
#serverTimezone 必須要配置
#可以不配置數據庫驅動,Spring Boot會自己發現
#spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
com.mysql.jdbc.Driver 是 mysql-connector-java 5中的,
com.mysql.cj.jdbc.Driver 是 mysql-connector-java 6中的
在設定時區的時候,如果設定serverTimezone=UTC,會比中國時間早8個小時,如果在中國,可以選擇Asia/Shanghai或者Asia/Hongkong,
serverTimezone=Asia/Shanghai
redis介紹和xml (spring-data-redis)
- 加速系統,redis 和 mongoDB
- redis 是運行在內存中的數據庫。支持7中數據類型
- 開源,使用 ansic語言編寫,遵循 BSD協議,基於內存可持久化的日誌型。
- 運行速度 是關係數據庫的 幾倍 到 幾十倍。
- redis 1秒內 完成 10萬次的 讀寫。
- 將常用的數據類型存儲在redis中,網站性能大幅提高
- 正常網站的查詢 和 更新 是 1:9 到 3:7
- 當一個會員登錄網站,可以把常用數據 一次性查詢放入redis
- redis提供簡單的事務,lua語言增加運算能力(原子性)
- spring 通過 spring-data-redis 對redis進行支持
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<!--不依賴Redis的異步客戶端lettuce -->
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--引入Redis的客戶端驅動jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
- redis是 鍵值數據庫,以字符串爲中心
- 包括:字符串,散列,列表(鏈表),集合,有序集合,基數 和 地理位置
Spring-data-redis
-
redis連接驅動有 Jedis ,其他還有 Lettuce (少), Jredis 和 Srp 不被推薦使用
-
提供 RedisConnectionFactory ,生成 RedisConnection接口對象(Redis底層封裝)
-
Spring提供實現類 JedisConnection 去封裝原有 jedis
-
第一步肯定要配置這個工廠了,先配置redis連接池 (最大連接數,超時時間)
-
RedisConnectionFactory —— JedisConnectionFactory —— 連接工廠管理redis連接池
-
通過工廠獲取到 RedisConnection
- AbstractRedisConnection——JedisConnection 封裝jedis操作
- RedisClusterConnection——JedisClusterConection 集羣支持
- StringRedisConnection——DefaultStringRedisConnection 字符串支持
創建redisConnectionFactory
//@Configuration
public class RedisConfig {
private RedisConnectionFactory connectionFactory = null;
@Bean(name = "redisConnectionFactory")
public RedisConnectionFactory initConnectionFactory() {
if (this.connectionFactory != null) {
return this.connectionFactory;
}
JedisPoolConfig poolConfig = new JedisPoolConfig();
// 最大空閒數
poolConfig.setMaxIdle(50);
// 最大連接數
poolConfig.setMaxTotal(100);
// 最大等待毫秒數
poolConfig.setMaxWaitMillis(2000);
// 創建Jedis連接工廠
JedisConnectionFactory connectionFactory = new JedisConnectionFactory(poolConfig);
// 配置Redis連接服務器
RedisStandaloneConfiguration rsc = connectionFactory.getStandaloneConfiguration();
rsc.setHostName("192.168.10.128");
rsc.setPort(6379);
rsc.setPassword(RedisPassword.of("123456"));
this.connectionFactory = connectionFactory;
return connectionFactory;
}
@Bean(name="redisTemplate")
public RedisTemplate<Object, Object> initRedisTemplate() {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(initConnectionFactory());
RedisSerializer<String> stringRedisSerializer = redisTemplate.getStringSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);//key 序列化
//setValueSerializer 也設置成string多好
redisTemplate.setHashKeySerializer(stringRedisSerializer);//hash key序列化
redisTemplate.setHashValueSerializer(stringRedisSerializer);//hash Value序列化
return redisTemplate;
}
}
- redisTemplate 會自動從工程獲取連接,關閉連接
測試redisTemplate
keys k* 命令行查看存儲的key
散列結構:hget hash field //hget map放入的鍵名字 hashMap的鍵 得到的是hvalue
public static void main(String[] args) {
//SpringApplication.run(DemoStartSevenApplication.class, args);
AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(RedisConfig.class);
RedisTemplate bean = context.getBean(RedisTemplate.class);
bean.opsForValue().set("key1","我的測試ke1");
bean.opsForHash().put("hash","field","hvalue");
}
自定義序列化
-
redis是一種基於字符串存儲的NoSQL,而java是基於對象的,
-
java提供了序列化機制。只要實現 java.io.serializable接口 即可。將對象進行序列化 得到二進制字符串
-
序列化器設置 RedisSerializer
- GenericToString Serializer
- Jackson2Json RedisSerializer
- GenericJackson2 RedisSerializer
- Jdk Serialization RedisSerializer (默認)
- OxmSerializer
- String RedisSerializer (常用)
- JacksonJson RedisSerializer (@Deprecated)
-
RedisSerializer 有 bye[] serialize(T t) 和 deserialize (byte[] b)
-
實現Serializable接口對象 ——redis序列化容器——二進制(存到redis)
RedisSerializer<String> stringRedisSerializer = redisTemplate.getStringSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);//key 序列化
redisTemplate.setValueSerializer(stringRedisSerializer);//value 序列化
redisTemplate.setHashKeySerializer(stringRedisSerializer);//hash key序列化
redisTemplate.setHashValueSerializer(stringRedisSerializer);//hash Value序列化
redisTemplate.setDefaultSerializer(stringRedisSerializer);
redisTemplate.setStringSerializer(stringRedisSerializer);//字符串序列化,自動賦值爲這個序列化器
bean.opsForValue().set("key1","我的測試ke1");
bean.opsForHash().put("hash","field","hvalue");
//是兩條連接的操作, 爲了克服這個問題spring提供了 RedisCallback和SessionCallback
//string操作最多,所以提供了 StringRedisTemplate 這個類繼承 RedisTemplate
spring 對redis數據類型的操作
redis支持7種數據類型: redisTemplate
-
字符串 Value Operations rt.opsForValue().set(“key1”,“我的測試ke1”);
-
散列 Hash Operations rt.opsForHash().put(“hash”,“field”,“hvalue”);
-
列表(鏈表)List Operatios rt.opsForList();
-
集合 Set Operatios bean.opsForSet();
-
有序集合 ZSet Operations bean.opsForZSet();
-
基數 HyperLog Operations bean.opsForHyperLogLog();
-
地理位置 Geo Operations 地理位置 bean.opsForGeo();
-
有時候需要對某一個鍵值對 做連續的操作,連續操作一個散列數據類型 或者 列表多次
-
我們就可以對某個鍵進行多次操作
- BoundXXX operations
-
Bound Value Operations rt.boundValueOps(“string”); //string是key
-
Bound Hash Operations XXXXX
-
Bound List Operations
-
Bound Set Operations
-
Bound ZSet Operations
-
Bound HyperLog Operations
-
Bound Geo Operations rt.boundGeoOps(“geo”);
BoundHashOperations hashOps = stringRedisTemplate.boundHashOps("hash"); // 刪除兩個個字段 hashOps.delete("field1", "field2"); // 新增一個字段 hashOps.put("filed4", "value5");
-
- BoundXXX operations
SessionCallback 和 RedisCallback接口
- 作用是讓redis回調 (使用同一條Redis連接進行回調,在同一條redis執行多個方法)
- 同一條連接下執行多個Redis命令
- SessionCallback 提供了良好的封裝(使用它)
- RedisCallback 接口比較底層
// 高級接口,比較友好,一般情況下,優先使用它
public static void useSessionCallback(RedisTemplate redisTemplate) {
redisTemplate.execute(new SessionCallback() {
@Override
public Object execute(RedisOperations ro)
throws DataAccessException {
ro.opsForValue().set("key1", "value1");
ro.opsForHash().put("hash", "field", "hvalue");
return null;
}
});
}
// 需要處理底層的轉換規則,如果不考慮改寫底層,儘量不使用它
public static void useRedisCallback(RedisTemplate redisTemplate) {
redisTemplate.execute(new RedisCallback() {
@Override
public Object doInRedis(RedisConnection rc)
throws DataAccessException {
rc.set("key1".getBytes(), "value1".getBytes());
rc.hSet("hash".getBytes(), "field".getBytes(), "hvalue".getBytes());
return null;
}
});
}
-
RedisCallback接口並不是那麼友好,能夠改一些底層的東西。比如序列化
-
lambda
//報錯,但可用
public static void useSessionCallback(RedisTemplate redisTemplate) {
redisTemplate.execute((RedisOperations ro) -> {
ro.opsForValue().set("key1", "value1");
ro.opsForHash().put("hash", "field", "hvalue");
return null;
});
}
public static void useRedisCallback(RedisTemplate redisTemplate) {
redisTemplate.execute((RedisConnection rc) -> {
rc.set("key1".getBytes(), "value1".getBytes());
rc.hSet("hash".getBytes(), "field".getBytes(), "hvalue".getBytes());
return null;
});
}
spring boot 配置和使用 Redis
#數據庫配置
spring.datasource.url=jdbc:mysql://localhost:3306/spring_boot_chapter7
spring.datasource.username=root
spring.datasource.password=123456
#可以不配置數據庫驅動,Spring Boot會自己發現
#spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#=========================數據庫相關配置完畢===================================
spring.datasource.tomcat.max-idle=10
spring.datasource.tomcat.max-active=50
spring.datasource.tomcat.max-wait=10000
spring.datasource.tomcat.initial-size=5
#設置默認的隔離級別爲讀寫提交
spring.datasource.tomcat.default-transaction-isolation=2
#=========================數據源相關配置完畢===================================
#mybatis配置
mybatis.mapper-locations=classpath:com/springboot/chapter7/mapper/*.xml
mybatis.type-aliases-package=com.springboot.chapter7.pojo
#日誌配置爲DEBUG級別,這樣日誌最爲詳細
logging.level.root=DEBUG #全局日誌爲debug
logging.level.org.springframework=DEBUG
logging.level.org.org.mybatis=DEBUG
#Redis配置
spring.redis.jedis.pool.min-idle=5 #連接池屬性
spring.redis.jedis.pool.max-active=10
spring.redis.jedis.pool.max-idle=10
spring.redis.jedis.pool.max-wait=2000
spring.redis.port=6379 #服務器屬性
spring.redis.host=192.168.11.128
spring.redis.password=123456
#redis連接超時時間,單位毫秒
spring.redis.timeout=1000
#緩存配置
spring.cache.type=REDIS
spring.cache.cache-names=redisCache
spring.cache.redis.use-key-prefix=false
spring.cache.redis.key-prefix=
spring.cache.redis.cache-null-values=true
spring.cache.redis.time-to-live=600000
-
會自動生成 RedisConnectioFactory,RedisTemplate,StringRedisTemplate (只使用字符串,不支持java對象)
-
通過RedisTemplate 序列化器 來處理
main的啓動類上都行: @Autowired private RedisTemplate redisTemplate = null; // 設置RedisTemplate的序列化器 private void initRedisTemplate() { RedisSerializer stringSerializer = redisTemplate.getStringSerializer(); redisTemplate.setKeySerializer(stringSerializer); redisTemplate.setHashKeySerializer(stringSerializer); } // 自定義初始化方法 @PostConstruct public void init() { initRedisTemplate(); }
操作redis數據類型(字符和散列)
-
字符串
-
散列
-
列表
-
集合
-
有序列表
-
大部分只需要一次操作所以使用RedisTemplate
-
需要多次執行Redis命令,可以用 SessionCallback
@RequestMapping("/stringAndHash") @ResponseBody public Map<String, Object> testStringAndHash() { //設置了一個 key1 redisTemplate.opsForValue().set("key1", "value1"); // 注意這裏使用了JDK的序列化器,所以Redis保存的時候不是整數,不能運算 //設置了一個1 redisTemplate.opsForValue().set("int_key", "1"); //設置了一個1 stringRedisTemplate.opsForValue().set("int", "1"); // 使用運算。+1 stringRedisTemplate.opsForValue().increment("int", 1); // 獲取底層Jedis連接 Jedis jedis = (Jedis) stringRedisTemplate.getConnectionFactory().getConnection().getNativeConnection(); // 減一 操作,這個命令RedisTemplate不支持,所以筆者先獲取底層的連接再操作 jedis.decr("int"); Map<String, String> hash = new HashMap<String, String>(); hash.put("field1", "value1");//定義散列的第一個值 hash.put("field2", "value2");//第二個值 // 存入一個散列數據類型 stringRedisTemplate.opsForHash().putAll("hash", hash); // 新增一個字段 stringRedisTemplate.opsForHash().put("hash", "field3", "value3"); // 綁定散列操作的key,這樣可以連續對同一個散列數據類型進行操作 BoundHashOperations hashOps = stringRedisTemplate.boundHashOps("hash"); // 刪除兩個個字段 hashOps.delete("field1", "field2"); // 新增一個字段 hashOps.put("filed4", "value5"); Map<String, Object> map = new HashMap<String, Object>(); map.put("success", true); return map; }
使用string 操作鏈表
- 列表是一種鏈表結構
- 查詢性能不高,而增刪節點的性能高。
- 存在從左到右 或 從右到左的操作
@RequestMapping("/list")
@ResponseBody
public Map<String, Object> testList() {
// 插入兩個列表,注意它們再鏈表的順序
// 鏈表從左到右順序爲v10,v8,v6,v4,v2
stringRedisTemplate.opsForList().leftPushAll("list1", "v2", "v4", "v6", "v8", "v10");
// 鏈表從左到右順序爲v1,v2,v3,v4,v5,v6
stringRedisTemplate.opsForList().rightPushAll("list2", "v1", "v2", "v3", "v4", "v5", "v6");
// 綁定list2鏈表操作
BoundListOperations listOps = stringRedisTemplate.boundListOps("list2");
// 從右邊彈出一個成員 v6
Object result1 = listOps.rightPop();
// 獲取定位元素,Redis從0開始計算,這裏值爲v2
Object result2 = listOps.index(1);
// 從左邊插入鏈表。插入第一個
listOps.leftPush("v0");
// 求鏈表長度
Long size = listOps.size();
// 求鏈表下標區間成員,整個鏈表下標範圍爲0到size-1,這裏不取最後一個元素
List elements = listOps.range(0, size - 2);
Map<String, Object> map = new HashMap<String, Object>();
map.put("success", true);
return map;
}
使用String操作集合
-
redis中是不允許成員重複的
-
是一個散列的結構,而且是無序的。
-
redis還提供了 交集,並集,差集
@RequestMapping("/set") @ResponseBody public Map<String, Object> testSet() { // 請注意:這裏v1重複2次,由於集合不允許重複,所以只是插入5個成員到集合中 stringRedisTemplate.opsForSet().add("set1", "v1", "v1", "v2", "v3", "v4", "v5"); stringRedisTemplate.opsForSet().add("set2", "v2", "v4", "v6", "v8"); // 綁定set1集合操作 BoundSetOperations setOps = stringRedisTemplate.boundSetOps("set1"); // 增加兩個元素 setOps.add("v6", "v7"); // 刪除兩個元素 setOps.remove("v1", "v7"); // 返回所有元素 Set set1 = setOps.members(); // 求成員數 Long size = setOps.size(); // 求交集 Set inter = setOps.intersect("set2"); // 求交集,並且用新集合inter保存 setOps.intersectAndStore("set2", "inter"); // 求差集 Set diff = setOps.diff("set2"); // 求差集,並且用新集合diff保存 setOps.diffAndStore("set2", "diff"); // 求並集 Set union = setOps.union("set2"); // 求並集,並且用新集合union保存 setOps.unionAndStore("set2", "union"); Map<String, Object> map = new HashMap<String, Object>(); map.put("success", true); return map; }
使用string操作有序集合
-
熱門商品,或 最大的購買買家。 刷新需要及時,用數據庫太慢
-
有序集合也是一種散列表存儲的方式
-
有序性 只是靠它在數據結構中增加一個屬性 score分數 得以支持
-
Spring提供了 TypedTuple 接口,有兩個方法。還提供了實現類 DefaultTypedTuple
-
TypedTuple——getValue getScore
-
DefaultTypedTuple —— score value
-
value 保存有序集合的值 (買家)
-
score 保存分數 (買家話費的金錢)
Tuple 英 /tjʊpəl; ˈtʌpəl/ 美 /ˈtjʊpəl; ˈtʌpəl/ 全球(美國) n. [計] 元組,重數
@RequestMapping("/zset") @ResponseBody public Map<String, Object> testZset() { Set<ZSetOperations.TypedTuple<String>> typedTupleSet = new HashSet<>(); for (int i = 1; i <= 9; i++) { // 分數 double score = i * 0.1; // 創建一個TypedTuple對象,存入值和分數 ZSetOperations.TypedTuple<String> typedTuple = new DefaultTypedTuple<String>("value" + i, score); typedTupleSet.add(typedTuple); } // 往有序集合插入元素 stringRedisTemplate.opsForZSet().add("zset1", typedTupleSet); // 綁定zset1有序集合操作 BoundZSetOperations<String, String> zsetOps = stringRedisTemplate.boundZSetOps("zset1"); // 增加一個元素 zsetOps.add("value10", 0.26); Set<String> setRange = zsetOps.range(1, 6); // 按分數排序獲取有序集合 Set<String> setScore = zsetOps.rangeByScore(0.2, 0.6); // 定義值範圍 RedisZSetCommands.Range range = new RedisZSetCommands.Range(); range.gt("value3");// 大於value3 // range.gte("value3");// 大於等於value3 // range.lt("value8");// 小於value8 range.lte("value8");// 小於等於value8 // 按值排序,請注意這個排序是按字符串排序 Set<String> setLex = zsetOps.rangeByLex(range); // 刪除元素 zsetOps.remove("value9", "value2"); // 求分數 Double score = zsetOps.score("value8"); // 在下標區間下,按分數排序,同時返回value和score Set<ZSetOperations.TypedTuple<String>> rangeSet = zsetOps.rangeWithScores(1, 6); // 在分數區間下,按分數排序,同時返回value和score Set<ZSetOperations.TypedTuple<String>> scoreSet = zsetOps.rangeByScoreWithScores(1, 6); // 按從大到小排序 Set<String> reverseSet = zsetOps.reverseRange(2, 8); Map<String, Object> map = new HashMap<String, Object>(); map.put("success", true); return map; }
-
-
TypeTuple保存集合的元素,默認從小到大排序
redis的一些特殊用法
- 事務
- 流水線:大批量執行Redis命令的時候
- 發佈訂閱
- Lua語 :利用 Redis執行 Lua的原子性 來達到 數據一致性的目的
使用Redis事務
-
命令組合 watch… multi … exec
-
在一個 Redis 連接中執行 多個命令,考慮使用 SessionCallback接口
-
watch命令是 可以監控 Redis的一些鍵,multi命令是開始事務
- 開始事務後,客戶端命令不會馬上執行,而是放到一個隊列裏
- 執行一些返回數據的命令,結果返回null
-
exe命令的意義在於執行事務
-
在隊列命令執行前會判斷 被 watch監控 的 redis的鍵 的數據是否發生過變化
-
(重新賦值 都算變化過)
-
如果變化過,那就取消事務。否則就執行事務。要麼全部執行,要麼全部不執行。
-
且不會被其他客戶端打斷。
-
-
watch命令 —— multi 命令開啓事務 —— 命令進入隊列 ——exec命令執行事務
-
監控鍵值對 是否發生變化 (會判斷watch命令的鍵值對是否發生變化)
- 是 ,取消事務,取消監控的鍵值對
- 否,執行事務,取消監控的鍵值對
-
需要保證 RedisTemplate的鍵和散列結構 的 field 使用 字符串 序列化(STringRedisSerializer)
@RequestMapping("/multi") @ResponseBody public Map<String, Object> testMulti() { redisTemplate.opsForValue().set("key1", "value1"); List list = (List) redisTemplate.execute((RedisOperations operations) -> { // 設置要監控key1 operations.watch("key1"); // 開啓事務,在exec命令執行前,全部都只是進入隊列 operations.multi(); operations.opsForValue().set("key2", "value2"); //operations.opsForValue().increment("key1", 1);// ① // 獲取值將爲null,因爲redis只是把命令放入隊列, Object value2 = operations.opsForValue().get("key2"); System.out.println("命令在隊列,所以value爲null【" + value2 + "】"); operations.opsForValue().set("key3", "value3"); Object value3 = operations.opsForValue().get("key3"); System.out.println("命令在隊列,所以value爲null【" + value3 + "】"); // 執行exec命令,將先判別key1是否在監控後被修改過,如果是不執行事務,否則執行事務 return operations.exec();// ② }); System.out.println(list); Map<String, Object> map = new HashMap<String, Object>(); map.put("success", true); return map; } // 設置要監控key1 operations.watch("key1"); // 開啓事務,在exec命令執行前,全部都只是進入隊列 operations.multi(); // 執行exec命令 return operations.exec();// ②
-
在 @2 處打斷點,並且手動修改redis中的key1值,此時跳過斷點,所有的不會被執行(在最後一步,會檢查key1,如果和最開始不一致,本次不執行)
- 因爲 先使用 watch 監控了 key1。operations.watch(“key1”);
- 之後的 multi 讓之後的命令進入隊列。
- 在exec 方法運行前,我們手動改了 key1 。事務規則,沒有修改過 纔會執行事務。
-
把 @1 處標記放開。 此處會報錯,沒法對 string類型 加1 ,但最終 key2 key3還是有值的。
- 這就是和 數據庫事務不一樣
- redis事務 先讓命令進入 隊列,並沒有檢測 +1 命令是否執行成功
- 只有 在 exec命令執行的時候 纔會發現錯誤
- 錯誤後面的依舊會執行
- 爲了解決這個問題,在執行事務前,都會嚴格的檢查,避免這樣的情況
Redis流水線
- 默認情況下,Redis客戶端是一條條發送給 Redis服務器的。(性能不高)
- 關係數據庫可以批量,執行SQL,一次發送所有SQL
- Redis也可以,叫做流水線。pipline技術
- 網絡傳輸造成了瓶頸,使用了流水線大幅度 提升Redis性能
-
Pipeline
英 /ˈpaɪplaɪn/ 美 /ˈpaɪplaɪn/ 全球(英國)
n. 管道;輸油管;傳遞途徑
-
```java
@RequestMapping("/pipeline")
@ResponseBody
public Map<String, Object> testPipeline() {
Long start = System.currentTimeMillis();
List list = (List) redisTemplate.executePipelined((RedisOperations operations) -> {
for (int i = 1; i <= 100000; i++) {
operations.opsForValue().set("pipeline_" + i, "value_" + i);
String value = (String) operations.opsForValue().get("pipeline_" + i);
if (i == 100000) {
System.out.println("命令只是進入隊列,所以值爲空【" + value + "】");
}
}
return null;
});
Long end = System.currentTimeMillis();
System.out.println("耗時:" + (end - start) + "毫秒。");
Map<String, Object> map = new HashMap<String, Object>();
map.put("success", true);
return map;
}
10萬條:耗時:417毫秒。
1萬條:90毫秒
redisTemplate.executePipelined
不使用流水線,每秒3萬條。
流水線大約能提升10倍的性能
- 這裏還是用的 SessionCallback 接口
使用Redis發佈訂閱
-
在企業分配任務後,可以通過郵件,短信或者微信,通知到相關責任人。
-
Redis提供一個通道,讓消息能發送到這個通道上。多個系統可以監聽這個渠道(短信,微信,郵件)
-
當一條消息發送到 渠道,渠道就會通知它的監聽者。
-
消息 發送到 渠道 ——短信,微信,郵件。 監聽
定義消息監聽器 MessageListener
@Component
public class RedisMessageListener implements MessageListener {
@Override //pattern渠道名稱
public void onMessage(Message message, byte[] pattern) {
// 消息體
String body = new String(message.getBody());
// 渠道名稱
String topic = new String(pattern);
System.out.println(body);
System.out.println(topic);
}
}
配置:需要工廠,定時任務,通道,消息監聽器
@SpringBootApplication(scanBasePackages = "com.springboot.chapter7")
public class Chapter7Application {
// RedisTemplate
@Autowired
private RedisTemplate redisTemplate = null;
//redis 連接工廠
@Autowired
private RedisConnectionFactory connectionFactory = null;
// Redis消息監聽器
@Autowired
private MessageListener redisMsgListener = null;
// 任務池
private ThreadPoolTaskScheduler taskScheduler = null;
/**
* 創建任務池,運行線程等待處理Redis的消息
* @return
*/
@Bean
public ThreadPoolTaskScheduler initTaskScheduler() {
if (taskScheduler != null) {
return taskScheduler;
}
//創建任務池
taskScheduler = new ThreadPoolTaskScheduler();
//設置任務池的大小
taskScheduler.setPoolSize(20);
return taskScheduler;
}
/**
* 定義Redis的監聽容器
* @return 監聽容器
*/
@Bean
public RedisMessageListenerContainer initRedisContainer() {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
// 1,Redis連接工廠
container.setConnectionFactory(connectionFactory);
// 2,設置運行任務池。
container.setTaskExecutor(initTaskScheduler());
// 3,定義監聽渠道,名稱爲topic1
Topic topic = new ChannelTopic("topic1");
// 4,使用監聽器監聽Redis的消息。(上面自定義的)
container.addMessageListener(redisMsgListener, topic);
return container;
}
public static void main(String[] args) {
SpringApplication.run(Chapter7Application.class, args);
}
}
- 定義了一個任務池,設置了任務池的大小(20個)
- 它將可以運行線程,等待redis消息的傳入
- 定義了接收 topic1 渠道的消息
測試
@Resource
private RedisTemplate redisTemplate;
@GetMapping("/myTest")
public String myTest(){
redisTemplate.convertAndSend("topic1","我的對象,哈哈哈,測試監聽者");
return "hahaha";
}
或者:publish topic1 哈哈哈
都會進入:自定義消息監聽器裏面
使用Lua腳本
-
Redis在 2.6 版本號 提供了 Lua腳本的支持
-
執行Lua腳本具備 原子性。保證數據的一致性
-
更強大的運算能力,比redis自身的事務,要好一些。
-
有兩種運行的方法
- 直接發送Lua到服務器去執行
- 先把Lua發給redis,Redis對 Lua緩存,返回一個 SHA1的 32位編碼回來,
- 之後只需發給SHA1和相關參數給Redis便可以執行了
-
爲什麼要通過32位編碼執行
- 如果Lua腳本長,需要網絡傳遞,傳遞的速度跟不上redis的執行速度
- 如果只傳遞32位編碼和參數,需要傳遞的消息就少了許多,可以極大的減少網絡傳輸的內容。
-
Spring提供了,RedisScript接口,還有實現類:DefaultRedisScript
public interface RedisScript<T> { String getSha1();//獲取腳本的 sha1 @Nullable Class<T> getResultType();//獲取返回值。返回的java的類型 String getScriptAsString();//獲取腳本的字符串 }
@RequestMapping("/lua") @ResponseBody public Map<String, Object> testLua() { DefaultRedisScript<String> rs = new DefaultRedisScript<String>(); // 設置腳本 rs.setScriptText("return 'Hello Redis'"); // 定義返回類型,注意如果沒有這個定義Spring不會返回結果 rs.setResultType(String.class); RedisSerializer<String> stringSerializer = redisTemplate.getStringSerializer(); // 執行Lua腳本 String str = (String) redisTemplate.execute(rs, stringSerializer, stringSerializer, null); Map<String, Object> map = new HashMap<String, Object>(); map.put("str", str); return map; }
Spring緩存註解操作Redis
-
簡化Redis使用,提供了緩存註解
-
CacheManager
- EhCache CacheManager
- JCache CacheManager
- Redis CacheManager
- SimpleCacheManager
-
需要配置緩存管理器,(緩存類型,超時時間)
-
RedisCacheManager 最常用
緩存管理器配置
#日誌配置爲DEBUG級別,這樣日誌最爲詳細
logging.level.root=DEBUG
logging.level.org.springframework=DEBUG
logging.level.org.org.mybatis=DEBUG
#Redis配置
spring.redis.jedis.pool.min-idle=5
spring.redis.jedis.pool.max-active=10
spring.redis.jedis.pool.max-idle=10
spring.redis.jedis.pool.max-wait=2000
spring.redis.port=6379
spring.redis.host=192.168.11.128
spring.redis.password=123456
#redis連接超時時間,單位毫秒
spring.redis.timeout=1000
spring.cache.caffeine.spec= #caffeine緩存配置細節
spring.cache.couchbase.expiration=0ms
spring.cache.ehcache.config= *
spring.cache.infinispan.config= *
spring.cache.jcache.config= #
spring.cache.jcache.provider= *
#是否允許redis緩存空值
spring.cache.redis.cache-null-values=true
#是否啓用鍵前綴
spring.cache.redis.use-key-prefix=false
#redis的鍵前綴
spring.cache.redis.key-prefix=
#緩存超時時間戳,配置爲0,則不設置超時時間
spring.cache.redis.time-to-live=600000
#緩存配置。會自動推測
spring.cache.type=REDIS
#配置緩存的名稱
spring.cache.cache-names=redisCache
開啓緩存
@EnableCaching
配置mybatis
pom
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<!--<version>1.3.1</version> 記得註釋 -->
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
properties
#數據庫配置
spring.datasource.url=jdbc:mysql://localhost:3306/spring_boot_chapter7
spring.datasource.username=root
spring.datasource.password=123456
#可以不配置數據庫驅動,Spring Boot會自己發現
#spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.tomcat.max-idle=10
spring.datasource.tomcat.max-active=50
spring.datasource.tomcat.max-wait=10000
spring.datasource.tomcat.initial-size=5
#設置默認的隔離級別爲讀寫提交
spring.datasource.tomcat.default-transaction-isolation=2
#mybatis配置
mybatis.mapper-locations=classpath:com/springboot/chapter7/mapper/*.xml
mybatis.type-aliases-package=com.springboot.chapter7.pojo
pojo
@Alias("user")
public class User implements Serializable {
private static final long serialVersionUID = 7760614561073458247L;
private Long id;
private String userName;
private String note;
}
mapper接口
@Repository
public interface UserDao {
// 獲取單個用戶
User getUser(Long id);
// 保存用戶
int insertUser(User user);
// 修改用戶
int updateUser(User user);
// 查詢用戶,指定MyBatis的參數名稱
List<User> findUsers(@Param("userName") String userName,
@Param("note") String note);
// 刪除用戶
int deleteUser(Long id);
}
主類的包掃描
//指定掃描的MyBatis Mapper
@MapperScan(basePackages = "com.springboot.chapter7", annotationClass = Repository.class)
userMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.springboot.demostartseven.dao.UserDao">
<select id="getUser" parameterType="long" resultType="user">
select id, user_name as userName, note from t_user
where id = #{id}
</select>
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id"
parameterType="user">
insert into t_user(user_name, note)
values(#{userName}, #{note})
</insert>
<update id="updateUser">
update t_user
<set>
<if test="userName != null">user_name =#{userName},</if>
<if test="note != null">note =#{note}</if>
</set>
where id = #{id}
</update>
<select id="findUsers" resultType="user">
select id, user_name as userName, note from t_user
<where>
<if test="userName != null">
and user_name = #{userName}
</if>
<if test="note != null">
and note = #{note}
</if>
</where>
</select>
<delete id="deleteUser" parameterType="long">
delete from t_user where id = #{id}
</delete>
</mapper>
-
useGeneratedKeys=“true” keyProperty=“id”
-
MyBatis機會將數據庫生成的主鍵 回填到POJO id中
mybatis配置思路
1.pom mybatis-starter 和 mysql驅動
2.properties 連接配置, mapper.xml位置,pojo位置
3.主類的main 配置mapper接口位置 @MapperScan(basePackages = "com.XX", annotationClass = Repository.class)
4.dao接口 @Repository
5.sql的xml配置
service層使用緩存
public interface UserService {
// 獲取單個用戶
User getUser(Long id);
// 保存用戶
User insertUser(User user);
// 修改用戶,指定MyBatis的參數名稱
User updateUserName(Long id, String userName);
// 查詢用戶,指定MyBatis的參數名稱
List<User> findUsers(String userName, String note);
// 刪除用戶
int deleteUser(Long id);
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao = null;
// 插入用戶,最後MyBatis會回填id,取結果id緩存用戶
@Override
@Transactional
//把結果放入redis註解
@CachePut(value = "redisCache", key = "'redis_user_'+#result.id")
public User insertUser(User user) {
userDao.insertUser(user);
return user;
}
// 獲取id,取參數id緩存用戶
@Override
@Transactional
@Cacheable(value = "redisCache", key = "'redis_user_'+#id")
public User getUser(Long id) {
return userDao.getUser(id);
}
// 更新數據後,充值緩存,使用condition配置項使得結果返回爲null,不緩存
@Override
@Transactional
@CachePut(value = "redisCache", condition = "#result != 'null'", key = "'redis_user_'+#id")
public User updateUserName(Long id, String userName) {
// 此處調用getUser方法,該方法緩存註解失效,
// 所以這裏還會執行SQL,將查詢到數據庫最新數據
User user = this.getUser(id);
if (user == null) {
return null;
}
user.setUserName(userName);
userDao.updateUser(user);
return user;
}
// 命中率低,所以不採用緩存機制
@Override
@Transactional
public List<User> findUsers(String userName, String note) {
return userDao.findUsers(userName, note);
}
// 移除緩存
@Override
@Transactional
@CacheEvict(value = "redisCache", key = "'redis_user_'+#id", beforeInvocation = false)
public int deleteUser(Long id) {
return userDao.deleteUser(id);
}
}
@CachePut(value = "redisCache", key = "'redis_user_'+#result.id") //放入redis_user_11
@Cacheable(value = "redisCache", key = "'redis_user_'+#id")
@CachePut(value = "redisCache", condition = "#result != 'null'", key = "'redis_user_'+#id") //在緩存中通過定義的鍵去查詢,查到就返回,否則執行該方法,返回結果。並放入緩存
@CacheEvict(value = "redisCache", key = "'redis_user_'+#id", beforeInvocation = false)//刪除。是否在方法之前就移除,默認爲false
action
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService = null;
@RequestMapping("/getUser")
@ResponseBody
public User getUser(Long id) {
return userService.getUser(id);
}
@RequestMapping("/insertUser")
@ResponseBody
public User insertUser(String userName, String note) {
User user = new User();
user.setUserName(userName);
user.setNote(note);
userService.insertUser(user);
return user;
}
@RequestMapping("/findUsers")
@ResponseBody
public List<User> findUsers(String userName, String note) {
return userService.findUsers(userName, note);
}
@RequestMapping("/updateUserName")
@ResponseBody
public Map<String, Object> updateUserName(Long id, String userName) {
User user = userService.updateUserName(id, userName);
boolean flag = user != null;
String message = flag? "更新成功" : "更新失敗";
return resultMap(flag, message);
}
@RequestMapping("/deleteUser")
@ResponseBody
public Map<String, Object> deleteUser(Long id) {
int result = userService.deleteUser(id);
boolean flag = result == 1;
String message = flag? "刪除成功" : "刪除失敗";
return resultMap(flag, message);
}
private Map<String, Object> resultMap(boolean success, String message) {
Map<String, Object> result = new HashMap<String, Object>();
result.put("success", success);
result.put("message", message);
return result;
}
}
緩存註解 自調用 失效的問題
@Override
@Transactional
@CachePut(value = "redisCache", condition = "#result != 'null'", key = "'redis_user_'+#id")
public User updateUserName(Long id, String userName) {
// 此處調用getUser方法,該方法緩存註解失效,
// 所以這裏還會執行SQL,將查詢到數據庫最新數據
User user = this.getUser(id);
if (user == null) {
return null;
}
user.setUserName(userName);
userDao.updateUser(user);
return user;
}
- String的緩存機制 也是 基於SpringAop原理,Aop是通過動態代理實現的
緩存的髒數據問題
-
修改id爲1的用戶,更新到數據庫,使用key_1 保存到redis
-
修改id爲1的用戶,更新到數據庫,使用key_2保存到redis
-
這樣 key_1 緩存爲髒數據了。 (說不通,此時 爲什麼 不用 key_1保存呢?)
-
如redis事務。 數據的讀寫 策略不一樣
-
比如排名,允許髒讀,不允許一直都是錯的。
-
可以設置redis失效時間
-
也可設置redis的超時時間(實時性高的,超時時間設置短點)
-
對於數據的寫操作,一般會從數據庫中先讀取最新的數據,在更新數據
自定義緩存管理器
-
不希望 採用Spring boot機制 帶來的 鍵命名方式
-
不希望緩存永不超時
-
有兩種方法:
- 通過配置消除緩存 的前綴 和 自定義 超時時間 的屬性 來定製生成創建 緩存管理器
- 不採用spring boot爲我們生成的,而是通過自己的代碼
#緩存配置
spring.cache.type=REDIS
spring.cache.cache-names=redisCache
#是否允許redis的值爲空
spring.cache.redis.cache-null-values=true
#是否啓用redis前綴。 false是禁用前綴
spring.cache.redis.use-key-prefix=false
#redis的前綴爲
spring.cache.redis.key-prefix=hua-test
#緩存超時時間
spring.cache.redis.time-to-live=600 000 //600 秒。 10分鐘過後redis就會超時。
keys * #查詢redis存在的鍵值對
get redis_user_1 #獲取Id爲1的用戶信息
ttl redis_user_1 #查詢鍵的剩餘超時秒數
@Autowired //連接工廠
private RedisConnectionFactory connectionFactory = null;
@Bean(name = "redisCacheManager" )
public RedisCacheManager initRedisCacheManager() {
// Redis加鎖的寫入器
RedisCacheWriter writer= RedisCacheWriter.lockingRedisCacheWriter(connectionFactory);
// 啓動Redis緩存的默認設置
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
// 設置JDK序列化器
config = config.serializeValuesWith(SerializationPair.fromSerializer(new JdkSerializationRedisSerializer()));
// 禁用前綴
config = config.disableKeyPrefix();
//設置10分鐘超時
config = config.entryTtl(Duration.ofMinutes(10));
//Duration.ofMinutes(10).getSeconds() 600秒
// 創建緩Redis存管理器
RedisCacheManager redisCacheManager = new RedisCacheManager(writer, config);
return redisCacheManager;
}