前言
大家五一快樂啊,上次小撰寫了映射體系一,具體講了MetaObject反射工具的使用以及原始碼解釋,接下來講一下真正的映射體系,
手動 自動映射
手動映射配置
xml
<!-- 復合映射-->
<resultMap id="baseMap" type="entity.Company">
<id property="id" column="id"/>
<result property="companyName" column="company_name" jdbcType="VARCHAR"/>
<association property="legalPerson" column="id">
<id property="id" column="id"/>
<result property="name" column="name"/>
</association>
</resultMap>
<!-- 外部映射-->
<resultMap id="baseMap1" type="entity.Company">
<id property="id" column="id"/>
<result property="companyName" column="company_name" jdbcType="VARCHAR"/>
<association property="legalPerson" column="id" resultMap="legalPersonMap">
</association>
</resultMap>
<resultMap id="legalPersonMap" type="entity.LegalPerson">
<id property="id" column="id"/>
<result property="name" column="name"/>
</resultMap>
<!-- 嵌套查詢-->
<resultMap id="baseMap2" type="entity.Company">
<id property="id" column="id"/>
<result property="companyName" column="company_name" jdbcType="VARCHAR"/>
<association property="legalPerson" column="id" select="selectByCompanyId"/>
</resultMap>
<select id="selectByCompanyId" resultType="entity.LegalPerson"/>
這個比較基礎,當然也可以使用java代碼,小伙伴自行研究啊,
一個ResultMap 中包含多個ResultMapping 表示一個具體的JAVA屬性到列的映射,其主要值如下:
| result id屬性 |
|---|
| property | 類屬性名(必填) |
|---|---|
| column | 資料庫列名(必填) |
| jdbcType | jdbc型別(自動推導) |
| javaType | java型別(自動推導) |
| TypeHandler | 型別處理器(自動推導) |
ResultMapping 有多種表現形式如下:
- constructor:構建引數欄位
- id:ID欄位
- result:普通結構集欄位
- association:1對1關聯欄位
- collection:1對多集合關聯欄位
上面手動映射圖如下:

自動映射配置
<resultMap id="baseMap" type="entity.Company" autoMapping="true">
</resultMap>
自動映射條件
- 列名和屬性名同時存在(勿略大小寫)
- 當前列未手動設定映射
- 屬性類別存在TypeHandler
- 開啟autoMapping (默認開啟)
自動映射圖如下:

嵌套子查詢
小編用上面的示例寫一個簡單測驗代碼這里用了lombok:
類測驗類以及介面代碼:
public interface CompanyMapper {
Company selectById(@Param("id")Long id);
}
@Data
public class Company {
private Long id;
private String companyName;
private LegalPerson legalPerson;
private List<Department> departmentList;
}
@Data
public class LegalPerson {
private Long id;
private String name;
private Long companyId;
}
@Data
public class Department {
private Long id;
private String departmentName;
private Long companyId;
private List<Employee> employeeList;
}
@Test
public void associationTest() {
try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.REUSE)) {
CompanyMapper companyMapper = sqlSession.getMapper(CompanyMapper.class);
Company company = companyMapper.selectById(1L);
System.out.println(company.getLegalPerson());
}
}
xml配置:
<resultMap id="CompanyMap" type="entity.Company">
<id property="id" column="id"/>
<result property="companyName" column="company_name" jdbcType="VARCHAR"/>
<association property="legalPerson" column="id" select="selectByCompanyId"/>
<collection property="departmentList" column="id" select="selectDepartByCompanyId"/>
</resultMap>
<resultMap id="LegalPersonMap" type="entity.LegalPerson">
<id property="id" column="id"/>
<result property="companyId" column="company_id" />
<result property="name" column="name"/>
</resultMap>
<resultMap id="DepartmentMap" type="entity.Department">
<id property="id" column="id"/>
<result property="companyId" column="company_id" />
<result property="departmentName" column="department_name"/>
</resultMap>
<select id="selectByCompanyId" resultMap="LegalPersonMap">
select * from legal_person where company_id = #{companyId}
</select>
<select id="selectDepartByCompanyId" resultMap="DepartmentMap">
select * from department where company_id = #{companyId}
</select>
<select id="selectById" resultMap="CompanyMap" parameterType="java.lang.Long">
select * from company where id = #{id}
</select>
提問:當這里需要傳遞多個引數時該怎么寫?
這里小撰寫偽xml column = “companyId =id,name=companyName” ,然后下面就可以company_id=#{companyId} and name = #{name}
上面小編為什么寫這樣的示例呢?其實小編想說明一個問題,我們來討論一個這樣的問題:假設我們的法人有一家公司,然后公司下面的法人又是自己,那當我們查詢法人的時候要填充公司屬性,但公司填充的時候又查詢到這個法人,那這樣不就死回圈了嗎?那mybatis到底會不會出現死回圈呢
答案:當然不會死回圈了(不信大家自己試一下,小編已經試過了),其實mybatis作者也想到了這個問題,spring中是不是也會有回圈依賴的問題,那mybatis是如何解決的呢,那下面小編繼續講解其中的原理,
回圈依賴
回圈依賴流程圖:

