最近整理一下面试题、以备不时只需,这些题目来源于网上各位大佬,也有一些是自己的理解,难免有不对的地方,如果有还请各位指正,谢谢
不是一次写完、持续更新
String类的声明时使用final修饰,所以不能被继承
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
因为字符串是不可变的,所以在它创建的时候Hash Code就被缓存了,不需要重新计算。Map中的键值往往使用这字符串。
因为它能够缓存结果,当你在传参时不需要考虑谁会修改它的值;如果是可变类的话,则有可能需要重新拷贝出来一个新值进行传参,这样在性能上就会有一定的损失
修饰变量,一旦赋值不可被修改,也就是我们说的常量
修饰方法则表示不可被覆盖
修饰类则不可被继承
注:final不可与abstract一起使用
不对,equals为true则hashcode则一定相同,而hashcode相同,equals则不一定相同
在Joshua Bloch的大作《Effective Java》中这样介绍equals:首先equals方法必须满足自反性(x.equals(x)必须返回true)、对称性(x.equals(y)返回true 时,y.equals(x)也必须返回true)、传递性(x.equals(y)和y.equals(z)都返回true时,x.equals(z)也必须返回true)和一致性(当x和y引用的对象信息没有被修改时,多次调用x.equals(y)应该得到同样的返回值),而且对于任何非null值的引用x,x.equals(null)必须返回false
在for前面加关键字,然后在需要跳出的地方 break 关键字
public void breakfor()
{
breakflag: for (int i=0;i<10;i++)
{
for (int j=0;j<10;j++)
{
break breakflag;
}
}
}
重载发生在同一个类中,就是说同一个方法可以有不同的参数,参数的个数顺序不同都可以称为重载
重写发生在两个类中,即子类继承父类的方法,然后可以重新改变这个方法的业务逻辑,但是必须与父类的放回值相同,不能比父类方法声明更多的异常(里氏替换原则)
是值传递。Java只支持参数的值传递,当一个对象被当作参数传递到方法时,参数的值就是该对象的内存地址。这个值(内存地址)被传递后,同一个内存地址指向堆内存当中的同一个对象,所以通过那个引用去操作这个对象,对象的属性都是改变的
注意:
基本数据类型的值传递,不会改变原值,因为调用后立马会弹栈,而所声明的局部变量则消失
public class test {
public static void main(String[]args){
int a=10;
change(a);
System.out.println(a);//a还是10
}
public static void change(int a)
{
//这个方法完后,栈中值被销毁,也叫弹栈
a=20;
}
}
引用数据类型的值传递,是改变原值的,因为即使方法弹栈,但是堆内存中数组对象还在,还可以通过地址继续访问的
相同
可以,因为Java中使用的编码是Unicode(不选择任何特定的编码,直接使用字符在字符集中的编号),一个char占2个字节(16比特),所以可以放一个中文
break用于完全结束一个或多个循环,continue用于跳过本次循环进行下一次循环
String str1=new String("123");
String str2=new String("123");
System.out.print(str1.equals(str2));//true
System.out.print(str1==str2);//false
String重写了equals
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
没有,因为String对象都是不可变对象,所以原始对象中的内容没有变化,我们看到后面s=“Hello World”是因为s指向了另外一个对象
StringBuilder和StringBuffer的reverse()
不需要,如下代码,没有抽象类但可以正常运行
abstract class test1 {
public static void test()
{
}
}
byte、boolean、char、short、int、long、float、double
//需要排序的集合
List<String>names= Arrays.asList("zz","dd","ssd");
//没用Lambda表达式
Collections.sort(names, new Comparator<String>() {@Override public int compare(String a,String b){return b.compareTo(a);}
});
//以下是两种用了Lambda表达式的排序
names= Arrays.asList("zz","dd","ssd");
Collections.sort(names,(a,b)->b.compareTo(a));
names= Arrays.asList("zz","dd","ssd");
Collections.sort(names,Comparator.reverseOrder());
Clock clock= Clock.systemDefaultZone();
long millis=clock.millis();
Instant instant=clock.instant();
ZoneId zoneId=ZoneId.of("Brazil/East");
ZoneId zoneId=ZoneId.of("Brazil/East");
LocalTime localTime=LocalTime.now();//10:38:44.656
localTime=LocalTime.now(zoneId);//23:39:14.125
LocalDateTime localDateTime=LocalDateTime.of(2011,Month.DECEMBER,31,23,31,31);
int dayOfMonth=localDateTime.getDayOfMonth();
int hour=localDateTime.getHour();
int minute=localDateTime.getMinute();
int second=localDateTime.getSecond();
//throws 放在方法的声明处
public void testThrow()throws IOException {
boolean a = false;
try {
//try 需要捕捉错误的代码
} catch (Exception e) {
//catch 出错后处理逻辑
//throw 抛出异常
throw e;
}
finally
{
//finally 不管代码是否异常都会进来,一般释放资源,比如IO流的释放
}
//throw 抛出自定义异常
if (a)
throw new IOException("IO异常");
}
两种异常处理的方式,try一定要有,可以搭配finally或catch,也可以像上面一样两个都搭配
public static void testThrow()
{
try{}
finally {}
try{}
catch (Exception e){}
}
RLock lock=redisson.getLock("anyLock");
//可以指定超时时间
lock.lock(10,TimeVnit.SECONDS);
boolean res=lock.tryLock(10,10,TimeUnit.SECONDS);
if(res)
try{}finally{lock.unlock()}
redis加锁产生死锁的两种情况:加锁没有释放锁(没有delete key)和加锁程序挂了(需要加过期时间)
使用先更新数据库再删除缓存的操作
操作缓存的套路
public void write(String key,Object data){
redis.delKey(key);
Thread.sleep(1000);
redis.delKey(key);
}
还有一个问题就是缓存更新失败,解决方案如下
Java集合关系
第一代:线程安全集合:Vector、Hashtable,使用synchronized保证线程安全
第二代:非线程安全集合:ArrayList、HashMap,如果要实现线程安全用Collections.synchronizedList(list);Collections.synchronizedMap(m),这里的锁在方法里面,比第一代效率高
第三代:线程安全集合:都在java.util.concurrent.* 包里面有ConcurrentHashMap,使用分块锁比第二代高
ArrayList、HashSet、HashMap都不是线程安全的。在集合中只有Vector和HashTable是线程安全的
java 集合既用来存放对象的容器
在对字符串,文件,甚至目录进行hash取值时,有可能得到的多个一样的hash值,这就是hash碰撞/hash冲突。举个众所周知的Hash函数CRC32,如果你给这个Hash函数“plumless” 和“buckeroo”这2个字符串,它会生成相同的Hash值,这是已知的Hash冲突
解决方法有:
1.开放地址法(再散列法)
开放地执法有一个公式:Hi=(H(key)+di) MOD m i=1,2,…,k(k<=m-1)
其中,m为哈希表的表长。di 是产生冲突的时候的增量序列。如果di值可能为1,2,3,…m-1,称线性探测再散列。如果di取1,则每次冲突之后,向后移动1个位置.如果di取值可能为1,-1,2,-2,4,-4,9,-9,16,-16,…kk,-kk(k<=m/2),称二次探测再散列。如果di取值可能为伪随机数列。称伪随机探测再散列。
2.再哈希法Rehash
当发生冲突时,使用第二个、第三个、哈希函数计算地址,直到无冲突时。缺点:计算时间增加。比如上面第一次按照姓首字母进行哈希,如果产生冲突可以按照姓字母首字母第二位进行哈希,再冲突,第三位,直到不冲突为止.这种方法不易产生聚集,但增加了计算时间。
3.链地址法(拉链法)
将所有关键字为同义词的记录存储在同一线性链表中.基本思想:将所有哈希地址为i的元素构成一个称为同义词链的单链表,并将单链表的头指针存在哈希表的第i个单元中,因而查找、插入和删除主要在同义词链中进行。链地址法适用于经常进行插入和删除的情况。对比JDK 1.7 hashMap的存储结构是不是很好理解。至于1.8之后链表长度大于6rehash 为树形结构不在此处讨论。
两个都实现List接口,但ArrayList底层是数组而LinkedList是链表
在ArrayList的前面或中间插入数据时,必须将其后所有的数据相应的后移,这会浪费很多时间,当然如果是只在集合后面添加元素,并随机访问其中元素时,使用ArrayList性能还是很出色的。
如果是对集合的前面或者红箭添加或删除数据时,并按照顺序访问其中的元素时,就应该使用LinkedList
总的来说ArrayList的查询效率高,增删效率差,适用于频繁查询和增删少的场景,而LinkedList的查询效率低,但增删效率高,使用与增删动作频繁和查询次数少的场景
同步性
Vector是线程安全的,而ArrayList不是线程安全的,如果是一个线程访问集合最好用ArrayList,因为它不考虑线程安全所以效率会高一些;如果有多个线程访问集合就用Vector,因为不需要再去考虑和编写线程安全的代码
数据增长
ArrayList与Vector都有一个初始的容量大小,当存储元素超过本身容量时,Vector默认增长原来的一倍,而ArrayList增加原来的0.5倍。Vector是可以设置增长的空间大小,而ArrayList没有提供设置增长空间的方法
存值
hashmap在存数据的时候是基于hash的原理,当我们调用put(key,value)方法的时候,其实我们会先对键key调用key.hashcode()方法,根据方法返回的hashcode来找到bucket的位置来存Entry对象。
如果出现hashcode相同的情况,这时候就会产生hash碰撞(hashmap的底层存储结构是 数组+链表),这时候根据hashcode找到对应的bucket,然后在链表逐一检查有没有存相同的key,用equals()进行比较,如果有则用新的value取代旧的value,如果没有就在链表的尾部加上这个新的Entry对象
取值
当使用get(key)时,会调用key的hashcode方法获得hashcode,然后根据hashcode找到bucket,优于bucket对应的链表中可能存有多个Entry,这个时候会调用key的equals()找到对应的Entry,然后把值返回
HashSet实际上为(key,null)类型的HashMap,而我们知道,HashSet的key是不能重复的,所以HashSet的值自然也是没有重复的.因为HashMap的key可以为null,所以HashSet的值可以为null
HashMap | HashSet |
---|---|
HashMap实现了Map接口 | HashSet实现了Set接口 |
HashMap储存键值对 | HashSet仅仅存储对象 |
使用put()方法将元素放入map中 | 使用add()方法将元素放入set中 |
HashMap中使用键对象来计算hashcode值 | HashSet使用成员对象来计算hashcode值,对于两个对象来说hashcode可能相同,所以equals()方法用来判断对象的相等性,如果两个对象不同的话,那么返回false |
HashMap比较快,因为是使用唯一的键来获取对象 | HashSet较HashMap来说比较慢 |
迭代器是一种设计模式,它可以遍历并选择序列中的对象,在使用时并不需要了解该对象的底层结构。迭代器通常也被称为轻量级对象,因为其创建的代价低
Redis全称Remote Dictionary Server(远程字典服务),用C编写,NoSQL数据库服务器。其本质是一个key-value的内存数据库,类似于memcached,其QPS能达到数十万次,单个value的最大限制1GB,而memcahed只有1MB,其支持的数据结构有: 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)
Redis的缺点就是其是内存数据库,其上限受限于运行的物理内存的大小
save:在主线程中执行,会导致阻塞
bgsave:创建一个子进程,专门写RDB文件,避免主线程阻塞,这是Redis RDB文件生成的默认配置。但是fork 创建过程中本身会阻塞主线程,主线程内存越大阻塞时间越长
● 会话缓存(Session Cache)
● 全页缓存(FPC)
● 队列
● 排行榜/计数器
● 发布/订阅
优点
● QPS能达到数十万
● 所有的操作都是原子性的,也就是说一个操作要么成功要么失败,就像关系型数据库中的事务
● 支持丰富的数据类型
缺点
●因为是内存数据库所以受限于机器本身的内存大小,Redis本身可以有过期策略,但内存增长过快时需要定期 删除数据
●在重启Redis时,持久化的数据越大加载越慢,在完成加载之前,Redis将不提供服务
RDB持久化:Redis默认的持久化方案。在指定的时间间隔内将内存中的数据写入到磁盘中,这些数据写在指定目录下的dump.rdb文件,Redis重启会通过dump.rdp文件恢复数据,因为是定时备份所以会有数据丢失的情况
AOF持久化:Redis默认不开启。它是为了弥补RDB的不足(数据丢失或数据不一致),所以它采用写日志的方式记录所有的写操作,Redis重启会通过日志文件恢复数据,这种情况如果数据量比较大的话,启动就会慢
RDB和AOF同时应用时:当Redis重启的时候,它会优先使用AOF来还原数据,因为数据比较全
优点
RDB:RDB是定时备份的,可以随时将数据还原到不同版本,也比较适用于灾难性恢复
AOF:AOF对日志文件进行追加,在日志进行写入过程中关机了或磁盘满了,使用redis-check-aof工具也可以修复这些问题
缺点
RDB:如果对数据完整性有要求的话就不适用于RDB,因为会丢数据。
每次保存RDB时,Redis都要fork()出一个子进程,由子进程进行数据持久化的工作。数据集比较大时,fork()会比较耗时,可能会在一定毫秒内停止处理客户端的请求
AOF:AOF数据量比较大,使用fsync策略,恢复数据速度慢于RDB
1.事务开始:MULTI,执行此命令代表事务开始
2.命令入队:QUEUED状态
3.事务执行:EXEC
redis> MULTI
OK
redis> SET "name" "ds"
QUEUED
redis> GET "name"
QUEUED
redis> EXEC
1) OK
2) "ds"
Redis不支持回滚:Redis命令只有因为错误的语法而失败,这些失败的命令在开发中就应该发现,而不是在生产环境中出现,所以不需要考虑
public String get(key) {
String value = redis.get(key);
if (value == null) { //代表缓存值过期
//设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能load db
if (redis.setnx(key_mutex, 1, 3 * 60) == 1) { //代表设置成功
value = db.get(key);
redis.set(key, value, expire_secs);
redis.del(key_mutex);
} else { //这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可
sleep(50);
get(key); //重试
}
} else {
return value;
}
}
穿透:缓存不存在,数据库不存在,高并发,少量key
击穿:缓存不存在,数据库存在,高并发,少量key
雪崩:缓存不存在,数据库存在,高并发,大量key
字符串(strings):String数据结构是最简单的key-value类型
用法:
192.168.1.167:6379> set name dd
OK
192.168.1.167:6379> get name
"dd"
散列(hashes):类似Java中的hash,可以存放对象值,如用户:用户名、年龄等,具体通过HMSET
指令设置 hash 中的多个域,而 HGET
取回单个域,hgetall
取得所有域
用法:
192.168.1.167:6379> hset user:1000 username dd birthyear 1999 verified 11
(integer) 3
192.168.1.167:6379> hget user:1000 username
"dd"
192.168.1.167:6379> hgetall user:1000
1) "username"
2) "dd"
3) "birthyear"
4) "1999"
5) "verified"
6) "11"
192.168.1.167:6379>
列表(lists):List其实就是双向链表,rpush 设置一个列表值,lrange 获取一个列表值
用法:
192.168.1.167:6379> rpush list a
(integer) 1
192.168.1.167:6379> rpush list b
(integer) 2
192.168.1.167:6379> rpush list c
(integer) 3
192.168.1.167:6379> lrange list 0 -1
1) "a"
2) "b"
3) "c"
192.168.1.167:6379>
集合(sets):Set 是 String 的无序排列,就是不根据插入顺序进行排序。集合中不能出现重复数据。set是通过hash实现的,所以增删查的时间复杂度都是o(1)。SADD
指令把新的元素添加到 set 中。对 set 也可做一些其他的操作,比如测试一个给定的元素是否存在,对不同 set 取交集,并集或差,等等
用法:
192.168.1.167:6379> sadd myset 2 3 1
(integer) 3
192.168.1.167:6379> smembers myset
1) "1"
2) "2"
3) "3"
192.168.1.167:6379>
有序集合(sorted sets) :有序集合和集合一样是string类型的集合,但不允许有重复元素。每个元素都有一个double类型的分数,redis通过这个分数从小到大进行排序。因为有序集合也通过hash实现,所以增删查的时间复杂度o(1)
用法:
192.168.1.167:6379> zadd mysortedset 1 b
(integer) 1
192.168.1.167:6379> zadd mysortedset 2 a
(integer) 1
192.168.1.167:6379> zadd mysortedset 3 c
(integer) 1
192.168.1.167:6379> zadd mysortedset 2 d
(integer) 1
192.168.1.167:6379> zrange mysortedset 0 10
1) "b"
2) "a"
3) "d"
4) "c"
192.168.1.167:6379>
范围查询bitmaps:通过一个bit来标识元素对应的值
用法:
192.168.1.167:6379> setbit mybit 1 1
(integer) 0
192.168.1.167:6379> setbit mybit 2 1
(integer) 0
192.168.1.167:6379> setbit mybit 3 1
(integer) 0
192.168.1.167:6379> bitcount mybit
(integer) 3
192.168.1.167:6379>
hyperloglogs :HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。
在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素
基数既不重复元素的个数
用法:
192.168.1.167:6379> pfadd myhyperloglog a
(integer) 1
192.168.1.167:6379> pfadd myhyperloglog b
(integer) 1
192.168.1.167:6379> pfadd myhyperloglog c
(integer) 1
192.168.1.167:6379> pfadd myhyperloglog c
(integer) 0
192.168.1.167:6379> pfcount myhyperloglog
(integer) 3
192.168.1.167:6379>
用法:
192.168.1.167:6379> geoadd point1 13.123 38.133 "name1" 13.223 38.333 "name2"
(integer) 2
192.168.1.167:6379> geopos point1 name1 name2
1) 1) "13.12299996614456177"
2) "38.13300034651486925"
2) 1) "13.22299808263778687"
2) "38.33299998487134985"
192.168.1.167:6379>
存储引擎:不同的数据文件在磁盘的不同组织形式
innodb和myisam的区别
不用hash的原因
不用二叉树和红黑树的原因
图片来源
每个磁盘块相当于一页,每页是4*4=16k的数据(总容量16k),如果一页中还存数据(date),那么一页中存的数据条数就十分有限,而我们平常一张表中有百万条或千万条数据,如果用B树会导致层数变多,从而影响io的速度
*
from table where id=1 or id=3 id是单列索引);如果是联合索引:1、在全部列都是联合索引的情况下,那么会走这些列的联合索引(select name,age from table where name=‘1’ and age=3 其中name和age是联合索引)。2、如果只有部分列是索引,那么不会走索引(select * from table name=‘1’ and age =3)数据库并发场景有三种
MVCC:Multi-Version Concurrency Control,即多版本并发控制,是一种用来解决读写冲突的无锁并发控制,也是为事务分配单项增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库快照,所以MVCC可以为数据库解决以下问题:
1、脏读
脏读是指一个事务读取了未提交事务执行过程中的数据
2、不可重复读
不可重复读是指一个事务查询多次相同的sql得到的结果不一致,因为一个事务执行过程中,另一个事务提交并修改了当前事务正在读取的数据
3、幻读
比如去取钱,卡上有100,你全部取出来,然后有人又给你转了100,你会发现卡上还有100,你就会产生一个错觉,是不是有问题了
10、什么是MVCC
#{}是预编译处理,${}是字符串替换;
Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;
Mybatis在处理 时 , 就 是 把 {}时,就是把 时,就是把{}替换成变量的值;
使用#{}可以有效的防止SQL注入,提高系统安全性。
数组分页
sql分页
拦截器分页
RowBounds分页
物理分页速度上并不一定快于逻辑分页,逻辑分页速度上也并不一定快于物理分页。
物理分页总是优于逻辑分页:没有必要将属于数据库端的压力加诸到应用端来,就算速度上存在优势,然而其它性能上的优点足以弥补这个缺点。
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,几乎所有的包括Hibernate,支持延迟加载的原理都是一样的。
一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。
二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置 ;
对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。
(1)Mybatis和hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句。
(2)Mybatis直接编写原生态sql,可以严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,因为这类软件需求变化频繁,一但需求变化要求迅速输出成果。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件,则需要自定义多套sql映射文件,工作量大。
(3)Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件,如果用hibernate开发可以节省很多代码,提高效率。
Mybatis有三种基本的执行器(Executor):
SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。
ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map内,供下一次使用。简言之,就是重复使用Statement对象。
BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。
分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。
Mybatis自定义插件针对Mybatis四大对象(Executor、StatementHandler 、ParameterHandler 、ResultSetHandler )进行拦截,具体拦截方式为:
Executor:拦截执行器的方法(log记录)
StatementHandler :拦截Sql语法构建的处理
ParameterHandler :拦截参数的处理
ResultSetHandler :拦截结果集的处理
Mybatis自定义插件必须实现Interceptor接口:
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
Object plugin(Object target);
void setProperties(Properties properties);
}
intercept方法:拦截器具体处理逻辑方法
plugin方法:根据签名signatureMap生成动态代理对象
setProperties方法:设置Properties属性
自定义插件demo:
// ExamplePlugin.java
@Intercepts({@Signature(
type= Executor.class,
method = "update",
args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
Object target = invocation.getTarget(); //被代理对象
Method method = invocation.getMethod(); //代理方法
Object[] args = invocation.getArgs(); //方法参数
// do something ...... 方法拦截前执行代码块
Object result = invocation.proceed();
// do something .......方法拦截后执行代码块
return result;
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties properties) {
}
}
一个@Intercepts可以配置多个@Signature,@Signature中的参数定义如下:
type:表示拦截的类,这里是Executor的实现类;
method:表示拦截的方法,这里是拦截Executor的update方法;
args:表示方法参数。