avatar

和我一起学MyBatis

简单介绍

  1. 什么是 MyBatis?

    MyBatis是一个数据持久层(ORM)框架。把实体类和SQL语句之间建立了映射关系,是一种半自动化的ORM实现。

    MyBatis 的主要思想是将程序中的大量 SQL 语句抽取出来,配置在配置文件中,以实现 SQL 的灵活配置。

    ORM又是什么?

    ORM(Object/Relational Mapping):对象关系映射,它完成面向对象的编程语言到关系数据库的映射。它的作用是把持久化对象的保存、修改、删除等操作,转换成对数据库的操作。

    ORM 基本映射关系:

    • 数据表映射类
    • 数据表的行映射对象(实例)
    • 数据表的列(字段)映射对象的属性
  2. MyBatis 的三层功能架构

    • API接口层:提供给外部使用的接口 API,通过本地 API 来操纵数据库。接口层一但接收到调用请求就会调用数据处理层来完成具体的数据处理。
    • 数据处理层:负责具体的 SQL 查找、SQL 解析、SQL 执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。
    • 基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件,为上层的数据处理层提供最基础的支撑
  3. MyBatis的优点

    • 简单小巧灵活易于上手,方便浏览修改 SQL 语句
    • 解除 SQL 与程序代码的耦合
    • 提供对象关系映射标签,支持对象与数据库的 ORM 字段关系映射
    • 提供 xml 标签,支持编写动态 SQL
  4. MyBatis 与 JDBC 的区别

    Mybatis 是基于 JDBC 的。Java 与数据库操作仅能通过 JDBC 完成。 Mybatis 也要通过 JDBC 完成数据查询、更新这些动作。Mybatis 仅仅是在 JDBC 基础上做了,OO 化、封装事务管理接口这些东西。