根據上面的流程圖,小編帶大家看一下原始碼,大家也可以自己打斷點除錯,在上一篇博客中,封裝行物件的時候用到DefaultResultSetHandler#getRowValue方法,里面用到了applyPropertyMappings方法,因為是嵌套查詢的最侄訓用到getPropertyMappingValue方法:
private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
if (propertyMapping.getNestedQueryId() != null) {
//是否是嵌套查詢 這個方法就開始和上面流程圖差不多了
return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
} else if (propertyMapping.getResultSet() != null) {
addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK?
return DEFERRED;
} else {
//直接獲取值
final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
return typeHandler.getResult(rs, column);
}
}
開始嵌套查詢
private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
//準備引數 ,獲取mappedStatement,動態sql 準備換成key
final String nestedQueryId = propertyMapping.getNestedQueryId();
final String property = propertyMapping.getProperty();
final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping, nestedQueryParameterType, columnPrefix);
Object value = null;
if (nestedQueryParameterObject != null) {
final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql);
final Class<?> targetType = propertyMapping.getJavaType();
//是否存在快取
if (executor.isCached(nestedQuery, key)) {
//命中快取 延時加載
executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType);
value = DEFERRED;
} else {
//沒有命中快取
final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql);
if (propertyMapping.isLazy()) {
//看是否懶加載
lazyLoader.addLoader(property, metaResultObject, resultLoader);
value = DEFERRED;
} else {
//否則直接查詢
value = resultLoader.loadResult();
}
}
}
return value;
}
延遲加載是在主查詢結束后再將屬性值填充進去,其具體實作在baseExecutor
@Override
public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
DeferredLoad deferredLoad = new DeferredLoad(resultObject, property, key, localCache, configuration, targetType);
//是否可延遲加載
if (deferredLoad.canLoad()) {
//可以直接延遲加載
deferredLoad.load();
} else {
//需要延遲加載的放入deferredLoads 這里就是法人下面查找的公司
deferredLoads.add(new DeferredLoad(resultObject, property, key, localCache, configuration, targetType));
}
}
public boolean canLoad() {
//本地快取有且不是EXECUTION_PLACEHOLDER值,在第一次查詢的時候會有占位符
return localCache.getObject(key) != null && localCache.getObject(key) != EXECUTION_PLACEHOLDER;
}
public void load() {
@SuppressWarnings("unchecked")
// we suppose we get back a List
//然后直接從key拿值,然后賦值即可
List<Object> list = (List<Object>) localCache.getObject(key);
Object value = resultExtractor.extractObjectFromList(list, targetType);
resultObject.setValue(property, value);
}
上面的流程圖和源代碼已經對應起來了,但是大家可能還沒明白mybatis怎么解決回圈依賴的,
首先先說結論,第一使用一級快取,第二個使用延遲加載(這里也間接說明了一級快取是不能關閉的),這邊還用了一個queryStack引數和快取占位符,
下面咱們再次捋一下
1、查詢公司 queryStack=0
2、公司下的法人 queryStack=1
3、法人下的公司 queryStack=2
其中第一次查詢將自己設定快取和第三次查詢時是同一家公司,所以會走一級快取,不會重復查詢資料庫,接著將自己放入到延遲加載集合中,回到主查詢,加載延遲加載中的屬性值即可,
小編完整說明一下回圈依賴的解決:
再次貼一下代碼BaseExecutor代碼:
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
//查詢一次 加一次查詢堆
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
//查詢完畢 減一次查詢堆
queryStack--;
}
//當查詢到0的時候就是回傳主查詢,進行延遲裝載
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
//裝載完延遲后清空延遲加載器
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
//查詢前放入占位符
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
//查詢并且賦值相應的引數值,會涉及上面的嵌套查詢
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
- 第一次查詢公司的時候:BaseExecutor.query方法中 queryStack++,并且設定一級快取占位符;
- 在嵌套查詢,即第二次查詢法人的時候,因為不是懶加載則會再次BaseExecutor.query這個方法,queryStack++然后設定查詢法人的一級快取占位符
- 查詢法人資訊的時候又查詢公司,即第三次查詢公司有快取了,因為還不能直接加載,則放入了deferredLoads
- 回傳結果,先是法人查詢填充屬性完畢queryStack – ,洗掉法人一級快取的占位符,將法人放入一級快取中,之后是公司查詢填充法人資訊完畢queryStack – ,洗掉公司一級快取占位符,將公司放入一級快取中
- queryStack為0,執行延遲加載,里面有剛剛放入的一個,并且可以加載了(公司一級快取不為空也不是占位符),延遲加載使用metaObject設定對應的值即可,
總結
今天主要講了映射體系中的手動自動,以及嵌套子查詢和回圈依賴mybatis是如何解決的,還有懶加載和延遲加載是兩回事情,希望小編講得足夠明白了,最近小編在講解程序中留下來一些問題,主要是希望大家不僅僅是看看,更得實踐,否則容易忘記,一起加油啊,接下來小編會講解懶加載,大家繼續關注吧
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/283213.html
標籤:其他
上一篇:架構師成長記_第八周_10_ES-分詞與五種內置分詞器
下一篇:一個自動化專案的思考總結
