互联网轻量级框架整合之MyBatis动态SQL

MyBatis的动态SQL是一项强大且实用的功能,它允许开发者在XML映射文件中编写可灵活变化的SQL语句,这些语句能够根据传入参数的条件或值动态地调整其结构和内容。这样,程序可以在运行时生成适应特定业务场景的SQL,避免了手动拼接SQL字符串带来的诸多问题,如语法错误、注入风险、维护困难等等

动态SQL的核心元素

<if>标签,其作用是根据表达式的值决定是否包含某段SQL片段,如下代码所示,test 属性用于指定一个OGNL表达式(Object-Graph Navigation Language),表达式的结果为布尔值。当结果为 true 时,包含该标签内的SQL语句片段;否则,忽略该片段。

<if test="condition">
    SQL语句片段
</if>

<where>标签,其作用是自动过滤掉无效的WHERE子句条件,避免出现诸如WHERE 1=1这样的无效条件,如下代码所示

<select id="selectByCondition" parameterType="com.example.ConditionBean" resultType="com.example.ResultBean">
    SELECT * FROM table
    <where>
        <!-- 各种条件判断 -->
        <if test="id != null">AND id = #{id}</if>
        <if test="name != null">AND name LIKE #{name}</if>
        <!-- ... -->
    </where>
</select>

<where>标签内部可以放置多个<if>或其他动态标签。MyBatis会智能地去掉那些条件表达式为假(false或空值)的子句,并确保最终生成的WHERE子句以正确的关键字(如AND或OR)开始,无需手动处理空格和连接词

<trim>标签,其作用是用于去除SQL语句片段首尾的指定字符或字符串,如下代码所示

<trim prefix="PREFIX_" suffix="SUFFIX_" prefixOverrides="PREFIX_TO_REMOVE_" suffixOverrides="SUFFIX_TO_REMOVE_">
    SQL语句片段
</trim>
  • prefix:在SQL片段开头添加指定字符串。
  • suffix:在SQL片段结尾添加指定字符串。
  • prefixOverrides:指定需要从SQL片段开头移除的字符或字符串。
  • suffixOverrides:指定需要从SQL片段结尾移除的字符或字符串。
  • <trim>常用于清理多余的逗号、空格等字符,特别是在动态构建IN列表或SET子句时非常有用

<set>标签,其作用是用于更新操作,动态生成SET子句,自动过滤掉值未改变的字段,避免不必要的更新,如下代码所示

<update id="updateUser" parameterType="com.example.User">
    UPDATE users
    <set>
        <if test="username != null">username = #{username},</if>
        <if test="email != null">email = #{email},</if>
        <!-- ... -->
    </set>
    WHERE id = #{id}
</update>

<set>标签内部同样可以放置多个<if>或其他动态标签。MyBatis会在执行更新时,仅保留那些值已改变的字段对应的SET项,同时自动处理好逗号的添加与移除,确保生成的SET子句语法正确

<choose>, <when>, <otherwise>标签其作用是实现类似于编程语言中的switch语句,根据不同的条件分支选择不同的SQL片段,如下代码所示

<choose>
    <when test="condition1">
        SQL语句片段1
    </when>
    <when test="condition2">
        SQL语句片段2
    </when>
    <!-- 可以有多个when -->
    <otherwise>
        默认SQL语句片段
    </otherwise>
</choose>

每个<when>标签对应一个条件分支,test属性的值为一个OGNL表达式。当匹配到某个条件为真时,执行相应的SQL片段。如果没有<when>标签的条件为真,则执行<otherwise>标签内的SQL

<foreach>标签其作用是遍历集合或数组,动态生成重复的SQL片段,常用于批量插入、批量更新或IN子句的构造,如下代码所示

<foreach item="item" index="index" collection="collection" open="(" separator="," close=")">
    #{item}
</foreach>
  • item:循环变量名,用于引用集合中的当前元素。
  • index:可选,循环索引变量名,用于获取当前元素的索引。
  • collection:OGNL表达式,用于指定要遍历的集合或数组。
  • open:遍历开始时添加的字符或字符串。
  • separator:元素间的分隔符。
  • close:遍历结束时添加的字符或字符串