入门程序

  1. 数据库准备,新建名为mybatis的数据库,建数据库表

  2. 程序生成器(修改generatorConfig.xml→选择lib文件夹,Shift键加鼠标右键进入li目录下,执行脚本java -jar mybatis-generator-core-1.3.2.jar -configfile generatorConfig.xml -overwrite)

    generatorConfig.xml文件:

    image-20200617170915673

    执行后生成的目录结果:

  3. 打开eclipse,新建名为Mybatis的项目,在src里新建三个包(如图所示),包名要和上图generatorConfig.xml文件的一样。

    image-20200617214801053

  4. 把生成的文件导入相应位置

    image-20200617225603859

    • 实体类:在包 com.dlnu.Mybatis.pojol 下放入类 Customer.java , 一个用户具有:id、username、jobs、phone四个属性。作为 mybatis 进行 sql 映射使用,与数据库表对应。

      代码如下:

    package com.dlnu.Mybatis.pojo;

    public class Customer {
    private Integer id;

    private String username;

    private String jobs;

    private String phone;

    // 补充说明,自动生成的程序是没有构造函数的,需要的需自己添加
    public Customer(String username, String jobs, String phone) {
    super();
    this.username = username;
    this.jobs = jobs;
    this.phone = phone;
    }

    public Customer(Integer id, String username, String jobs, String phone) {
    super();
    this.id = id;
    this.username = username;
    this.jobs = jobs;
    this.phone = phone;
    }

    public Customer() {
    super();
    }

    // 打印对象,也需要自己添加
    @Override
    public String toString() {
    return "Customer [Id=" + id + ", username=" + username + ", jobs=" + jobs + ", phone=" + phone + "]";
    }

    public Integer getId() {
    return id;
    }

    public void setId(Integer id) {
    this.id = id;
    }

    public String getUsername() {
    return username;
    }

    public void setUsername(String username) {
    this.username = username == null ? null : username.trim();
    }

    public String getJobs() {
    return jobs;
    }

    public void setJobs(String jobs) {
    this.jobs = jobs == null ? null : jobs.trim();
    }

    public String getPhone() {
    return phone;
    }

    public void setPhone(String phone) {
    this.phone = phone == null ? null : phone.trim();
    }
    }
    • DAO层:新建包 com.dlnu.Mybatis.mapper ,并在包下放入方法接口 CustomerMapper.java ,提供简单的增删改查数据和mybatis分页操作。

      代码如下:

    package com.dlnu.Mybatis.dao;

    import java.util.List;

    import org.apache.ibatis.annotations.Param;

    import com.dlnu.Mybatis.pojo.Customer;

    public interface CustomerMapper {

    // 删除客户
    int delete(Integer id);

    // 新增客户
    int insert(Customer cust);

    // 查询所有客户信息
    List<Customer> query();

    // 更新某客户信息
    int update(Customer cust);

    // 分页查询员工
    List<Customer> queryByPage(@Param("start") int start, @Param("pageSize") int pageSize);

    }
    • 映射文件:在包 com.dlnu.Mybatis.mapper 下放入映射文件 CustomerMapper.xml` ,用来定义各种 SQL 语句和这些语句的参数,以及要返回的类型等。CustomerMapper.xml写好后,需要在mybatis-config.xml文件加载它

      CustomerMapper.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">
    <!-- namespace属性是名称空间,必须唯一 -->
    <mapper namespace="com.dlnu.Mybatis.dao.CustomerMapper">

    <!-- resultMap标签:映射实体与表
    type属性:表示实体全路径名
    id属性:为实体与表的映射取一个任意的唯一的名字
    -->
    <resultMap id="BaseResultMap" type="com.dlnu.Mybatis.pojo.Customer">
    <!-- id标签:映射主键属性
    result标签:映射非主键属性
    property属性:实体的属性名
    column属性:表的字段名
    -->
    <id column="id" jdbcType="INTEGER" property="id" />
    <result column="username" jdbcType="VARCHAR" property="username" />
    <result column="jobs" jdbcType="VARCHAR" property="jobs" />
    <result column="phone" jdbcType="VARCHAR" property="phone" />
    </resultMap>

    <!-- 定义 SQL 语句,其中 id 需要和接口中的方法名一致 -->
    <!-- default:数据库中我设置id自增,所以需要默认 -->
    <!-- parameterType 指明查询时使用的参数类型,resultType 指明查询返回的结果集类型 -->
    <!-- 新增客户 -->
    <insert id="insert" parameterType="Customer">
    insert into customer (id, username, jobs, phone)
    values (default, #{username,jdbcType=VARCHAR}, #{jobs,jdbcType=VARCHAR},
    #{phone,jdbcType=VARCHAR})
    </insert>

    <!--根据id更新客户-->
    <update id="update" parameterType="Customer">
    update customer
    set username = #{username,jdbcType=VARCHAR},
    jobs = #{jobs,jdbcType=VARCHAR},
    phone = #{phone,jdbcType=VARCHAR}
    where id = #{id,jdbcType=INTEGER}
    </update>

    <!-- 查询所有客户 -->
    <select id="query" resultMap="BaseResultMap" >
    select * from customer
    </select>

    <!-- 根据id删除客户 -->
    <delete id="delete" parameterType="java.lang.Integer">
    delete from customer
    where id = #{id,jdbcType=INTEGER}
    </delete>

    <!-- 分页查询客户 -->
    <select id="queryByPage" resultMap="BaseResultMap">
    SELECT *
    FROM customer
    LIMIT #{start},#{pageSize}
    </select>
    </mapper>
  5. 导入jar包(目录如下)

    image-20200617230246519

  6. 配置文件

    mybatis-config.xml,用来配置 Mybatis 的运行环境、数据源、事务等。

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE configuration
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
    <!-- 引用外部配置文件路径 -->
    <properties resource="db_config.properties"></properties>

    <!-- 实体类的别名 -->
    <typeAliases>
    <typeAlias type="com.dlnu.Mybatis.pojo.Customer" alias="Customer"/>
    </typeAliases>

    <!-- 环境配置 -->
    <environments default="development">
    <environment id="development">
    <!--type="JDBC"表示使用JDBC的提交和回滚-->
    <transactionManager type="JDBC" />

    <!--type="POOLED"表示支持JDBC数据库连接池 -->
    <dataSource type="POOLED">
    <property name="driver" value="${driver}" />
    <property name="url" value="${url}"/>
    <property name="username" value="${username}" />
    <property name="password" value="${password}" />
    </dataSource>
    </environment>
    </environments>

    <!--映射文件路径 -->
    <mappers>
    <mapper resource="com/dlnu/Mybatis/mapper/CustomerMapper.xml" />
    </mappers>

    </configuration>

    db_config.properties,用于存放数据库连接参数

    driver=com.mysql.jdbc.Driver
    url=jdbc:mysql://127.0.0.1:3306/mybatis
    username=root
    password=123456

    log4j.properties,日志记录文件方便查看控制台输出的 SQL 语句

    log4j.rootLogger=INFO, stdout  
    log4j.logger.com.abc.mapper=DEBUG
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

    log4j.category.org.springframework = OFF
  7. 测试。在包 com.dlnu.mybatis.test 下新建测试类 TestCustomer.java , 用来测试数据的增删改查操作和MyBatis分页。

    Mybatis工作流程

    • 通过InputStream对象读取Mybatis映射文件
    • 通过SqlSessionFactoryBuilder对象创建SqlSessionFactory对象
    • 获取当前线程的SQLSession
    • 事务默认开启
    • 获得Mapper 对象
    • 调用接口方法(insert,delete,update,select)
    • 提交事务关闭资源

    TestCustomer.java:

    package com.dlnu.test;

    import java.io.IOException;
    import java.io.InputStream;
    import java.util.List;

    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    import org.junit.Test;

    import com.dlnu.Mybatis.dao.CustomerMapper;
    import com.dlnu.Mybatis.pojo.Customer;

    public class TestCustomer {

    // Mybatis 配置文件
    private String fileName = "mybatis-config.xml";

    // 新增客户
    @Test
    public void testInsert() throws IOException {
    // 读取配置文件
    InputStream is = Resources.getResourceAsStream(fileName);
    // 获得一个链接工厂对象 用于产生数据库连接
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);

    // 获得数据库连接Session对象 (相当于Connection)
    SqlSession session = factory.openSession();

    // 获得dao对象
    CustomerMapper mapper = session.getMapper(CustomerMapper.class);

    // 插入数据
    Customer cust = new Customer("joy", "经理", "18269151234");
    mapper.insert(cust);

    // 事务需要手动提交
    session.commit();
    // 关闭连接
    session.close();

    }

    // 更新客户信息
    @Test
    public void testUpdate() throws IOException {
    // 读取配置文件
    InputStream is = Resources.getResourceAsStream(fileName);
    // 获得一个链接工厂对象 用于产生数据库连接
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
    // 获得数据库连接Session对象 (相当于Connection)
    SqlSession session = factory.openSession();
    // 获得dao对象
    CustomerMapper mapper = session.getMapper(CustomerMapper.class);
    // 插入更新数据
    Customer cust = new Customer(1, "lili", "Hr", "17456421562");
    mapper.update(cust);
    // 事务需要手动提交
    session.commit();
    // 关闭连接
    session.close();

    }

    // 删除客户
    @Test
    public void testDelete() throws IOException {
    // 读取配置文件
    InputStream is = Resources.getResourceAsStream(fileName);
    // 获得一个链接工厂对象 用于产生数据库连接
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
    // 获得数据库连接Session对象 (相当于Connection)
    SqlSession session = factory.openSession();
    // 获得dao对象
    CustomerMapper mapper = session.getMapper(CustomerMapper.class);
    // 执行删除操作
    mapper.delete(3);

    // 事务需要手动提交
    session.commit();
    // 关闭连接
    session.close();

    }

    // 查询所有的客户信息
    @Test
    public void testQuery() throws IOException {
    // 读取配置文件
    InputStream is = Resources.getResourceAsStream(fileName);
    // 获得一个链接工厂对象 用于产生数据库连接
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
    // 获得数据库连接Session对象 (相当于Connection)
    SqlSession session = factory.openSession();
    // 获得dao对象
    CustomerMapper mapper = session.getMapper(CustomerMapper.class);
    // 执行查询操作
    List<Customer> list = mapper.query();

    System.out.println("查询所有客户结果:");
    for (Customer cust : list) {
    System.out.println(cust);
    }

    // 关闭连接
    session.close();

    }

    @Test
    public void testQueryByPage() throws IOException {
    // 读取配置文件
    InputStream is = Resources.getResourceAsStream(fileName);
    // 获得一个链接工厂对象 用于产生数据库连接
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
    // 获得数据库连接Session对象 (相当于Connection)
    SqlSession session = factory.openSession();
    // 获得dao对象
    CustomerMapper mapper = session.getMapper(CustomerMapper.class);
    // 执行查询操作
    List<Customer> list = mapper.queryByPage(0, 3);

    System.out.println("分页结果:");
    for (Customer customer : list) {
    System.out.println(customer);
    }
    }
    }

​ 测试成功!可以在数据库查看信息了。这也算是入门成功了!

配置文件

mybatis-config.xml的内容:

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 引用外部配置文件路径 -->
<properties resource="db_config.properties"></properties>

<!-- 实体类的别名,将包内的 Java 类的类名作为类的类别名-->
<typeAliases>
<typeAlias type="com.dlnu.Mybatis.pojo.Customer" alias="Custmer"/>
</typeAliases>

<!-- 环境配置 -->
<environments default="development">
<environment id="development">
<!--type="JDBC"表示使用JDBC的提交和回滚-->
<transactionManager type="JDBC" />

<!--type="POOLED"表示支持JDBC数据库连接池 -->
<!-- 数据库连接池由MyBatis管理,数据库名是mybatis -->
<dataSource type="POOLED">
<property name="driver" value="${driver}" />
<property name="url" value="${url}"/>
<property name="username" value="${username}" />
<property name="password" value="${password}" />
</dataSource>
</environment>
</environments>

<!--映射文件路径 -->
<!-- 通过 mapper 接口包加载整个包的映射文件 -->
<mappers>
<mapper resource="com/dlnu/Mybatis/mapper/CustomerMapper.xml" />
</mappers>

</configuration>

![*](file:///C:\Users\Administrator\AppData\Local\Temp\artDD7.tmp)MyBatis 配置文件的 configuration 标签主要包括:

  • configuration 配置
  • properties 配置文件中属性值
  • settings 修改 MyBat is 在运行时的行为方式
  • typeAliases 为 Java 类型命名一个短名字
  • typeHandlers 类型处理器
  • objectFactory 对象工厂
  • plugins 插件
  • environments 环境
    • environment 环境变量
    • transactionManager 事务管理器
  • databaseIdProvider 数据库厂商标识
  • mappers 映射器

下面来详细介绍在程序中出现的属性:

  1. properties用于引用外部配置文件路径, properties 元素中的 resource 属性读取类路径下属性文件或根据 url 属性指定的路径读取属性文件。

  2. typeAliases 元素,与XML 配置文件相关联,减少输入多余的完整类名

<typeAliases>
<typeAlias type="com.dlnu.Mybatis.pojo.Customer" alias="Custmer"/>
</typeAliases>

在映射文件中 parameterTyperesultType 就可以直接把长长的"com.dlnu.Mybatis.pojo.Customer"写成“Customer”

  1. environments 环境,是对数据源的配置。MyBatis 能够配置多套运行环境,这有助于将您的SQL 映射到多个数据库上。

    • transactionManager:事务管理器,

      MyBatis 中的两种事务管理器,即 type="[JDBC|MANAGED]":

      • JDBC:直接使用 JDBC 的提交和回滚设置
      • MANAGED:让容器来管理事务的整个生命周期
    • dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。MyBatis 三种內建的数据源类型,即 type="[UNPOOLED|POOLED|JNDI]"。

      • "POOLED"表示支持JDBC数据库连接池
      • “UNPOOLED”表示不支持 JDBC 数据源连接池,在配置该数据源类型后,在每次被请求时打开和关闭连接。
      • “JNDI”支持外部数据源连接池,可以在EJB 或应用服务器等容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。其包含的属性:
        • initial_context:用来在 InitialContext 中寻找上下文
        • data_source:表示引用数据源实例位置的上下文的路径
  2. mapper映射器, 用于引用已经定义好的映射文件,告诉 MyBatis 去哪寻找映射 SQL 的语句。

映射文件

映射文件是所有 SQL 语句放置的地方,写好 SQL 语句映射文件后,需要在配置文件的 mappers 标签中引用。

在映射文件中,元素是映射文件的根元素,其他元素都是它的子元素。如下图所示:

![](C:\Users\Administrator\Desktop\新建文件夹\mapper .png)

  1. select

    select元素用来映射查询语句,它可以帮助我们从数据库中读取出数据,并组装数据给业务开发人员。

    示例:

    <!--查询所有数据,返回值类型是List<Customer>的,只要写集合中的类型就行了-->
    <select id="query" resultMap="BaseResultMap" >
    select * from customer
    </select>

    我们查询出来的结果是多个对象了,所以我们使用的是以下方法:

    public void testQuery() throws IOException {
    // 读取配置文件
    InputStream is = Resources.getResourceAsStream(fileName);
    // 获得一个链接工厂对象 用于产生数据库连接
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
    // 获得数据库连接Session对象 (相当于Connection)
    SqlSession session = factory.openSession();
    // 获得dao对象
    CustomerMapper mapper = session.getMapper(CustomerMapper.class);
    // 执行查询操作
    List<Customer> list = mapper.query();

    for (Customer cust : list) {
    System.out.println(cust);
    }

    // 关闭连接
    session.close();

    }
  2. 元素用于映射插入语句,在执行完元素中定义的SQL
    语句后,会返回一个表示插入记录数的整数。

    示例:

    <insert id="insert" parameterType="Customer">
    insert into customer (id, username, jobs, phone)
    values (default, #{username,jdbcType=VARCHAR}, #{jobs,jdbcType=VARCHAR},
    #{phone,jdbcType=VARCHAR})
    </insert>

    调用方法:

    public void testInsert() throws IOException {
    // 读取配置文件
    InputStream is = Resources.getResourceAsStream(fileName);
    // 获得一个链接工厂对象 用于产生数据库连接
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);

    // 获得数据库连接Session对象 (相当于Connection)
    SqlSession session = factory.openSession();

    // 获得dao对象
    CustomerMapper mapper = session.getMapper(CustomerMapper.class);

    // 插入数据
    Customer cust = new Customer("joy", "经理", "18269151234");
    mapper.insert(cust);

    // 事务需要手动提交
    session.commit();
    // 关闭连接
    session.close();

    }
  3. 示例:

<!--根据id更新-->
<update id="update" parameterType="Customer">
update customer
set username = #{username,jdbcType=VARCHAR},
jobs = #{jobs,jdbcType=VARCHAR},
phone = #{phone,jdbcType=VARCHAR}
where id = #{id,jdbcType=INTEGER}
</update>

​ 调用方法:

public void testUpdate() throws IOException {
// 读取配置文件
InputStream is = Resources.getResourceAsStream(fileName);
// 获得一个链接工厂对象 用于产生数据库连接
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
// 获得数据库连接Session对象 (相当于Connection)
SqlSession session = factory.openSession();
// 获得dao对象
CustomerMapper mapper = session.getMapper(CustomerMapper.class);
// 插入数据
Customer cust = new Customer(1, "lili", "Hr", "17456421562");
mapper.update(cust);
// 事务需要手动提交
session.commit();
// 关闭连接
session.close();

}
<!--根据id删除-->
<delete id="delete" parameterType="java.lang.Integer">
delete from customer
where id = #{id,jdbcType=INTEGER}
</delete>

​ 根据id删除信息:

public void testDelete() throws IOException {
// 读取配置文件
InputStream is = Resources.getResourceAsStream(fileName);
// 获得一个链接工厂对象 用于产生数据库连接
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
// 获得数据库连接Session对象 (相当于Connection)
SqlSession session = factory.openSession();
// 获得dao对象
CustomerMapper mapper = session.getMapper(CustomerMapper.class);
// 执行删除操作
mapper.delete(3);

// 事务需要手动提交
session.commit();
// 关闭连接
session.close();

}

关系映射

MyBatis 的关联映射可以大大简化持久层数据的访问,关联关系的分类如下:

  • 一对一:一个学号只属于一个学生,一个学生也只能有一个学号
  • 一对多:一个班级有多个学生,一个学生只属于一个班级
  • 多对多:一个学生可以选多门课,一门课可以有多个学生选
  1. 一对一

    • 设计数据库表

      CREATE TABLE card (
      c_id int(11) primary key,
      c_num varchar(50)
      );

      CREATE TABLE student (
      s_id int(11) PRIMARY KEY ,
      s_name varchar(20) ,
      s_cid int(11) ,
      CONSTRAINT student_fk FOREIGN KEY (s_cid) REFERENCES card (c_id)
      );

      insert into card(c_id,c_num) values (1,'10086'),(2,'10010'),(3,'10000');

      insert into `student(s_id,s_name,s_cid) values (1,'sam',1),(2,'tom',2),(3,'zhangsan',3);
    • 导入jar包(目录如下)

      image-20200617230246519

    • 配置文件

      mybatis-config.xml,用来配置 Mybatis 的运行环境、数据源、事务等。

      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE configuration
      PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
      "http://mybatis.org/dtd/mybatis-3-config.dtd">
      <configuration>
      <!-- 引用外部配置文件路径 -->
      <properties resource="db_config.properties"></properties>

      <!-- 实体类的别名,将包内的 Java 类的类名作为类的类别名-->
      <typeAliases>
      <typeAlias type="com.dlnu.humen.pojo.Card" alias="Card"/>
      <typeAlias type="com.dlnu.humen.pojo.Student" alias="Student"/>
      </typeAliases>

      <!-- 环境配置 -->
      <environments default="development">
      <environment id="development">
      <!--type="JDBC"表示使用JDBC的提交和回滚-->
      <transactionManager type="JDBC" />

      <!--type="POOLED"表示支持JDBC数据库连接池 -->
      <!-- 数据库连接池由MyBatis管理,数据库名是mybatis -->
      <dataSource type="POOLED">
      <property name="driver" value="${driver}" />
      <property name="url" value="${url}"/>
      <property name="username" value="${username}" />
      <property name="password" value="${password}" />
      </dataSource>
      </environment>
      </environments>

      <!--映射文件路径 -->
      <!-- 通过 mapper 接口包加载整个包的映射文件 -->
      <mappers>
      <mapper resource="com/dlnu/humen/mapper/CardMapper.xml" />
      <mapper resource="com/dlnu/humen/mapper/StudentMapper.xml" />
      </mappers>

      </configuration>

      db_config.properties,用于存放数据库连接参数

      driver=com.mysql.jdbc.Driver
      url=jdbc:mysql://127.0.0.1:3306/humen
      username=root
      password=123456

      log4j.properties,日志记录文件方便查看控制台输出的 SQL 语句

      log4j.rootLogger=INFO, stdout  
      log4j.logger.com.abc.mapper=DEBUG
      log4j.appender.stdout=org.apache.log4j.ConsoleAppender
      log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
      log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

      log4j.category.org.springframework = OFF
    • 实体,放入包com.dlnu.humen.pojo中

      Card.java

      package com.dlnu.humen.pojo;

      public class Card {
      private Integer cId;

      private String cNum;

      public Card() {
      super();
      }

      public Card(Integer cId, String cNum) {
      super();
      this.cId = cId;
      this.cNum = cNum;
      }

      public Integer getcId() {
      return cId;
      }

      public void setcId(Integer cId) {
      this.cId = cId;
      }

      public String getcNum() {
      return cNum;
      }

      public void setcNum(String cNum) {
      this.cNum = cNum == null ? null : cNum.trim();
      }

      @Override
      public String toString() {
      return "Card [cId=" + cId + ", cNum=" + cNum + "]";
      }

      }

      Student.java

      package com.dlnu.humen.pojo;

      public class Student {
      private Integer sId;

      private String sName;

      private Integer sAge;

      private Card card;// 关联属性

      public Student() {
      super();
      }

      public Student(Integer sId, String sName, Integer sAge, Card card) {
      super();
      this.sId = sId;
      this.sName = sName;
      this.sAge = sAge;
      this.card = card;
      }

      public Integer getsId() {
      return sId;
      }

      public void setsId(Integer sId) {
      this.sId = sId;
      }

      public String getsName() {
      return sName;
      }

      public void setsName(String sName) {
      this.sName = sName == null ? null : sName.trim();
      }

      public Integer getsAge() {
      return sAge;
      }

      public void setsAge(Integer sAge) {
      this.sAge = sAge;
      }

      public Card getCard() {
      return card;
      }

      public void setCard(Card card) {
      this.card = card;
      }

      @Override
      public String toString() {
      return "Student [sId=" + sId + ", sName=" + sName + ", sAge=" + sAge + "]";
      }

      }
    • 映射文件,放入包com.dlnu.humen.mapper中

      CardMapper.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.dlnu.humen.dao.CardMapper" >

      <resultMap id="BaseResultMap" type="Card" >
      <id column="c_id" property="cId" jdbcType="INTEGER" />
      <result column="c_num" property="cNum" jdbcType="VARCHAR" />
      </resultMap>

      </mapper>

      StudentMapper.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.dlnu.humen.dao.StudentMapper">

      <resultMap id="BaseResultMap" type="Student">
      <id column="s_id" jdbcType="INTEGER" property="sId" />
      <result column="s_name" jdbcType="VARCHAR" property="sName" />
      <result column="s_age" jdbcType="INTEGER" property="sAge" />

      <!-- 一对一关联映射:association -->
      <association property="card" javaType="Card">
      <id column="c_id" property="cId" />
      <result column="c_num" property="cNum" />
      </association>

      </resultMap>

      <!--查询某身份证对应的学生姓名-->
      <select id="queryByNum" parameterType="java.lang.Integer" resultMap="BaseResultMap">
      select s.s_name,s.s_id
      from student s
      left outer join card c ON s.`s_cid`=c.`c_id`
      where c.c_num=#{cNum};
      </select>
      </mapper>
    • Dao层,放入包com.dlnu.humen.dao中

      CardMapper.java

      package com.dlnu.humen.dao;

      import com.dlnu.humen.pojo.Card;

      public interface CardMapper {
      int delete(Integer cId);

      int insert(Card card);

      Card query(Integer cId);

      int update(Card card);
      }

      StudentMapper.java

      package com.dlnu.humen.dao;

      import java.util.List;

      import com.dlnu.humen.pojo.Student;

      public interface StudentMapper {
      int delete(Integer sId);

      int insert(Student stu);

      List<Student> queryByNum(String cNum);

      int update(Student stu);
      }
    • 测试,根据身份证号码查对应的学生姓名。

      TestStudent.java

      package com.dlnu.humen.test;

      import java.io.IOException;
      import java.io.InputStream;
      import java.util.List;

      import org.apache.ibatis.io.Resources;
      import org.apache.ibatis.session.SqlSession;
      import org.apache.ibatis.session.SqlSessionFactory;
      import org.apache.ibatis.session.SqlSessionFactoryBuilder;
      import org.junit.Test;

      import com.dlnu.humen.dao.StudentMapper;
      import com.dlnu.humen.pojo.Student;

      public class TestStudent {
      // Mybatis 配置文件
      private String fileName = "mybatis-config.xml";

      @Test
      public void testqueryByNum() throws IOException {
      // 读取配置文件
      InputStream is = Resources.getResourceAsStream(fileName);
      // 获得一个链接工厂对象 用于产生数据库连接
      SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
      // 获得数据库连接Session对象 (相当于Connection)
      SqlSession session = factory.openSession();

      // 获得mapper对象
      StudentMapper mapper = session.getMapper(StudentMapper.class);

      List<Student> list = mapper.queryByNum("10086");

      for (Student stu : list) {
      System.out.println(stu.getsName());
      }

      // 关闭连接
      session.close();
      }

      }

      测试结果:

      image-20200620123239917

  2. 一对多

    • 设计数据库表

      create table student(
      s_id int(11) primary key,
      s_name varchar(20),
      s_age int(11),
      s_gid int(11),
      constraint student_fk foreign key(s_gid) references grade (g_id)
      );

      create table grade(
      g_id int(11) primary key,
      g_name varchar(20)
      );

      insert into student(s_id,s_name,s_aga,s_gid) values (1,'zhangsan',18,1),(2,'lisi',20,2),(3,'zhaowu',19,1);

      insert into grade(g_id,g_name) values (1,'sql'),(2,'java');

      select * from grade;
      select * from student;
    • 实体,放入包com.dlnu.school.pojo中

      Grade.java

      package com.dlnu.school.pojo;

      /**
      *
      * 班级(单方)
      *
      */
      public class Grade {

      private Integer gId;

      private String gName;

      public Grade() {
      super();
      }

      public Grade(Integer gId, String gName) {
      super();
      this.gId = gId;
      this.gName = gName;
      }

      public Integer getgId() {
      return gId;
      }

      public void setgId(Integer gId) {
      this.gId = gId;
      }

      public String getgName() {
      return gName;
      }

      public void setgName(String gName) {
      this.gName = gName == null ? null : gName.trim();
      }

      @Override
      public String toString() {
      return "Class [gId=" + gId + ", gName=" + gName + "]";
      }

      }

      Student.java

        package com.dlnu.school.pojo;

      /**
      *
      * 学生(多方)
      *
      */
      public class Student {
      private Integer sId;

      private String sName;

      private Integer sAge;

      private Grade grade;// 关联属性

      public Student() {
      super();
      }

      public Student(Integer sId, String sName, Integer sAge, Grade grade) {
      super();
      this.sId = sId;
      this.sName = sName;
      this.sAge = sAge;
      this.grade = grade;
      }

      public Integer getsId() {
      return sId;
      }

      public void setsId(Integer sId) {
      this.sId = sId;
      }

      public String getsName() {
      return sName;
      }

      public void setsName(String sName) {
      this.sName = sName;
      }

      public Integer getsAge() {
      return sAge;
      }

      public void setsAge(Integer sAge) {
      this.sAge = sAge;
      }

      public Grade getGrade() {
      return grade;
      }

      public void setGrade(Grade grade) {
      this.grade = grade;
      }

      @Override
      public String toString() {
      return "Student [sId=" + sId + ", sName=" + sName + ", sAge=" + sAge + ", grade=" + grade + "]";
      }

      }
    • 映射文件,放入包com.dlnu.school.mapper中

      GradeMapper.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.dlnu.school.dao.GradeMapper" >

      <!-- resultMap: 映射实体类和字段之间的一一对应的关系 -->
      <resultMap id="BaseResultMap" type="Grade" >
      <id column="g_id" property="gId" jdbcType="INTEGER" />
      <result column="g_name" property="gName" jdbcType="VARCHAR" />
      </resultMap>

      </mapper>

      StudentMapper.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.dlnu.school.dao.StudentMapper" >

      <resultMap id="BaseResultMap" type="com.dlnu.school.pojo.Student" >
      <id column="s_id" property="sId" jdbcType="INTEGER" />
      <result column="s_name" property="sName" jdbcType="VARCHAR" />
      <result column="s_age" property="sAge" jdbcType="INTEGER" />

      <!-- 多对一关联班级 一个班级有多个学生 -->
      <association property="grade" javaType="com.dlnu.school.pojo.Grade">
      <id column="g_id" property="gId" jdbcType="INTEGER" />
      <result column="g_name" property="gName" jdbcType="VARCHAR" />
      </association>
      </resultMap>

      <!--查询某班级有多少位学生-->
      <select id="queryStuByGrade" parameterType="java.lang.String" resultMap="BaseResultMap">
      select s.s_name,s.s_id
      from student s
      left OUTER JOIN grade g ON s.`s_gid`=g.`g_id`
      where g.g_name = #{gName};
      </select>

      </mapper>
    • Dao层,放入包com.dlnu.school.dao中

      GradeMapper.java

      package com.dlnu.school.dao;

      import java.util.List;

      import com.dlnu.school.pojo.Grade;

      public interface GradeMapper {
      int delete(Integer gId);

      int insert(Grade grade);

      int update(Grade grade);

      List<Grade> query();
      }

      StudentMapper.java

      package com.dlnu.school.dao;

      import java.util.List;

      import com.dlnu.school.pojo.Student;

      public interface StudentMapper {
      int delete(Integer sId);

      int insert(Student stu);

      List<Student> queryStuByGrade(String gName);

      int update(Student stu);

      List<Student> query();
      }
    • 测试,根据班级名称查班级的学生名字。因为我们主要查的是学生的信息,应把sql语句写在学生的映射文件中。

      测试前请查看mybatis-config.xml和db-config.properties文件导入和是否修改相应位置,还要导入相应jar包

      TestStudent.java

      package com.dlnu.school.test;

      import java.io.IOException;
      import java.io.InputStream;
      import java.util.List;

      import org.apache.ibatis.io.Resources;
      import org.apache.ibatis.session.SqlSession;
      import org.apache.ibatis.session.SqlSessionFactory;
      import org.apache.ibatis.session.SqlSessionFactoryBuilder;
      import org.junit.Test;

      import com.dlnu.school.dao.StudentMapper;
      import com.dlnu.school.pojo.Student;

      public class TestStudent {
      // Mybatis 配置文件
      private String fileName = "mybatis-config.xml";

      @Test
      public void testqueryStuByGrade() throws IOException {
      // 读取配置文件
      InputStream is = Resources.getResourceAsStream(fileName);
      // 获得一个链接工厂对象 用于产生数据库连接
      SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
      // 获得数据库连接Session对象 (相当于Connection)
      SqlSession session = factory.openSession();

      // 获得mapper对象
      StudentMapper mapper = session.getMapper(StudentMapper.class);

      //查询sql班级的学生
      List<Student> list = mapper.queryStuByGrade("sql");

      for (Student student1 : list) {
      System.out.println(student1.getsName());
      }

      // 关闭连接
      session.close();
      }

      }

      测试结果:

