当前位置: 首页 > 科技观察

用了这么久的Mybatis,结果面试官问的问题,我竟然还犹豫了

时间:2023-03-18 22:47:24 科技观察

用了这么久的Mybatis,对于面试官问的问题犹豫了。转载本文请联系Java极客技术公众号。前段时间,阿芬的一个朋友约阿芬吃饭。吃饭的时候,他和阿芬疯狂吐槽面试官,说面试官问的什么问题。问一些靠谱的问题。阿芬很好奇。问完基础问题,一般不都是根据自己的简历问问题吗?至于他接下来问的问题,阿芬说阿芬还需要继续学习。什么是Mybatis?说到这里,读者一定在想,阿粉是不是在开玩笑呢?你是Java程序员,不知道Mybatis是什么?不就是一个持久层的框架吗?这件事有什么好说的?怎么样?但是阿芬还是要告诉大家。Mybatis是一个半自动的ORM(对象关系映射)框架。它在内部封装了JDBC、加载驱动、创建连接、创建语句等复杂的过程。开发的时候,我们只需要关注SQL语句怎么写,其他的不用管。为什么Mybatis是一个半自动化的ORM框架?ORM是Object和Relation之间的映射,而Mybatis在查询关联对象或关联集合对象时,需要手动编写sql来完成,因此称为半自动ORM框架。Hibernate是一个全自动的ORM映射工具。使用Hibernate查询关联对象或关联集合对象时,可以根据对象关系模型直接获取,因此是全自动的。这也是为什么有些面试官在面试初级程序员的时候喜欢说,你觉得Mybatis和Hibernate的优缺点是什么,为什么选择用Mybatis而不是Hibernate?大家都说Mybatis是怎么回事,接下来肯定要说说面试官问的是什么问题,这让阿芬的小伙伴们很是犹豫。你知道Mybatis的一级缓存和二级缓存是什么吗?先说下Mybatis的一级缓存,因为如果不手动配置的话是默认开启的一级缓存。一级缓存只是和同一个SqlSession比较,当参数和SQL完全一样的时候,我们用同一个SqlSession对象去调用一个Mapper方法,往往只执行一次SQL,因为第一次查询使用SelSession后,MyBatis会把它放在缓存中,在以后的查询中,如果没有需要刷新的语句,并且缓存还没有超时,SqlSession会取当前缓存的数据,不会再向数据库发送SQL。我们在面试的时候,说完这个之后,一般情况下,面试官肯定会继续提问。毕竟,只有当你问及你的知识盲点时,技术才会停止。那我们画个图给一级缓存看看:面试官肯定会说不能直接从数据库查,为什么要一级缓存?当我们使用MyBatis打开一个与数据库的会话时,MyBatis会创建一个SqlSession对象来表示与数据库的一次信息传递。在执行SQL语句的过程中,我们可能会重复执行同一条查询语句。如果我们不采取一些措施,我们会为每个查询查询一次数据库。但是,如果在很短的时间内多次执行相同的查询操作,则这些查询返回的结果很可能是相同的。也就是说,如果我们在短时间内频繁地执行一条SQL,查询返回的结果应该已经发生了变化,但是当我们查询的时候,结果是一致的。正是为了解决这个问题,也是为了减少数据库的开销,Mybatis默认开启了一级缓存。Mybatis的二级缓存Mybatis的二级缓存如果不设置一般是打不开的,但是什么是二级缓存呢?Mybatis中的二级缓存其实就是mapper级缓存,这时候肯定会有人说,那么不同的Mapper是同一个缓存吗?答案是否定的,不是一个,Mapper级缓存其实就是同一个Mapper使用了一个二级缓存,只是在二级缓存中,并且有多个不同的SqlSession,以及不同Mapper之间的二级缓存不会互相影响。类似于下图:这个二级缓存是不是看起来很有意思?那么如何开启二级缓存呢?1.MyBatis配置文件>/settings>2.MyBatis要求返回的POJO必须是可序列化的3.在Mapper的xml配置文件中添加标签由于我们要要看懂这个二级缓存,就要知道里面的配置是什么意思。我们先从标签说起,再看源码,看看有哪些配置信息可供我们使用:blocking:直译就是调度,在Mybatis中,如果在缓存中找不到对应的key,会不会一直阻塞,直到有相应的数据进入缓存。eviction:Cacherecoverystrategy缓存回收策略在源码中有直接的体现,那么它们分别对应什么形式呢?typeAliasRegistry.registerAlias("PERPETUAL",PerpetualCache.class);typeAliasRegistry.registerAlias("FIFO",FifoCache.class);typeAliasRegistry.registerAlias("LRU",LruCache.class);typeAliasRegistry.registerAlias("软",SoftCache.class);typeAliasRegistry.registerAlias("WEAK",WeakCache.class);PERPETUAL:选择PERPETUAL来命名Cache,暗示这是一个底层缓存。数据一旦存储,将永远不会被清除。好像这种缓存不是很流行。FIFO:先进先出:按照对象进入缓存的顺序移除对象LRU:最近最少使用:移除最长时间未使用的对象。SOFT:软引用:根据垃圾收集器状态和软引用规则删除对象。WEAK:弱引用:根据垃圾收集器状态和弱引用规则更积极地删除对象。虽然大家看到PERPETUAL排在第一位,但并不是默认的。在Mybatis的缓存策略中,默认是LRU。PERPETUAL:源码如下:publicclassPerpetualCacheimplementsCache{privatefinalStringid;privateMapcache=newHashMap<>();publicPerpetualCache(Stringid){this.id=id;}嗯?是不是很眼熟?为什么只是包装HashMap呢?不要惊讶,他真的用了HashMap。不得不说,虽然人家用的是HashMap,但是比我们写的高级多了。既然使用HashMap,那么必然就会有Key,那么他们的Key是怎么设计的?CacheKey:publicclassCacheKeyimplementsCloneable,Serializable{privatestaticfinallongserialVersionUID=1146682552656046210L;publicstaticfinalCacheKeyNULL_CACHE_KEY=newNullCacheKey();privatestaticfinalintDEFAULT_MULTIPLYER=37;privatestaticfinalintDEFAULT_HASHCODE=17;privatefinalintmultiplier;privateinthashcode;//用于表示CacheKey的哈希码privatelongchecksum;//求和校验,当出现复合键时,分布式计算每个key的哈希码,然后求和privateintcount;//当出现复合键时,计算thekeyTotalnumber//8/21/2017-Sonarlintflagsthisasneedingtobemarkedtransient.Whiletrueifcontentisnotserializable,thisisnotalwaystrueandthusshouldnotbemarkedtransient.privateListupdateList;//当组合键出现时,保存每个键真的很棒。至于内部如何初始化,如何操作,大家有兴趣可以阅读源码,导入一个源码包,打开看看。FIFO:先进先出缓存淘汰策略publicclassFifoCacheimplementsCache{privatefinalCachedelegate;//修饰的Cache对象privatefinalDequekeyList;//用于记录key进入缓存的顺序privateintsize;//记录上限缓存页面,超过这个值需要清理缓存(FIFO)FIFO淘汰策略,而Deque是一种常用的数据结构,队列可以看作是一个特殊的线性表,遵循先进先出的原则。在Java中,LinkedList实现了Queue接口,因为LinkedList执行插入和删除操作的效率更高。大家在阅读源码的时候是不是觉得源码其实并没有那么难懂,里面包含了我们掌握的所有知识,只是中间做了一些操作,进行了一些封装。LRU:最近最少使用的缓存策略和LUR算法,之前阿芬说过,如果对这个算法感兴趣,文章地址发给你。你真的了解经典的LRU算法吗?而我们要看的源码是Mybatis中的源码,publicclassLruCacheimplementsCache{privatefinalCachedelegate;privateMapkeyMap;privateObjecteldestKey;//记录最少使用的缓存项keypublicLruCache(Cachedelegate){this.delegate=delegate;setSize(1024);//重置缓存大小会重置KeyMap字段,如果达到上限则更新oldestKey}publicvoidputObject(Objectkey,Objectvalue){delegate.putObject(key,value);//删除最近的unusedkeycycleKeyList(key);}SOFT:基于当看到基于垃圾收集器状态和软引用规则的对象时,阿芬已经兴奋了。竟然还有GC这玩意儿,还不快点看,这么高大上(cup)的东西,一起来看看吧!publicclassSoftCacheimplementsCache{//在SoftCache中,有一部分最近使用的缓存项不会被GC回收,这是通过将其值添加到privatefinalDequehardLinksToAvoidGarbageCollection;//引用队列,用来记录GC的SoftEntry对象privatefinalReferenceQueuequeueOfGarbageCollectedEntries对应回收的缓存项;//底层修饰的Cache对象privatefinalCachedelegate;//连接数,默认为256privateintnumberOfHardLinks;publicSoftCache(缓存代理){this.delegate=delegate;this.numberOfHardLinks=256;this.hardLinksToAvoidGarbageCollection=newLinkedList<>();this.queueOfGarbageCollectedEntries=newReferenceQueue<>();}publicvoidputObject(Objectkey,Objectvalue){//清除GC回收的缓存项removeGarbageCollectedItems();//添加缓存项委托给缓存。putObject(key,newSoftEntry(key,value,queueOfGarbageCollectedEntries));}publicObjectgetObject(Objectkey){Objectresult=null;//找到对应的缓存项@SuppressWarnings("unchecked")//assumeddelegatecacheistallymanagedbythiscacheSoftReferencesoftReference=(SoftReference)delegate.getObject(key);if(softReference!=null){result=softReference.get();//已经被GC回收if(result==null){//从缓存中清除对应的缓存项delegate.removeObject(key);}else{//参见#586(and#335)modificationsneedmorethanareadlocksynchronized(hardLinksToAvoidGarbageCollection){hardLinksToAvoidGarbageCollection.addFirst(result);if(hardLinksToAvoidGarbageCollection.size()>numberOfHardLinks){hard}LGarbageToAvo}}}returnr结果;}公开oidclear(){synchronized(hardLinksToAvoidGarbageCollection){//清理强引用集合hardLinksToAvoidGarbageCollection.clear();}//清理GC回收的缓存项removeGarbageCollectedItems();delegate.clear();}//引用指向键的是强引用,而指向值的引用是弱引用}WEAK:basedongarbage收集器状态和弱引用规则的对象publicclassWeakCacheimplementsCache{privatefinalDequehardLinksToAvoidGarbageCollection;privatefinalReferenceQueuequeueOfGarbageCollectedEntries;privatefinalCachedelegate;privateintnumberOfHardLinks;publicWeakCache(Cachedelegate){this.delegate=delegate;this.numberOfHardLinks=256;this.hardLinksToAvoidGarbageCollection=newLinkedList<>();this.queueOfGarbageCollectedEntries=newReferenceQueue<>();}WeakCache在实现上和SoftCache几乎一样,只是引用对象换成了SoftReference软引用到WeakReference弱引用这里我就不多说了。Mybatis的二级缓存你知道吗?下次遇到问这个问题的面试官,你应该知道如何成功(倒满一杯)而不被撞到。