SELECT * FROM users WHERE id IN
<foreach item="userId" collection="userIds" open="(" separator="," close=")">
    #{userId}
</foreach>

动态SQL的执行原理:MyBatis在解析XML映射文件时,会将动态SQL标签及其内容转换为一个抽象语法树(AST)。当实际执行SQL时,根据传入的参数对象,动态计算OGNL表达式的值,进而根据表达式的真假状态决定是否包含某段SQL、如何拼接条件等。这个过程是在运行时完成的,因此能够动态生成适应不同业务场景的SQL语句。
通过以上这些动态SQL标签,MyBatis极大地简化了复杂查询和更新逻辑的编写,使得SQL更加简洁、清晰,易于维护,同时也提高了开发效率,减少了手动拼接SQL引发的错误

动态SQL实例

<!-- 根据提供的字符串参数查询角色信息 -->
<select id="findRoles" parameterType="string" resultType="role">
	<!-- 基本查询语句,选取角色表中的id, role_name, note列 -->
	select id, role_name, note from t_role where 1=1
	<!-- 如果传入的roleName不为空且不为零长度字符串,则在查询条件中添加角色名的模糊匹配 -->
	<if test="roleName != null and roleName !=''">
		and role_name like concat('%', #{roleName}, '%')
	</if>
</select>

当参数roleName被传递给映射器时,如果参数不为空,则构造对roleName的模糊查询,否则不构造这个条件

    <!-- 根据条件查询角色信息 -->
    <!-- 参数说明:
         id: 角色的唯一标识符
         roleName: 角色名称
    -->
    <!-- 返回值:查询到的角色信息列表 -->
    <select id="findRoles2" parameterType="role" resultType="role">
        select id, role_name, note from t_role
        where 1=1
        <!-- 根据参数条件动态生成查询语句 -->
        <choose>
            <!-- 如果提供了id,则在查询条件中加入id的等于比较 -->
            <when test="id != null and id !=''">
                AND id = #{id}
            </when>
            <!-- 如果提供了roleName,则在查询条件中加入角色名称的模糊匹配 -->
            <when test="roleName != null and roleName !=''">
                AND role_name like concat('%', #{roleName}, '%')
            </when>
            <!-- 如果未提供id和roleName,则查询note字段不为空的角色信息 -->
            <otherwise>
                AND note is not null
            </otherwise>
        </choose>
    </select>

代码中使用了choose、when、otherwise元素,MyBatis会根据参数进行判断来动态组装SQL,以满足不同的也无需求

在之前的SQL中,都加入了一个条件1=1,其作用主要是为了不报错,顺带解释一下这个条件的几种作用:

  • 简化动态查询条件的拼接:当应用程序需要根据用户的选择或输入动态构建 SQL 查询时,WHERE 1=1 可作为初始条件,确保查询语句的语法完整性。这样,无论用户是否提供额外的筛选条件,查询都能正常执行。
    后续根据实际需要,可以在 WHERE 1=1 后面通过 AND 连接更多的具体条件。例如,如果用户指定了角色名称,可以追加 AND role_name = ‘用户指定的角色名’。由于 WHERE 子句已经存在且始终为真,添加其他条件时无需考虑是否存在初始条件。
  • 便于条件的增删与调试:
    开发过程中,程序员可能需要临时注释掉某些查询条件进行测试。如果查询开始于 WHERE 1=1,那么只需注释相应的 AND 后面的条件即可,无需调整 WHERE 子句本身。这使得代码更易于维护和调整。
  • 作为占位符或模板:
    在编写通用的 SQL 模板或框架代码时,WHERE 1=1 可作为一个标准起始点,待具体使用时再填充实际条件。这种做法有利于保持查询结构的一致性,尤其是在使用代码生成工具或预编译 SQL 语句的场景下。
  • 在特定上下文中表示“全选”:
    尽管直接省略 WHERE 子句也能达到查询全部记录的效果,但在一些特定情况下(比如需要动态添加条件的代码逻辑),保留 WHERE 1=1 可能有助于清晰表达查询意图为获取所有满足可能附加条件的数据。
  • 特殊情况下的特殊用途:
    在极少数情况下,WHERE 1=1 或类似的永真条件可能会用于特定的数据库操作,如某些数据库迁移脚本或数据复制任务中,用来仅复制表结构而不获取任何数据(如使用 CREATE TABLE AS SELECT … WHERE 1=0 创建空表结构)。

WHERE 1=1 主要作为一种编程技巧,用于简化动态查询条件的构造、维护和调试,以及在特定场景下作为查询条件的占位符或全选标志。尽管它本身对查询结果并无实际影响(因为 1=1 永远为真),但其在实际编程实践中具有一定的实用价值。

<!-- 根据角色名和备注信息查询角色信息 -->
<select id="findRoles3" parameterType="role" resultType="role">
    select id, role_name, note from t_role
    <where>
        <!-- 如果提供了角色名,则按角色名进行模糊查询 -->
        <if test="roleName != null and roleName !=''">
            and role_name like concat('%', #{roleName}, '%')
        </if>
        <!-- 如果提供了备注信息,则按备注信息进行模糊查询 -->
        <if test="note != null and note !=''">
            and note like concat('%', #{note}, '%')
        </if>
    </where>
</select>

只有当where条件成立时,才会加入“and”这个SQL关键字到组装的SQL执行,否则不加入,就相当于where 1=1的作用

有时候要去掉的是一些特殊的SQL语法,比如常见的and、or,使用trim元素也可以达到预期效果

<!-- 根据提供的字符串参数查询角色信息 -->
<select id="findRoles4" parameterType="string" resultType="role">
    <!-- 从t_role表中选择id, role_name, note列 -->
    select id, role_name, note from t_role
    <!-- 在where条件前添加前缀,并且忽略掉所有的and前缀,以防止多个条件连接时的语法错误 -->
    <trim prefix="where" prefixOverrides="and">
        <!-- 如果roleName不为空且不为零长度字符串,则添加like条件进行角色名的模糊匹配 -->
        <if test="roleName != null and roleName !=''">
            and role_name like concat('%', #{roleName}, '%')
        </if>
    </trim>
</select>

trim元素意味着要去掉一些特殊的字符串,prefix代表语句的前缀,prefixOverdridesd代表需要去掉的字符串

在Hibernate中,常常因为更新某一个对象,而发送全部字段给持久对象,但现实场景中可能只需要更新某一个字段,这个情况下如果发送所有字段那很可能造成过大的网络开销而降低了性能,最佳的办法是只对主键和要更新的字段进行操作

如果一个表需要更新两个字段,那写两条SQL又会降低可读性和增加代码冗余,Hibernate就是把全部字段发送给SQL,而避免多条SQL的情况

<!-- 更新角色信息的SQL语句 -->
<update id="updateRole" parameterType="role">
    update t_role
    <set>
        <!-- 如果角色名不为空,则更新角色名 -->
        <if test="roleName != null and roleName !=''">
            role_name = #{roleName},
        </if>
        <!-- 如果备注信息不为空,则更新备注信息 -->
        <if test="note != null and note != ''">
            note = #{note}
        </if>
    </set>
    <!-- 根据角色ID进行更新 -->
    where id = #{id}
</update>

MyBatis中,可以使用set元素,来避免这个问题,而且当set元素遇到了多余的逗号,会把对应的逗号去掉

foreach元素是一个循环语句,它的作用是遍历集合,它能够很好地支持数组和List、Set接口的集合,对此进行遍历功能,往往用于SQL中的in关键字场景

<!-- 根据角色id列表查询角色信息 -->
<select id="findRoleByIds" resultType="role">
    <!-- 使用foreach标签处理idList中的每个id,构建查询条件 -->
    select id, role_name, note from t_role where id in
    <foreach item="id" index="index" collection="idList" open="("
        separator="," close=")">
        #{id}
    </foreach>
</select>

  • item属性:属性值id作用:定义在循环体中用于引用当前迭代元素的变量名。在这个例子中,item=“id” 表示在循环内部,可以通过${id}#{id}引用来访问集合中当前迭代到的单个角色ID。
  • index属性:属性值index作用:定义在循环体中用于引用当前迭代索引(下标)的变量名。这里没有直接使用到index变量,但如果有需要,可以通过${index}在 SQL 中引用当前元素在 idList 集合中的位置。
  • collection属性:属性值idList作用:指定要遍历的集合或数组的名称。这是 <foreach>标签必须提供的属性,它指定了传入 SQL 语句的方法参数中所包含的集合或数组的变量名。在本例中,方法调用时应传入一个名为 idList 的 List 或数组,其中包含了要查询的角色ID列表。
  • open属性:属性值(作用:定义在循环开始时输出的文本内容。此处设置为(,表示在生成的 SQL 中,在 IN 子句的起始处插入一个左括号。
  • separator属性:属性值,作用:定义在每次迭代之间输出的分隔文本。这里设定为 ,,意味着在相邻两个角色ID之间插入逗号,以符合 SQL IN 子句中列举多个值时的语法要求。
  • close属性:属性值)作用:定义在循环结束时输出的文本内容。这里设置为),表示在所有角色ID循环完成后,在 IN 子句的末尾插入一个右括号,完成整个条件表达式的闭合。
  • #{id}:在<foreach>循环体内,使用 #{id}表达式来引用当前迭代元素(即item属性指定的变量)。MyBatis 将会将其替换为实际的值,并且会对该值进行预编译参数化,提供 SQL 注入攻击防护。
<!-- 根据提供的字符串参数查询角色信息 -->
<select id="getRoleTest" parameterType="string" resultType="role">"
	<!-- 从t_role表中选择id, role_name, note列 -->
	 select id, role_name, note from t_role
	 <!-- 如果参数type等于'Y',则添加where条件 -->
	 <if test="type=='Y'.toString()">
		 where 1=1
	 </if>
</select>
<!-- 
    查询满足特定条件的角色信息。
    参数:
    - id: 角色的唯一标识符。
    - parameterType: 参数类型,此处为字符串类型。
    - resultType: 查询结果类型,此处为角色类型。
    返回值:一个包含角色信息的结果集。
 -->
<select id="findRoles5" parameterType="string" resultType="role">
    <bind name="pattern" value="'%' + _parameter + '%'" /> <!-- 构造匹配模式,实现模糊搜索 -->
    select id, role_name as roleName, note FROM t_role
    where role_name like
    #{pattern}
</select>

bind 元素是 MyBatis 框架中的一个动态 SQL 标签,允许开发者定义一个变量并赋予其特定值,以便后续在 SQL 语句中作为参数值使用,在这个例子中,定义了一个名为 pattern 的变量,其值由表达式 ‘%’ + _parameter + ‘%’ 计算得出。其中 _parameter 是 MyBatis 中预定义的变量,代表传入方法的参数。这样,通过 bind 标签,我们创建了一个用于模糊查询的 SQL 模式

<!-- 
    查询同时满足角色名和备注信息条件的角色信息。
    参数:
    - resultType: 查询结果类型,此处为角色类型。
    返回值:一个包含满足条件的角色信息的结果集。
 -->
<select id="findRoles6" resultType="role">
    <bind name="pattern_roleName" value="'%' + roleName + '%'" /> <!-- 构造角色名的匹配模式,实现模糊搜索 -->
    <bind name="pattern_note" value="'%' + note + '%'" /> <!-- 构造备注信息的匹配模式,实现模糊搜索 -->
    SELECT id, role_name as roleName, note FROM t_role
    where role_name like #{pattern_roleName}
    and note like #{pattern_note}
</select>

因为MyBatis支持多个参数,因此,可是使用bind元素定义两个新的变量

如此看来,bind元素还带来了如下几个优点:

  • 简化复杂逻辑处理: 有时 SQL 查询可能涉及复杂的逻辑判断或字符串拼接,直接在 SQL 语句中编写可能会导致代码难以阅读和维护。使用 bind 标签可以在 XML 映射文件中以编程方式处理这些逻辑,将计算结果封装成变量,使得 SQL 语句保持简洁清晰。例如,根据不同的业务规则动态生成 WHERE 子句的条件部分。
  • 提高 SQL 可移植性: 不同数据库系统对于某些函数或操作可能存在语法差异。bind 标签可以通过在 Java 层处理这些差异,确保生成的 SQL 语句适应目标数据库。比如,使用 bind 来拼接字符串进行模糊查询,可以避免直接在 SQL 中使用特定数据库的字符串连接函数(如 MySQL 的 CONCAT() 或 Oracle 的 ||),从而增强 SQL 语句在跨数据库环境下的可移植性。
  • 防范 SQL 注入: MyBatis 使用预编译的 PreparedStatement 执行 SQL,其参数替换机制能够有效防止 SQL 注入攻击。当使用 bind 标签构造参数值时,即使表达式中包含了用户输入的部分(如 _parameter),生成的最终值也会被安全地传递给预编译的 SQL 语句,避免了原始数据直接拼接到 SQL 字符串中可能导致的安全风险。
  • 复用与组织: 对于需要在多个地方使用的相同逻辑或值,可以使用 bind 标签预先计算并绑定,避免重复书写。这有助于减少代码冗余,提高映射文件的结构化程度和可读性

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/567455.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C语言单向链表的经典算法

1.分割链表 2.移除链表元素 3.反转链表 4.合并两个有序链表 5.链表的中间结点 6.环形链表的约瑟夫问题 1.分割链表: 1.思路&#xff1a;创建新链表&#xff0c;小链表和大链表。如图 代码如下 /*** Definition for singly-linked list.* struct ListNode {* int val…

android学习笔记(二)

1、自定义View。 package com.example.view; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.AttributeSet; import android.view.View; //可以在View测量和布局完成后…

前端性能分析工具及使用

Lighthouse Lighthouse &#xff08;谷歌浏览器的插件商店中搜索并安装&#xff0c;浏览器中点击F12&#xff0c;开发者工具中可使用&#xff09;是 Google 开发的一款工具&#xff0c;用于分析网络应用和网页&#xff0c;收集现代性能指标并提供对开发人员最佳实践的意见。只要…

医学访问学者专栏—研究领域及工作内容

在国外访问学者申请中&#xff0c;医学领域的研究、教学及从业人员占有相当大的比例&#xff0c;这些医学访问学者的研究领域及工作内容都有哪些&#xff1f;本文知识人网小编就相关问题进行详细阐述&#xff0c;并附带案例说明。 一、在国外做医学访问学者可以从事哪些工作&am…

智慧水务是什么样的?如何打造智慧水务大屏?

在信息化和数字化快速发展的今天&#xff0c;智慧水务作为城市供水管理的重要组成部分&#xff0c;正变得越来越重要。智慧水务大屏作为智慧水务系统的可视化核心&#xff0c;不仅提升了水务管理的效率&#xff0c;而且通过数据的实时监控和分析&#xff0c;为决策者提供了强有…

2024最新SSL证书在线申请系统源码 | 支持API接口 支持在线付费 二开优化版

内容目录 一、详细介绍二、效果展示1.部分代码2.效果图展示 三、学习资料下载 一、详细介绍 2024最新SSL证书在线申请系统源码 | 支持API接口 支持在线付费 二开优化版 最新SSL证书在线申请系统源码 | 支持API接口 SSL证书保证网络安全的基本保障。向您介绍我们的在线生成SSL…

电路中的过压(OVP)保护电路

硬件工程师常会遇到这种情况&#xff0c;比如芯片的工作电压是5V ,但供电电压因浪涌或静电造成电压会出现超过6.5V或更高&#xff0c;而芯片能承受最高工作电压6.3V&#xff0c;这时如果选用TVS&#xff08;ESD&#xff09;, TVS因为钳位电压VC超过6.5V &#xff0c;所以无法起…

C++ 模板详解——template<class T>

一. 前言 在我们学习C时&#xff0c;常会用到函数重载。而函数重载&#xff0c;通常会需要我们编写较为重复的代码&#xff0c;这就显得臃肿&#xff0c;且效率低下。重载的函数仅仅只是类型不同&#xff0c;代码的复用率比较低&#xff0c;只要有新类型出现时&#xff0c;就需…

2024统计建模:大数据与人工智能时代的统计研究

文章目录 题目解读你需要具备的知识点课题推荐视频分析 题目解读 主要做的是“大数据”与“人工智能”。 其中“大数据”所涉及的的第一个就是大量的数据&#xff0c;数据从哪里来&#xff1f;拿到数据后&#xff0c;我们需要做基本的数据分析&#xff0c;如何对大量的数据进…

图像处理技术与应用(一)

图像处理技术与应用入门 使用skimage进行图像读取和显示 skimage库&#xff08;Scikit-image&#xff09;提供了一个强大的工具集&#xff0c;用于执行各种图像处理任务。以下是如何使用skimage读取和显示图像的基本示例&#xff1a; from skimage import ioimg io.imread(…

Shopee日破8000单无货源大卖选品案例分享

选品是电商成功路上至关重要的一环&#xff0c;为了帮助虾皮商家更好地掌握选品技巧和打造爆款&#xff0c;在知虾当中涵盖了22项极具实用性的选品方法。本文以男士包类目&#xff0c;结合比较常用的热销跟卖法为例&#xff0c;介绍下如何通过核心指标及维度去落地选品。 分析…

AI人工智能培训老师叶梓:大数据治理的关键工具:开源数据血缘分析系统

在大数据时代&#xff0c;数据的产生和传播速度日益加快&#xff0c;数据之间的关系也变得日益复杂。为了更好地管理和理解数据之间的关系&#xff0c;数据血缘分析系统应运而生。本文将介绍几个开源的数据血缘分析系统&#xff0c;它们在数据治理、数据质量管理和数据隐私保护…

我宣布!软考真的是0基础小白的福音

大家为什么觉得有的证书是智商税呢&#xff1f;无非就是证书含金量达不到企业对于人才的选拔标准&#xff0c;或是满足不了自身的职业发展需要。 但是一方面大家又知道&#xff0c;含金量高且企业认可度高的证书&#xff0c;要么是价格太贵&#xff0c;要不就是考试难度大&…

个人音乐播放网站项目(SpringBoot+Linux部署上线)

在做完第一个博客系统项目以后&#xff0c;接着做下一个项目&#xff1a;音乐播放网站项目&#xff0c;此项目应用的技术栈和第一个项目是差不多的&#xff0c;即算是学完SSM等知识以后的两个入门级Java开发项目吧。 此项目包含的核心功能有&#xff1a; 一、登录、注册、退出…

知了汇智携手西科大举办“知了杯”网络安全趣味赛,共筑网络空间安全防线

为积极响应国家网络空间安全人才战略&#xff0c;加快攻防兼备网络创新人才培养步伐&#xff0c;实现以赛促学、以赛促教、以赛促用&#xff0c;推动网络空间安全人才培养和产学研用生态发展&#xff0c;成都知了汇智科技有限公司&#xff08;以下简称&#xff1a;知了汇智&…

随笔Ubuntu上的的一些使用

Ubuntu简易使用 常用指令 cdlsmkdirrf -rm 路径 换源 备份镜像 sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak编辑文件设置 sudo gedit /etc/apt/sources.list清华源 # 阿里源 deb http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe mul…

数据库轻松切换:解读Spring中的AbstractRoutingDataSource

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 数据库轻松切换&#xff1a;解读Spring中的AbstractRoutingDataSource 前言AbstractRoutingDataSource介绍作用和优势&#xff1a;作用&#xff1a;优势&#xff1a; 使用 AbstractRoutingDataSource …

鬼手剪辑如何导入剪映草稿箱?含工程文件

鬼手剪辑导入剪映功能介绍 功能概述 鬼手剪辑导入剪映功能可以让您将鬼手剪辑翻译、克隆和一键解说等作品的工程文件导入到剪映草稿&#xff0c;以便通过剪映进行精细化调整。 推荐使用场景 视频二创 视频语音翻译 短剧解说等作品的微调 导出的工程文件包含以下元素 视频…

windows10小皮安装不同版本composer,实现自由切换使用

1、使用phpstudy小皮面板安装composer1.8.5和composer2.5.8两个版本&#xff1b; 2、打开刚才安装的composer安装目录&#xff1a;D:\phpstudy_pro\Extensions 3、打开composer1.8.5版本&#xff0c;修改composer.bat名称为composer1.8.5.bat&#xff1a; 4、打开composer2.5.8…

uniapp制作多选下拉框和富文本(短信页面)

实例 多选下拉框实现 http://t.csdnimg.cn/TNmcF 富文本实现 http://t.csdnimg.cn/Ei1iV