前言
一:知识回顾
前面系列文章我们已经探讨过:Mybatis核心运行源码分析、Mybatis当中.getMapper()方法的源码分析等
二:后续Mybatis我们会研究那些内容?
Mybatis中缓存的使用、Mybatis与Spring集成、Mybatis 插件。
Mybatis的插件可以对Mybatis内核功能或者是业务功能进行拓展,内核的话我们拓展意义不大,业务拓展是非常具有含义的,可以把Mybatis操作的更细节。
插件拓展内容举例:
1:分页操作:PageHelper,这个本质上就是一个Mybatis插件。
2:乐观锁拓展:实际上我们说乐观锁在Mybatis当中是不支持的。
3:数据权限:-- 拿到SQL语句,拿到paramter就可以为所欲为。
三:为什么引入缓存
数据库和程序之间的交互永远是性能瓶颈
1:程序性能瓶颈点
1:网络通信 数据传输。
2:RDB(关系型数据库):这种一定是内存和硬盘并用的,传统数据库硬盘存储大量数据不利于查询。SQL优化,
3:Java对象的复用问题:JDBC
Connection -- 池化思想 -- 链接池 Statment -- 对象的复用 -- xxx
2:Mybatis引入缓存解决的是哪个问题
1):Mybatis引入缓存解决的问题
Mybatis引入了缓存。优先访问到缓存然后将数据,缓存中没有在去查询数据库,将数据保存到缓存中进行使用
使用缓存不是为了第一次交互的性能,而是为了后续的此数据的交互。这样就解决了硬盘存储大量数据不利于查询的问题。硬盘随机读写数据是毫秒级别,内存随机读取数据是纳秒级别。
注意:
当前个人PC硬盘已经到了2T-5T,内存基本上最大才128G,受限于内存的大小和断电安全性,所以缓存中获取的应该是热点数据。
2):什么是换出策略
当缓存中的空间不够使用时,先讲缓存中数据临时拿出来临时存储到硬盘中,这就叫换出,换出是使用的是序列化的方式。
当然,这里的序列化是一个很宽泛的概念,可以理解为文件序列化到硬盘中、可以理解为Java对象进行了网络传输。这些都可以称之为序列化,与之对应的是反序列化。序列化的方式:Java、Json、其他的序列化方式。
这里使用Json的方式进行序列化好不好呢?一定是不好的,传统的序列化方式一定是二进制方式存储数据的,Json是以字符串的方式存储数据的。存储相同数量级的数据,使用二进制体量一定更小。
字符串底层不也是二进制么?字符串最后也会存成二进制的,这是没有任何问题的。但是,字符串的二进制体量一定要比纯粹二进制的体量要大,因为他有他的格式+还有一些特殊的字符,他有他的解析协议(格式、编解码)。层层解析协议(格式、编解码)嵌套的话,数据量就很大了。所以,同样数据大小的前提下,字符串一定是更占地方。
所以说,使用Json存字符串行不行?行,但是不好,因为数据量大。所以,一般在内部我们不会使用字符串进行序列化。但是像Http协议和RPC的时候,经常会使用字符串做序列化方案。
3):什么是换出算法
LLU和FIFO是两个经典算法。
A:LLU
最不常用的对象会被换出,对象使用次数。
B:FIFO
典型的一个队列,缓存的1000个对象在队列当中,先入先出。进来一个新的挤出去一个老的。
三:缓存分类
1:ORM框架集成缓存
ORM框架解决缓存的问题,是最合理的。Hibernate或者Mybatis、JDO都会提供缓存功能。
这种方式也是最省心的,直接按照对应框架的开发步骤搞即可。
2:第三方中间件充当缓存。
Redis、Memcache 自研方式。
这种中间件的方式如何编码呢?
/** * 用于测试:创建DAO接口的代理 */ @Test public void test() { ProductDAO productDAO = new ProductDAOImpl(); ProductDAO productDAOProxy = (ProductDAO) Proxy.newProxyInstance(TestMybaits2.class.getClassLoader(), new Class[]{ProductDAO.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //方法 只有以query开头,在进行缓存的处理 如果不是以query开头,就直接运行 // if (method.getName().startsWith("query")) { // System.out.println("连接redis 查看数据是否 存在 如果存在 则直接返回 return data"); // return method.invoke(productDAO, args); // } //定义了一个这样的注解 Cache cache = method.getDeclaredAnnotation(Cache.class); if (cache != null) { System.out.println("连接redis 查看数据是否 存在 如果存在 则直接返回 return data"); return method.invoke(productDAO, args); } //非查询方法 return method.invoke(productDAO, args); } }); productDAOProxy.save(); System.out.println("---------------------------------"); productDAOProxy.queryProductById(10); System.out.println("---------------------------------"); productDAOProxy.queryAllProducts(); }
public interface ProductDAO { public void save(); public Product queryProductById(int id); @Cache public List<Product> queryAllProducts(); }
public class ProductDAOImpl implements ProductDAO { @Override public void save() { System.out.println("jdbc 的方式操作 数据库 完成 插入的操作"); } @Override public Product queryProductById(int id) { System.out.println("jdbc 的方式基于ID 进行查询 " + id); return new Product(); } @Override public List<Product> queryAllProducts() { System.out.println("jdbc 的方式进行全表查询 "); return new ArrayList(); } }
第三方的集成一定是基于代理设计模式做的。我们使用ORM框架做缓存的话,会提供对应的编码方式,日过是使用这种第三方中间件的话,一定是基于代理设计模式做的。
3:两种缓存方式的优缺点
使用ORM框架集成缓存的话,这样的话缓存是存储在本JVM内存中的,空间较小,但是很快因为没有其他的开销。
使用中间件作为缓存的话,这样的话缓存是存储在中间件内存中,空间较大,但是就没那么快因为有网络IO,涉及到JVM虚拟机进程和Redis进程之间的网络通信。考虑到内网使用的话,也是可以接受的。