設置Mybatis(3.2.8)實體嵌套關係(一對多,多對多)遇到的問題及經驗總結記錄

原始目標:
配置好mapper,使得能夠在實體中表示表之間的聯繫(一個表外鍵,用另外一個實體表示)

深讀了mybatis 官方的文章,最後總結出一最重要的的一條,必定要好好利用官方 的autoMapping 特性,不然就得一條條寫映射關係了。固然對於實體的嵌套填充, 我只作一層,再往下走就須要在程序邏輯上作一些處理 html

這裏配置的邏輯只與表在邏輯上的鏈接相關,是否在數據中實現關係無關。 java

1、犯過的錯誤
        一開始我把全部相關的表的列都放進來,致使有錯誤不能排除,因此搞了一天加一夜。後面我只要了幾個關鍵的,這樣語句有錯誤也好調試
2、正確的認識
1.
    爲了有效利用automaping 特性,在取別名時除前綴外,後面的名稱咱們將採起與實體屬性同樣的名稱,這樣,咱們在resultmap中就無需再一一對上屬性
2.
    <resultMap id="psProjectPushResultMap"         type="com.thinkgem.jeesite.modules.projectschedule.entity.PsProjectPush"  autoMapping ="true">
  <id property="id"  column="id"/>
</resultMap>
    這裏的column咱們只須要考慮不包括前綴的
