懶加載原理
懶加載是為改善,解析對象屬性時大量的嵌套子查詢的并發問題。設定懶加載後,隻有在使用指定屬性時才會加載,進而分散SQL請求。
通過對Bean的動态代理,重寫所有屬性的getXxx方法。在擷取屬性前先判斷屬性是否被加載,如果否則加載。
調用流程圖如下:
其中第一步的Bean代理過程發生在結果集解析 交建立對象之後(DefaultResultSetHandler.createResultObject),如果對應的屬性設定了懶加載,則會通過ProxyFactory 建立代理對象,該對象繼承自原對象,然後将對象的值全部拷貝到代理對像。并設定相應MethodHandler(原對象直接抛棄)
下面我們來看看代理後的結構
調試時的屬性值如下:
需要注意的是懶加載隻會觸發一次,因為執行懶加載的時候在map中移除了:
開啟方法
懶加載的開啟方式有以下幾種:
lazyLoadingEnabled 全局懶加載開關 預設false
aggressiveLazyLoading 任意方法觸發加載 預設false。
fetchType 加載方式 eager實時 lazy懶加載。預設eager
在調用對象配置了懶加載屬性的的get方法,如:
<resultMap id="blogMap" type="com.entity.Blog" autoMapping="true">
<result column="title" property="title"></result>
<collection property="comments" column="id" select="selectCommentsByBlogId" fetchType="lazy">
</collection>
</resultMap>
調用了getComments會觸發懶加載,
或者調用對象的"equals", “clone”, “hashCode”, “toString” 均會觸發目前對象所有未執行的懶加載,源碼位置:
驗證幾種操作還會不會觸發懶加載
- 先調用配置了懶加載的set方法,再調用get方法的情況
@Test
public void lazySetTest(){
Blog blog=blogMapper.selectBlogById(1);
blog.setComments(new ArrayList<>());
for(Comment comment:blog.getComments()){
System.out.println(DateUtil.formatDate(comment.getDate())+"評論了本部落格:"+comment.getContent());
}
System.out.println(blog.getComments());
}
我們可以看到不能擷取到comments的記錄,而正常情況下是可以的,說明被我們設定後的屬性會使得懶加載失效
2、 序列化與反序列化的情況
<!--懶加載-->
<resultMap id="blogMap" type="com.entity.Blog" autoMapping="true">
<result column="title" property="title"></result>
<collection property="comments" column="id" select="selectCommentsByBlogId" fetchType="lazy">
</collection>
</resultMap>
@Before
public void init(){
DruidDataSource druidDataSource=new DruidDataSource();
druidDataSource.setUrl("jdbc:mysql://localhost:3306/test");
druidDataSource.setUsername("root");
druidDataSource.setPassword("root");
druidDataSource.setValidationQuery("select 1");
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, druidDataSource);
configuration = new Configuration(environment);
//測試懶加載序列化時需要自行設定一個configurationFactory的類。
configuration.setConfigurationFactory(LazyTest.ConfigurationFactory.class);
configuration.addMapper(BlogMapper.class);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
this.session=sqlSessionFactory.openSession();
blogMapper=session.getMapper(BlogMapper.class);
}
/**
* 測試序列化和反序列化
*/
@Test
public void lasySerializableTest() throws IOException, ClassNotFoundException {
Blog blog=blogMapper.selectBlogById(1);
byte[] bytes=writeObject(blog);
Blog newBlog= (Blog) readObject(bytes);
System.out.println("反序列化完成");
newBlog.getComments();
}
private static byte[] writeObject(Object object) throws IOException {
ByteArrayOutputStream out=new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(out);
objectOutputStream.writeObject(object);
return out.toByteArray();
}
private static Object readObject(byte[] bytes) throws IOException, ClassNotFoundException {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
return objectInputStream.readObject();
}
我們可以看到,在反序列化後,還是能調用成功,是因為在序列化過程中時,會通過實作Externalizable接口的writeExternal()和readExternal() 兩個方法将相關的環境變量設定和還原,使得懶加載能正常生效,相關源碼如下:
本文章的參考資料為:
https://www.coderead.cn/