1. 程式人生 > >TKmybatis的框架介紹和原理分析及Mybatis新特性

TKmybatis的框架介紹和原理分析及Mybatis新特性

tkmybatis是在mybatis框架的基礎上提供了很多工具,讓開發更加高效,下面來看看這個框架的基本使用,後面會對相關原始碼進行分析,感興趣的同學可以看一下,挺不錯的一個工具

實現對員工表的增刪改查的程式碼
java的dao層介面

public interface WorkerMapper extends Mapper<Worker> {
}
  • 1
  • 2

xml對映檔案

<?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="com.jjs.kaiwen.dao.WorkerMapper"> <resultMap id="BaseResultMap" type="com.jjs.kaiwen.model.Worker"> <!-- WARNING - @mbggenerated --> <id column="id" jdbcType="INTEGER" property="id" /> <result column="worker_id" jdbcType="VARCHAR"
property="workerId" />
<result column="name" jdbcType="VARCHAR" property="name" /> <result column="org_id" jdbcType="INTEGER" property="orgId" /> <result column="status" jdbcType="VARCHAR" property="status" /> <result column="role_id" property="roleId" jdbcType="INTEGER"
/>
</resultMap> </mapper>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

實體物件

@Table(name = "worker")
public class Worker {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Column(name = "worker_id")
    private String workerId;

    private String name;

    @Column(name = "org_id")
    private Integer orgId;

    private String status;

    @Column(name = "role_id")
    private Integer roleId;

    // getters and setters ...
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

以上就是實現對Worker進行增刪改查的所有程式碼,包括選擇性更新、插入、刪除等,所有的方法列表如下

這裡寫圖片描述

以後對錶欄位的新增或修改只需要更改實體物件的註解,不需要修改xml對映檔案,如將worker_id改成worker_no

@Column(name = "worker_no")
private String workerNo;
  • 1
  • 2

資料來源的配置,只需要將org.mybatis.spring.mapper.MapperScannerConfigurer改成tk.mybatis.spring.mapper.MapperScannerConfigurer,然後加一個屬性
,也可不加,因為框架提供了預設實現

