MetaObject
首先来看一下MetaObject,在MyBatis中很多地方都是用到了MetaObjet,例如BaseExecutor、BeanWapper等,只要是对Java属性的操作基本上都会涉及到MetaObject。
那么MetaObject到底有什么作用?
首先看一下是用反射对一个Blog类的方法调用
@Test
我们发现这样写是很麻烦的,那是否有一个东西可以更简便的来对属性进行赋值等操作呢 ?
@Test
![20e6230eda81d3aafebd4da4886c3c6e.png](https://i-blog.csdnimg.cn/blog_migrate/b86096d87faf4e5b453edf90c92b0e52.png)
这只是一个简单的例子,至于MetaObject来说,里面的功能超级超级强大,比如说MetaObject还支持子属性中的子属性的赋值操作
@Test
![c0824a863e088bbda13e2e479d11e372.png](https://i-blog.csdnimg.cn/blog_migrate/b3f3da7b1112253524120f5a5dc4c9cc.png)
@Test
![0bde5d156d8e3eb703519165ab561606.png](https://i-blog.csdnimg.cn/blog_migrate/94ee8a81ec6ae72ddf00d1f42eb8d162.png)
@Test
![b0e1d1459b4b5a9a11da99d94f83f4e7.png](https://i-blog.csdnimg.cn/blog_migrate/81e75b8a58210d370d01e30aeda01bd2.png)
@Test
![0fa95124b01e9be2cac17b499348d29e.png](https://i-blog.csdnimg.cn/blog_migrate/58392da224069658d902fed9fd9cd9fc.png)
@Test
![6d956e0db74905888c0db2e3d0ccaf4a.png](https://i-blog.csdnimg.cn/blog_migrate/d5887d07350510ad6b9e7aeaa40ca315.png)
@Test
![34292af69938a6ae78e422954e778959.png](https://i-blog.csdnimg.cn/blog_migrate/5536e51f495f3eb927f08e91fc45bf18.png)
总结:
- 直接操作属性
- 操作子属性
- 自动创建属性对象
- 自动查找属性名,支持下划线转驼峰
- 基于索引访问数组
![43c68b89e3d3ab4a2badccf53ed672b2.png](https://i-blog.csdnimg.cn/blog_migrate/68b29b652cf722dd10f69a4b34d3556f.png)
MetaObject的底层实现是BeanWapper,基本上带有Wapper的类的作用都是装饰器
![4d3f8b8133687f6b240c516e2dbd239f.png](https://i-blog.csdnimg.cn/blog_migrate/9883c47775979ec4d75245ef24e20d2d.jpeg)
BeanWapper只能操纵当前的属性,不能操作属性中的属性
MetaObject用于解析属性的表达式,例如:labels[res].name,BeanWapper的底层使用反射MetaClass用于处理反射操作
![767bf39f450755213d5f4474110a1ed7.png](https://i-blog.csdnimg.cn/blog_migrate/5cbb4794635c745901869f160ace7f8e.jpeg)
MetaObject的底层是Reflector反射器
![c80ed639d88536de019a5dfedc1725f6.png](https://i-blog.csdnimg.cn/blog_migrate/3c8765f9e4d7cef56e2cef98143236c1.jpeg)
MetaClass和Reflector的区别?
MetaClass可以操作属性下的子属性,例:可以获取labels[res].name的get方法
Reflector只能获取当前属性的get方法
MetaObject获取属性值流程
![360638537b0c1f3041507bb4e7bce144.png](https://i-blog.csdnimg.cn/blog_migrate/ec489612b61929d6ff14ea9b9a42457c.png)
假如现在要获取某个博客下面的某一个评论的某一个评论者的名称
![e6d9a4feaba120820d8284e33385f1b2.png](https://i-blog.csdnimg.cn/blog_migrate/5093d2ce1d70a3dc8e1b083ae49373d2.png)
- 首先判断表达式的下面是否还有子属性,如果有子属性,就先获取当前的自属性,再将当前的属性获取子属性,即将comments[0]转换成一个新的MetaObject,MetaObject( comments )
![98ac7ff0bbce402444d60a1475a187f0.png](https://i-blog.csdnimg.cn/blog_migrate/5ddcf3523b92bd166659d9f3cdd01435.jpeg)
2. 继续获得下面的属性,如果要将其封装成MetaObject,那么就需要获取其对应的值,所以同样要调用getValue,此时通过comments调用
![8aee2d40d188bf2aadda9c378e35ea1a.png](https://i-blog.csdnimg.cn/blog_migrate/24f6be27d652d30f65ce226663f64025.png)
3. 拿到一个新的MetaObject
![6ecb315fda82c26007937e74824e21cb.png](https://i-blog.csdnimg.cn/blog_migrate/39a595e5ccc7b3c028d017ac69c1a8fc.png)
4. 拿到评论中的User信息
![fb1434eedc689fe8d3160e3e9a323555.png](https://i-blog.csdnimg.cn/blog_migrate/7cc6d5f6ffc8c2b5653b63bea5004a56.png)
5. 同理,再经过一圈获取name属性
![06cd6a7de85ab712f251ba05044d875d.png](https://i-blog.csdnimg.cn/blog_migrate/e063894e9534ebc8454ec9644067a3aa.png)
6, 此时不存在子属性,调用BeanWrapper.get()获取当前属性值,判断是否有集合索引?如果不存在则直接调用集合反射获取属性值
![df8d7bfdc71fb9291a994d63d71f64ea.png](https://i-blog.csdnimg.cn/blog_migrate/5a7c399ef3fb95b1f40f08cec4f6e793.png)
7. 如果存在集合索引,那么首先先获取集合索引(下标index),再基于索引获取集合中的值,例如:首先获取comments,得到一个List索引,在根据List获取想要得到的集合中的值comments[0]
![c4a071e74b25f23fa18904dbb5e3100c.png](https://i-blog.csdnimg.cn/blog_migrate/67b70f88f04c863d360e8d6aced1965a.jpeg)
下面结合源码来看一下上面所示的流程
@Test
![d8b0d1d60b42fec5980ba5b0f81cfd6e.png](https://i-blog.csdnimg.cn/blog_migrate/9532e099ce85fae9fccbfafc91268291.png)
![f63a00843994784974763dea1fd18e95.png](https://i-blog.csdnimg.cn/blog_migrate/68cf47caf8da9b6ac8e2b710ac9fc564.jpeg)
PropertyTokenizer:属性分词器,即将表达式进行分词
例如,comments[0].user.name
![9a9d32f78774ebe8920ec0ef6f51b1a8.png](https://i-blog.csdnimg.cn/blog_migrate/131f559c0849ecf5744713847494a8d4.png)
name -> comments
indexedName -> comments[0]
index -> 0
children -> user.name
在获取children时,又会变成一个PropertyTokenizer,继续向下解析
![27843d6316faaf0e7effe4ed464e0c1b.png](https://i-blog.csdnimg.cn/blog_migrate/33765101c8e5cdc9ff07a85660730dd9.png)
- 判断是否存在子属性
![a91272d86b074363750fdf806940e6f3.png](https://i-blog.csdnimg.cn/blog_migrate/f76bc0823e953b09d2cfa583676386cd.jpeg)
![5362594f8f172606d7a9829c153bbdbd.png](https://i-blog.csdnimg.cn/blog_migrate/c4b871d2569fa164d6ba0dce0da58d4f.png)
此时children = user.name
如果不存在子属性,例如直接查找ID
![b1274f5e00bc2124132bde656cfcca92.png](https://i-blog.csdnimg.cn/blog_migrate/6c625d6daf027672b62de93ac56bc2b1.jpeg)
![9e5e5406fe99f9af95ec366467ee0a67.png](https://i-blog.csdnimg.cn/blog_migrate/bfb97b08345f2652f68d478eac5239da.png)
![0d625fa7144c39eb4c0c8f07cc9c39f9.png](https://i-blog.csdnimg.cn/blog_migrate/192ab49ddb749b97471606324c439ef6.png)
在本例中,是存在子属性的(user.name),所以需要把自属性转换成MetaObject
![5bf52dbfe7acc084e3dde76f724c880b.png](https://i-blog.csdnimg.cn/blog_migrate/b0cfd31e2048158d244a7b9095e77de6.jpeg)
![bc9f1fa2c207a2f07a4ec582b2db7e5b.png](https://i-blog.csdnimg.cn/blog_migrate/e0e4d2910e5a70352ff1811dff4e1165.png)
![eebf5ae80c968d5d328b2364a1b23857.png](https://i-blog.csdnimg.cn/blog_migrate/a3372bee8bf771317b06777fd42ae3ad.png)
![e96e6dcb78041ba92f83c33027923a8c.png](https://i-blog.csdnimg.cn/blog_migrate/c8674c7b835cd5b1b26424c6bfdccf26.jpeg)
![0b528e51a011450ab421c565f5728299.png](https://i-blog.csdnimg.cn/blog_migrate/b04b98e6c0ea9742be42297673f9b20e.jpeg)
即如果获取的是一个数组,则会返回一个List
![698fca805d3f53699185876558abd740.png](https://i-blog.csdnimg.cn/blog_migrate/9ecebcf7ceeb264b18fc2aaf05a03aae.jpeg)
如果获取的是一个数组中的一个值,则会返回一个对应的对象
![004bf7a02cc3e888b89fc4c37f3c6305.png](https://i-blog.csdnimg.cn/blog_migrate/38816cd7e0f7231330140403e8245247.jpeg)
将返回的结果包装成一个MetaObject
![49e405832440ada06596f62fa02e7fa2.png](https://i-blog.csdnimg.cn/blog_migrate/ce644e48c9e3760718677113e43dc347.jpeg)
可以看到,返回的MetaObject已经包装了Comment
![aced84f7f52da19ab35145d36fabb7ba.png](https://i-blog.csdnimg.cn/blog_migrate/c4fd727c51930e0ee099f7c0ea16ac3c.jpeg)
![205b18463b4ab10aa5ebc42fcdc2904a.png](https://i-blog.csdnimg.cn/blog_migrate/98d7e8698866d4586325bb13a174574e.png)
紧接着继续获取子属性,此时prop.getChildren() = “user.name“,即再走一次循环
![5ba03f38cb5223796a3d4ac6e9fe2b97.png](https://i-blog.csdnimg.cn/blog_migrate/284f463c7c4e297cb4a8292be57d55e4.jpeg)
一下就是大致流程,让大家明白即可,就是一个递归过程。
根据user得到User值
![8507fd13b4fc4de6ba2ad71702113bc8.png](https://i-blog.csdnimg.cn/blog_migrate/8991efae5cd21bf2aa0c44826a495bd4.jpeg)
![1a8298f5fd5c19129d22d5977b2a3604.png](https://i-blog.csdnimg.cn/blog_migrate/248a324e2e88c04fa9c59312921967cc.png)
再次循环,查询name,最终得到值
![b31757adb0136670fc05aeb585f0ac31.png](https://i-blog.csdnimg.cn/blog_migrate/6977d4da631b117fc8c7761d8566fe15.jpeg)
![13aaf77d3ae4fd79f52b1b9f6ccd33da.png](https://i-blog.csdnimg.cn/blog_migrate/599e5a55020b5280766276bb8208c95b.png)
![06b268293086fcc5c21af466e2847eba.png](https://i-blog.csdnimg.cn/blog_migrate/129cf926dd890921a502712d08f37951.jpeg)
是不是有点蒙,大家可以试一下看着上面的流程图debug一下,多试几次就可以理解了
对于BeanWapper,我们可以模拟几个例子
public
首先断点打到第一个get
![1710834e4f8a5915223b8ec761ff235d.png](https://i-blog.csdnimg.cn/blog_migrate/e3353a803866143c9e2f35c2af867153.jpeg)
如果有坐标说明是对集合的访问,那么接下来需要做两件事情。
- 获取集合的List
- 通过坐标获取List中具体的值
![15a591617bcab2cbaabd968e486d117e.png](https://i-blog.csdnimg.cn/blog_migrate/807098cc1cd0282631e79ec703e103bf.png)
![9447e52070487bce36ce827b75050ba4.png](https://i-blog.csdnimg.cn/blog_migrate/bae686f5c58d13d07ce53ec87e1289fa.jpeg)
又回到了getValue方法
![82a1207d9dd317e2132516ed2eaefdda.png](https://i-blog.csdnimg.cn/blog_migrate/f41c143e555573bdc4ced111e5ce45c2.jpeg)
![28eee354adeffb7824e203f372f668b9.png](https://i-blog.csdnimg.cn/blog_migrate/1f708eea6d57e08c3e75d0157f196b66.jpeg)
![663599251ea9721f2684580fb84db54a.png](https://i-blog.csdnimg.cn/blog_migrate/17f3792b2b85c16b1a5acf3b6aceff3c.jpeg)
![99802e006adc521a88d1a9753f9b8391.png](https://i-blog.csdnimg.cn/blog_migrate/efb3e7cc5cdd2a4588f4e3f9c2dd4c55.jpeg)
最后通过下标拿到返回值,底层是list.get(index)方法获取值
![38bfef026963326f5a967d8f35dd380d.png](https://i-blog.csdnimg.cn/blog_migrate/79f84c91854a356dc6a97fe32bc536ee.jpeg)
![be73a2dcb5427f94cafcea3c448ab4d5.png](https://i-blog.csdnimg.cn/blog_migrate/4f023f145e7245bbd2c07cf3d1af8d97.jpeg)
对于第二个get来说,其没有index下标值,则直接通过反射拿去相关的值
![5d35477f982babc863d88f176c2984d3.png](https://i-blog.csdnimg.cn/blog_migrate/2c48d520f6962a1123dce2a9ebd0cfeb.jpeg)
![adc16c11e86557267eb87f9eb9576d68.png](https://i-blog.csdnimg.cn/blog_migrate/c091436e118b530ea9d976882a98bb2a.jpeg)
![3856b89cb261d9b99462c6f1e8758b24.png](https://i-blog.csdnimg.cn/blog_migrate/2abde948a07a20268c83aac2292ac4b8.jpeg)
通过反射调用get和set方法、
ResultMap结果集映射
ResultMap的作用就是Java对象和表格行进行转化,即由属性名和数据库中列名进行对应
![f812957d2eb05399026378b55cadce86.png](https://i-blog.csdnimg.cn/blog_migrate/76a7e3f3be6ff9ac62d4327507cad60b.png)
即就是将JavaBean中的属性指向数据库中的每一列
在ResultMap中进行映射操作,ResultMap#type表示映射到哪个JavaBean,所谓type就是JavaBean对应的Class,type中的多个属性对应表格行的多个列。那如何表达属性和列之间的关系?--- ResultMapping(其和ResultMap为一对多关系)
![5ef05886da5585fe1a751d491f396329.png](https://i-blog.csdnimg.cn/blog_migrate/f631d1fbeb7afa25c0f88a52bf315060.png)
什么是ResultMapping?
<resultMap
在resultMap中包含的部分都属于ResultMapping
ResultMapping表现形式:
![7cd56ba402cf6e617f904059438b6a94.png](https://i-blog.csdnimg.cn/blog_migrate/e261329c72363b2acc299b59f414590f.png)
association:一对一,类似于上面一个评论一个评论者
collection:一对多,类似于上面一个博客,一组评论
例:符合映射
<resultMap
例:嵌套查询,如果设置了select,那么association中配置的id和result就失效了
<resultMap
例:外部映射,如果设置了resultMap,那么select就不起作用了,并且如果同时设置运行时会报错
<resultMap
缺点:手动映射写起来很麻烦,所以可以采用自动映射
自动映射
当属性名和列名相同时,将会触发自动映射,将其转换成UnMappedColumnAutoMapping,若属性名和列名相同时,我们同样手动设置了两者的关系,那么就自动忽略掉自动映射,优先以手动映射为主。
![f85e6cf1c6100aba4d1b70c2a58444f1.png](https://i-blog.csdnimg.cn/blog_migrate/aedc16190144fba099e5d3affe6128b9.png)
ResultSetMetaData可以获取列的总数、名称、类别、类型......
![bb0803f76e46771d12fd2751fd3ca345.png](https://i-blog.csdnimg.cn/blog_migrate/5cf4cb428f577de44bbc6cdb3665f005.jpeg)
生成的UnMappedColumnAutoMapping类似于刚才说的ResultMapping,只不过UnMappedColumnAutoMapping没有关联字段和集合关联字段,即只有基本的转换映射操作
例子:
<mapper
测试代码:
@Test
![2e8184edd7fbcb066257fa47b0ed5589.png](https://i-blog.csdnimg.cn/blog_migrate/9135a2765d6f5874f9d36d4e5d54d3e4.png)
只查询出resultMap中所指出的字段
开启自动映射:
<resultMap
![68596b87be801c40f8e4d29710095613.png](https://i-blog.csdnimg.cn/blog_migrate/dac2cfb36f042b4c015aecd40120799e.png)
因为author和comments都属于复杂属性,不能跟数据库的字段对应上,所以需要设置association
总之,最后resultMap中的所有属性都会转换成 ResultMap#ResultMapping
<mapper
![81f268e3503b73460b4be5bae08eec2d.png](https://i-blog.csdnimg.cn/blog_migrate/e6a7541a0af5b275bdf6f4e3ed60890b.jpeg)
即通过association查询得到复杂属性
通过配置Collection可以将comments信息也查找出来
<mapper
![6d973e36b44a346b32087f0253d76572.png](https://i-blog.csdnimg.cn/blog_migrate/db41779dac17736449e7e4860358ad0f.png)
接下来就开始他们底层的原理到底是什么
嵌套子查询
首先搞清楚结构关系
![5e46be331f2aeee66f68e19fb212fc30.png](https://i-blog.csdnimg.cn/blog_migrate/0791966820b3f294e36e3f77430b9327.png)
![ee279402f9ed7034d1f325b5e236e412.png](https://i-blog.csdnimg.cn/blog_migrate/9347563bd4ef4f619a760f985bee2b0e.png)
查询评论时,同样需要查询用户,因为需要知道这个评论时谁写的
此时,如果步骤(1)的useId和步骤(2)的userId相等,就没有必要再次查询相同的userId,直接去一级缓存中查找即可
假设,现在在Comment中添加一个Blog属性,即Blog和Comments互相依赖
![172247150112f7bb4d428afa44f8b407.png](https://i-blog.csdnimg.cn/blog_migrate/da0c6222f31aaa51393601263fe94458.png)
Q:在触发一级缓存时,是否会产生死循环?
![d702ec97c9b69245778211bfbe937006.png](https://i-blog.csdnimg.cn/blog_migrate/e6f1faedc4200602affa945528cfd474.png)
例:
<mapper
![25bba3bc73d2a04c90e936ecea65b81a.png](https://i-blog.csdnimg.cn/blog_migrate/d90b7255b743e8124cb8f4bed4f58ae9.jpeg)
发现并不会触发死循环,MyBatis是如何解决这种问题的?
- 延迟加载
- 缓存占位符
循环依赖流程解析
![4904dba94964c3c8d8e6f73e4759b850.png](https://i-blog.csdnimg.cn/blog_migrate/69433f78d1ea7cb3974e883d2a4d7c76.png)
在准备之后,判断一级缓存是否命中(通过延迟加载的方式)
即使一级缓存有值,也不填充进去,直到主查询结束之前再填充进去
![0c5ec2e5d865c2375b085194e963b2ca.png](https://i-blog.csdnimg.cn/blog_migrate/8231a24317bb87d87141bd839bfa4571.png)
如果没有命中一级缓存,判断是否需要懒加载,否则去进行实时加载
![71aa6f28ab03c3531aa32092a9561be7.png](https://i-blog.csdnimg.cn/blog_migrate/fa420da99158a6a4d835b15389047d48.png)
结合源码分析:
填充属性
只有手动填充时才会触发懒加载
![542c3dd692876a59d7c1ff1190a55b84.png](https://i-blog.csdnimg.cn/blog_migrate/34d4b00286b00be9039a5caba67103f9.jpeg)
![e205d7a989291ae4c5ebaa1b0c7474bd.png](https://i-blog.csdnimg.cn/blog_migrate/61aecbba72aa678ee71f6a6c16edfad4.jpeg)
获取嵌套查询的映射值:
![f127c579a2a404209d4a2d0c0ca06bf5.png](https://i-blog.csdnimg.cn/blog_migrate/8c2e15e655d7d33f494a5858d1cab6b7.jpeg)
延迟加载
在主查询结束后再填充属性值
![c1e4ce7e766a11aba91468b0c1768d72.png](https://i-blog.csdnimg.cn/blog_migrate/53f3c2205de55c9256066e39ab6dc026.png)
从Cachekey中拿出缓存值,给MetaObject填充property属性
延迟加载的实现就是BaseExecutor中
![e063c58e398b5e68e31dd4c328082cf0.png](https://i-blog.csdnimg.cn/blog_migrate/79202ccb65e3f69d884eac6ea7f62ced.jpeg)
DeferredLoad:判断当前这个类是否是可加载的,如果是可加载的,直接在缓存中拿出值然后setValue即可
![7e51f2953d787732bb7e02fa0cfad3b8.png](https://i-blog.csdnimg.cn/blog_migrate/9b3ff77d8bfdb070fe5eeff6ad26ea4e.png)
当前缓存有值,且当前的值不等于当前的占位符,那就说明是可加载的
![b702e0dd0036fe072f90c922b5468c9c.png](https://i-blog.csdnimg.cn/blog_migrate/4e6f0fa9bc890317bd0ac10188b972d7.jpeg)
那么这些跟循环依赖有什么联系呢?
在查询时,有一个queryStack = 0
![8d00537c8eb2ec87582bb9db566925ac.png](https://i-blog.csdnimg.cn/blog_migrate/98e384b9b8af9cbe57ccde236de351d8.png)
这个queryStack对应的就是当前查询栈的层级(对应图中的0、1、2)
![3c20474a1c3d01aded7319a5cfe916b7.png](https://i-blog.csdnimg.cn/blog_migrate/6591dc03feb2147a73c9eeda26241d09.png)
接下来开始调试代码:
![8d9a6f18a6cec557feb7fa9c9bee512a.png](https://i-blog.csdnimg.cn/blog_migrate/24bb88b7088cae034c0447c314bbcf22.jpeg)
![c2c6c08062819351f2f907b0210bbd23.png](https://i-blog.csdnimg.cn/blog_migrate/cbf3f972098d0d83474dc53778988c6a.jpeg)
![6e6e7a6401fd3e1cf01abea57855ef02.png](https://i-blog.csdnimg.cn/blog_migrate/50ffb4d129c80b028a498ecc3aa89230.jpeg)
在查询数据库前,先给缓存的key添加一个占位符
![ed48b0ad403142fda25f8ffd15ee0453.png](https://i-blog.csdnimg.cn/blog_migrate/41be0a02570fa104a11c279f2fa932aa.jpeg)
接下来去查询数据库,然后解析结果集,在解析结果集时同时也会解析comment结果集,所以会触发comment查询操作,在getNestedQueryMappingValue中进行触发
![b8e2e6d2168e2ea30286c0f22fec51ba.png](https://i-blog.csdnimg.cn/blog_migrate/28ff7e277802ee446cc4c58458bf22c0.jpeg)
在填充comment属性时,会触发缓存操作
![9c869f98f80b903770a03ba38190992c.png](https://i-blog.csdnimg.cn/blog_migrate/61899ec118838d54635a5a07cfedb59b.png)
此时,开始解析comment
此时comment没有缓存值,由于我们已经关闭了懒加载,所以去执行查询操作
![4d373ebae25be6133a9f9dd4fa653742.png](https://i-blog.csdnimg.cn/blog_migrate/1fb38e2ec5167e8a40507e062dc3cb1d.jpeg)
查询操作:
![ef4289786cc14d9ddb7f21dc9428a32e.png](https://i-blog.csdnimg.cn/blog_migrate/5bcf0d649221917f79f4c5951f58797e.png)
通过Executor直接发球query操作,那么query操作就又会到达我们之前说的BaseExecutor的query操作
![1e935db7ce3835a20c5415fed2286b31.png](https://i-blog.csdnimg.cn/blog_migrate/8108eeaa3acdfa7b04a405b839800839.jpeg)
此时的query对应的是(1)comment
![058a92acea164c8fc81dbb80f191759c.png](https://i-blog.csdnimg.cn/blog_migrate/5471b0b210ddad53117355bed685aafa.jpeg)
在comment中解析Blog
此时可以看到,缓存中已经有值了
![821e001503e73b4df2df2da52e9e435f.png](https://i-blog.csdnimg.cn/blog_migrate/7b72de4c4dcd009b8724a1f744e3bb38.jpeg)
此时缓存中存在的值就是一个占位符
![f81b96cefd4370744c49d08cce4148fa.png](https://i-blog.csdnimg.cn/blog_migrate/98f255ec3d2c3fdcf07fd9a90b7253a5.png)
之后进行延迟加载操作
![6baa37adfa4f6bec9410ce89f640e79f.png](https://i-blog.csdnimg.cn/blog_migrate/b3a2ada8cfaa8801fa098126b66c23d9.jpeg)
可以看到所谓的延迟加载就是将其放入延迟加载的链表中
![d9a30d359cab41c92a170253370af7f5.png](https://i-blog.csdnimg.cn/blog_migrate/55aaf46189503f0ca8247dd976461940.png)
![4e1d427e1039108f1bcc7d9b3f980634.png](https://i-blog.csdnimg.cn/blog_migrate/1a3f61bd65f4401ac936c9fed128eaf0.jpeg)
当查询返回值主查询中,再将延迟加载的值真正的放入其中
只有是占位符的情况下,才会延迟加载,否则缓存有的情况下就直接加载
![5f31ea6fdd2f8e68e0ed25c5ecc90783.png](https://i-blog.csdnimg.cn/blog_migrate/d1eb00c29b8da8e92fc09339fc7c6d86.png)
在源码中可以发现,其在查询完数据库之后,将占位符删除,再存入真正的值
![31ecc7ec6f1116f83e83230de4e254ad.png](https://i-blog.csdnimg.cn/blog_migrate/06abdccb0e71e7aa9b8d9c9aab2cfd5e.jpeg)
在查询完数据库之后,再将stack-1,就相当于回到了主查询
![568c2d99774eb0c3dedaf487e29cc763.png](https://i-blog.csdnimg.cn/blog_migrate/3929f33b66f6fe4e815126808d570716.jpeg)
最后将延迟加载的值填充进去,完成了主流程
![de84bf18aebf08471d5b23b2944fcf4b.png](https://i-blog.csdnimg.cn/blog_migrate/b8cfd95d77d1607c2cef218647540201.jpeg)
此时发现有三个延迟加载操作
![e4260704ef1cd6c20f182e337b2b09bb.png](https://i-blog.csdnimg.cn/blog_migrate/0a24105062df09a9bcf4eb961a51f836.jpeg)
![67f7fd81462b1399edecc5f94c222a86.png](https://i-blog.csdnimg.cn/blog_migrate/44ee442a519507134dc40441229edccb.jpeg)
总结:
通过一级缓存、延迟加载、占位符解决了循环依赖问题
这就是为什么有stack这个属性,为什么一级缓存不能关闭的原因(解决循环依赖)