本文将为您提供关于MySQL与Redis实现二级缓存的详细介绍,我们还将为您解释redis和mybatis二级缓存的相关知识,同时,我们还将为您提供关于Canal+Kafka实现MySql与Redis
本文将为您提供关于MySQL与Redis实现二级缓存的详细介绍,我们还将为您解释redis和mybatis二级缓存的相关知识,同时,我们还将为您提供关于Canal+Kafka实现MySql与Redis数据一致性、canal解决mysql与redis数据同步框架、Mybatis 一级缓存与二级缓存的实现、mybatis 二级缓存集成 redis 的问题的实用信息。
本文目录一览:- MySQL与Redis实现二级缓存(redis和mybatis二级缓存)
- Canal+Kafka实现MySql与Redis数据一致性
- canal解决mysql与redis数据同步框架
- Mybatis 一级缓存与二级缓存的实现
- mybatis 二级缓存集成 redis 的问题
MySQL与Redis实现二级缓存(redis和mybatis二级缓存)
<h2 id="redis简介">redis简介
- Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库
- Redis 与其他 key - value 缓存产品有以下三个特点:
- Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用
- Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储
- Redis支持数据的备份,即master-slave模式的数据备份
- 性能极高 - Redis能读的速度是110000次/s,写的速度是81000次/s
- 丰富的数据类型 – Redis支持二进制案例的 Strings,Lists,Hashes,Sets 及 Ordered Sets 数据类型操作
- 原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来
- 下载并解压缩
wget http://download.redis.io/releases/redis-5.0.3.tar.gz
tar xzf redis-5.0.3.tar.gz
- 将文件夹移动到/usr/local/中
mv redis-5.0.3 /usr/local/
- 进入到文件夹中并编译测试
cd /usr/local/redis-5.0.3
sudo make test
- 编译安装
sudo make install
- 启动redis
redis-server
若出现以下画面则表示redis数据库已经启动了: jpg
dis做二级缓存">MysqL与redis做二级缓存
- 对于访问量比较大的数据我们为了能够更快的获取到数据需要对数据库中获取的数据进行数据缓存。
- 在项目当中使用Redis缓存流程
- 查询时先从缓存当中查询
- 缓存当中如果没有数据再从数据库查询,并将数据保存进缓存当中
- 如果缓存中查询到了数据直接返回,不再需要查询数据库
数据缓存应该考虑同步问题:如果对数据进行了缓存,当查询数据时,如果缓存中有数据则直接返回缓存数据不会查询数据库,当数据库数据改变的时候就有可能出现数据库不一致的问题。可以考虑在每次修改数据库的时候同时将对应的缓存数据删除,这样重新查询的时候就会查询数据库并缓存
- 创建redisPool.go文件用于连接池的初始化
package redigo_pool
import (
"flag"
"github.com/garyburd/redigo/redis"
"time"
)
var (
Pool *redis.Pool
RedisServer = flag.String("redisServer",":6379","")
)
func init() {
Pool = &redis.Pool{
MaxIdle: 3,//最大空闲链接数,表示即使没有redis链接事依然可以保持N个空闲链接,而不被清除
MaxActive: 3,//最大激活连接数,表示同时最多有多少个链接
IdleTimeout: 240 time.Second,//最大空闲链接等待时间,超过此时间,空闲将被关闭
Dial: func() (redis.Conn,error) {
c,err := redis.Dial("tcp",RedisServer)
if err != nil {
return nil,err
}
return c,err
},TestOnBorrow: func(c redis.Conn,t time.Time) error {
if time.Since(t) < time.Minute {
return nil
}
_,err := c.Do("PING")
return err
},}
}
import (
"flag"
"github.com/garyburd/redigo/redis"
"time"
)
var (
Pool *redis.Pool
RedisServer = flag.String("redisServer",":6379","")
)
func init() {
Pool = &redis.Pool{
MaxIdle: 3,//最大空闲链接数,表示即使没有redis链接事依然可以保持N个空闲链接,而不被清除
MaxActive: 3,//最大激活连接数,表示同时最多有多少个链接
IdleTimeout: 240 time.Second,//最大空闲链接等待时间,超过此时间,空闲将被关闭
Dial: func() (redis.Conn,error) {
c,err := redis.Dial("tcp",RedisServer)
if err != nil {
return nil,err
}
return c,err
},TestOnBorrow: func(c redis.Conn,t time.Time) error {
if time.Since(t) < time.Minute {
return nil
}
_,err := c.Do("PING")
return err
},}
}
- 创建main.go文件实现二级缓存
package mainimport (
"database/sql"
"encoding/json"
"fmt"
"github.com/garyburd/redigo/redis"
_ "github.com/go-sql-driver/MysqL"
"strconv"
"web/redis/redigopool"
"web/redis/redigo_pool"
)type Person struct {
Id intdb:"id"
Name stringdb:"name"
Age intdb:"age"
Rmb intdb:"rmb"
}func main() {
var cmd string
for{
fmt.Println("输入命令")
fmt.Scan(&cmd)
switch cmd {
case "getall":
getAll()
default:
fmt.Println("不能识别其他命令")
}
fmt.Println()
}
}func getAll() {
//从连接池当中获取链接
conn := redigo_pool.Pool.Get()
//先查看redis中是否有数据
//conn,_ :=redis.Dial("tcp","localhost:6379")
defer conn.Close()
values,_ := redis.Values(conn.Do("lrange","mlist",-1))if len(values) > 0 { //如果有数据 fmt.Println("从re<a href="https://www.jb51.cc/tag/dis/" target="_blank">dis</a><a href="https://www.jb51.cc/tag/huoqu/" target="_blank">获取</a>数据") //从re<a href="https://www.jb51.cc/tag/dis/" target="_blank">dis</a>中直接<a href="https://www.jb51.cc/tag/huoqu/" target="_blank">获取</a> for _,key := range values{ pid :=string(key.([]byte)) id,_:= strconv.Atoi(pid) results,_ := re<a href="https://www.jb51.cc/tag/dis/" target="_blank">dis</a>.Bytes(conn.Do("GET",id)) var p Person err := json.Unmarshal(results,&p) if err != nil { fmt.Println("json 反序列化出错") }else { fmt.Printf("name = <a href="https://www.jb51.cc/tag/s/" target="_blank">%s</a>\n",p.Name) } } }else { fmt.Println("从<a href="https://www.jb51.cc/tag/MysqL/" target="_blank">MysqL</a>中<a href="https://www.jb51.cc/tag/huoqu/" target="_blank">获取</a>") //<a href="https://www.jb51.cc/tag/chaxun/" target="_blank">查询</a><a href="https://www.jb51.cc/tag/shujuku/" target="_blank">数据库</a> db,_ := <a href="https://www.jb51.cc/tag/sql/" target="_blank">sql</a>.Open("<a href="https://www.jb51.cc/tag/MysqL/" target="_blank">MysqL</a>","root:Szt930708@tcp(localhost:3306)/mydb") defer db.Close() var persons []Person rows,_ := db.Query("select id,name,age,rmb from person") for rows.Next() { var id int var name string var age int var rmb int rows.Scan(&id,&name,&age,&rmb) per := Person{id,rmb} persons = append(persons,per) } //写入到re<a href="https://www.jb51.cc/tag/dis/" target="_blank">dis</a>中:将person以hash的方式写入到re<a href="https://www.jb51.cc/tag/dis/" target="_blank">dis</a>中 for _,p := range persons{ p_byte,_ := json.Marshal(p) _,err1 := conn.Do("SETNX",p.Id,p_byte) _,err2 := conn.Do("lpush",p.Id) // 设置过期时间 conn.Do("EXPIRE",60*5) if err1 != nil || err2 != nil { fmt.Println("写入失败") }else { fmt.Println("写入成功") } } conn.Do("EXPIRE",60*5) }
}
Canal+Kafka实现MySql与Redis数据一致性
在生产环境中,经常会遇到MySql与Redis数据不一致的问题。那么如何能够保证MySql与Redis数据一致性的问题呢?话不多说,咱们直接上解决方案。
如果对Canal还不太了解的可以先去看一下官方文档:https://github.com/alibaba/canal
首先,咱们得先开启MySql的允许基于BinLog文件主从复制。因为Canal的核心原理也是相当于把自己当成MySql的一个从节点,然后去订阅主节点的BinLog日志。
开启BinLog文件配置
1. 配置MySQL的 my.ini/my.cnf 开启允许基于binlog文件主从同步
log-bin=mysql-bin #添加这一行就ok
binlog-format=ROW #选择row模式
server_id=1 #配置mysql replaction需要定义,不能和canal的slaveId重复
配置该文件后,重启mysql服务器即可
show variables like ''log_bin'';//查询MySql是否开启了log_bin.没有开启log_bin的值是OFF,开启之后是ON
2. 添加cannl的账号或者直接使用自己的root账号。添加完后一定要检查mysql user 权限为y(SELECT* from `user` where user=''canal'')
drop user ''canal''@''%'';
CREATE USER ''canal''@''%'' IDENTIFIED BY ''canal'';
grant all privileges on *.* to ''canal''@''%'' identified by ''canal'';
flush privileges;
整合Kafka
1. 由于Kafka依赖Zookeeper,先安装zookeeper
zoo_sample.cfg 修改为 zoo.cfg
修改 zoo.cfg 中的 dataDir=E:\zkkafka\zookeeper-3.4.14\data
新增环境变量:
ZOOKEEPER_HOME: E:\zkkafka\zookeeper-3.4.14 (zookeeper目录)
Path: 在现有的值后面添加 ";%ZOOKEEPER_HOME%\bin;"
运行zk zkServer.cmd。
针对闪退,可按照以下步骤进行解决(参考:https://blog.csdn.net/pangdongh/article/details/90208230):
1 、编辑zkServer.cmd文件末尾添加pause
。这样运行出错就不会退出,会提示错误信息,方便找到原因。
2.如果报错内容为:-Dzookeeper.log.dir=xxx"'' 不是内部或外部命令,也不是可运行的程序 或批处理文件的解决。则建议修改zkServer.cmd文件:
@echo off
REM Licensed to the Apache Software Foundation (ASF) under one or more
REM contributor license agreements. See the NOTICE file distributed with
REM this work for additional information regarding copyright ownership.
REM The ASF licenses this file to You under the Apache License, Version 2.0
REM (the "License"); you may not use this file except in compliance with
REM the License. You may obtain a copy of the License at
REM
REM http://www.apache.org/licenses/LICENSE-2.0
REM
REM Unless required by applicable law or agreed to in writing, software
REM distributed under the License is distributed on an "AS IS" BASIS,
REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
REM See the License for the specific language governing permissions and
REM limitations under the License.
setlocal
call "%~dp0zkEnv.cmd"
set ZOOMAIN=org.apache.zookeeper.server.quorum.QuorumPeerMain
echo on
java "-Dzookeeper.log.dir=%ZOO_LOG_DIR%" "-Dzookeeper.root.logger=%ZOO_LOG4J_PROP%" -cp "%CLASSPATH%" %ZOOMAIN% "%ZOOCFG%" %*
endlocal
pause
2. 安装kafka
解压 kafka_2.13-2.4.0 改名为 kafka
修改 server.properties中的配置
log.dirs=E:\zkkafka\kafka\logs
启动Kafka:
Cmd 进入到该目录:
cd E:\zkkafka\kafka
.\bin\windows\kafka-server-start.bat .\config\server.properties
如果启动报系统找不到指定的路径,进入kafka目录kafka\bin\windows\kafka-run-class.bat,将set JAVA="%JAVA_HOME%/bin/java"改为java环境安装的绝对路径
例如:set JAVA="D:\LI\JDK\jdk1.8.0_152\bin\java"
Canal配置更改
1.修改 example/instance.properties
canal.mq.topic=maikt-topic
2.修改 canal.properties
# tcp, kafka, RocketMQ
canal.serverMode = kafka
canal.mq.servers = 127.0.0.1:9092
3.启动startup.bat 查看 \logs\example example.log日志文件是否有 start successful....
SpringBoot项目整合kafka
maven依赖
<!-- springBoot集成kafka -->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
application.yml
# kafka
spring:
kafka:
# kafka服务器地址(可以多个)
bootstrap-servers: 127.0.0.1:9092
consumer:
# 指定一个默认的组名
group-id: kafka2
# earliest:当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,从头开始消费
# latest:当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,消费新产生的该分区下的数据
# none:topic各分区都存在已提交的offset时,从offset后开始消费;只要有一个分区不存在已提交的offset,则抛出异常
auto-offset-reset: earliest
# key/value的反序列化
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
producer:
# key/value的序列化
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.apache.kafka.common.serialization.StringSerializer
# 批量抓取
batch-size: 65536
# 缓存容量
buffer-memory: 524288
redis:
host: 1.1.1.1
# password:
port: 6379
database: 10
password: 123456
Redis工具类
@Component
public class RedisUtils {
/**
* 获取我们的redis模版
*/
@Autowired
private StringRedisTemplate stringRedisTemplate;
public void setString(String key, String value) {
setString(key, value, null);
}
public void setString(String key, String value, Long timeOut) {
stringRedisTemplate.opsForValue().set(key, value);
if (timeOut != null) {
stringRedisTemplate.expire(key, timeOut, TimeUnit.SECONDS);
}
}
public String getString(String key) {
return stringRedisTemplate.opsForValue().get(key);
}
/**
* redis当成数据库中
* <p>
* 注意事项:对我们的redis的key设置一个有效期
*/
public boolean deleteKey(String key) {
return stringRedisTemplate.delete(key);
}
}
Kafka主题监听方法(往redis同步数据的代码可以根据自己的需求去完善,本代码只是做测试用)
@KafkaListener(topics = "maikt-topic")
public void receive(ConsumerRecord<?, ?> consumer) {
System.out.println("topic名称:" + consumer.topic() + ",key:" +
consumer.key() + "," +
"分区位置:" + consumer.partition()
+ ", 下标" + consumer.offset() + "," + consumer.value());
String json = (String) consumer.value();
JSONObject jsonObject = JSONObject.parseObject(json);
String sqlType = jsonObject.getString("type");
JSONArray data = jsonObject.getJSONArray("data");
if(data!=null)
{
JSONObject userObject = data.getJSONObject(0);
String id = userObject.getString("id");
String database = jsonObject.getString("database");
String table = jsonObject.getString("table");
String key = database + "_" + table + "_" + id;
if ("UPDATE".equals(sqlType) || "INSERT".equals(sqlType)) {
redisUtils.setString(key, userObject.toJSONString());
return;
}
if ("DELETE".equals(sqlType)) {
redisUtils.deleteKey(key);
}
}
}
第一次写文章,如果有不足的地方,欢迎各位大佬指正。
来源于:蚂蚁课堂
canal解决mysql与redis数据同步框架
一、通过canal-client同步;
1、安装canalServer
[root@localhost software]# mkdir canal
[root@localhost software]# cd canal/
[root@localhost canal]# rz
rz waiting to receive.
?a? zmodem ′??. °′ Ctrl+C ??.
Transferring canal.deployer-1.1.5-SNAPSHOT.tar.gz...
100% 51102 KB 8517 KB/s 00:00:06 0 ′?
?[root@localhost canal]# tar -zxvf canal.deployer-1.1.5-SNAPSHOT.tar.gz
2、修改配置,启动
#配置好mysql的数据库地址和帐号密码
#canal.instance.master.address=127.0.0.1:3306
#canal.instance.dbUsername=root
#canal.instance.dbPassword=root
[root@localhost canal]# vi conf/example/instance.properties
[root@localhost canal]# ./bin/startup.sh
#看到successful表示启动成功
[root@localhost canal]# cat logs/example/example.log
2020-03-15 22:45:07.762 [main] INFO c.a.o.c.i.spring.support.PropertyPlaceholderConfigurer - Loading properties file from class path resource [canal.properties]
2020-03-15 22:45:07.765 [main] INFO c.a.o.c.i.spring.support.PropertyPlaceholderConfigurer - Loading properties file from class path resource [example/instance.properties]
2020-03-15 22:45:07.905 [main] WARN o.s.beans.GenericTypeAwarePropertyDescriptor - Invalid JavaBean property ''connectionCharset'' being accessed! Ambiguous write methods found next to actually used [public void com.alibaba.otter.canal.parse.inbound.mysql.AbstractMysqlEventParser.setConnectionCharset(java.lang.String)]: [public void com.alibaba.otter.canal.parse.inbound.mysql.AbstractMysqlEventParser.setConnectionCharset(java.nio.charset.Charset)]
2020-03-15 22:45:07.934 [main] INFO c.a.o.c.i.spring.support.PropertyPlaceholderConfigurer - Loading properties file from class path resource [canal.properties]
2020-03-15 22:45:07.935 [main] INFO c.a.o.c.i.spring.support.PropertyPlaceholderConfigurer - Loading properties file from class path resource [example/instance.properties]
2020-03-15 22:45:08.270 [main] INFO c.a.otter.canal.instance.spring.CanalInstanceWithSpring - start CannalInstance for 1-example
2020-03-15 22:45:08.276 [main] WARN c.a.o.canal.parse.inbound.mysql.dbsync.LogEventConvert - --> init table filter : ^.*\..*$
2020-03-15 22:45:08.276 [main] WARN c.a.o.canal.parse.inbound.mysql.dbsync.LogEventConvert - --> init table black filter :
2020-03-15 22:45:08.282 [main] INFO c.a.otter.canal.instance.core.AbstractCanalInstance - start successful....
2020-03-15 22:45:08.345 [destination = example , address = /127.0.0.1:3306 , EventParser] WARN c.a.o.c.p.inbound.mysql.rds.RdsBinlogEventParserProxy - ---> begin to find start position, it will be long time for reset or first position
2020-03-15 22:45:08.345 [destination = example , address = /127.0.0.1:3306 , EventParser] WARN c.a.o.c.p.inbound.mysql.rds.RdsBinlogEventParserProxy - prepare to find start position just show master status
2020-03-15 22:45:08.937 [destination = example , address = /127.0.0.1:3306 , EventParser] WARN c.a.o.c.p.inbound.mysql.rds.RdsBinlogEventParserProxy - ---> find start position successfully, EntryPosition[included=false,journalName=mysql-bin.000001,position=4,serverId=1,gtid=<null>,timestamp=1584282800000] cost : 582ms , the next step is binlog dump
3、搭建canal-client,引入依赖
<dependencies>
<dependency>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal.client</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
</dependencies>
4、同步代码
import com.alibaba.fastjson.JSONObject;
import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.client.CanalConnectors;
import com.alibaba.otter.canal.protocol.CanalEntry.*;
import com.alibaba.otter.canal.protocol.Message;
import java.net.InetSocketAddress;
import java.util.List;
public class CanalClient {
public static void main(String args[]) {
CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress("127.0.0.1",
11111), "example", "", "");
int batchSize = 100;
try {
connector.connect();
connector.subscribe("user.users");
connector.rollback();
while (true) {
// 获取指定数量的数据
Message message = connector.getWithoutAck(batchSize);
long batchId = message.getId();
int size = message.getEntries().size();
System.out.println("batchId = " + batchId);
System.out.println("size = " + size);
if (batchId == -1 || size == 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
printEntry(message.getEntries());
}
// 提交确认
connector.ack(batchId);
// connector.rollback(batchId); // 处理失败, 回滚数据
}
} finally {
connector.disconnect();
}
}
private static void printEntry(List<Entry> entrys) {
for (Entry entry : entrys) {
if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN || entry.getEntryType() == EntryType.TRANSACTIONEND) {
continue;
}
RowChange rowChage = null;
try {
rowChage = RowChange.parseFrom(entry.getStoreValue());
} catch (Exception e) {
throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:" + entry.toString(),
e);
}
EventType eventType = rowChage.getEventType();
System.out.println(String.format("================> binlog[%s:%s] , name[%s,%s] , eventType : %s",
entry.getHeader().getLogfileName(), entry.getHeader().getLogfileOffset(),
entry.getHeader().getSchemaName(), entry.getHeader().getTableName(),
eventType));
for (RowData rowData : rowChage.getRowDatasList()) {
if (eventType == EventType.DELETE) {
redisDelete(rowData.getBeforeColumnsList());
} else if (eventType == EventType.INSERT) {
redisInsert(rowData.getAfterColumnsList());
} else {
System.out.println("-------> before");
printColumn(rowData.getBeforeColumnsList());
System.out.println("-------> after");
redisUpdate(rowData.getAfterColumnsList());
}
}
}
}
private static void printColumn(List<Column> columns) {
for (Column column : columns) {
System.out.println(column.getName() + " : " + column.getValue() + " update=" + column.getUpdated());
}
}
private static void redisInsert(List<Column> columns) {
JSONObject json = new JSONObject();
for (Column column : columns) {
json.put(column.getName(), column.getValue());
}
if (columns.size() > 0) {
RedisUtil.stringSet(columns.get(0).getValue(), json.toJSONString());
}
}
private static void redisUpdate(List<Column> columns) {
JSONObject json = new JSONObject();
for (Column column : columns) {
json.put(column.getName(), column.getValue());
}
if (columns.size() > 0) {
RedisUtil.stringSet(columns.get(0).getValue(), json.toJSONString());
}
}
private static void redisDelete(List<Column> columns) {
JSONObject json = new JSONObject();
for (Column column : columns) {
json.put(column.getName(), column.getValue());
}
if (columns.size() > 0) {
RedisUtil.delKey(columns.get(0).getValue());
}
}
}
import redis.clients.jedis.Jedis;
public class RedisUtil {
private static Jedis jedis = null;
public static synchronized Jedis getJedis() {
if (jedis == null) {
jedis = new Jedis("127.0.0.1", 6379);
}
return jedis;
}
public static boolean existKey(String key) {
return getJedis().exists(key);
}
public static void delKey(String key) {
getJedis().del(key);
}
public static String stringGet(String key) {
return getJedis().get(key);
}
public static String stringSet(String key, String value) {
return getJedis().set(key, value);
}
public static void hashSet(String key, String field, String value) {
getJedis().hset(key, field, value);
}
}
二、通过MQ方法同步;
Mybatis 一级缓存与二级缓存的实现
mybatis作为一个流行的持久化工具,缓存必然是缺少不了的组件。通过这篇文章,就让我们来了解一下一级缓存与二级缓存的实现,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
mybatis缓存
mybatis作为一个流行的持久化工具,缓存必然是缺少不了的组件。通过这篇文章,就让我们来了解一下mybatis的缓存。
mybatis缓存类型
说起mybatis的缓存,了解过的同学都知道,mybatis中可以有两种缓存类型:
第一种,我们通常称为以及缓存,或者sqlSession级别的缓存,这种缓存是mybatis自带的,如果mapper中的配置都是默认的话,那么一级缓存也是默认开启的。
第二种,就是非sqlSession级别的缓存了,我们通常称为二级缓存,mybatis中的二级缓存需要实现Cache接口,并且配置在mapper中,要先开启的话,需要一些配置,下面我们会详细说到。
一级缓存
作为mybatis自带的缓存,我们通过代码来分析一下其原理。
首先,我们来看下一级缓存的效果。
测试代码:
@Test public void test_Cache() throws Exception { InputStream input = Resources.getResourceAsstream("mybatis-config.xml"); sqlSessionFactory factory = new sqlSessionFactoryBuilder().build(input); sqlSession sqlSession = factory.openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); System.out.println("the first query : "); mapper.queryAllUsers(); System.out.println("===================================="); System.out.println("the second query : "); mapper.queryAllUsers(); sqlSession.commit(); }
mapper配置如下,我们采用默认配置:
select * from hwc_users
运行结果如下:
Created connection 1191654595.
Setting autocommit to false on JDBC Connection [com.MysqL.jdbc.JDBC4Connection@470734c3]
==> Preparing: select * from hwc_users
==> Parameters:
====================================
the second query :
Cache Hit Ratio [com.huwc.mapper.UserMapper]: 0.0
Process finished with exit code 0
从上述结果可以看到,第二次查询并没有从数据库获取,并且没有从二级缓存中获取,由此可见,默认配置情况下,同一个sqlSession中会默认使用mybatis的一级缓存。
下面,我们从mybatis源码来看一下:
从上面的代码中,我们可以看到:一级缓存是在BaseExecutor中命中的,BaseExecutor中的localCache属性应该就是用来存储查询结果的。
localCache的定义代码如下:
从上述代码可以看出:
BaseExecutor中集成了一级缓存,一级缓存为PerpetualCache(永久缓存?)的对象,其也是实现了Cache接口的对象,并且其存储结果就是简单的HashMap。
并且从代码上来看,一级缓存是无法禁止的。但是如果一个查询,我们就是不想让其从缓存中获取,必须从数据库查询,那我们岂不是无法处理了?
答案必然是否定的,我们从代码中可以看到:虽然一级缓存无法跳过,但是我们可以将缓存中数据进行清除处理,这样一级缓存中就获取不到结果集了:
如何让mybatis每次查询都flush缓存结果集呢?答案是通过mapper配置中的flushCache属性来处理:
select * from hwc_users
加上这个属性后,我们来看下程序执行结果:
Created connection 1191654595.
Setting autocommit to false on JDBC Connection [com.MysqL.jdbc.JDBC4Connection@470734c3]
==> Preparing: select * from hwc_users
==> Parameters:
====================================
the second query :
Cache Hit Ratio [com.huwc.mapper.UserMapper]: 0.0
==> Preparing: select * from hwc_users
==> Parameters:
Process finished with exit code 0
可以看到,第二次查询也检索了数据库来获取结果。
一级缓存就说到这里吧,下面我们来看看二级缓存。
二级缓存
mybatis的二级缓存是需要借助第三方的缓存来实现,常用的有ehcache或者redis,其存储类型不同,但是在mybatis中的使用方式是一样的,简单处理,我们使用ehcache来说明。
通常来说,在mybatis中启用二级缓存,我们需要以下的步骤:
1、在项目中引入ehcache模块和mybatis-ehcache模块
2、在项目中加如ehcache配置文件
3、在mybatis配置文件中我们需要在setting中配置cacheEnabled属性;
4、在mapper配置文件中配置cache属性,并指定缓存的实现类;
5、在statement中配置useCache属性为”true“
第一步:首先我们在项目中引入相关模块:
net.sf.ehcacheehcache2.10.0org.mybatis.cachesmybatis-ehcache1.2.1
第二步:我们从网上抄一个ehcache的配置文件:ehcache.xml
第三步:配置mybatis属性
之前,我们说过,mybatis的配置,主要是为了初始化Configuration对象,从Configuration代码中我们看到,对应的属性默认值就是为true,因此,此步骤也可以跳过,直接采用mybatis的默认值:
第四步:配置mapper中的缓存属性:
......
第五步:在statement中开启二级缓存:
select * from hwc_users
测试代码如下,为了屏蔽一级缓存,我们在第一次查询和第二次查询中将sqlSession进行关闭并重新open:
@Test public void test_Cache() throws Exception { InputStream input = Resources.getResourceAsstream("mybatis-config.xml"); sqlSessionFactory factory = new sqlSessionFactoryBuilder().build(input); sqlSession sqlSession = factory.openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); System.out.println("the first query : "); mapper.queryAllUsers(); sqlSession.close(); sqlSession = factory.openSession(); mapper = sqlSession.getMapper(UserMapper.class); System.out.println("===================================="); System.out.println("the second query : "); mapper.queryAllUsers(); sqlSession.commit(); }
执行结果如下:
Created connection 1191654595.
Setting autocommit to false on JDBC Connection [com.MysqL.jdbc.JDBC4Connection@470734c3]
==> Preparing: select * from hwc_users
==> Parameters:
Resetting autocommit to true on JDBC Connection [com.MysqL.jdbc.JDBC4Connection@470734c3]
Closing JDBC Connection [com.MysqL.jdbc.JDBC4Connection@470734c3]
Returned connection 1191654595 to pool.
====================================
the second query :
Cache Hit Ratio [com.huwc.mapper.UserMapper]: 0.5
Process finished with exit code 0
从结果中,我们可以看到,二级缓存起到了作用,并且命中率为0.5(查询两次,一次命中)
下面,我们从mybatis的代码来看下二级缓存使用:
代码截图中,我们看到,二级缓存是在CacheExecutor中进行的调用,并且最终使用的就是我们的Ehcache:
并且,如果我们在mapper中的statement中也配置了flushCache,那么二级缓存也将在查询前被清除掉,我们通过测试来看以下:
select * from hwc_users
执行结果如下:
Created connection 1191654595.
Setting autocommit to false on JDBC Connection [com.MysqL.jdbc.JDBC4Connection@470734c3]
==> Preparing: select * from hwc_users
==> Parameters:
Resetting autocommit to true on JDBC Connection [com.MysqL.jdbc.JDBC4Connection@470734c3]
Closing JDBC Connection [com.MysqL.jdbc.JDBC4Connection@470734c3]
Returned connection 1191654595 to pool.
====================================
the second query :
Cache Hit Ratio [com.huwc.mapper.UserMapper]: 0.5
opening JDBC Connection
Checked out connection 1191654595 from pool.
Setting autocommit to false on JDBC Connection [com.MysqL.jdbc.JDBC4Connection@470734c3]
==> Preparing: select * from hwc_users
==> Parameters:
Process finished with exit code 0
1、mybatis的缓存处理,都交由Executor来处理,一级缓存是由BaseExecutor处理,二级缓存则由CacheExecutor处理;
2、statement中如果配置了flushCache为true,那么不论是一级缓存还是二级缓存都会失效;
3、要启用二级缓存,需要在statement中配置useCache为true。
到此这篇关于Mybatis 一级缓存与二级缓存的实现的文章就介绍到这了,更多相关Mybatis 一级缓存与二级缓存内容请搜索小编以前的文章或继续浏览下面的相关文章希望大家以后多多支持小编!
mybatis 二级缓存集成 redis 的问题
mybatis 集成了二级缓存,正常都是可以使用的,但是把 redis 服务停了,
查询就不好使了,报异常
nested exception is org.apache.ibatis.exceptions.PersistenceException: \n### Error querying database. Cause: org.springframework.data.redis.RedisConnectionFailureException: Cannot get Jedis connection; nested exception is redis.clients.jedis.exceptions.JedisConnectionException: java.net.ConnectException: Connection refused (Connection refused)\n### Cause: org.springframework.data.redis.RedisConnectionFailureException: Cannot get Jedis connection; nested exception is redis.clients.jedis.exceptions.JedisConnectionException: java.net.ConnectException: Connection refused (Connection refused)
个人理解,缓存不好使,正常来说不应该查询数据库么?求大牛给指导一下!
关于MySQL与Redis实现二级缓存和redis和mybatis二级缓存的介绍已经告一段落,感谢您的耐心阅读,如果想了解更多关于Canal+Kafka实现MySql与Redis数据一致性、canal解决mysql与redis数据同步框架、Mybatis 一级缓存与二级缓存的实现、mybatis 二级缓存集成 redis 的问题的相关信息,请在本站寻找。
本文标签: