https://www.jianshu.com/p/ba19fca88a08
一,谈谈你对Mybatis 的理解
1,Mybatis是一个半ORM(对象关系映射)框架,他内部封装了JDBC,开发时候只需要关注SQL语句本身。不需要花费精力去处理加载驱动,创建连接,创建Statement 等繁杂的过程。程序员直接编写原生态SQL, 可以严格控制SQL 执行性能,灵活性高。
2,Mybatis 可以使用XML 或朱姐来配置和映射原生信息。将pojo映射成数据库中的记录,避免了几乎所有的JDBC 代码和手动设置参数以及获取结果集。
3,通过 XML 文件或注解的方式将执行的各种Statement配置起来,并通过java对象和Statement 中sql 的动态参数进行映射生成最终执行的SQL语句,最后由Mybatis框架执行sql 并将结果映射为java 对象并返回(从执行sql到返回Result 的过程)。
二 myBatis的优缺点由哪些?
缺点
1,SQL 语句的编写工作量比较大,尤其当字段多,关联表多时,对开发人员编写sql语句的功底有一定要求;
2,SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。
三,Mybatis 与hibernate 有哪些不同?
1,mybatis与hibernate不透明和,他不完全是一个ORM框架,因为Mybatis需要程序员自己编写SQL语句,;hibernate 对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件,如果用Hibernate开发可以节省很多的代码,提高效率。
2,Mybatis 直接编写原生态SQL,可以严格控制SQL执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,因为这类软件需求变化频繁。一旦需求变化要求迅速输出结果。但是灵活的前提是Mybatis 无法做到数据无关性,如果需要支持多种数据库的软件,则需要自定义多套SQL映射文件,工作量大。
四,Mybatis中 #{}和${}的区别是什么?
{}是预编译处理,${} 是字符串替换
1,Mybatis 在处理#{} 时,会将sql 中的#{} 替换为 ?号,调用 PreparedStatement的set 方法来赋值, 使用#{} 可以有效的防止sql 注入,提高系统安全性;
2, Mybatis 在处理 ${}时,就是把{} 替换成变量的值。
五,Mybatis 是如何进行分页的? 分页插件的原理是什么?
Mybatis 使用 RowBounds 对象进行分页,他是针对ResultSet 结果集执行的内存分页,而非物理分页。可以在SQL 内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。
分页插件的基本原理是使用MYbatis 提供的分页插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的SQL,根据dialect 方言,添加对应的物理分页语句和物理分页参数。
六,mybatis 有几种分页方式?
1,数组分页
2,SQL 分页
3, 拦截器分页
4,RowBounds 分页
七,Mybatis 逻辑分页和物理分页的区别是什么?
1,物理分页速度上并不一定快于逻辑分页,逻辑分页速度上也并不一定快于物理分页
2,物理分页总是优于逻辑分页,没有必要将属于数据库端的压力加到应用端来,就算速度上存在优势,然而其性能是哪个的优点足以弥补这个缺点
八, Mybatis 是否支持延迟加载?如果支持,他的实现原理是什么?
Mybatis 仅仅支持 association 关联对象和collection 关联集合对象的延迟加载,association 指的是一对一,collection 指的是一对多查询。在mybatis配置文件中,可以配置是否启用延迟加载 lazyLoadingEnabled=true|false
他的原理是,使用CGLIB 创建对象的代理对象,当调用目标时,进入拦截方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null 值,那么就会单独发送事保存好的查询关联B 对象的SQL,把B 查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用,这就是延迟加载的基本原理。
九: 说下Mybatis 的一级缓存和二级缓存
一级缓存:基于PerpetualCache的hashMap 本地缓存,其存储作用域为Session,当session flush 或close 之后,该session中的所有Cache 就将清空,默认打开一级缓存;
二级缓存: 与一级缓存其机制相同,默认也是采用PerpetualCache,HashMap存储,不同之处在于其存储作用域为mapper(Namespace),并且可以自定义存储源,如Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可以在他的映射文件中配置
对于缓存数据更新机制,当某个作用域 的进行了C/u/d操作后。默认该作用域下所有的select中的缓存被clear
十,Mybatis有哪些执行器(Executor)
Mybatis有三种执行器(Executor):
1,SimpleExecutor: 每执行一次update或者select,就开启一个Statement对象,用完立刻关闭 statement对象:
2,ReuseExecutor: 执行update 或select,以SQL作为key 查找statement 对象,存在就使用,不存在就创建,用完后,不关闭statement 对象,而是存放在Map内,供下一次使用,简言之就是重复使用Statement对象:
3,BatchExecutor:执行update(没有select,JDBC 批处理不支持select),将所有SQL都添加到批处理中(addBatch()),等待统一执行(executeBatch()),他缓存了多个Statement 对象,每个Statement对象都是addBatch()完毕后。等待逐一执行executeBatch()批处理,与jdbc 批处理相同。
十一,Mybatis动态sql 是做什么的? 都有哪些动态SQL?能简述下动态SQL的执行原理不?
1,Mybatis 动态Sql 可以让我们在XMl 映射文件内,以标签的形式编写动态Sql,完成逻辑判断和动态拼接SQL的功能;
2,Mybatis 提供了9种动态SQL 标签:trim,where,set,foreach,if,choose,when,otherwise,bind;
3,执行原理:使用OGNL从参数对象中计算表达式的值,根据表达式的值动态拼接SQL,已完成动态SQL的功能;
消息队列面试题:
1,消息队列的基本作用?
消息队列,一般我们会简称他为MQ(Message Queue),队列是一种先进先出的数据结构。
消息队列可以简单的理解为:
1,把要传输的数据放在队列中
2,把数据放到消息队列叫做生产者
3,从消息队列里边取数据叫做消费者
作用:
1,降低系统耦合性;
举个例子:
现在我有一个系统A,系统A可以产生一个userId,然后,现在有系统B和系统C都需要这个userId去做相关的操作,系统A给系统B和系统C传入userId。好,现在问题解决了。
然后,某一天,系统B的负责人告诉系统A的负责人,现在系统B的SystemBNeed2do(String userId)这个接口不再使用了,让系统A别去调它了。于是,系统A的负责人说"好的,那我就不调用你了。",于是就把调用系统B接口的代码给删掉了。
又过了几天,系统D的负责人接了个需求,也需要用到系统A的userId,于是就跑去跟系统A的负责人说:“老哥,我要用到你的userId,你调一下我的接口吧”。于是系统A说:“没问题的,这就搞”
然后,又过了几天,系统E的负责人过来了,告诉系统A,需要userId。又过了几天,系统B的负责人过来了,告诉系统A,还是重新掉那个接口吧。又过了几天,系统F的负责人过来了,告诉系统A,需要userId。
最后,系统A的负责人,总是改来改去,特别闹心,就跑路了。
然后,公司招来一个大佬,大佬经过几天熟悉,上来就说:将系统A的userId写到消息队列中,这样系统A就不用经常改动了。为什么呢?
系统A将userId写到消息队列中,系统C和系统D从消息队列中拿数据。
系统A只负责把数据写到队列中,谁想要或不想要这个数据(消息),系统A一点都不关心。即便现在系统D不想要userId这个数据了,系统B又突然想要userId这个数据了,都跟系统A无关,系统A一点代码都不用改。
系统D拿userId不再经过系统A,而是从消息队列里边拿。系统D即便挂了或者请求超时,都跟系统A无关,只跟消息队列有关。
这样一来,系统A与系统B、C、D都解耦了。
总结来说:
模块之间不存在直接调用,那么新增模块或者修改模块就对其他模块影响较小,这样系统的可扩展性无疑就会更好一些;
消息队列使利用发布-订阅模式工作,消息发送者(生产者)发布消息,一个或多个消息接受者(消费者)订阅消息。消息发送者(生产者)和消息接受者(消费者)之间没有直接耦合,消息发送者将消息发送至分布式消息队列既结束对消息的处理,消息接受者从分布式消息队列获取该消息后进行后续处理,并不需要知道该消息从何而来,对新增业务,只要对该类消息感兴趣,既订阅该消息,对原有系统和业务没有任何影响,从而实现网站业务的可扩展性设计。
另外为了避免消息队列服务器宕机造成消息消失,会将成功发送消息队列的消息存储在生产者服务器上,等消息真正被消费者服务处理后才删除消息。在消息队列服务器宕机后,生产者服务器会选择分布式消息队列服务器集群中的其他服务器发布消息。
不要认为消息队列只能利用发布-订阅模式工作,只不过在解耦这个特定业务环境下是使用发布-订阅模式的,除了发布--订阅模式,还有点对点订阅模式(一个消息只有一个消费者),我们比较常用的是发布-订阅模式。
另外这2种消息模型是JMS 提供的,AMQp 协议还提供了5种消息模型
2,通过异步处理提高系统性能(削峰,减少响应所需时间)
在不使用消息队列服务器的时候,用户的请求数据直接写入数据库,在高并发的情况下数据库压力剧增,使得响应速度变慢。但是在使用消息队列后,用户的请求数据发给消息队列之后立即返回,再由消息队列的消费者进程从消息队列中获取数据,异步写入数据库。由于消息队列服务器处理速度快于数据库(消息队列也比数据库有更好的伸缩性),因此响应速度得到答复改善。
消息队列具有很好的削峰作用的功能-----即通过异步处理,将时间高并发产生的事务消息存储在消息队列中,从而削平高峰期的并发事务。
因为用户请求数据写入消息队列中后就立即返回给用户了,但是请求数据在后续的业务校验,写数据库等操作中可能失败。因此使用消息队列进行一步处理后,需要适当的修改业务流程进行配合,比如用户在提交订单后,订单数据写入消息队列,不能立即返回用户订单提交成功,需要在消息队列的订单消费者进程真正处理完该订单后,甚至出库后,在通过电子邮件或短信通知用户订单成功,以免交易纠纷。
使用消息队列带来的一些问题:
1,系统可用性降低:系统可用性在某种程度上降低,为什么这么说呢?在加入MQ之前,你不用考虑消息丢失或者说MQ挂掉等等情况,但是,引入mq之后你就需要去考虑了!
2,系统复杂性提高:加入MQ之后,你需要保证消息没有被重复消费,处理消息丢失的情况,保证消息传递的顺序性等等问题!
3,一致性问题: 我们上面提到了消息队列可以实现异步,消息队列带来的异步确实可以提高系统响应速度。但是,万一消息的真正消费者并没有正确消费消息怎么办?这样就会导致数据不一致的情况了!
2,如何保证消息的顺序性?
主要思路有2种:1,单线程消费来保证消息的顺序性;
2,对消息进行编号,消费者处理时根据编号判断顺序;
3,大量消息在MQ 里长时间积压,该如何解决?
这时候只能做临时扩容,以更快的速度去消费数据了,具体操作步骤和思路如下:
1,先修复consumer的问题,确保其恢复消费速度,然后将现有的consumer 都停掉;
2,临时建立好原先10倍或者20倍的queue 数量(新建一个topic,partition 是原来的10倍);
3,然后写一个临时分发消息的consumer程序,这个程序部署上去消费积压的消息,消费之后不做耗时处理,直接均匀轮询写入临时建好10 数量的queue 里面;
4, 征用10倍的机器来部署consumer,每一批consumer 消费一个queue的消息
5,这种做法相当于临时将queue 资源和consumer 资源扩大10倍,以正常速度的10倍来消费消息;
6,等快速消费完了之后,恢复原来的部署架构,重新用原来的consumer 机器来消费消息;
3.消息设置了过期时间,过期就丢了怎么办
假设你用的是rabbitmq,rabbitmq是可以设置过期时间的,就是TTL,如果消息在queue中积压超过一定的时间就会被rabbitmq给清理掉,这个数据就没了。那这就是第二个坑了。这就不是说数据会大量积压在mq里,而是大量的数据会直接搞丢。
解决方案:
这种情况下,实际上没有什么消息挤压,而是丢了大量的消息。所以第一种增加consumer肯定不适用。
这种情况可以采取 “批量重导” 的方案来进行解决。
在流量低峰期(比如夜深人静时),写一个程序,手动去查询丢失的那部分数据,然后将消息重新发送到mq里面,把丢失的数据重新补回来。
4.积压消息长时间没有处理,mq放不下了怎么办
如果走的方式是消息积压在mq里,那么如果你很长时间都没处理掉,此时导致mq都快写满了,咋办?这个还有别的办法吗?
解决方案:
这个就没有办法了,肯定是第一方案执行太慢,这种时候只好采用 “丢弃+批量重导” 的方式来解决了。
首先,临时写个程序,连接到mq里面消费数据,收到消息之后直接将其丢弃,快速消费掉积压的消息,降低MQ的压力,然后走第二种方案,在晚上夜深人静时去手动查询重导丢失的这部分数据。
Redis面试题:
1,redis支持哪几种数据类型?
String: 最基本的数据类型,二进制安全的字符串,最大512M
list: 按照添加顺序保持顺的, 字符串列表
set: 无序的字符串集合,不存在重复的元素
sorted set: 已排序的字符串集合
hash key/value对的一种集合
2,redis 是单进程的还是单线程的?
Redis 是单进程单线程的,Redis利用队列技术将并发访问变为串行访问,消除了传统数据库行控制的开销。
3,Redis 为什么事单线程的?
多线程处理会涉及到锁,而且多线程处理会涉及到线程切换而消耗CPU.因为CPU不会redis 的瓶颈最有可能是机器内存或者网络宽带。单线程无法发挥多核cpu性能,不过可以通过在单机开启redis实例来解决。
4,Redis 的优势:
速度快,因为数据存储于内存中,类似于hashmap,HashMap的优势就是查找和操作时间复杂度都是0(1)
速度快,支持丰富的数据类型,支持String,list,set,sorted set,hash
支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行,
丰富的特性:可以用于缓存,消息,按key设置过期时间,过期后将会自动删除
5 Redis 和memcached 有哪些优势?
memcached所有的值军事简单的字符串,Redis作为其替代者,支持更为丰富的数据类型
Redis的速度比memcached快很多
Redis可以持久化其数据
Redis支持数据的备份,即master/slave模式的数据备份
Redis有几种数据淘汰策略
在redis中,允许用户设置最大使用内存大小server.maxmemory,当redis内存数据集大小上升到一定大小的时候,就会执行数据淘汰策略,
volatile-lru: 从已过期的数据集中挑选最近最少使用的淘汰,
volatile-ttl 从已设置过期的数据集中挑选将要过期的数据淘汰
volatile-random: 从已设置过期的数据集中任意挑选数据淘汰
allkeys-lru: 从数据集中挑选最近最少使用的数据淘汰,
allkeys-random: 从数据集中任意挑选最少使用的数据淘汰
noenvication: 禁止淘汰数据
6 Redis支持哪几种持久化方式:
RDB(append only file) 持久化
原理是将redis的操作日志以追加的方式写入文件。
指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。
AoF(append only file) 持久化
原理是将Redis的操作日志以追加的方式写入文件。
以日志的形式记录服务器搜处理的每一个写,删除操作,查询操作不会记录,已文本的方式记录,可以打开文件看到详细的操作记录。当服务器重启的时候会重新执行这些命令来恢复原始的数据。AOF 命令以Redis 协议追加保存每次写的操作到文件末尾。Redis还能对AOF文件进行后台重写,使得AOF 文件的体积不至于过大。
7,Jedis 与 Redisson 对比有什么优缺点?
jedis 是redis 的java 实现的客户端,其API 提供了比较全面的Redis 命令的支持;
Redisson 实现了分布式和可扩展的 java 数据结构,和jedis 相比,功能较为简单,不支持字符串操作,不支持排序,事务,管道,分区等Redis 特性、Redisson 的宗旨是促进使用者对Redis 的关注分离,从而让使用者能够将精力更多集中的存放在处理业务的逻辑中。
9,Redis 集群会有写操作丢失吗? 为什么?
Redis 并不能保证数据的强一致性,这意味着在实际中集群特定的条件下可能会丢失写的操作。
10 Redis 集群之间是如何复制的?
异步复制
11 Redis集群如何选择数据库?
Redis 集群目前无法做数据库选择,默认在0 数据库。
12,Redis中的管道有什么用?
一次请求/响应服务器能实现处理新的请求,即使旧的请求还未被响应,这样就可以将多个命令发送到服务器,而不用等待回复,最后在一个步骤中读取该回复。
这就是管道(pipelinling), 是一种几十年来广泛使用的技术。例如 许多 POP3 协议已经实现支持这个功能,大大加快了从服务器下载新邮件的过程。
13 怎么理解Redis事务?
事务是一个单独的隔离操作:事务中所有的命令都会序列化,按顺序执行,事务在执行过程中,不会被其他客户端发送来的命令请求搜打断。事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
14 Redis 如何做内存优化?
尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少),使用的内存非常小,所以你应该尽可能的将你的数据模型抽象到一个散列表里面。
比如您的web 系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的key,而是应该把这个用户的所有信息存储到一张散列表里面。
15 Redsi 回收进程如何工作的?
一个客户端运行了新的命令,添加了新的数据。Redis 检查内存使用情况,如果大于maxmemory的限制,则根据设定好的策略进行回收。一个新的命令被执行。等等。
所以我们不断的穿越内存限制的边界,通过不断达到边界然后不断的回收到边界以下。
如果一个命令的结果导致大量的内存被使用(例如很大的稽核的交集保存到一个新的键),不用多久内存就会被这个内存使用量超越。
16 watch dog 自动延期机制
客户端 1 加锁的锁key 默认生存时间才 30秒,如果过了30秒,客户端1 还想一直持有这把锁,该怎么办呢?
简单: 只要客户端1 一旦加锁成功,就会启动一个watch dog 看门狗,他是一个后台线程,会每隔10秒检查一下,如果客户端1 还持有锁key, 那么就会不断的延长锁key的生存空间。
17 使用过Redis 做异步队列吗?有什么缺点?
一般使用list 结构作为队列,rpush 生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep 一会在重试。
缺点:
在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如 rabbitmq 等。
能不能生产一次消费多次呢?
使用 pub/sub 主题订阅模式,可以实现1:N 的消息队列。
18 什么事缓存穿透?如何避免?什么是缓存雪崩?如何避免?
缓存穿透:
一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB).一些恶意的请求会故意查询不存在的key,请求量很大,就会对后端系统造成很大的压力。这就是缓存穿透。
如何避免?
1,对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key 对应的数据insert 了之后清理缓存。
2,对一定不存在的key 进行过滤,可以把所有坑存在的key放到一个大的bitmap中,查询时候通过bitmap 过滤。
缓存雪崩:
当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端带来很大的压力,导致系统崩溃。
如何避免?
1,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key 只允许一个线程查询数据和写缓存,其他线程等待。
2,做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2 设置为长期,
3,不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀
二, Spring 缓存抽象
Spring从3.1开始定义了org.springframework.cache.Cahce和org.springframework.cache.CacheManager接口来统一不同的缓存技术;并支持使用JCache(JSR-107)注解简化我们的开发;
每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定目标方法是否已经被调用过,如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。
使用spring缓存抽象时我们需要注意一下俩点:
1,确定方法需要被缓存以及他们的缓存策略
2,从缓存中读取之前缓存存储的数据
几个重要概念&缓存注解
1,Cache:
缓存接口。定义缓存操作。实现有:RedisCache,EhCacheCache,ConcurrentMapCache 等
2,CacheManager
缓存管理器,管理各种缓存(Cache)组件
3,@Cacheable
主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
4,@CacheEvict
清空缓存
5,@CachePut
保证方法被调用,又希望结果被缓存
四springboot 整合redis 实现缓存
1,引入依赖
在pom.xml中引入spring-boot-starter-data-redis依赖:
org.springframework.boot
spring-boot-starter-data-redis
2,配置redis连接地址
在application.yml或者application.properties中配置redis连接地址
这里还需要配置下数据库的地址,方便测试使用
application.yml配置\
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://MySQL的主机地址:3306/数据库名
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource #配置Druid数据源
# 数据源其他配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
redis:
host: redis主机地址
#mybatis:
# config-location: classpath:mybatis/mybatis-config.xml
# 开启驼峰命名
mybatis:
configuration:
map-underscore-to-camel-case: true
logging:
level:
com.canghe.springboot.mapper: debug
debug: true
# mapper-locations: classpath:mybatis/mapper/*.xml
3,安装redis
4,使用RestTemplate操作redis
@Autowired
StringRedisTemplate StringRedisTemplate;//操作k-v都是字符串
@Autowired
RedisTemplate redistemplate;//操作k-v对象的,
5,redis常见的5大数据类型:
String(字符串),list(列表),set(集合),hash(散列),Zset(有序集合)
1.redisTemplate.opsForValue();//操作字符串
2,redisTemplate.opsForHash();//操作hash
3,redisTemplate.opsForList();//操作list
4,redisTemplate.opsForSet();//操作set
5,redis.Template.opsForZSet();//操作有序的set
测试代码:
@Test
public void test01() {
// stringRedisTemplate.opsForValue().append("key","helloword");
// String msg = stringRedisTemplate.opsForValue().get("key");
// System.out.println("msg:"+msg);
stringRedisTemplate.opsForList().leftPush("firstList","1");
stringRedisTemplate.opsForList().leftPush("firstList","2");
}