    <bean class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
        <property name="basePackage" value="com.jjs.zanbi.dao" />
        <property name="properties">
            <value>
                mappers=tk.mybatis.mapper.common.Mapper
            </value>
        </property>
    </bean>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

用這個庫之後寫程式碼感覺在飛…….如果只是簡單的瞭解此框架到這裡就可以了,下面是對框架實現原理的分析

原理的簡單分析

此框架為我們實現這些功能所有的改動都在Mapper層面,所有的Mapper都繼承了tk.mybatis.mapper.common.Mapper

public interface WorkerMapper extends Mapper<Worker> {}
  • 1

Mapper介面的宣告如下,可以看到Mapper介面實現了所有常用的方法

public interface Mapper<T> extends
        BaseMapper<T>,
        ExampleMapper<T>,
        RowBoundsMapper<T>,
        Marker {

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

看一下完整的UML圖,太大了,可以用新視窗開啟,放大之後再看
這裡寫圖片描述

這裡選擇一個介面:SelectOneMapper介面,對於原始碼進行簡單分析,此介面宣告如下:

public interface SelectOneMapper<T> {

    /**
     * 根據實體中的屬性進行查詢,只能有一個返回值,有多個結果是丟擲異常,查詢條件使用等號
     *
     * @param record
     * @return
     */
    @SelectProvider(type = BaseSelectProvider.class, method = "dynamicSQL")
    T selectOne(T record);

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

@SelectProvider是mybatis3之後提供的,用於靈活的設定sql來源,這裡設定了服務提供類和方法,但這個庫並沒有直接用method指定的方法來返回sql,而是在執行時進行解析的,程式碼如下

public class BaseSelectProvider extends MapperTemplate {

    public String selectOne(MappedStatement ms) {
        Class<?> entityClass = getEntityClass(ms);
        //修改返回值型別為實體型別
        setResultType(ms, entityClass);
        StringBuilder sql = new StringBuilder();
        sql.append(SqlHelper.selectAllColumns(entityClass));
        sql.append(SqlHelper.fromTable(entityClass, tableName(entityClass)));
        sql.append(SqlHelper.whereAllIfColumns(entityClass, isNotEmpty()));
        return sql.toString();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

到這裡我們就大概知道了這個庫為我們提供便利的原理了,總的來說就是這個庫幫我們提供了對錶的基本操作的sql,幫我們省了很多工作量,而且維護起來也很方便,否則我們的xml檔案動不動就幾百行甚至上千行

對原始碼的探索不能到這裡停止,最起碼要分析到與另一個框架的整合點

我們知道,mybatis的mapper介面是在啟動的時候被框架以JdkProxy的形式封裝了的,具體對應的類是MapperFactoryBean,這個類中有一個checkDaoConfig()方法,是從父類繼承並重寫了該方法,繼承結構如下

MapperFactoryBean -> SqlSessionDaoSupport -> DaoSupport
  • 1

這裡的DaoSupport就是spring提供的Dao的抽象,程式碼如下

public abstract class DaoSupport implements InitializingBean {

// spring 完成屬性設定後會呼叫此方法
    @Override
    public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
        // 這裡提供了介面供子類去實現
        checkDaoConfig();

        // Let concrete implementations initialize themselves.
        try {
            initDao();
        }
        catch (Exception ex) {
            throw new BeanInitializationException("Initialization of DAO failed", ex);
        }
    }

    protected abstract void checkDaoConfig() throws IllegalArgumentException;

    protected void initDao() throws Exception {
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

框架自定義的MapperFactoryBean重寫了checkDaoConfig()方法,完成對所有sql語句的設定,程式碼如下

    @Override
    protected void checkDaoConfig() {
        super.checkDaoConfig();
        //通用Mapper
        if (mapperHelper.isExtendCommonMapper(getObjectType())) {
        //這裡去處理該類所對應的MappedStatement,封裝在helper類中處理
            mapperHelper.processConfiguration(getSqlSession().getConfiguration(), getObjectType());
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

MapperHelper的processConfiguration方法如下

    public void processConfiguration(Configuration configuration, Class<?> mapperInterface) {
        String prefix;
        if (mapperInterface != null) {
            prefix = mapperInterface.getCanonicalName();
        } else {
            prefix = "";
        }
        for (Object object : new ArrayList<Object>(configuration.getMappedStatements())) {
            if (object instanceof MappedStatement) {
                MappedStatement ms = (MappedStatement) object;
                //檢查這個MappedStatement是否屬於此對映物件
                if (ms.getId().startsWith(prefix) && isMapperMethod(ms.getId())) {
                    if (ms.getSqlSource() instanceof ProviderSqlSource) {
                       //去設定該statement的sql語句
                        setSqlSource(ms);
                    }
                }
            }
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

設定sql的邏輯,提供了幾種不同型別的sqlsource

    public void setSqlSource(MappedStatement ms) throws Exception {
        if (this.mapperClass == getMapperClass(ms.getId())) {
            throw new RuntimeException("請不要配置或掃描通用Mapper介面類:" + this.mapperClass);
        }
        Method method = methodMap.get(getMethodName(ms));
        try {
            //第一種,直接操作ms,不需要返回值
            if (method.getReturnType() == Void.TYPE) {
                method.invoke(this, ms);
            }
            //第二種,返回SqlNode
            else if (SqlNode.class.isAssignableFrom(method.getReturnType())) {
                SqlNode sqlNode = (SqlNode) method.invoke(this, ms);
                DynamicSqlSource dynamicSqlSource = new DynamicSqlSource(ms.getConfiguration(), sqlNode);
                setSqlSource(ms, dynamicSqlSource);
            }
            //第三種,返回xml形式的sql字串
            else if (String.class.equals(method.getReturnType())) {
                String xmlSql = (String) method.invoke(this, ms);
                SqlSource sqlSource = createSqlSource(ms, xmlSql);
                //替換原有的SqlSource
                setSqlSource(ms, sqlSource);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

到這裡整個sql的獲取流程就分析完了,本人用這個庫寫過一個小專案,確實節省了開發的工作量,而且DAO層的結構更加清晰簡潔了

關於mybatis新特性

從3.4.0開始,mybatis提供對外部表的alias引用方法,多表聯合查詢就方便多了,我們先看原始的方式是怎樣做的

select a.id,a.name,b.bid,b.bname .....
from user a 
left join room b 
  • 1
  • 2
  • 3

原始的方式是將所有的表字段列出來,再來看用新特性怎樣做

select id="selectUsers" resultType="map">
  select
    <include refid="user_col_sql_id"><property name="alias" value="t1"/>,
    <include refid="room_col_sql_id"><property name="alias" value="t2"/>
  from user t1
    left join room t2
</select>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

這裡主要就是對基本的sql進行了複用,如果對錶進行了修改只要在原始的sql節點修改就可以了,就算是5個表的聯合查詢,sql也是清晰易懂,維護起來會更輕鬆

新版本的mybatis對於物件對映也提供了更友好的方式,直接使用外部的ResultMap再加上查詢語句中的別名就對映完成了

    <resultMap id="workerResultMap" type="com.jjs.kaiwen.model.Worker" extends="BaseResultMap">
        <association property="room" columnPrefix="b_"  resultMap="com.jjs.kaiwen.dao.OrgMapper.BaseResultMap"/>
    </resultMap>
  • 1
  • 2
  • 3

更進一步

敏銳的程式設計師可能會提出問題,如當多表查詢的時候可能會存在欄位名稱相同的情況,這裡的解決方案是給include新增另一個屬性

<include refid="user_col_sql_id_with_alias">
<property name="alias" value="t"/>
<property name="prefix" value="t_"/>
</include>
  • 1
  • 2
  • 3
  • 4

包含prefix的sqlNode如下

    <sql id="base_column_with_alias">
        ${alias}.ID as ${prefix}ID,
        ${alias}.WORKER_ID as ${prefix}WORKER_ID,
        ${alias}.NAME as ${prefix}NAME,
        ${alias}.ZB_ROLE_ID as ${prefix}ZB_ROLE_ID,
        ${alias}.ORG_ID as ${prefix}ORG_ID,
        ${alias}.STATUS as ${prefix}STATUS
    </sql>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

如果說覺得手動寫包含alias和prefix的欄位麻煩,可以用,mybatis程式碼生成器的外掛的方式實現,我自己寫了一個生成器的外掛,可以程式碼再這裡,僅供參考

通用Service類

/**
 * Created by Kaiwen
 */
@Service
public abstract class CommonServiceImpl<T,PK extends Serializable> implements CommonService<T,PK> {
    /**
     * 泛型注入
     */
    @Autowired
    private Mapper<T> mapper;

    public T selectByPrimaryKey(PK entityId) {

        return mapper.selectByPrimaryKey(entityId);
    }

    public int deleteByPrimaryKey(PK entityId) {
        return mapper.deleteByPrimaryKey(entityId);
    }

    public int insert(T record) {
        return mapper.insert(record);
    }

    public int insertSelective(T record) {
        return mapper.insertSelective(record);
    }

    public int updateByPrimaryKeySelective(T record) {
        return mapper.updateByPrimaryKeySelective(record);
    }

    public int updateByPrimaryKey(T record) {
        return mapper.updateByPrimaryKey(record);
    }

    public List<T> selectByExample(Example example) {
        return mapper.selectByExample(example);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

注入方式區別

    <bean class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
        <property name="basePackage" value="com.jjshome.esf.core.dao.school" />
        <property name="properties">
            <value>
                mappers=tk.mybatis.mapper.common.Mapper
            </value>
        </property>
    </bean>


    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.jjshome.esf.core.dao.community,com.jjshome.esf.core.dao.hsl"/>
    </bean>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

實體類

package com.jjshome.esf.common.entity.school;

import java.util.Date;
import javax.persistence.*;

@Table(name = "XQ_SCHOOL_AREA")
public class SchoolArea {
    /**
     * 主鍵ID
     */
    @Id
    @Column(name = "ID")
    private Integer id;

    /**
     * 城市編碼
     */
    @Column(name = "CITY_CODE")
    private String cityCode;

    /**
     * 學區名稱
     */
    @Column(name = "NAME")
    private String name;

    /**
     * 學區名稱拼音
     */
    @Column(name = "NAME_SPELL")
    private String nameSpell;

    /**
     * 狀態,1:正常,0:刪除
     */
    @Column(name = "STATUS")
    private Byte status;

    /**
     * 新增人
     */
    @Column(name = "CREATE_ID")
    private String createId;


    @Transient
    private Integer primaryCount; //小學數量
    @Transient
    private Integer middleCount; //初中數量
    @Transient
    private Integer highCount;//高中數量
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

TK mybatis Mapper檔案內容

<?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="com.jjshome.esf.core.dao.school.ISchoolAreaDAO" >
  <resultMap id="BaseResultMap" type="com.jjshome.esf.common.entity.school.SchoolArea" >
    <!--
      WARNING - @mbggenerated
    -->
    <id column="ID" property="id" jdbcType="INTEGER" />
    <result column="CITY_CODE" property="cityCode" jdbcType="VARCHAR" />
    <result column="NAME" property="name" jdbcType="VARCHAR" />
    <result column="NAME_SPELL" property="nameSpell" jdbcType="VARCHAR" />
    <result column="STATUS" property="status" jdbcType="TINYINT" />
    <result column="CREATE_ID" property="createId" jdbcType="VARCHAR" />
    <result column="CREATE_DATE" property="createDate" jdbcType="TIMESTAMP" />
    <result column="UPDATE_ID" property="updateId" jdbcType="VARCHAR" />
    <result column="UPDATE_DATE" property="updateDate" jdbcType="TIMESTAMP" />
    <result column="CITY_NAME" property="cityName"/>
    <result column="PRIMARY_COUNT" property="primaryCount"/>
    <result column="MIDDLE_COUNT" property="middleCount"/>
    <result column="HIGH_COUNT" property="highCount"/>
  </resultMap>

    <resultMap id="SchoolDetailArea" type="com.jjshome.esf.common.entity.school.SchoolAreaDetail"
               extends="com.jjshome.esf.core.dao.school.ISchoolInfoDAO.SchoolInfo">
        <result column="SCHOOL_AREA_NAME" property="schoolAreaName"/>
    </resultMap>

    <select id="selectByPage" parameterType="map" resultMap="BaseResultMap">

        SELECT A.*, C.NAME AS CITY_NAME,
        (SELECT COUNT(*) FROM XQ_SCHOOL_INFO B WHERE A.ID=B.AREA_ID AND B.TYPE='553' AND B.STATUS = 1 ) AS PRIMARY_COUNT,
        (SELECT COUNT(*) FROM XQ_SCHOOL_INFO B WHERE A.ID=B.AREA_ID AND B.TYPE='554' AND B.STATUS = 1 ) AS MIDDLE_COUNT,
        (SELECT COUNT(*) FROM XQ_SCHOOL_INFO B WHERE A.ID=B.AREA_ID AND B.TYPE='555' AND B.STATUS = 1 ) AS HIGH_COUNT
        FROM XQ_SCHOOL_AREA A
        LEFT JOIN YW_CITY_SETTING C ON A.CITY_CODE = C.CODE
        <where>
            <if test="name != null and name != '' "> A.NAME LIKE CONCAT('%',#{NAME},'%')  </if>
            <if test="areaCityCode != null and areaCityCode != '' "> A.CITY_CODE = #{areaCityCode}  </if>
            <if test="keywords != null and keywords != '' ">
                ( A.NAME LIKE CONCAT('%',#{keywords},'%')
                )
            </if>
        </where>
    </select>


    <select id="selectAreaIdAndKeyWord" parameterType="java.util.Map" resultMap="BaseResultMap">
        SELECT
        *
        FROM
        XQ_SCHOOL_AREA
        WHERE
        1=1
        <if test="cityId != null">
            AND CITY_CODE=#{cityId}
        </if>
        <if test="key != null and key!=''">
            AND (NAME like CONCAT(#{key},'%' ) or NAME_SPELL like CONCAT(#{key},'%' ))
        </if>
        AND
        STATUS=1
        <if test="pageSize != null">
            limit #{pageSize}
        </if>
    </select>


    <!--查詢學區詳情列表-->
    <select id="selectAreaDetailByPage" parameterType="map" resultMap="SchoolDetailArea">

        SELECT A.* ,B.NAME AS SCHOOL_AREA_NAME ,C.NAME AS CITY_NAME,D.NAME AS AREA_NAME FROM XQ_SCHOOL_INFO A
        LEFT JOIN XQ_SCHOOL_AREA B ON A.AREA_ID = B.ID
        LEFT JOIN YW_CITY_SETTING C ON A.CITY_CODE = C.CODE
        LEFT JOIN YW_CITY_SETTING D ON A.AREA_CODE = D.CODE

        WHERE A.STATUS = 1 AND B.STATUS =1
        <if test="areaId != null and areaId.length() &gt; 0">  AND A.AREA_ID = #{areaId} </if>
        <if test="typeList != null and typeList.size &gt; 0">
            AND
            A.TYPE IN
            <foreach collection="typeList"  item="item" index="index" open="(" close=")" separator=",">
                #{item}
            </foreach>
        </if>
        <if test="name != null and name != '' "> AND   A.NAME LIKE CONCAT('%',#{name},'%')  </if>
    </select>

</mapper>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88

普通mybatisMapper檔案

<?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="com.jjshome.esf.core.dao.school.ISchoolInfoDAO">
    <resultMap id="SchoolInfo" type="com.jjshome.esf.common.entity.school.SchoolInfo">
        <id column="ID" property="id"/>
        <result column="NAME" property="name"/>
        <result column="NAME_SPELL" property="nameSpell"/>
        <result column="ALIAS" property="alias"/>
        <result column="ALIAS_SPELL" property="aliasSpell"/>
        <result column="TYPE" property="type" typeHandler="com.jjshome.esf.core.component.handler.DictValueTypeHandler"/>
        <result column="AREA_ID" property="areaId"/>
        <result column="CITY_CODE" property="cityCode"/>
        <result column="AREA_CODE" property="areaCode"/>
        <result column="ADDR" property="addr"/>
        <result column="START_TIME" property="startTime"/>
        <result column="MOTTO" property="motto"/>
        <result column="WEB_SITE" property="webSite"/>
        <result column="PHONE" property="phone"/>
        <result column="FEATURE" property="feature" typeHandler="com.jjshome.esf.core.component.handler.DictValueListTypeHandler"/>
        <result column="LNG" property="lng"/>
        <result column="LAT" property="lat"/>
        <result column="UNIT_PRICE" property="unitPrice"/>
        <result column="SALE_PRICE" property="salePrice"/>
        <result column="NATURE_TYPE" property="natureType" typeHandler="com.jjshome.esf.core.component.handler.DictValueTypeHandler"/>
        <result column="NATURE_CITY" property="natureCity" typeHandler="com.jjshome.esf.core.component.handler.DictValueTypeHandler"/>
        <result column="SCHOOL_DEGREE" property="schoolDegree"/>
        <result column="ENROL_DEGREE" property="enrolDegree"/>
        <result column="IMG_DEGREE" property="imgDegree"/>
        <result column="STATUS" property="status"/>
        <result column="CREATE_ID" property="createId"/>
        <result column="CREATE_DATE" property="createDate"/>
        <result column="UPDATE_ID" property="updateId"/>
        <result column="UPDATE_DATE" property="updateDate"/>

        <result column="CITY_NAME" property="cityName" />
        <result