image-20200619211945786

  1. 多对多

    • 设计数据库表

      CREATE TABLE course (
      c_id int(11) PRIMARY KEY,
      c_name varchar(20),
      `c_credit` int(11)
      );

      CREATE TABLE student (
      s_id int(11) PRIMARY KEY,
      s_name varchar(20),
      s_age int(11)
      ) ;

      CREATE TABLE sc (
      s_id int(11),
      c_id int(11),
      grade int(11) ,
      PRIMARY KEY (s_id,c_id),
      CONSTRAINT sc_ibfk_1 FOREIGN KEY (s_id) REFERENCES student (s_id),
      CONSTRAINT sc_ibfk_2 FOREIGN KEY (c_id) REFERENCES course (c_id)
      );

      insert into student(s_id,s_name,s_age) values (20201,'sam',20),(20202,'john',21),(20203,'lili',20);

      insert into course(c_id,c_name,c_credit) values (1,'sql',3),(2,'java',4),(3,'c++',2);

      insert into sc(s_id,c_id,grade) values (20201,3,85),(20202,2,88),(20203,1,90);
      • 导入jar包(目录如下)

        image-20200617230246519

      • 配置文件

        mybatis-config.xml,用来配置 Mybatis 的运行环境、数据源、事务等。

        <?xml version="1.0" encoding="UTF-8"?>
        <!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
        <configuration>
        <!-- 引用外部配置文件路径 -->
        <properties resource="db_config.properties"></properties>

        <!-- 实体类的别名,将包内的 Java 类的类名作为类的类别名-->
        <typeAliases>
        <typeAlias type="com.dlnu.sc.pojo.Course" alias="Course"/>
        <typeAlias type="com.dlnu.sc.pojo.Student" alias="Student"/>
        <typeAlias type="com.dlnu.sc.pojo.SC" alias="SC"/>
        </typeAliases>

        <!-- 环境配置 -->
        <environments default="development">
        <environment id="development">
        <!--type="JDBC"表示使用JDBC的提交和回滚-->
        <transactionManager type="JDBC" />

        <!--type="POOLED"表示支持JDBC数据库连接池 -->
        <!-- 数据库连接池由MyBatis管理,数据库名是mybatis -->
        <dataSource type="POOLED">
        <property name="driver" value="${driver}" />
        <property name="url" value="${url}"/>
        <property name="username" value="${username}" />
        <property name="password" value="${password}" />
        </dataSource>
        </environment>
        </environments>

        <!--映射文件路径 -->
        <!-- 通过 mapper 接口包加载整个包的映射文件 -->
        <mappers>
        <mapper resource="com/dlnu/sc/mapper/CourseMapper.xml" />
        <mapper resource="com/dlnu/sc/mapper/StudentMapper.xml" />
        <mapper resource="com/dlnu/sc/mapper/SCMapper.xml" />
        </mappers>

        </configuration>

        db_config.properties,用于存放数据库连接参数

        driver=com.mysql.jdbc.Driver
        url=jdbc:mysql://127.0.0.1:3306/stu_course
        username=root
        password=123456

        log4j.properties,日志记录文件方便查看控制台输出的 SQL 语句

        log4j.rootLogger=INFO, stdout  
        log4j.logger.com.abc.mapper=DEBUG
        log4j.appender.stdout=org.apache.log4j.ConsoleAppender
        log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
        log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

        log4j.category.org.springframework = OFF
      • 实体,放入包com.dlnu.sc.pojo中

        Course.java

        package com.dlnu.sc.pojo;

        import java.util.List;

        public class Course {
        private Integer cId;

        private String cName;

        private Integer cCredit;

        private List<Student> student;
        private List<SC> sc;

        public Course() {
        super();
        // TODO Auto-generated constructor stub
        }

        public Course(Integer cId, String cName, Integer cCredit, List<Student> student) {
        super();
        this.cId = cId;
        this.cName = cName;
        this.cCredit = cCredit;
        this.student = student;
        }

        public Integer getcId() {
        return cId;
        }

        public void setcId(Integer cId) {
        this.cId = cId;
        }

        public String getcName() {
        return cName;
        }

        public void setcName(String cName) {
        this.cName = cName == null ? null : cName.trim();
        }

        public Integer getcCredit() {
        return cCredit;
        }

        public void setcCredit(Integer cCredit) {
        this.cCredit = cCredit;
        }

        public List<Student> getStudent() {
        return student;
        }

        public void setStudent(List<Student> student) {
        this.student = student;
        }

        @Override
        public String toString() {
        return "cId=" + cId + ", cName=" + cName + ", cCredit=" + cCredit;
        }

        }

        Student.java

        package com.dlnu.sc.pojo;

        import java.util.List;

        public class Student {
        private Integer sId;

        private String sName;

        private Integer sAge;

        private List<Course> courses;

        private List<SC> sc;

        public Student() {
        super();
        // TODO Auto-generated constructor stub
        }

        public Student(Integer sId, String sName, Integer sAge, List<Course> courses) {
        super();
        this.sId = sId;
        this.sName = sName;
        this.sAge = sAge;
        this.courses = courses;
        }

        public Integer getsId() {
        return sId;
        }

        public void setsId(Integer sId) {
        this.sId = sId;
        }

        public String getsName() {
        return sName;
        }

        public void setsName(String sName) {
        this.sName = sName == null ? null : sName.trim();
        }

        public Integer getsAge() {
        return sAge;
        }

        public void setsAge(Integer sAge) {
        this.sAge = sAge;
        }

        public List<Course> getCourses() {
        return courses;
        }

        public void setCourses(List<Course> courses) {
        this.courses = courses;
        }

        public List<SC> getSc() {
        return sc;
        }

        public void setSc(List<SC> sc) {
        this.sc = sc;
        }

        @Override
        public String toString() {
        return "Student [sId=" + sId + ", sName=" + sName + ", sAge=" + sAge + ", courses=" + courses + ", sc=" + sc
        + "]";
        }

        }

        SC.java

        package com.dlnu.sc.pojo;

        public class SC {

        private Student student;
        private Course course;
        private Integer grade;

        public SC() {
        super();
        // TODO Auto-generated constructor stub
        }

        public SC(Student student, Course course, Integer grade) {
        super();
        this.student = student;
        this.course = course;
        this.grade = grade;
        }

        public Integer getGrade() {
        return grade;
        }

        public void setGrade(Integer grade) {
        this.grade = grade;
        }

        public Student getStudent() {
        return student;
        }

        public void setStudent(Student student) {
        this.student = student;
        }

        public Course getCourse() {
        return course;
        }

        public void setCourse(Course course) {
        this.course = course;
        }

        @Override
        public String toString() {
        return "grade=" + grade;
        }

        }
      • 映射文件,放入包com.dlnu.sc.mapper中

        CourseMapper.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.dlnu.sc.dao.CourseMapper" >
        <resultMap id="BaseResultMap" type="Course" >
        <id column="c_id" property="cId" jdbcType="INTEGER" />
        <result column="c_name" property="cName" jdbcType="VARCHAR" />
        <result column="c_credit" property="cCredit" jdbcType="INTEGER" />
        </resultMap>

        </mapper>

        StudentMapper.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.dlnu.sc.dao.StudentMapper">

        <resultMap id="BaseResultMap" type="com.dlnu.sc.pojo.Student">
        <id column="s_id" jdbcType="INTEGER" property="sId" />
        <result column="s_name" jdbcType="VARCHAR" property="sName" />
        <result column="s_age" jdbcType="INTEGER" property="sAge" />

        <!-- 多对多关联映射:collection -->
        <collection property="courses" ofType="Course">
        <id property="cId" column="c_id" />
        <result property="cName" column="c_name" />
        <result property="cCredit" column="c_credit" />
        </collection>

        <!-- 多对多关联映射:collection -->
        <collection property="sc" ofType="SC">
        <result property="grade" column="grade" />
        </collection>
        </resultMap>

        <!-- 查询所有学生及他们的课程的信息和成绩 -->
        <select id="queryStudentCourse" resultMap="BaseResultMap">
        select s.*,c.*,sc.*
        from student s,course c,sc
        where s.s_id=sc.s_id and c.c_id=sc.c_id
        </select>

        </mapper>

        SCMapper.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.dlnu.sc.dao.SCMapper" >

        <resultMap id="BaseResultMap" type="SC" >
        <id column="s_id" property="sId" jdbcType="INTEGER" />
        <id column="c_id" property="cId" jdbcType="INTEGER" />
        <result column="grade" property="grade" jdbcType="INTEGER" />
        </resultMap>

        </mapper>
      • Dao层,放入包com.dlnu.sc.dao中

        CourseMapper.java

        package com.dlnu.sc.dao;

        import java.util.List;

        import com.dlnu.sc.pojo.Course;

        public interface CourseMapper {
        int delete(Integer cId);

        int insert(Course course);

        List<Course> query(Integer cId);

        int update(Course course);
        }

        StudentMapper.java

        package com.dlnu.sc.dao;

        import java.util.List;

        import com.dlnu.sc.pojo.Student;

        public interface StudentMapper {
        int delete(Integer sId);

        int insert(Student stu);

        List<Student> query(Integer sId);

        int update(Student stu);

        List<Student> queryStudentCourse();

        }
      • 测试,查询所有学生信息、课程信息和成绩。

        测试前请查看mybatis-config.xml和db-config.properties文件导入和是否修改相应位置,还要导入相应jar包

        TestStudent.java

        package com.dlnu.sc.test;

        import java.io.IOException;
        import java.io.InputStream;
        import java.util.List;

        import org.apache.ibatis.io.Resources;
        import org.apache.ibatis.session.SqlSession;
        import org.apache.ibatis.session.SqlSessionFactory;
        import org.apache.ibatis.session.SqlSessionFactoryBuilder;
        import org.junit.Test;

        import com.dlnu.sc.dao.StudentMapper;
        import com.dlnu.sc.pojo.Student;

        public class TestStudent {
        // Mybatis 配置文件
        private String fileName = "mybatis-config.xml";

        @Test
        public void testqueryStuByGrade() throws IOException {
        // 读取配置文件
        InputStream is = Resources.getResourceAsStream(fileName);
        // 获得一个链接工厂对象 用于产生数据库连接
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
        // 获得数据库连接Session对象 (相当于Connection)
        SqlSession session = factory.openSession();

        // 获得mapper对象
        StudentMapper mapper = session.getMapper(StudentMapper.class);

        List<Student> list = mapper.queryStudentCourse();

        for (Student stu : list) {
        System.out.println(stu.toString());
        }

        // 关闭连接
        session.close();
        }

        }

        测试结果:

        image-20200620222728956

动态SQL

开发人员在使用JDBC或其他框架进行数据库开发时,通常要根据需求去手动拼装SQL,这样容易出错,而MyBatis提供的对SQL语句动态组装的功能,恰能很好的解决这个问题。

MyBatis 常用的动态 SQL 元素包括:

image-20200621224843743

文章作者: 舍予
文章链接: https://zshuhan.github.io/2020/06/14/MyBatis/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 (✿◡‿◡)胖涵
打赏
  • 微信
    微信
  • 支付宝
    支付宝

评论