本文主要介紹下mybatis的延遲加載,從原理上介紹下怎么使用、有什么好處能規避什么問題,延遲加載一般用于級聯查詢(級聯查詢可以將主表不能直接查詢的資料使用自定義映射規則呼叫字表來查,主查詢查完之后通過某個column列或多個列將查詢結果傳遞給子查詢,子查詢再根據主查詢傳遞的引數進行查詢,最后將子查詢結果進行映射),mybatis的懶加載是通過創建代理物件來實作的,只有當呼叫getter等方法的時候才會去查詢子查詢,查詢后完成設值再獲取值,
1. 什么時候會創建代理物件
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
this.useConstructorMappings = false; // reset previous mapping result
final List<Class<?>> constructorArgTypes = new ArrayList<>();
final List<Object> constructorArgs = new ArrayList<>();
// 創建result接收物件
Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
// 處理其他屬性properties
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
// issue gcode #109 && issue #149 創建代理
if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
break;
}
}
}
// 使用有參建構式創建了物件
this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
return resultObject;
}
通過mybatis代碼propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()發現只有當存在嵌套查詢select子句和isLazy=true的時候才會創建代理,那么isLazy=true是什么條件,從創建ResultMapping的代碼中可以看到boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager")); 只有手動設定fetchType=lazy或者全域設定configuration的lazyLoadingEnabled=true,兩者缺一不可,
關于代理是通過Javassist創建的,下面有一個簡單的例子
public class HelloMethodHandler implements MethodHandler {
private Object target;
public HelloMethodHandler(Object o) {
this.target = o;
}
@Override
public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {
String methodName = thisMethod.getName();
if (methodName.startsWith("get")) {
System.out.println("select database....");
// 進行sql查詢到結果并set設定值
((Student)self).setName("monian");
}
return proceed.invoke(self, args);
}
public static void main(String[] args) throws Exception {
Student student = new Student();
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setSuperclass(Student.class);
Constructor<Student> declaredConstructor = Student.class.getDeclaredConstructor();
Object o = proxyFactory.create(declaredConstructor.getParameterTypes(), new Object[]{});
((Proxy)o).setHandler(new HelloMethodHandler(student));
Student proxy = (Student)o;
System.out.println(proxy.getName());
}
}
mybatis的原理就是通過創建一個代理物件,當通過這個代理物件呼叫getter、is、equals、clone、toString、hashCode等方法時會呼叫select子查詢,然后完成設定,最后取值就像早就獲取到一樣,
2. 如何使用
public class UserDO {
private Integer userId;
private String username;
private String password;
private String nickname;
private List<PermitDO> permitDOList;
public UserDO() {}
}
<resultMap id="BaseMap" type="org.apache.ibatis.study.entity.UserDO">
<id column="user_id" jdbcType="INTEGER" property="userId" />
<result column="username" jdbcType="VARCHAR" property="username" />
<result column="password" jdbcType="VARCHAR" property="password" />
<result column="nickname" jdbcType="VARCHAR" property="nickname"/>
<collection property="permitDOList" column="user_id" select="getPermitsByUserId"
fetchType="lazy">
</collection>
</resultMap>
<resultMap id="PermitBaseMap" type="org.apache.ibatis.study.entity.PermitDO">
<id column="id" jdbcType="INTEGER" property="id"/>
<result column="code" jdbcType="VARCHAR" property="code"/>
<result column="name" jdbcType="VARCHAR" property="name"/>
<result column="type" jdbcType="TINYINT" property="type"/>
<result column="pid" jdbcType="INTEGER" property="pid"/>
</resultMap>
<select id="getByUserId2" resultMap="BaseMap">
select * from user
where user_id = #{userId}
</select>
<select id="getPermitsByUserId" resultMap="PermitBaseMap">
select p.*
from user_permit up
inner join permit p on up.permit_id = p.id
where up.user_id = #{userId}
</select>
通過fetchType=lazy指定子查詢getPermitsByUserId使用懶加載,這樣的話就不用管全域配置lazyLoadingEnabled是true還是false了,當然這里可以直接用多表關聯查詢不使用子查詢,使用方法在上一篇文章
測驗代碼
public class Test {
public static void main(String[] args) throws IOException {
try (InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml")) {
// 構建session工廠 DefaultSqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
UserDO userDO = userMapper.getByUserId2(1);
System.out.println(userDO);
}
}
}
結果如下,打了斷點可以看到原userDO物件已被代理并且permitDOList是null需要呼叫get方法才會去查詢拿到值,咳咳這邊之前直接運行顯示是已經把permitDOList查詢出來了,想了半天啥原因后來才發現println會呼叫userDO物件的toString方法,而toString方法也會走代理方法直接去呼叫子查詢的


3.延遲加載的好處
延遲加載主要能解決mybatis的N+1問題,什么是N+1問題其實叫1+N更為合理,以上面的業務例子來說就是假設一次查詢出來10000個用戶,那么還需要針對這10000個用戶使用子查詢getPermitsByUserId獲取每個用戶的權限串列,需要10000次查詢,總共10001次,真實情況下你可能并不需要每個子查詢的結果,這樣就浪費資料庫連接資源了,如果使用延遲加載的話就相當于不用進行這10000次查詢,因為它是等到你真正使用的時候才會呼叫子查詢獲取結果,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/499329.html
標籤:其他
下一篇:final關鍵字簡介說明