3.映射方式主要有兩種
     第一種:直接取別名,用resultType返回結果,這種方式只能針對簡單的,且沒有List<Entity>之類的    collection; 直接取名時,對於entity屬性別名通常取屬性.id( a.customer_id as customer.id),這樣mybatis會將這個值送給對像的ID字段.
    第二種:用resultMap返回結果,當有List<Entity>這種Collection時必須用這一種方式,如下爲相關解釋
  <association  property="customer"  column=" customerid"  resultMap="psCustomerResultMap" columnPrefix="customer_"/>
    property 爲實體中的屬性值,customerid爲sql語句中的別名(通常是用一關聯另外一實體,在使用association這種方式下,這個別名保留與表格相同就好

4關於嵌套執行sql仍是一條SQL鏈接多表
    嵌套執行:官方演示了此方式,也方便理解,但因爲效率太差不推薦
    一條大SQL(JOIN):此方式是官方 推薦方式,映射時官方 會自本身移除重複記錄,此也爲推薦方式。
5.關聯的主表字段取別名時不須要加前綴。
6.鏈接時注意鏈接方式,通常是以左鏈接爲主
  • INNER JOIN: Returns all rows when there is at least one match in BOTH tables
  • LEFT JOIN: Return all rows from the left table, and the matched rows from the right table
  • RIGHT JOIN: Return all rows from the right table, and the matched rows from the left table
  • FULL JOIN: Return all rows when there is a match in ONE of the tables
3、注意事項
官方舉例在別名時推薦使用的是下劃線,
1. 但在如下的別名方式下,.也是能夠的。
 <sql id="psCustomerColumns">
        c.id as "customer_id",
        c.code as "customer_code",
        c.name as "customer_name"
    </sql>
2.此類別名方式下
<sql id="psProjectPushColumns">
        push.id as "push.id",
        push.projectid AS "push.project.id",
        push.userid AS "push.user.id",
        push.is_readed AS "push.isReaded"
    </sql>
也是能夠的
若是下劃線之外方式不能正確工做,就改用官方的下劃線前綴。
3、其實應該注意的點

如今來看看這個mapper的映射配置如何編寫,注意示例中的如下幾點:
一、constructor  實體的構造方法
二、autoMapping  自動屬性映射
三、collection  集合屬性的映射
四、association  關聯屬性的映射 git

五、mapUnderscoreToCamelCase 是否開啓自動駝峯命名規則,全局配置. github

實例一,使用下劃線: sql

PsProjectDao.xml api

<?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.thinkgem.jeesite.modules.projectschedule.dao.PsProjectDao">
 <!-- <typeAlias type="com.someapp.model.User" alias="User"/> -->
 <!-- begin result map area -->
<resultMap id="psProjectResultMap" type="com.thinkgem.jeesite.modules.projectschedule.entity.PsProject"  autoMapping ="true">
  <id property="id" column="id" />
  <!--  <result property="title" column="blog_title"/>-->
  <association property="customer"  column="customerid"  resultMap="psCustomerResultMap" columnPrefix="customer_"/>
  <association property="department" column="departmentid" resultMap="departmentResultMap" columnPrefix="department_"/>
  <association property="projectManager" column="projectManagerid" resultMap="userResultMap" columnPrefix="projectManager_"/>
  <association property="marketer" column="marketerid" resultMap="userResultMap" columnPrefix="marketer_"/>
  <collection  property="push"  column="push" ofType="PsProjectPush" resultMap="psProjectPushResultMap"  columnPrefix="push_"/>
</resultMap>
<resultMap id="userResultMap" type="com.thinkgem.jeesite.modules.sys.entity.User"  autoMapping ="true">
  <id property="id" column="id"/>
</resultMap>
<resultMap id="departmentResultMap" type="com.thinkgem.jeesite.modules.sys.entity.Office" autoMapping ="true" >
  <id property="id" column="id"/>
</resultMap>
<resultMap id="psCustomerResultMap" type="com.thinkgem.jeesite.modules.projectschedule.entity.PsCustomer"  autoMapping ="true">
  <id property="id" column="id"/>
</resultMap>
<resultMap id="psProjectPushResultMap" type="com.thinkgem.jeesite.modules.projectschedule.entity.PsProjectPush"  autoMapping ="true">
  <id property="id" column="id"/>
</resultMap>
<!-- end resultmap area -->
<!-- begin sql columns area -->
    <sql id="psProjectColumns">
        a.id AS "id",
        a.code AS "code",
        a.name AS "name",
        a.type AS "type",
        a.importance_degree AS "importanceDegree",
        a.tech_state AS "techState",
        a.customerid AS "customerid",
        a.departmentid AS "departmentid",
        a.project_managerid AS "projectManagerid",
        a.plan_starttime AS "planStarttime",
        a.plan_endtime AS "planEndtime",
        a.state AS "state",
        a.act_starttime AS "actStarttime",
        a.act_endtime AS "actEndtime",
        a.sending_time AS "sendingTime",
        a.old_plan_starttime AS "oldPlanStarttime",
        a.old_plan_endtime AS "oldPlanEndtime",
        a.delivery_time AS "deliveryTime",
        a.complete_status AS "completeStatus",
        a.marketerid AS "marketerid",
        a.create_by AS "createBy.id",
        a.create_date AS "createDate",
        a.update_by AS "updateBy.id",
        a.update_date AS "updateDate",
        a.remarks AS "remarks",
        a.del_flag AS "delFlag"
    </sql>
    <sql id="departmentColumns">
         d.id as "department_id",
         d.name as "department_name"
    </sql>
        <sql id="projectManagerColumns">
        pm.id as "projectManager_id",
        pm.name as "projectManager_name"
    </sql>
    <sql id="marketerColumns">
        m.id as "marketer_id",
        m.name as "marketer_name"
    </sql>
    <sql id="psCustomerColumns">
        c.id as "customer_id",
        c.code as "customer_code",
        c.name as "customer_name"
    </sql>
    <sql id="psProjectPushColumns">
        ps.id as "push_id",
        ps.projectid AS "push_project.id",
        ps.userid AS "push_user.id",
        ps.is_readed AS "push_isReaded"
    </sql>
    <!-- end sql columns area -->
    <sql id="psProjectJoins">
        LEFT JOIN ps_customer c ON c.id = a.customerid 
        LEFT JOIN sys_office  d ON d.id = a.departmentid 
        LEFT JOIN sys_user pm ON pm.id = a.project_managerid 
        LEFT JOIN sys_user m ON m.id = a.marketerid
        LEFT JOIN ps_project_push ps ON ps.projectid=a.id
    </sql>

    <select id="findAllList" resultMap="psProjectResultMap">
        SELECT 
            <include refid="psProjectColumns"/>,
            <include refid="departmentColumns"/>,
            <include refid="projectManagerColumns"/>,
            <include refid="marketerColumns"/>,
            <include refid="psCustomerColumns"/>,
            <include refid="psProjectPushColumns"/>
        FROM ps_project a 
        <include refid="psProjectJoins"/>
        <where>
            a.del_flag = #{DEL_FLAG_NORMAL}
        </where>        
        <choose>
            <when test="page !=null and page.orderBy != null and page.orderBy != ''">
                ORDER BY ${page.orderBy}
            </when>
            <otherwise>
                ORDER BY a.update_date DESC
            </otherwise>
        </choose>
    </select>

    <insert id="insert">
        INSERT INTO ps_project(
            id,
            code,
            name,
            type,
            importance_degree,
            tech_state,
            customerid,
            departmentid,
            managerid,
            plan_starttime,
            plan_endtime,
            state,
            act_starttime,
            act_endtime,
            sending_time,
            old_plan_starttime,
            old_plan_endtime,
            delivery_time,
            complete_status,
            marketerid,
            create_by,
            create_date,
            update_by,
            update_date,
            remarks,
            del_flag
        ) VALUES (
            #{id},
            #{code},
            #{name},
            #{type},
            #{importanceDegree},
            #{techState},
            #{customer.id},
            #{department.id},
            #{projectManager.id},
            #{planStarttime},
            #{planEndtime},
            #{state},
            #{actStarttime},
            #{actEndtime},
            #{sendingTime},
            #{oldPlanStarttime},
            #{oldPlanEndtime},
            #{deliveryTime},
            #{completeStatus},
            #{marketer.id},
            #{createBy.id},
            #{createDate},
            #{updateBy.id},
            #{updateDate},
            #{remarks},
            #{delFlag}
        )
    </insert>

    <update id="update">
        UPDATE ps_project SET     
            code = #{code},
            name = #{name},
            type = #{type},
            importance_degree = #{importanceDegree},
            tech_state = #{techState},
            customerid = #{customer.id},
            departmentid = #{department.id},
            managerid = #{projectManager.id},
            plan_starttime = #{planStarttime},
            plan_endtime = #{planEndtime},
            state = #{state},
            act_starttime = #{actStarttime},
            act_endtime = #{actEndtime},
            sending_time = #{sendingTime},
            old_plan_starttime = #{oldPlanStarttime},
            old_plan_endtime = #{oldPlanEndtime},
            delivery_time = #{deliveryTime},
            complete_status = #{completeStatus},
            marketerid = #{marketer.id},
            update_by = #{updateBy.id},
            update_date = #{updateDate},
            remarks = #{remarks}
        WHERE id = #{id}
    </update>

    <update id="delete">
        UPDATE ps_project SET 
            del_flag = #{DEL_FLAG_DELETE}
        WHERE id = #{id}
    </update>

</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.thinkgem.jeesite.modules.projectschedule.dao.PsProjectDao">
    <!-- <typeAlias type="com.someapp.model.User" alias="User"/> -->
    <!-- begin result map area -->
    <resultMap id="psProjectResultMap"
        type="com.thinkgem.jeesite.modules.projectschedule.entity.PsProject"
        autoMapping="true">
        <id property="id" column="id" />
        <!-- <result property="title" column="blog_title"/> -->
        <association property="customer" column="customerid"
            resultMap="psCustomerResultMap" columnPrefix="customer." />
        <association property="department" column="departmentid"
            resultMap="departmentResultMap" columnPrefix="department." />
        <association property="projectManager" column="projectManagerid"
            resultMap="userResultMap" columnPrefix="projectManager." />
        <association property="marketer" column="marketerid"
            resultMap="userResultMap" columnPrefix="marketer." />
        <collection property="push" column="push" ofType="PsProjectPush"
            resultMap="psProjectPushResultMap" columnPrefix="push." />
    </resultMap>
    <resultMap id="userResultMap" type="com.thinkgem.jeesite.modules.sys.entity.User"
        autoMapping="true">
        <id property="id" column="id" />
    </resultMap>
    <resultMap id="departmentResultMap"
        type="com.thinkgem.jeesite.modules.sys.entity.Office" autoMapping="true">
        <id property="id" column="id" />
    </resultMap>
    <resultMap id="psCustomerResultMap"
        type="com.thinkgem.jeesite.modules.projectschedule.entity.PsCustomer"
        autoMapping="true">
        <id property="id" column="id" />
    </resultMap>
    <resultMap id="psProjectPushResultMap"
        type="com.thinkgem.jeesite.modules.projectschedule.entity.PsProjectPush"
        autoMapping="true">
        <id property="id" column="id" />
    </resultMap>
    <!-- end resultmap area -->
    <!-- begin sql columns area -->
    <sql id="psProjectColumns">
        a.id AS "id",
        a.code AS "code",
        a.name AS "name",
        a.type AS
        "type",
        a.importance_degree AS "importanceDegree",
        a.tech_state AS
        "techState",
        a.customerid AS "customerid",
        a.departmentid AS
        "departmentid",
        a.project_managerid AS "projectManagerid",
        a.plan_starttime AS "planStarttime",
        a.plan_endtime AS "planEndtime",
        a.state AS "state",
        a.act_starttime AS "actStarttime",
        a.act_endtime AS
        "actEndtime",
        a.sending_time AS "sendingTime",
        a.old_plan_starttime AS
        "oldPlanStarttime",
        a.old_plan_endtime AS "oldPlanEndtime",
        a.delivery_time AS "deliveryTime",
        a.complete_status AS
        "completeStatus",
        a.marketerid AS "marketerid",
        a.create_by AS
        "createBy.id",
        a.create_date AS "createDate.id",
        a.update_by AS
        "updateBy.id",
        a.update_date AS "updateDate.id",
        a.remarks AS "remarks",
        a.del_flag AS "delFlag"
    </sql>
    <sql id="departmentColumns">
        department.id as "department.id",
        department.name as "department.name"
    </sql>
    <sql id="projectManagerColumns">
        projectManager.id as "projectManager.id",
        projectManager.name as "projectManager.name"
    </sql>
    <sql id="marketerColumns">
        marketer.id as "marketer.id",
        marketer.name as "marketer.name"
    </sql>
    <sql id="psCustomerColumns">
        customer.id as "customer.id",
        customer.code as
        "customer.code",
        customer.name as "customer.name"
    </sql>
    <sql id="psProjectPushColumns">
        push.id as "push.id",
        push.projectid AS "push.project.id",
        push.userid AS "push.user.id",
        push.is_readed AS "push.isReaded"
    </sql>
    <!-- end sql columns area -->
    <sql id="psProjectJoins">
        LEFT JOIN ps_customer customer ON customer.id =
        a.customerid
        LEFT JOIN sys_office department ON department.id = a.departmentid
        LEFT JOIN sys_user projectManager ON projectManager.id =
        a.project_managerid
        LEFT JOIN sys_user marketer ON marketer.id = a.marketerid
        LEFT JOIN ps_project_push push ON push.projectid=a.id
    </sql>

    <select id="findAllList" resultMap="psProjectResultMap">
        SELECT
        <include refid="psProjectColumns" />
        ,
        <include refid="departmentColumns" />
        ,
        <include refid="projectManagerColumns" />
        ,
        <include refid="marketerColumns" />
        ,
        <include refid="psCustomerColumns" />
        ,
        <include refid="psProjectPushColumns" />
        FROM ps_project a
        <include refid="psProjectJoins" />
        <where>
            a.del_flag = #{DEL_FLAG_NORMAL}
        </where>
        <choose>
            <when test="page !=null and page.orderBy != null and page.orderBy != ''">
                ORDER BY ${page.orderBy}
            </when>
            <otherwise>
                ORDER BY a.update_date DESC
            </otherwise>
        </choose>
    </select>

    <insert id="insert">
        INSERT INTO ps_project(
        id,
        code,
        name,
        type,
        importance_degree,
        tech_state,
        customerid,
        departmentid,
        managerid,
        plan_starttime,
        plan_endtime,
        state,
        act_starttime,
        act_endtime,
        sending_time,
        old_plan_starttime,
        old_plan_endtime,
        delivery_time,
        complete_status,
        marketerid,
        create_by,
        create_date,
        update_by,
        update_date,
        remarks,
        del_flag
        ) VALUES (
        #{id},
        #{code},
        #{name},
        #{type},
        #{importanceDegree},
        #{techState},
        #{customer.id},
        #{department.id},
        #{projectManager.id},
        #{planStarttime},
        #{planEndtime},
        #{state},
        #{actStarttime},
        #{actEndtime},
        #{sendingTime},
        #{oldPlanStarttime},
        #{oldPlanEndtime},
        #{deliveryTime},
        #{completeStatus},
        #{marketer.id},
        #{createBy.id},
        #{createDate},
        #{updateBy.id},
        #{updateDate},
        #{remarks},
        #{delFlag}
        )
    </insert>

    <update id="update">
        UPDATE ps_project SET
        code = #{code},
        name = #{name},
        type = #{type},
        importance_degree = #{importanceDegree},
        tech_state = #{techState},
        customerid = #{customer.id},
        departmentid = #{department.id},
        managerid = #{projectManager.id},
        plan_starttime = #{planStarttime},
        plan_endtime = #{planEndtime},
        state = #{state},
        act_starttime = #{actStarttime},
        act_endtime = #{actEndtime},
        sending_time = #{sendingTime},
        old_plan_starttime = #{oldPlanStarttime},
        old_plan_endtime = #{oldPlanEndtime},
        delivery_time = #{deliveryTime},
        complete_status = #{completeStatus},
        marketerid = #{marketer.id},
        update_by = #{updateBy.id},
        update_date = #{updateDate},
        remarks = #{remarks}
        WHERE id = #{id}
    </update>

    <update id="delete">
        UPDATE ps_project SET
        del_flag = #{DEL_FLAG_DELETE}
        WHERE id = #{id}
    </update>

</mapper>



實體文件(PsProject.java)

/**
 * 項目管理Entity
 * @author xiaohelong
 * @version 2016-01-06
 */
public class PsProject extends DataEntity<PsProject> {
	
	private static final long serialVersionUID = 1L;
	private String code;		// 項目編號
	private String name;		// 項目名稱
	private String type;		// 項目類型(科研項目,仍是交付)
	private String importanceDegree;		// 重要程度(通常,重要,緊急)
	private String techState;		// 技術狀態(徹底沿用,設計更改,全新設計)
	private PsCustomer customer;		// 客戶單位
	private Office department;		// 所屬部門
	private User projectManager;		// 項目經理
	private Date planStarttime;		// 計劃開始時間
	private Date planEndtime;		// 計劃結束時間
	private String state;		// 項目當前狀態(等待開始,正常進行,交付延期,節點延期,項目暫停,項目終止,項目結束)
	private Date actStarttime;		// 實際開始時間
	private Date actEndtime;		// 實際結束時間
	private Date sendingTime;		// 項目下發時間
	private Date oldPlanStarttime;		// 計劃開始時間(只讀字段,初始化後不更改)
	private Date oldPlanEndtime;		// 計劃結束時間(只讀字段,初始化後不更改)
	private Date deliveryTime;		// 交付時間
	private String completeStatus;		// 完成狀況(正常完成,延期完成,未完成)
	private User marketer;		// 市場人員ID
	private List<PsProjectPush> push; //相關領導數據表中並無,放在這裏便於表單操做,由於該字段數據送到另一個表中了。
	public PsProject() {
		super();
	}

	public PsProject(String id){
		super(id);
	}

	public String getCode() {
		return code;
	}

	public void setCode(String code) {
		this.code = code;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getType() {
		return type;
	}

	public void setType(String type) {
		this.type = type;
	}

	public String getImportanceDegree() {
		return importanceDegree;
	}

	public void setImportanceDegree(String importanceDegree) {
		this.importanceDegree = importanceDegree;
	}

	public String getTechState() {
		return techState;
	}

	public void setTechState(String techState) {
		this.techState = techState;
	}

	public PsCustomer getCustomer() {
		return customer;
	}

	public void setCustomer(PsCustomer customer) {
		this.customer = customer;
	}

	public Office getDepartment() {
		return department;
	}

	public void setDepartment(Office department) {
		this.department = department;
	}

	public User getProjectManager() {
		return projectManager;
	}

	public void setProjectManager(User projectManager) {
		this.projectManager = projectManager;
	}

	public Date getPlanStarttime() {
		return planStarttime;
	}

	public void setPlanStarttime(Date planStarttime) {
		this.planStarttime = planStarttime;
	}

	public Date getPlanEndtime() {
		return planEndtime;
	}

	public void setPlanEndtime(Date planEndtime) {
		this.planEndtime = planEndtime;
	}

	public String getState() {
		return state;
	}

	public void setState(String state) {
		this.state = state;
	}

	public Date getActStarttime() {
		return actStarttime;
	}

	public void setActStarttime(Date actStarttime) {
		this.actStarttime = actStarttime;
	}

	public Date getActEndtime() {
		return actEndtime;
	}

	public void setActEndtime(Date actEndtime) {
		this.actEndtime = actEndtime;
	}

	public Date getSendingTime() {
		return sendingTime;
	}

	public void setSendingTime(Date sendingTime) {
		this.sendingTime = sendingTime;
	}

	public Date getOldPlanStarttime() {
		return oldPlanStarttime;
	}

	public void setOldPlanStarttime(Date oldPlanStarttime) {
		this.oldPlanStarttime = oldPlanStarttime;
	}

	public Date getOldPlanEndtime() {
		return oldPlanEndtime;
	}

	public void setOldPlanEndtime(Date oldPlanEndtime) {
		this.oldPlanEndtime = oldPlanEndtime;
	}

	public Date getDeliveryTime() {
		return deliveryTime;
	}

	public void setDeliveryTime(Date deliveryTime) {
		this.deliveryTime = deliveryTime;
	}

	public String getCompleteStatus() {
		return completeStatus;
	}

	public void setCompleteStatus(String completeStatus) {
		this.completeStatus = completeStatus;
	}

	public User getMarketer() {
		return marketer;
	}

	public void setMarketer(User marketer) {
		this.marketer = marketer;
	}

	public List<PsProjectPush> getPush() {
		return push;
	}

	public void setPush(List<PsProjectPush> push) {
		this.push = push;
	}

}



另附直接用別名簡單映射方式(無LIST collection)--JeeSite默認的方式,沒有繼續研究其List是如何實現的,因其並無在XML中體現出來,因此我的以爲極可能是在JAVA SERVICE程進行了體現。

<?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.thinkgem.jeesite.modules.sys.dao.OfficeDao">

	<sql id="officeColumns">
		a.id,
		a.parent_id AS "parent.id",
		a.parent_ids,
		a.area_id AS "area.id",
		a.code,
		a.name,
		a.sort,
		a.type,
		a.grade,
		a.address, 
		a.zip_code, 
		a.master, 
		a.phone, 
		a.fax, 
		a.email, 
		a.remarks,
		a.create_by AS "createBy.id",
		a.create_date,
		a.update_by AS "updateBy.id",
		a.update_date,
		a.del_flag,
		a.useable AS useable,
		a.primary_person AS "primaryPerson.id",
		a.deputy_person AS "deputyPerson.id",
		p.name AS "parent.name",
		ar.name AS "area.name",
		ar.parent_ids AS "area.parentIds",
		pp.name AS "primaryPerson.name",
		dp.name AS "deputyPerson.name"
	</sql>
	
	<sql id="officeJoins">
		LEFT JOIN sys_office p ON p.id = a.parent_id
		LEFT JOIN sys_area ar ON ar.id = a.area_id
		LEFT JOIN SYS_USER pp ON pp.id = a.primary_person
		LEFT JOIN SYS_USER dp ON dp.id = a.deputy_person
    </sql>	
	<select id="findAllList" resultType="Office">
		SELECT
			<include refid="officeColumns"/>
		FROM sys_office a
		<include refid="officeJoins"/>
		WHERE a.del_flag = #{DEL_FLAG_NORMAL}
		ORDER BY a.code
	</select>
</mapper>


參考資料: mybatis

1. http://www.mybatis.org/mybatis-3/sqlmap-xml.html#Auto-mapping app

2.http://leeyee.github.io/blog/2013/05/30/mybatis-association-autoMapping/ this

相關文章
相關標籤/搜索