1.动态SQL

1.1 什么是动态SQL?

  动态SQL就是通过传入的参数不一样,可以组成不同结构的SQL语句。 这种可以根据参数的条件而改变SQL结构的SQL语句,我们称为动态SQL语句。使用动态SQL可以提高代码重用性。

1.2 XML方式的实现

  1.2.1 需要使用到的标签

<if> 用于判断,类似java的if(){}
<foreach>一般用户批量处理的SQL语句,类似java的foreach循环,
<trim> :切割标签,主要用于切割关键字的头和尾的字符.新版的Mybatis使用的几率很少.
<set>:使用 set标签就是SQL语言的set关键字,可以在update 的时候set 关键字后面的,逗号可以自动忽略
<where>:使用where标签作为SQL语言的where关键字,好处如果where后面的条件都不成立,忽略where关键字.
<choose> <when> <otherwise> : java的swithc case
<sql> 用于声明公有的SQL语句块.,在操作标签中使用<include>调用 [一般不建议用]

不建议的原因,会导致代码难以维护。

  1.2.2 使用示例

  条件查询:(where)

public List<User> selectByCondition(User user);
<!-- 条件查询 -->
<select id="selectByCondition" parameterType="com.gjs.pojo.User" resultType="com.gjs.pojo.User">
select * from user
<where>
<!-- if标签:条件判断标签 -->
<if test="name!=null">
name = #{name}
<!--或者使用模糊查询: name like concat('%',#{name},'%') -->
</if>
<if test="age != null">
and age = #{age}
</if>
</where>
</select>

  修改:(set)

public int updateByNotNull(User user);
<update id="updateByNotNull" parameterType="com.gjs.pojo.User">
update user
<!-- set -->
<set>
<if test="name != null">name=#{name},</if>
<if test="password != null">password=#{password},</if>
<if test="age != null">age=#{age}</if>
</set>
where id=#{id}
</update>

  根据条件统计个数(trim )

public Long selectTotalByCondition(User user);
    <!-- 动态SQL语句trim标签
perfix : 动态sql语句的前缀 (WHERE,SET)
prefixOverrides : 自动截取掉或者替换条(WHERE 多余后面 关键字 :AND-OR)
-->
<select id="selectTotalByCondition" parameterType="com.gjs.pojo.User" resultType="long" >
select count(*) from user
<trim prefix="WHERE" prefixOverrides="AND|OR">
<if test="name!=null">
name like concat('%',#{name},'%')
</if>
<if test="age != null">
and age = #{age}
</if>
</trim>
</select>
<!-- set操作: <trim prefix="SET" suffixOverrides=","> -->

  批量删除:(foreach)

public int deleteByIds(@Param("ids")List<Integer> ids);
    <delete id="deleteByIds" parameterType="Integer">
delete from user where id in
<!-- 动态sql语句 foreach 循环标签
<foreach collection="" open="" close="" item="" separator=""></foreach>
collection : 要循环集合或者数组
open :开始位置符号 前小括号 (
close : 开始位置符号 后小括号 )
item : 每次循环的数据
separator : 分隔符 逗号 ,
-->
<foreach collection="ids" open="(" close=")" item="id" separator=",">
#{id}
</foreach>
</delete>

<sql>的使用:

     <sql id="condition_sql">
<where>
<if test="name !=null">
<!-- name like '%${name}%' -->
name like concat('%',#{name},'%')
</if>
<if test="age !=null">
and age = #{age}
</if>
</where>
</sql> <select id="selectByCondition" parameterType="com.gjs.pojo.User" resultType="com.gjs.pojo.User">
select * from user
<!-- 引入sql片段. refid :被引入sql片段的id -->
<include refid="condition_sql_by_trim"/
</select>

1.3 注解方式实现

动态sql除了支持xml方式以外,还是支持使用纯注解的方式
主要一下四个注解+对应动态sql语句的类文件

1.@SelectProvider 动态查询SQL语句对应注解
2.@InsertProvider 动态插入SQL语句对应注解
3.@UpdateProvider 动态修改SQL语句对应注解
4.@DeleteProvider 动态删除SQL语句对应注解

  

示例:
Usermapper

package com.gjs.mapper;

import java.util.List;

import org.apache.ibatis.annotations.DeleteProvider;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.SelectProvider;
import org.apache.ibatis.annotations.UpdateProvider; import com.gjs.pojo.User;
import com.gjs.pojo.UserProvider; public interface UserMapper { /*
* 条件查询
* type : 编写动态sql语句的类对应的字节码
* method : 编写动态sql语句类对应的方法名称
* 此方法返回的是一个String字符串,字符串就是用于注解方法查询的sql语句
*/
@SelectProvider(type= UserProvider.class,method="selectByCondition")
public List<User> selectByCondition(User user); //根据条件统计总数
@SelectProvider(type= UserProvider.class,method="selectTotalByCondition")
public Long selectTotalByCondition(User user); //修改
@UpdateProvider(type= UserProvider.class,method="updateByNotNull")
public int updateByNotNull(User user); //批量删除
@DeleteProvider(type=UserProvider.class,method="deleteByIds")
public int deleteByIds(@Param("ids")List<Integer> ids);
}

  UserProvider
  构建方法参数规则:
    1.非数组、集合的参数,调用方法是什么,构建SQL语句的方法就是什么
    2.是数组、集合的参数,构建的方法需要包一层Map。如:调用方法为:String[] ids ,构建方法格式为Map<String,String[]> ids

package com.gjs.pojo;

import java.util.List;

import org.apache.ibatis.annotations.Param;

public class UserProvider {

    public String selectByCondition(User user) {
StringBuilder sb = new StringBuilder();
sb.append("select * from user where 1=1 ");
if(user.getName()!=null) {
//由于最后字符串还是返回给Mybatis执行的所有这里需要使用OGNL表达式来获取对象属性的值
sb.append("and name like concat('%',#{name},'%') ");
}
if(user.getAge()!=null) {
sb.append("and age = #{age}");
}
return sb.toString();
} public String selectTotalByCondition(User user) {
StringBuilder sb = new StringBuilder();
sb.append("select count(1) from user where 1=1 ");
if(user.getName()!=null) {
sb.append("and name like concat('%',#{name},'%') ");
}
if(user.getAge()!=null) {
sb.append("and age = #{age}");
}
return sb.toString();
} public String updateByNotNull(User user) {
StringBuilder sb = new StringBuilder();
sb.append("update user set ");
if(user.getName()!=null) {
sb.append("name = #{name},");
}if(user.getPassword()!=null) {
sb.append("password = #{password},");
}if(user.getAge()!=null) {
sb.append("age = #{age},");
}
sb.deleteCharAt(sb.length()-1);//删除末尾多余的逗号","
sb.append("where id = #{id}");
return sb.toString();
} public String deleteByIds(@Param("ids")List<Integer> ids) {
StringBuilder sb = new StringBuilder();
sb.append("delete from user where id in(");
for (int i = 0; i < ids.size(); i++) {
sb.append("#{ids["+i+"]},");
}
sb.deleteCharAt(sb.length()-1);//删除末尾多余的逗号","
sb.append(")");
return sb.toString();
}
}

2.缓存

  在Mybatis里面,所谓的缓存就是将已经查询过的记录放在内存的缓冲区或文件上,这样如果再次查询,可以通过配置的策略,命中已经查询过的记录.从而提高查询的效率。
Mybatis的缓存分为一级缓存和二级缓存

  2.1 一级缓存

   所谓的一级缓存就是会话(SqlSesion对象)级别的缓存,就是同一个会话,如果已经查询过的数据会保存一份在内存中,如果会话没有关闭,再次调用同样的方法查询,不会再查询数据库,而是直接从缓存中取出之前查询的数据。一级缓存默认是打开的,而且是关闭不了的。
  以下几种情况一级缓存会被清空:
    1.关闭会话.close()
    2.进行了操作(增删改),提交了commit();
    3.手工清除缓存clearCache()

  2.2 二级缓存

二级缓存是 SqlSessionFactory级别,在整个应用都有效,可以在多个会话有效
MyBatis本身并没有实现二级缓存,二级缓存需要第三方缓存提供商的支持
Ehcache:
下载地址:https://github.com/mybatis/ehcache-cache/releases
学习地址:http://www.mybatis.org/ehcache-cache/

3.MyBatis的对象关系映射

  在实际开发中,一个业务可能涉及到多个数据表的查询,那么多表查询就涉及连接查询(等值连接), 等值连接 表与表之间有一个外键关键。
  但是程序中最终获取的表封装的对象, 对象与对象之间是没有外键关系的,对象和对象之间只有依赖关系

  对象之间关系主要是四种(什么关系应该看从哪个对象的角度)

一对一 关系
  一个人对应身份证号
一对多 关系
  一个部门对应多个员工
多对一 关系
  多个员工对应一个部门
多对多 关系
  多个学生对应多个老师,多个学生对应多个课程

  MyBatis框架支持多表查询封装对象之间关系:
    <collection>标签: 一对多查询
    <association>标签:多对一和一对一查询
  注:<collection>和<association>为<resultMap>的子标签

3.1 多对一查询(<association>联合查询标签)(N+1)

  例:以员工为中心来查询关联部门(多对一关系,多个员工对应一个部门)

数据库表

员工表
CREATE TABLE `employee` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
`dept_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
部门表
CREATE TABLE `department` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

部门类

package com.gjs.pojo;

public class Department {
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Department(Integer id, String name) {
super();
this.id = id;
this.name = name;
}
public Department() {
super();
}
@Override
public String toString() {
return "Department [id=" + id + ", name=" + name + "]";
} }

员工类(Employee)

package com.gjs.pojo;

public class Employee {
private Integer id;
private String name; //以员工为中心来关联部门,多对一关系,多个员工对应一个部门 : many2one
private Department dept; public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Department getDept() {
return dept;
}
public void setDept(Department dept) {
this.dept = dept;
}
@Override
public String toString() {
return "Employee [id=" + id + ", name=" + name + ", dept=" + dept + "]";
}
}

接口

package com.gjs.mapper;

import com.gjs.pojo.Employee;

public interface Many2OneMapper {
/**
* 根据与员工的编码查询出员工对应的所有信息(包含部门)
* @param id
* @return
*/
public Employee selectByEmpId(Integer id);
}

接口映射配置文件

<?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">
<!-- 配置映射
namespace : 命名空间(通俗说法: 给当前映射文件的唯一标识:起一个唯一的名字)
-->
<mapper namespace="com.gjs.mapper.Many2OneMapper"> <!-- 由于查询出来的结果于com.gjs.pojo.Employee中的属性不一致(Department dept),所以不能自动映射 -->
<select id="selectByEmpId" resultMap="emp_map" parameterType="int">
select * from employee where id = #{id}
</select> <resultMap type="com.gjs.pojo.Employee" id="emp_map">
<id property="id" column="id"/>
<result property="name" column="name"/>
<!--
问题:private Department dept; 对象如何映射?
解决方案: 使用联合查询标签
<association property="" column="" select=""></association>
property : 需要映射额属性 dept
column : 已知的部门外检列 dept_id
select : 调用查询通过部门id查询出对应部门对象的功能的id
值规则 : 映射文件的命名空间 + 点儿(.) + 功能id
如果是在同一个命名空间,可以省略命名空间和点
-->
<association property="dept" column="dept_id" select="selectByDeptId"/>
</resultMap> <select id="selectByDeptId" parameterType="int" resultType="com.gjs.pojo.Department">
select * from department where id = #{id}
</select>
</mapper>

3.2 多对一查询(<collection>集合映射标签)(N+1)

例:以部门为中心查询部门的所有信息(包括员工),一个部门对应多个员工
部门类

package com.gjs.pojo;

import java.util.List;

public class Department {
private Integer id;
private String name;
//以部门为中心查询部门的所有信息(包含员工)
//一个部门有多个员工 一对多关系:one2many
// 部门的元员工使用 list集合包装
private List<Employee> emps; public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Employee> getEmps() {
return emps;
}
public void setEmps(List<Employee> emps) {
this.emps = emps;
}
public Department(Integer id, String name) {
super();
this.id = id;
this.name = name;
}
public Department() {
super();
}
@Override
public String toString() {
return "Department [id=" + id + ", name=" + name + ", emps=" + emps + "]";
} }

员工类

package com.gjs.pojo;

public class Employee {
private Integer id;
private String name; public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
} @Override
public String toString() {
return "Employee [id=" + id + ", name=" + name + "]";
} }

接口

package com.gjs.mapper;

import com.gjs.pojo.Department;

public interface One2ManyMapper {
/**
* 根据与部门的编码查询出部门对应的所有信息(包含所有员工)
* @param id
* @return
*/
public Department selectByDeptId(Integer id);
}

接口对应的映射配置文件

<?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">
<!-- 配置映射
namespace : 命名空间(通俗说法: 给当前映射文件的唯一标识:起一个唯一的名字)
-->
<mapper namespace="com.gjs.mapper.One2ManyMapper"> <select id="selectByDeptId" parameterType="int" resultMap="dept_map">
select * from department where id = #{id}
</select> <resultMap type="com.gjs.pojo.Department" id="dept_map">
<id property="id" column="id"/>
<result property="name" column="name"/>
<!--
问题 :List<Employee> emps; 集合如何映射?
解决方案: 使用 <collection>集合映射
<collection property="" column="" select=""/>
property :需要映射的属性 emps 对应的list集合
column : 部门本身的主键 id
select : 关联查询的功能id
规则 : 命名空间+点+功能id。 如果同一个命名空间下面直接 功能id即可
-->
<collection property="emps" column="id" select="selectEmpsByDeptId"/>
</resultMap> <select id="selectEmpsByDeptId" resultType="com.gjs.pojo.Employee" parameterType="int">
select * from employee where id = #{dept_id}
</select>
</mapper>

3.3 等值连接方式查询

  以上都是用N+1的方式。MyBatis的对象关系映射还有一种等值连接方式。
  以一对多为例:

pojo类和接口皆与3.2的相同

映射配置文件:

<?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">
<!-- 配置映射
namespace : 命名空间(通俗说法: 给当前映射文件的唯一标识:起一个唯一的名字)
-->
<mapper namespace="com.gjs.mapper.One2ManyMapper"> <select id="selectByDeptId" parameterType="int" resultMap="dept_map">
select e.id e_id ,e.name e_name,d.id d_id,d.name d_name
from department d JOIN employee e ON d.id = e.dept_id WHERE d.id = #{id};
</select> <resultMap type="com.gjs.pojo.Department" id="dept_map">
<id property="id" column="d_id"/>
<result property="name" column="d_name"/>
<!--
<collection property="emps" ofType="">
在标签内部属性进行手动映射
</collection>
property : 要映射的属性
ofType: 要映射集合泛型的类型 -->
<collection property="emps" ofType="com.gjs.pojo.Employee">
<id property="id" column="e_id"/>
<result property="name" column="e_name"/>
</collection>
</resultMap>
</mapper>

4.MyBatis的逆向工程

  MyBatis的逆向工程能自动帮开发者生成数据库表对应的 pojo实体文件,自动生成映射文件
  自定生成表的各种(CRUD)的sql语句, 但是只能做单表操作,联合查询还得开发者自己编写

  4.1逆向工程的插件安装步骤

    使用逆向工程得先在Eclipse安装逆向工程的插件

   

  

  

   

  判断是否安装成功

  

  4.2 逆向工程创建步骤

    4.2.1.新建一个项目,导入mybatis.jar包和数据库驱动包

    4.2.2 创建生成配置文件

  generatorConfig.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="context1">
<!-- 注释构建 -->
<commentGenerator>
<!-- 去掉所有的注释 -->
<property name="suppressAllComments" value="true"/>
<property name="suppressDate" value="true"/>
</commentGenerator> <!-- 数据库四要素 -->
<jdbcConnection connectionURL="jdbc:mysql://localhost:3306/mybatis"
driverClass="com.mysql.jdbc.Driver"
userId="root"
password=""/>
<!-- 实体类 : pojo
targetPackage : 实体类生成后存放的包
targetProject : 存放的目录一般都放在 src下面
-->
<javaModelGenerator targetPackage="com.gjs.pojo" targetProject="mybatis-generator/src" />
<!-- 映射文件 -->
<sqlMapGenerator targetPackage="com.gjs.mapper" targetProject="mybatis-generator/src" />
<!-- 操作接口
type 生成映射的形式
ANNOTATEDMAPPER : 纯注解的,没有xml映射
XMLMAPPER : 生成的有xml映射文件
-->
<javaClientGenerator targetPackage="com.gjs.mapper" targetProject="mybatis-generator/src" type="XMLMAPPER" /> <!-- 要生成对应表的配置
tableName : 数据库表名
//如果下面全部是true,mybatis直接可以使用纯面向对象开发
enableCountByExample : 是否生成查询总数的 Example
enableDeleteByExample : 是否生成删除的 Example
enableSelectByExample : 是否生成查询集合的 Example
enableUpdateByExample : 是否生成修改的 Example
-->
<table tableName="user" enableCountByExample="false" enableDeleteByExample="false" enableSelectByExample="true" enableUpdateByExample="false"></table>
<table tableName="employee" enableCountByExample="false" enableDeleteByExample="false" enableSelectByExample="true" enableUpdateByExample="false"></table>
<table tableName="department" enableCountByExample="false" enableDeleteByExample="false" enableSelectByExample="true" enableUpdateByExample="false"></table>
</context>
</generatorConfiguration>

主要是数据库四要素、实体类、映射文件、操作接口的配置视情况进行修改

    4.2.3 开始逆向工程

  选中generatorConfig.xml文件右击运行

  

  4.3.逆向功能的缺点

  逆向功能不能逆向多表操作,只能逆向单表操作,多表之间有外键对应java关联关系没办法映射,需要开发者手动编写对应代码。

深入学习Mybatis框架(二)- 进阶的更多相关文章

  1. 学习mybatis框架>从零开始学JAVA

    目录 学习mybatis框架 mybatis框架的介绍 什么是mybatis框架 为什么要使用mybatis框架 mybatis的优点 Mybatis框架环境搭建 去官网下载jar包 创建一个普通的j ...

  2. 深入学习Mybatis框架(一)- 入门

    1.什么是Mybatis? Mybatis是一个优秀持久层框架,提供了对数据库的一系列操作(增删改查).Mybatis可以避免重复的写JDBC代码,让我们以较少的代码实现对数据库的操作,从而提高开发效 ...

  3. 快速学习mybatis框架

    一.介绍Mybatis(主要从以下两点进行介绍) 1.MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动 ...

  4. Mybatis框架二:增删改查

    这里是搭建框架和准备数据: http://www.cnblogs.com/xuyiqing/p/8600888.html 实现增删改查功能: 测试类: package junit; import ja ...

  5. 后端框架的学习----mybatis框架(3、配置解析)

    3.配置解析 1.核心配置文件 2.环境配置(environment) 3.属性(properties) 可以通过properties属性来实现引用配置文件 这些属性可以在外部进行配置,并可以进行动态 ...

  6. 后端框架的学习----mybatis框架(5、分页)

    七.分页 简单使用 1.在要使用Log4j的类中,导入包import org.apache.log4j.Logger; 2.日志对象,参数为当前类的class static Logger logger ...

  7. 学习mybatis框架时配置xml文件解决select莫名其妙报错问题

    遇到这种情况,如果语法没有错误,那就可能是你的eclipse在耍你!!! 怎么弄呢,重新建立一个文件,把原来的代码复制到新的文件中就ok啦!不用谢我,我叫雷锋

  8. 后端框架的学习----mybatis框架(9、多对一处理和一对多处理)

    9.多对一处理和一对多处理 #多对一 <!--按照结果集嵌套查询--> <select id="getAllStudent1" resultMap="S ...

  9. 后端框架的学习----mybatis框架(8、lombok)

    8.lombok #测试环境搭建 1.导入lombok 2.新建实体类Teacher,Student 3.建立mapper接口 4.建立mapper.xml文件 5.在核心配置文件中绑定注册mappe ...

随机推荐

  1. windows下安装redis数据库

    第一步: 下载windows版本的Redis:https://github.com/MSOpenTech/redis/releases 这里我下载的是msi安装程序版: 安装时会让你指定Redis使用 ...

  2. TTL 传输中过期,内部网络环路

    ping目标地址的时候,如果不是显示超时,而是很快出现TTL 传输中过期,很可能情况是内部网络出现环路 tracert一下目标地址,如果路由不断重复,说明是环路

  3. something about gdb

    1 gdb 基础命令 b(break):    添加断点 r(run):  重头开始运行程序 n(next): 下一步 c(continue):  程序继续运行,直到下一处断点,或者程序运行到结束 q ...

  4. 使用Postman如何做接口自动化测试

    师从‘百测’! 一.简介 Postman是一款非常流行的API调试工具,很多攻城狮都应该用过,或听说过,这里不做过多介绍. 官方网站:http://www.getpostman.com 二.接口自动化 ...

  5. 随便写的Gost安装脚本,作用你懂的,目前只支持CentOS,可以在Aliyun ECS中使用

    服务器 执行下面命令: curl -L aux.pub/gost | bash 或者: curl -L https://gist.githubusercontent.com/inrg/03da1ded ...

  6. 【图像处理与医学图像处理】YUV与RGB格式转换速度几种方法对比

    [视频处理]YUV与RGB格式转换 YUV格式具有亮度信息和色彩信息分离的特点,但大多数图像处理操作都是基于RGB格式. 因此当要对图像进行后期处理显示时,需要把YUV格式转换成RGB格式. RGB与 ...

  7. 蓝鲸智云安装proxy和p-agent过程记录

    1.agent_setup_pro.sh: no such file or directory 2.参考:https://bk.tencent.com/s-mart/community/questio ...

  8. 论文阅读 | Adversarial Example Generation with Syntactically Controlled Paraphrase Networks

    [pdf] [code] 句法控制释义网络 SCPNS  生成对抗样本 我们提出了句法控制意译网络(SCPNs),并利用它们来生成对抗性的例子.给定一个句子和一个目标语法形式(例如,一个选区解析),s ...

  9. 阅读《C Primer Plus》收获

    190927 知识内容: 1.了解到C语言混乱代码大赛.评选谁的程序最有创意但又让人难以理解 2.了解最初的c语言的规则,所有编译器依照规则而设计. 3.编程前先要确定好目标对象,并且在纸上大概写出流 ...

  10. Python之Web前端Ajax

    Ajax: 对于WEB应用程序:用户浏览器发送请求,服务器接收并处理请求,然后返回结果,往往返回就是字符串(HTML),浏览器将字符串(HTML)渲染并显示浏览器上. 1.传统的Web应用 一个简单操 ...