首页 文章 精选 留言 我的

精选列表

搜索[原理],共10002篇文章
优秀的个人博客,低调大师

深度解析ThreadLocal原理

今天呢,和大家聊一下ThreadLocal。 1. 是什么? JDK1.2提供的的一个线程绑定变量的类。 他的思想就是:给每一个使用到这个资源的线程都克隆一份,实现了不同线程使用不同的资源,且该资源之间相互独立 2. 为什么用? 思考一个场景:数据库连接的时候,我们会创建一个Connection连接,让不同的线程使用。这个时候就会出现多个线程争抢同一个资源的情况。 这种多个线程争抢同一个资源的情况,很常见,我们常用的解决办法也就两种:空间换时间,时间换空间 没有办法,鱼与熊掌不可兼得也。就如我们的CAP理论,也是牺牲其中一项,保证其他两项。 而针对上面的场景我们的解决办法如下: 空间换时间:为每一个线程创建一个连接。 直接在线程工作中,创建一个连接。(重复代码太多) 使用ThreadLocal,为每一个线程绑定一个连接。 时间换空间:对当前资源加锁,每一次仅仅存在一个线程可以使用这个连接。 通过ThreadLocal为每一个线程绑定一个指定类型的变量,相当于线程私有化 3. 怎么用? ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); threadLocal.get(); threadLocal.set(1); threadLocal.remove(); 没错,这四行代码已经把ThreadLocal的使用方法表现得明明白白。 get从ThreadLocal拿出一个当前线程所拥有得对象 set给当前线程绑定一个对象 remove将当前线程绑定的当前对象移除 记住在使用的以后,一定要remove,一定要remove,一定要remove 为什么要remove。相信不少小伙伴听到过ThreadLocal会导致内存泄漏问题。 没错,所以为了解决这种情况,所以你懂吧,用完就移除,别浪费空间(渣男欣慰) 看到这,脑袋上有好多问号出现了(小朋友你是否有很多问号?) 为啥会引发内存泄漏? 为啥不remove就内存泄漏了 它是怎么讲对象和线程绑定的 为啥get的时候拿到的就是当前线程的而不是其他线程的 它怎么实现的??? 来吧,开淦,源码来 4. 源码解读 先来说一个思路:如果我们自己写一个ThreadLocal会咋写? 线程绑定一个对象。**这难道不是我们熟知的map映射?**有了Map我们就可以以线程为Key,对象为value添加到一个集合中,然后各种get,set,remove操作,想怎么玩就怎么玩,搞定。😀 这个时候,有兄弟说了。你这思路不对啊,你这一个线程仅仅只能存放一个类型的变量,那我想存多个呢? 摸摸自己充盈的发量,你说出了一句至理名言:万般问题,皆系于源头和结果之中。 从结果考虑,让开发者自己搞线程私有(估计被会开发者骂死) 来吧,从源头考虑。现在我们的需求是:线程可以绑定多个值,而不仅仅是一个。嗯,没错,兄弟们把你们的想法说出来。 让线程自己维护一个Map,将这个ThreadLocal作为Key,对象作为Value不就搞定了 兄弟,牛掰旮旯四 此时,又有兄弟说了。按照你这样的做法,将ThreadLocal扔到线程本身的的Map里,那岂不是这个ThreadLocal一直被线程对象引用,所以在线程销毁之前都是可达的,都无法GC呀,有BUG啊??? **好,问题。**这样想,既然由于线程和ThreadLocal对象存在引用,导致无法GC,那我将你和线程之间的引用搞成弱引用或者软引用不就成了。一GC你就没了。 啥,你不知道啥是弱引用和软引用??? 前面讲过的东西,算啦再给你们复习一波。 JDK中存在四种类型引用,默认是强引用,也就是我们经常干的事情。疯狂new,new,new。这个时候创建的对象都是强引用。 强引用。直接new 软引用。通过SoftReference创建,在内存空间不足的时候直接销毁,即它可能最后的销毁地点是在老年区 弱引用。通过WeakReference创建,在GC的时候直接销毁。即其销毁地点必定为伊甸区 虚引用。通过PhantomReference创建,它和不存也一样,非常虚,只能通过引用队列在进行一些操作,主要用于堆外内存回收 好了,回到正题,上面的引用里最适合我们当前的场景的就是弱引用了,为什么这个样子说: 在以往我们使用完对象以后等着GC清理,但是对于ThreadLocal来说,即使我们使用结束,也会因为线程本身存在该对象的引用,处于对象可达状态,垃圾回收器无法回收。这个时候当ThreadLocal太多的时候就会出现内存泄漏的问题。 而我们将ThreadLocal对象的引用作为弱引用,那么就很好的解决了这个问题。当我们自己使用完ThreadLocal以后,当GC的时候就会将我们创建的强引用直接干掉,而这个时候我们完全可以将线程Map中的引用干掉,于是使用了弱引用,这个时候大家应该懂了为啥不使用软引用了吧 还有一个问题:为什么会引发内存泄漏呢? 了解Map结构的兄弟们应该清楚,内部实际就一个节点数组,对于ThreadLocalMap而言,内部是一个Entity,它将Key作为弱引用,Value还是强引用。如果我们在使用完ThreadLocal以后,没有对Entity进行移除,会引发内存泄漏问题。 ThreadLocalMap提供了一个方法expungeStaleEntry方法用来排除无效的Entity(Key为空的实体) 说到这里,有一个问题我思考了蛮久的,value为啥不搞成弱引用,用完直接扔了多好 最后思考出来得答案(按照源码推了一下): 不设置为弱引用,是因为不清楚这个Value除了map的引用还是否还存在其他引用,如果不存在其他引用,当GC的时候就会直接将这个Value干掉了,而此时我们的ThreadLocal还处于使用期间,就会造成Value为null的错误,所以将其设置为强引用。 而为了解决这个强引用的问题,它提供了一种机制就是上面我们说的将Key为Null的Entity直接清除 到这里,这个类的设计已经很清楚了。接下来我们看一下源码吧! 需要注意的一个点是:ThreadLocalMap解决哈希冲突的方式是线性探测法。 人话就是:如果当前数组位有值,则判断下一个数组位是否有值,如果有值继续向下寻找,直到一个为空的数组位 Set方法 class ThreadLocal public void set(T value) { //拿到当前线程 Thread t = Thread.currentThread(); //获取当前线程的ThreadLocalMap ThreadLocalMap map = getMap(t); if (map != null) //如果当前线程的Map已经创建,直接set map.set(this, value); else //没有创建,则创建Map createMap(t, value); } private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); //拿到当前数组位,当前数组位是否位null,如果为null,直接赋值,如果不为null,则线性查找一个null,赋值 for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; //清除一些失效的Entity if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } ThreadLocalMap getMap(Thread t) { //获取当前线程的ThreadLocalMap return t.threadLocals; } void createMap(Thread t, T firstValue) { //当前对象作为Key,和我们的设想一样 t.threadLocals = new ThreadLocalMap(this, firstValue); } Get方法 public T get() { //获取当前线程 Thread t = Thread.currentThread(); //拿到当前线程的Map ThreadLocalMap map = getMap(t); if (map != null) { //获取这个实体 ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; //返回 return result; } } return setInitialValue(); } private Entry getEntry(ThreadLocal<?> key) { //计算数组位 int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; //如果当前数组有值,且数组位的key相同,则返回value if (e != null && e.get() == key) return e; else //线性探测寻找对应的Key return getEntryAfterMiss(key, i, e); } private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; while (e != null) { ThreadLocal<?> k = e.get(); if (k == key) return e; if (k == null) //排除当前为空的Entity expungeStaleEntry(i); else //获取下一个数组位 i = nextIndex(i, len); e = tab[i]; } //如果没有找到直接返回空 return null; } remove public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); } private void remove(ThreadLocal<?> key) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); //拿到当前的数组,判断是否为需要的数组位,如果不是线性查找 for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { if (e.get() == key) { e.clear(); //清空位NUll的实体 expungeStaleEntry(i); return; } } } 我们可以看到一个现象:在set,get,remove的时候都调用了expungeStaleEntry来将所有失效的Entity移除 看一下这个方法做了什么 private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; int len = tab.length; // 删除实体的Value tab[staleSlot].value = null; //置空这个数组位 tab[staleSlot] = null; //数量减一 size--; // 重新计算一次哈希,如果当前数组位不为null,线性查找直到一个null Entry e; int i; for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get(); if (k == null) { e.value = null; tab[i] = null; size--; } else { int h = k.threadLocalHashCode & (len - 1); if (h != i) { tab[i] = null; // Unlike Knuth 6.4 Algorithm R, we must scan until // null because multiple entries could have been stale. while (tab[h] != null) h = nextIndex(h, len); tab[h] = e; } } } return i; } 更多原创内容请关注博主

优秀的个人博客,低调大师

HBase blockcache原理介绍

1.核心组件 总入口是CacheConfig,这个类根据配置信息,来返回不同的具体cache组件; 默认会返回LruBlockCache,所有类型的block都会存入; 启用了BucketCache时,会返回CombinedBlockCache,此类中根据block类型,data block存入BucketCache,其它存入LruBlockCache; 2.LruBlockCache LruBlockCache内部较为简单,主要就是一个map,如上图所示,由hfilename+offset来唯一标识一个block; LruBlockCache所能够使用的内存为堆的一定比例,通过hfile.block.cache.size设置,默认是0.4; so,maxSize = heapSize * hfile.block.cache.size,以下参数都根据maxSize计算; acceptSize: 使用量达到一定比例时会触发驱逐,该阈值通过hbase.lru.blockcache.acceptable.factor设置,默认是0.99; minSize: 驱逐后最少剩余比例,该阈值通过hbase.lru.blockcache.min.factor设置,默认是0.95; hardLimit: 使用量达到一定比例时则拒绝写入,该阈值通过hbase.lru.blockcache.hard.capacity.limit.factor设置,默认是1.2,这意味允许一定的超出; 关于驱逐: block分为3种类型,由BlockPriority字段区分,取值为single、mutli、inMem,空间分配默认为0.25:0.5:0.25; 系统表以及其它指定了InMem的表所含block会标记为inMem,其它block初次存入时标记为single,再次访问时会修改为multi; 存放时只要还有空间即可放入,空间分配比例只是在驱逐发生时进行计算使用; 驱逐时,会用minSize乘以各类型的比例,得到各类型最少要保留的minSize; 根据目前的算法,驱逐后的size,应该是略大于minSize的一个值,伪代码如下; expectFreeSize = usedSize - minSize;//预期释放总大小 freedSize = 0;//当前已释放总大小 n=3;//类型数量 for type in ('single','multi','inMem'): overFlow = type.usedSize - type.minSize toBeFree = min(overFlow,(expectFreeSize - freedSize)/n) free(toBeFree) freedSize += toBeFree n--; 3.BucketCache LruBlockCache的优点是实现简单,缺点是block的存入和释放伴随着内存的申请和释放,会带来内存碎片和gc过多的问题; BucketCache采用了类似池的思路,预先申请内存并划分为一个个的bucket,这些bucket会一直存在并重复使用; 总体的读写流程如下图所示: Block缓存写入流程: 将block写入RAMCache,然后系统会根据blockkey进行hash,根据hash结果将block分配到一组blockingQueue中; HBase会同时启动多个WriteThead,分别关联一个blockingQueue,并发的执行异步写入; 每个WriteThead读取到block数据后,调用bucketAllocator为这些block分配内存空间; BucketAllocator会选择与block大小对应的bucket进行存放,并且返回对应的物理地址偏移量offset; WriteThead将block以及分配好的物理地址偏移量传给IOEngine模块,执行具体的内存写入操作; 写入成功后,将类似这样的映射关系写入BackingMap中,方便后续查找时根据blockkey可以直接定位; Block缓存读取流程: 首先从RAMCache中查找,对于还没有来得及写入到bucket的缓存block,一定存储在RAMCache中; 如果在RAMCache中没有找到,再在BackingMap中根据blockKey找到对应entry; 根据entry中的offset可以直接从内存中查找对应的block数据; 其中最核心的组件是BucketAllocator和IoEngine,前者负责block的逻辑地址分配,后者负责block的实际物理存放,内部结构如下: hbase中blocksize是可以灵活设置的,bucketCache预设了一组支持的大小,从4K~512k不等; 一个Bucket只能存放一种size的block,一种size对应一个BucketSizeInfo进行管理; 初始化时,每种size先分配1个bucket,剩余的都分配给最大的那个size,如黑色箭头所示; 分配过程中当前size如果空间不够,会挪用其它size的空闲bucket,如棕色箭头所示,这意味着有可能某个Bucket一开始存放了32k的block ,后面释放后空闲,被挪用后变成存放64k的block; ioEngine有多种实现,可支持onheap、offheap、disk等; 关于驱逐: 2种情况下会触发,1是已使用超过95%(acceptableFactor),2是某个size的block分配不了(总量虽然没达到阈值,但不存在完全空闲的bucket供挪用); 驱逐后的最少剩余比例为85%(minFactor),遍历各个bucketSizeInfo,把超过85%的部分加起来,再乘以一个系数0.1(extraFreeFactor),就是要释放的大小; 具体计算方法复用了LruBlockCache的代码,也是按照single、multi、inMem及其比例进行计算和释放; 实际清理动作是修改一些状态数据,比如Bucket对象的freeList、freeCount,以及backMapping的键值对等,并不需要对底层的byteBuffer做什么操作; 对于refCount大于0的block,会先将其markedForEvict置为true,待各个使用方读取完成后调用returnBlock进行释放; 参考资料 http://hbasefly.com/2016/04/26/hbase-blockcache-2/?xuxezc=17idz1

优秀的个人博客,低调大师

HIVE TopN shuffle 原理

HIVE TopN Shuffle TopN 问题是排序中的一个经典问题。对于一个长度为 m 的数组,取其最大的 n (n <= m) 条数据,可以不必对整个数组进行全排。一般的算法对 m 进行全排的复杂度大约为 mlog2(m)。假设我们只取其中最大的 n 条,那么可以把这个复杂度降低到 m * log2(n)。如果 n << m,那么收益还是很大的。 HIVE-3562 引入了一个针对 TopN 的优化,即将带有 limit 算子的 order by 推至 map 端,这样 map 不必将所有数据 shuffle 到 reduce。order by 和 limit 算子在日常使用场景中经常一起出现,因此这个优化就显得很有必要。 抛开 limit 是如何下推的不管,我们这里只关注 ReduceSinkOperator

优秀的个人博客,低调大师

Tomcat结构原理详解

客户端用户点击浏览器服务连接,浏览器通过客户端底层服务通过路由传送报文,目标服务器获取解析报文,Tomcat监听程序触发处理请求 一、Tomcat 软件目录结构及功能 bin: 服务相关脚本,例如:启动、关闭等 conf: 存放不同的配置文件,列如:server.xml、web.xml lib: tomcat 运行需要的库文件 logs: 运行的日志文件 webapps: web部署的根目录 work :存放jsp编译后的class文件 二、server分析系统结构 1、server 提供一个接口让其它程序能够访问到这个 Service 集合、同时要维护它所包含的所有 Service 的生命周期,包括如何初始化、如何结束服务、如何找到别人要访问的 Service 2、service service 是server下一个集合,service包含多个接收请求

优秀的个人博客,低调大师

Go map实现原理

1. map数据结构 Golang的map使用哈希表作为底层实现,一个哈希表里可以有多个哈希表节点,也即bucket,而每个bucket就保存了map中的一个或一组键值对。 map数据结构由runtime/map.go/hmap定义: type hmap struct { count int // 当前保存的元素个数 ... B uint8 // 指示bucket数组的大小 ... buckets unsafe.Pointer // bucket数组指针,数组的大小为2^B ... } 下图展示一个拥有4个bucket的map: 本例中, hmap.B=2, 而hmap.buckets长度是2^B为4. 元素经过哈希运算后会落到某个bucket中进行存储。查找过程类似。 bucket很多时候被翻译为桶,所谓的哈希桶实际上就是bucket。 2. bucket数据结构 bucket数据结构由runtime/map.go/bmap定义: type bmap struct { tophash [8]uint8 //存储哈希值的高8位 data byte[1] //key value数据:key/key/key/.../value/value/value... overflow *bmap //溢出bucket的地址 } 每个bucket可以存储8个键值对。 tophash是个长度为8的数组,哈希值相同的键(准确的说是哈希值低位相同的键)存入当前bucket时会将哈希值的高位存储在该数组中,以方便后续匹配。 data区存放的是key-value数据,存放顺序是key/key/key/...value/value/value,如此存放是为了节省字节对齐带来的空间浪费。 overflow 指针指向的是下一个bucket,据此将所有冲突的键连接起来。 注意:上述中data和overflow并不是在结构体中显示定义的,而是直接通过指针运算进行访问的。 下图展示bucket存放8个key-value对: 3. 哈希冲突 当有两个或以上数量的键被哈希到了同一个bucket时,我们称这些键发生了冲突。Go使用链地址法来解决键冲突。 由于每个bucket可以存放8个键值对,所以同一个bucket存放超过8个键值对时就会再创建一个键值对,用类似链表的方式将bucket连接起来。 下图展示产生冲突后的map: bucket数据结构指示下一个bucket的指针称为overflow bucket,意为当前bucket盛不下而溢出的部分。事实上哈希冲突并不是好事情,它降低了存取效率,好的哈希算法可以保证哈希值的随机性,但冲突过多也是要控制的,后面会再详细介绍。 4. 负载因子 负载因子用于衡量一个哈希表冲突情况,公式为: 负载因子 = 键数量/bucket数量 例如,对于一个bucket数量为4,包含4个键值对的哈希表来说,这个哈希表的负载因子为1. 哈希表需要将负载因子控制在合适的大小,超过其阀值需要进行rehash,也即键值对重新组织: 哈希因子过小,说明空间利用率低 哈希因子过大,说明冲突严重,存取效率低 每个哈希表的实现对负载因子容忍程度不同,比如Redis实现中负载因子大于1时就会触发rehash,而Go则在在负载因子达到6.5时才会触发rehash,因为Redis的每个bucket只能存1个键值对,而Go的bucket可能存8个键值对,所以Go可以容忍更高的负载因子。 5. 渐进式扩容 5.1 扩容的前提条件 为了保证访问效率,当新元素将要添加进map时,都会检查是否需要扩容,扩容实际上是以空间换时间的手段。 触发扩容的条件有二个: 负载因子 > 6.5时,也即平均每个bucket存储的键值对达到6.5个。 overflow数量 > 2^15时,也即overflow数量超过32768时。 5.2 增量扩容 当负载因子过大时,就新建一个bucket,新的bucket长度是原来的2倍,然后旧bucket数据搬迁到新的bucket。 考虑到如果map存储了数以亿计的key-value,一次性搬迁将会造成比较大的延时,Go采用逐步搬迁策略,即每次访问map时都会触发一次搬迁,每次搬迁2个键值对。 下图展示了包含一个bucket满载的map(为了描述方便,图中bucket省略了value区域): 当前map存储了7个键值对,只有1个bucket。此地负载因子为7。再次插入数据时将会触发扩容操作,扩容之后再将新插入键写入新的bucket。 当第8个键值对插入时,将会触发扩容,扩容后示意图如下: hmap数据结构中oldbuckets成员指身原bucket,而buckets指向了新申请的bucket。新的键值对被插入新的bucket中。 后续对map的访问操作会触发迁移,将oldbuckets中的键值对逐步的搬迁过来。当oldbuckets中的键值对全部搬迁完毕后,删除oldbuckets。 搬迁完成后的示意图如下: 数据搬迁过程中原bucket中的键值对将存在于新bucket的前面,新插入的键值对将存在于新bucket的后面。 实际搬迁过程中比较复杂,将在后续源码分析中详细介绍。 5.3 等量扩容 所谓等量扩容,实际上并不是扩大容量,buckets数量不变,重新做一遍类似增量扩容的搬迁动作,把松散的键值对重新排列一次,以使bucket的使用率更高,进而保证更快的存取。 在极端场景下,比如不断的增删,而键值对正好集中在一小部分的bucket,这样会造成overflow的bucket数量增多,但负载因子又不高,从而无法执行增量搬迁的情况,如下图所示: 上图可见,overflow的buckt中大部分是空的,访问效率会很差。此时进行一次等量扩容,即buckets数量不变,经过重新组织后overflow的bucket数量会减少,即节省了空间又会提高访问效率。 6. 查找过程 查找过程如下: 跟据key值算出哈希值 取哈希值低位与hmpa.B取模确定bucket位置 取哈希值高位在tophash数组中查询 如果tophash[i]中存储值也哈希值相等,则去找到该bucket中的key值进行比较 当前bucket没有找到,则继续从下个overflow的bucket中查找。 如果当前处于搬迁过程,则优先从oldbuckets查找 注:如果查找不到,也不会返回空值,而是返回相应类型的0值。 7. 插入过程 新员素插入过程如下: 跟据key值算出哈希值 取哈希值低位与hmap.B取模确定bucket位置 查找该key是否已经存在,如果存在则直接更新值 如果没找到将key,将key插入 赠人玫瑰手留余香,如果觉得不错请给个赞~ 本篇文章已归档到GitHub项目,求星~ 点我即达

优秀的个人博客,低调大师

Mybatis架构与原理

MyBatis功能架构设计 image.png 功能架构讲解: 我们把Mybatis的功能架构分为三层: (1)API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。 (2)数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。 (3)基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。 框架架构 框架架构讲解: 这张图从上往下看。MyBatis的初始化,会从mybatis-config.xml配置文件,解析构造成Configuration这个类,就是图中的红框。 (1)加载配置:配置来源于两个地方,一处是配置文件,一处是Java代码的注解,将SQL的配置信息加载成为一个个MappedStatement对象(包括了传入参数映射配置、执行的SQL语句、结果映射配置),存储在内存中。 (2)SQL解析:当API接口层接收到调用请求时,会接收到传入SQL的ID和传入对象(可以是Map、JavaBean或者基本数据类型),Mybatis会根据SQL的ID找到对应的MappedStatement,然后根据传入参数对象对MappedStatement进行解析,解析后可以得到最终要执行的SQL语句和参数。 (3)SQL执行:将最终得到的SQL和参数拿到数据库进行执行,得到操作数据库的结果。 (4)结果映射:将操作数据库的结果按照映射的配置进行转换,可以转换成HashMap、JavaBean或者基本数据类型,并将最终结果返回。 MyBatis核心类 1、SqlSessionFactoryBuilder 每一个MyBatis的应用程序的入口是SqlSessionFactoryBuilder。 它的作用是通过XML配置文件创建Configuration对象(当然也可以在程序中自行创建),然后通过build方法创建SqlSessionFactory对象。没有必要每次访问Mybatis就创建一次SqlSessionFactoryBuilder,通常的做法是创建一个全局的对象就可以了。示例程序如下: private static SqlSessionFactoryBuilder sqlSessionFactoryBuilder; private static SqlSessionFactory sqlSessionFactory; private static void init() throws IOException { String resource = "mybatis-config.xml"; Reader reader = Resources.getResourceAsReader(resource); sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); sqlSessionFactory = sqlSessionFactoryBuilder.build(reader); } org.apache.ibatis.session.Configuration 是mybatis初始化的核心。 mybatis-config.xml中的配置,最后会解析xml成Configuration这个类。 SqlSessionFactoryBuilder根据传入的数据流(XML)生成Configuration对象,然后根据Configuration对象创建默认的SqlSessionFactory实例。 2、SqlSessionFactory对象由SqlSessionFactoryBuilder创建: 它的主要功能是创建SqlSession对象,和SqlSessionFactoryBuilder对象一样,没有必要每次访问Mybatis就创建一次SqlSessionFactory,通常的做法是创建一个全局的对象就可以了。SqlSessionFactory对象一个必要的属性是Configuration对象,它是保存Mybatis全局配置的一个配置对象,通常由SqlSessionFactoryBuilder从XML配置文件创建。这里给出一个简单的示例: <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- 配置别名 --> <typeAliases> <typeAlias type="org.iMybatis.abc.dao.UserDao" alias="UserDao" /> <typeAlias type="org.iMybatis.abc.dto.UserDto" alias="UserDto" /> </typeAliases> <!-- 配置环境变量 --> <environments default="development"> <environment id="development"> <transactionManager type="JDBC" /> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://127.0.0.1:3306/iMybatis?characterEncoding=GBK" /> <property name="username" value="iMybatis" /> <property name="password" value="iMybatis" /> </dataSource> </environment> </environments> <!-- 配置mappers --> <mappers> <mapper resource="org/iMybatis/abc/dao/UserDao.xml" /> </mappers> </configuration> 3、SqlSession SqlSession对象的主要功能是完成一次数据库的访问和结果的映射,它类似于数据库的session概念,由于不是线程安全的,所以SqlSession对象的作用域需限制方法内。SqlSession的默认实现类是DefaultSqlSession,它有两个必须配置的属性:Configuration和Executor。Configuration前文已经描述这里不再多说。SqlSession对数据库的操作都是通过Executor来完成的。 SqlSession :默认创建DefaultSqlSession 并且开启一级缓存,创建执行器 、赋值。 SqlSession有一个重要的方法getMapper,顾名思义,这个方式是用来获取Mapper对象的。什么是Mapper对象?根据Mybatis的官方手册,应用程序除了要初始并启动Mybatis之外,还需要定义一些接口,接口里定义访问数据库的方法,存放接口的包路径下需要放置同名的XML配置文件。 SqlSession的getMapper方法是联系应用程序和Mybatis纽带,应用程序访问getMapper时,Mybatis会根据传入的接口类型和对应的XML配置文件生成一个代理对象,这个代理对象就叫Mapper对象。应用程序获得Mapper对象后,就应该通过这个Mapper对象来访问Mybatis的SqlSession对象,这样就达到里插入到Mybatis流程的目的。 SqlSession session= sqlSessionFactory.openSession(); UserDao userDao = session.getMapper(UserDao.class); UserDto user = new UserDto(); user.setUsername("iMybatis"); List<UserDto> users = userDao.queryUsers(user); public interface UserDao { public List<UserDto> queryUsers(UserDto user) throws Exception; } <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="org.iMybatis.abc.dao.UserDao"> <select id="queryUsers" parameterType="UserDto" resultType="UserDto" useCache="false"> <![CDATA[ select * from t_user t where t.username = #{username} ]]> </select> </mapper> 4、Executor Executor对象在创建Configuration对象的时候创建,并且缓存在Configuration对象里。Executor对象的主要功能是调用StatementHandler访问数据库,并将查询结果存入缓存中(如果配置了缓存的话)。 5、StatementHandler StatementHandler是真正访问数据库的地方,并调用ResultSetHandler处理查询结果。 6、ResultSetHandler 处理查询结果。 MyBatis成员层次&职责 image.png SqlSession 作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能 Executor MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护 StatementHandler 封装了JDBC Statement操作,负责对JDBCstatement的操作,如设置参数、将Statement结果集转换成List集合。 ParameterHandler 负责对用户传递的参数转换成JDBC Statement 所需要的参数 ResultSetHandler *负责将JDBC返回的ResultSet结果集对象转换成List类型的集合; TypeHandler 负责java数据类型和jdbc数据类型之间的映射和转换 MappedStatement MappedStatement维护了一条<select|update|delete|insert>节点的封 SqlSource 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回 BoundSql 表示动态生成的SQL语句以及相应的参数信息 Configuration MyBatis所有的配置信息都维持在Configuration对象之中

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册