一文教你玩转Mybatis,超详细代码讲解与实战

一、Mybatis 入门

1.1 什么是MyBatis

    MyBatis本是apache的一个开源项目iBatis,2010年这个项目由apache software foundation迁移到了google code,并且改名为MyBatis。2013年11月迁移到Github。
    
    iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAOs)。

    软件框架(software framework),通常指的是为了实现某个业界标准或完成特定基本任务的软件组件规范,也指为了实现某个软件组件规范时,提供规范所要求之基础功能的软件产品。可以简单理解为框架是一个半成品的项目,我们可以基于框架进行项目开发,提高开发效率。现在互联网企业都是效率至上,如果开发周期过长,可能市场很快就会出现同类产品,失去先机。
    
    当前,最新版本是MyBatis 3.5.9,其发布时间是2021年12月26日。
    
    MyBatis是一个优秀的持久层框架,它对JDBC操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等JDBC繁杂的过程代码。
    
    MyBatis通过 xml 文件或注解的方式将要执行的各种 statement 配置起来,并通过 Java 对象和 statement 中 sql 的动态参数进行映射生成最终执行的 sql 语句,最后由mybatis框架执行sql并将结果映射成Java对象并返回。

1.2 MyBatis的优点

    简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件。易于学习,易于使用。通过文档和源代码,可以比较完全的掌握它的设计思路和实现。

    灵活:Mybatis不会对应用程序或者数据库的现有设计强加任何影响。sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。

    解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。

    提供映射标签,支持对象与数据库的orm字段关系映射。

    提供对象关系映射标签,支持对象关系组建维护。

    提供xml标签,支持编写动态sql。

1.3 JDBC编程存在的问题

    在实际开发过程中,我们一般使用ORM框架来代替传统的JDBC,例如Mybatis或者Hibernate,但JDBC是Java用来实现数据访问的基础,掌握它对于我们理解Java的数据操作流程很有帮助。

    ORM:对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。

    Java典型的ORM中间件有:Hibernate,Mybatis,speedframework。

    ORM技术特点:
        1、提高了开发效率。由于ORM可以自动对Entity对象与数据库中的Table进行字段与属性的映射,所以我们实际可能已经不需要一个专用的、庞大的数据访问层。 

        2、ORM提供了对数据库的映射,不用sql直接编码,能够像操作对象一样从数据库获取数据。 

    那为什么我们在实际开发中都是用一些ORM框架而不用传统的JDBC进行开发呢?接下来我们来看一下一个JDBC程序的简单实现并分析其缺点,如下:

1.3.1 JDBC程序

@Override
public void insertUser(User user) {
    Connection conn = DBUtil.getConnection();
    PreparedStatement stmt = null;
    String sql = "insert into sys_user(NAME,ACCT,PWD,CRTIME,UPTIME) values(?,?,?,now(),now())";
    try {
        stmt = conn.prepareStatement(sql);
        stmt.setString(1, user.getName());
        stmt.setString(2, user.getAcct());
        stmt.setString(3, user.getPwd());
        stmt.executeUpdate();
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        DBUtil.closeAll(conn, stmt, null);
    }
}

1.3.2 JDBC编程步骤

    1、加载数据库驱动;

    2、创建并获取数据库连接Connection;

    3、创建执行SQL语句的PreparedStatement对象;

    4、设置SQL语句中的占位符参数;

    5、通过PreparedStatement执行Sql并获取结果集;

    6、对Sql执行结果进行解析处理;

    7、释放资源(Connection、Preparedstatement、ResultSet)。

1.3.3 JDBC问题总结如下

从以上的程序中,进行总结如下:

    1、数据库连接使用时就创建,不使用时便立即释放,从而对数据库进行频繁的操作,导致资源的浪费、影响性能;
        优化设想:使用数据库连接池管理数据库对象;

    2、sql都是硬编码到Java程序中,如果改变sql,那么得重新编译Java代码,不利于系统后期的维护;
        优化设想:将sql语句配置在xml中,即使改变sql也不用重新编译源代码;

    3、向PreparedStatement设置参数,也是硬编码到Java程序中,不利于后期的维护;
        优化设想:将sql语句以及占位符和参数全部配置在xml中,改动也不需要重新编译源代码;

    4、从resultset遍历结果集数据时,也存在硬编码,不利于后期系统的维护;
        优化设想:将查询的结果集自动映射成Java对象;

    针对以上问题,顺其自然的就出现了许多优化JDBC的方案,也就是后期出现的ORM持久层框架,例如Mybatis以及Hibernate等等;这也就是为什么在实际开发中都比较喜欢用ORM框架的原因了。
    
    有了这个概念,那么接下来我们就开始对Mybatis进行学习...

    注:硬编码是将数据直接嵌入到程序或其他可执行对象的源代码中的软件开发实践,与从外部获取数据或在运行时生成数据不同。硬编码数据通常只能通过编辑源代码和重新编译可执行文件来修改。

1.4 MyBatis架构

    1、MyBatis配置:
        mybatis-config.xml(名称不固定),此文件作为MyBatis的全局(核心)配置文件,配置了MyBatis的运行环境等信息。
        mapper.xml文件即Sql映射文件,文件中配置了操作数据库的Sql语句。此文件需要在mybatis-config.xml中加载。

    2、通过MyBatis环境等配置信息构造SqlSessionFactory,即会话工厂。

    3、由会话工厂创建SqlSession即会话,操作数据库需要通过SqlSession进行。

    4、MyBatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器、一个是缓存执行器。

    5、MappedStatement也是MyBatis一个底层封装对象,Mybatis将SQL的配置信息加载成为一个个MappedStatement对象(包括了传入参数映射配置、执行的SQL语句、结果映射配置),存储在内存中。mapper.xml文件中一个Sql对应一个MappedStatement对象,Sql的id即是MappedStatement的id。

    6、MappedStatement对Sql执行输入参数进行定义,包括HashMap、基本类型、字符串类型、实体类类型,Executor通过MappedStatement在执行Sql前将输入的Java对象映射至Sql中,输入参数映射就是JDBC编程中对PreparedStatement设置参数。

    7、MappedStatement对Sql执行输出结果进行定义,包括HashMap、基本类型、字符串类型、实体类类型,Executor通过MappedStatement在执行sql后将输出结果映射至Java对象中,输出结果映射过程相当于JDBC编程中对结果的解析处理过程。

原理分析:

    1、加载配置:配置来源于两个地方,一处是配置文件,一处是Java代码的注解,将SQL的配置信息加载成为一个个MappedStatement对象(包括了传入参数映射配置、执行的SQL语句、结果映射配置),存储在内存中。

    2、SQL解析:当API接口层接收到调用请求时,会接收到传入SQL的ID和传入对象(可以是Map、JavaBean或者基本数据类型),Mybatis会根据SQL的ID找到对应的MappedStatement,然后根据传入参数对象对MappedStatement进行解析,解析后可以得到最终要执行的SQL语句和参数。

    3、SQL执行:将最终得到的SQL和参数拿到数据库进行执行,得到操作数据库的结果。

    4、结果映射:将操作数据库的结果按照映射的配置进行转换,可以转换成HashMap、JavaBean或者基本数据类型,并将最终结果返回。

1.5 搭建MyBatis项目

1.5.1 建立Java项目

    MyBatis是一个持久层框架,是操作数据库时使用的。无需创建JavaWEB项目,建立Maven Java项目即可。

1.5.2 导入MyBatis框架jar包

    Mybaits的代码由github.com管理,下载地址:https://github.com/mybatis/mybatis-3/releases

mybatis目录结构:

    lib -> mybatis附属工具包

    LICENSE -> 许可证

    mybatis-3.4.6.jar -> mybatis的核心包
    
    mybatis-3.4.6.pdf -> mybatis使用手册

    NOTICE -> 法律声明

方式一:导入jar包

    mybatis-3.4.6.jar为核心jar包,必须引入。
    
    lib目录下的jar包为工具包,可有可无,我们一般会引入log4j作为mybatis日志输出组件。

    mysql或oracle数据库的驱动包,必须引入。  

方式二:maven依赖配置

<dependencies>
    <!-- mybatis -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.4.6</version>
    </dependency>
    <!-- oracle:大家在引入oracle依赖的时候肯定出错 -->
    <dependency>
        <groupId>com.oracle</groupId>
        <artifactId>ojdbc6</artifactId>
        <version>11.2.0.1.0</version>
    </dependency>
    <!--  mysql -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.49</version>
    </dependency>
    <!-- log4j Mybatis的日志输出组件 -->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>

    oracle依赖报错解决:https://blog.csdn.net/qq_38000902/article/details/82759268

    在oracle安装目录里面就有ojdbc6.jar:D:\Tools\product\11.2.0\dbhome_1\jdbc\lib(我的路径)

    mybatis默认使用log4j作为输出日志信息,你也可以引入log4j相关文件。

1.5.3 编写MyBatis中全局配置文件

    在src/main/resources目录下创建mybatis-config.xml,作用:配置了数据源、事务等MyBatis运行环境等。

    注:如果src/main下面没有resources目录,那么我们手动创建一个,让其成为Resources Root。

<?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 文件的根节点 -->
<configuration>
    <!--
      properties 用于引入外部的properties配置文件
      resource:引入类路径下的文件
      url:引入磁盘或网路
     -->
    <properties/>

    <!-- environments:多个配置环境;通过default属性可以对多个环境快速切换 -->
    <!-- environments default属性的值必须和某个environment的id值一致 -->
    <!-- 和spring整合后 environments配置将废除,了解即可 -->
    <environments default="mysql">
        <environment id="oracle">
            <!-- 配置事务:使用jdbc的事务管理 -->
            <transactionManager type="JDBC"/>
            <!-- 配置数据源:连接数据库的信息
                type: 表示连接是否使用连接池,POOLED表示mybatis中自带的连接池
    JNDI、POOLED、UNPOOLED
             -->
            <dataSource type="POOLED">
                <property name="driver" value="oracle.jdbc.driver.OracleDriver"/>
                <property name="url" value="jdbc:oracle:thin:@localhost:1521:orcl"/>
                <property name="username" value="scott"/>
                <property name="password" value="tiger"/>
            </dataSource>
        </environment>
        <environment id="mysql">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/ssm?characterEncoding=utf8&amp;useSSL=false"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
</configuration>

MyBatis配置文件报错:

    在编写xml配置文件引入DTD约束时,可能会出现这个错误提示 URI is not registered(Settings | Languages & Frameworks | Schemas and DTDs),此时使用快捷键,选择Fetch external resource 或 Ignore external resource选项。

    或者直接在设置settings -> languages&frameworks -> schemas and dtds 中添加出问题的路径。

1.5.4 数据库SQL

CREATE TABLE `dept`  (
  `deptno` int PRIMARY KEY AUTO_INCREMENT,
  `dname` varchar(20),
  `loc` varchar(40)
);

INSERT INTO `dept` VALUES (10, 'ACCOUNTING', 'NEW YORK');
INSERT INTO `dept` VALUES (20, 'RESEARCH', 'DALLAS');
INSERT INTO `dept` VALUES (30, 'SALES', 'CHICAGO');
INSERT INTO `dept` VALUES (40, 'OPERATIONS', 'BOSTON');

CREATE TABLE `emp`  (
  `empno` int PRIMARY KEY AUTO_INCREMENT,
  `ename` varchar(20),
  `job` varchar(20),
  `mgr` int,
  `hiredate` date,
  `sal` double,
  `comm` double,
  `deptno` int,
  CONSTRAINT `FK_EMP_DEPTNO` FOREIGN KEY (`deptno`) REFERENCES `dept` (`deptno`)
);

INSERT INTO `emp` VALUES (7369, 'SMITH', 'CLERK', 7902, '1980-12-17', 1300, NULL, 20);
INSERT INTO `emp` VALUES (7499, 'ALLEN', 'SALESMAN', 7698, '1981-02-20', 2100, 300, 30);
INSERT INTO `emp` VALUES (7521, 'WARD', 'SALESMAN', 7698, '1981-02-22', 1750, 500, 30);
INSERT INTO `emp` VALUES (7566, 'JONES', 'MANAGER', 7839, '1981-04-02', 3475, NULL, 20);
INSERT INTO `emp` VALUES (7654, 'MARTIN', 'SALESMAN', 7698, '1981-09-28', 1750, 1400, 30);
INSERT INTO `emp` VALUES (7698, 'BLAKE', 'MANAGER', 7839, '1981-05-01', 3350, NULL, 30);
INSERT INTO `emp` VALUES (7782, 'CLARK', 'MANAGER', 7839, '1981-06-09', 2950, NULL, 10);
INSERT INTO `emp` VALUES (7788, 'SCOTT', 'ANALYST', 7566, '1987-04-19', 3500, NULL, 20);
INSERT INTO `emp` VALUES (7839, 'KING', 'PRESIDENT', NULL, '1981-11-17', 5500, NULL, 10);
INSERT INTO `emp` VALUES (7844, 'TURNER', 'SALESMAN', 7698, '1981-09-08', 2000, 0, 30);
INSERT INTO `emp` VALUES (7876, 'ADAMS', 'CLERK', 7788, '1987-05-23', 1600, NULL, 20);
INSERT INTO `emp` VALUES (7900, 'JAMES', 'CLERK', 7698, '0198-12-31', 1450, NULL, 30);
INSERT INTO `emp` VALUES (7902, 'FORD', 'ANALYST', 7566, '1981-12-03', 3500, NULL, 20);
INSERT INTO `emp` VALUES (7934, 'MILLER', 'CLERK', 7782, '1982-01-23', 1800, NULL, 10);

1.5.5 编写实体类

    实体类作为Mybatis进行sql映射使用,实体类通常与数据库表对应,Emp.java如下:

    注:实体类是用来和数据库表对应的,我们最好全部使用引用类型。

package com.newcapec.entity;

import java.util.Date;

public class Emp {

    private Integer empno;
    private String ename;
    private String job;
    private Integer mgr;
    private Date hiredate;
    private Double sal;
    private Double comm;
    private Integer deptno;

    public Integer getEmpno() {
        return empno;
    }

    public void setEmpno(Integer empno) {
        this.empno = empno;
    }

    public String getEname() {
        return ename;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    public String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }

    public Integer getMgr() {
        return mgr;
    }

    public void setMgr(Integer mgr) {
        this.mgr = mgr;
    }

    public Date getHiredate() {
        return hiredate;
    }

    public void setHiredate(Date hiredate) {
        this.hiredate = hiredate;
    }

    public Double getSal() {
        return sal;
    }

    public void setSal(Double sal) {
        this.sal = sal;
    }

    public Double getComm() {
        return comm;
    }

    public void setComm(Double comm) {
        this.comm = comm;
    }

    public Integer getDeptno() {
        return deptno;
    }

    public void setDeptno(Integer deptno) {
        this.deptno = deptno;
    }

    @Override
    public String toString() {
        return "Emp{" +
            "empno=" + empno +
            ", ename='" + ename + '\'' +
            ", job='" + job + '\'' +
            ", mgr=" + mgr +
            ", hiredate=" + hiredate +
            ", sal=" + sal +
            ", comm=" + comm +
            ", deptno=" + deptno +
            '}';
    }
}

1.5.6 编写映射文件

    在src/main/resources下创建mapper目录,在该目录下创建sql映射文件Emp.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文件进行分类管理,用于隔离sql语句。
    注意:如果使用mapper代理的方式进行开发,namespace有特殊的作用。
-->
<mapper namespace="emp">
    <!-- 在映射文件中编写sql语句 -->
    <!-- 通过员工编号查询员工信息 -->
    <!--
        通过<select>标签编写查询语句
        id: 映射文件中SQL语句的唯一标识
             mybatis会将SQL语句封装到MappedStatement对象中,所以此处的id也可以标识MappedStatement对象的id
             注意:同一个mapper文件中id不能重复,而且id在mapper代理模式下有着重要作用
        parameterType: 输入参数的类型
            sql语句的占位符:#{}
            #{empno}:其中empno表示接收输入的参数值,参数名称为empno
			但是如果参数的类型为简单类型(基本数据类型、包装类、字符串类型),参数名称可以任意指定
		resultType: 输出参数的类型
			需要指定输出数据为Java中的数据类型(实体类的全限定名)
    -->
    <select id="selectById" parameterType="java.lang.Integer" resultType="com.newcapec.entity.Emp">
        select empno,ename,job,hiredate,mgr,sal,comm,deptno from emp where empno=#{empno}
    </select>
</mapper>

1.5.7 加载映射文件

    在MyBatis的全局配置文件中添加映射文件位置。

<!-- 加载映射文件的位置 -->
<mappers>
    <mapper resource="mapper/Emp.xml"/>
</mappers>

1.5.8 log4j配置

    Mybatis日志输出:log4j.properties配置文件。

#井号表示注释,配置内容为键值对格式,每行只能有一个键值对,键值对之间以=连接
#指定logger
#设定log4j的日志级别和输出的目的地
#INFO日志级别 ,Console和logfile输出的目的地
#等级 OFF,ERROR,WARN,INFO,DEBUG,TRACE,ALL
log4j.rootLogger=DEBUG,Console

#指定appender
#设定Logger的Console,其中Console为自定义名称,类型为控制台输出
log4j.appender.Console=org.apache.log4j.ConsoleAppender

#设定Logger的logfile,其中logfile为自定义名称,类型为文件
#org.apache.log4j.FileAppender文件
#org.apache.log4j.RollingFileAppender文件大小到达指定尺寸后产生一个新的文件
#org.apache.log4j.DailyRollingFileAppender每天产生一个日志文件
log4j.appender.logfile=org.apache.log4j.RollingFileAppender
#设定文件的输出路径
log4j.appender.logfile.File=d:/log/test.log
#设定文件最大尺寸  单位可以使KB,MB,GB
log4j.appender.logfile.MaxFileSize=2048KB

#输出格式
#设定appender布局Layout
#   %d 输出日志的日期和时间,指定格式:%d{yyyy-MM-dd HH:mm:ss SSS}
#   %p 输出的日志级别
#   %c 输出所属类的全类名
#   %M 方法名
#   %m 输出代码中指定消息
#   %n 一个换行符
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d %p %c.%M() --%m%n
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p %c.%M() --%m%n

1.5.9 编写测试程序

    推荐使用Junit单元测试。

package com.newcapec;

import com.newcapec.entity.Emp;
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 java.io.IOException;
import java.io.InputStream;

/**
 * 测试程序
 */
public class MybatisTest {

    @Test
    public void test() throws IOException {
        //1.创建读取全局配置文件的流
        InputStream in = Resources.getResourceAsStream("mybatis-config.xml");

        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();

        //2.通过配置文件流创建会话工厂
        SqlSessionFactory factory = builder.build(in);

        //3.通过会话工厂创建会话对象(SqlSession)
        SqlSession session = factory.openSession();

        //4.通过会话对象操作数据
        /**
         * 查询单条记录
         * selectOne(String statementId, Object param)
         * 参数1:映射文件中的statementId,命名空间名.statementId
         * 参数2:向sql语句中传入的数据,注意:传入的数据类型必须与映射文件中配置的parameterType保持一致
         * 返回值:就是映射文件中配置的resultType的类型
         * 查询多条记录
         * selectList()
         */
        Emp emp = session.selectOne("emp.selectById", 7369);
        System.out.println(emp);
        //5.关闭资源
        session.close();
    }
}

1.6 增删改查的基本操作

    实现以下功能:
        查询所有员工信息

        添加员工

        更新员工

        删除员工

        根据员工名模糊查询

1.6.1 查询操作

mapper文件:

<!--
	查询到数据返回多条记录,每一条封装在一个实体类对象中,所有的实体类对象封装在List集合中
	resultType:指定的并不是集合的类型,而是单条数据所对应实体类类型
	resultType="java.util.List" 错误的配置方式
-->
<select id="select" resultType="com.newcapec.entity.Emp">
    select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp order by empno desc
</select>

Java代码:

public class CURDTest {

    @Test
    public void testSelect() throws IOException {
        InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        SqlSession session = factory.openSession();

        /**
         * 查询多条记录
         * selectList(statement-sql语句的id, parameter-参数)
         * 表示将单条记录都存储输出映射对象中,每条记录的映射对象存放在List集合中
         */
        List<Emp> list = session.selectList("emp.select");
        for (Emp emp : list) {
            System.out.println(emp);
        }
        session.close();
    }
}

1.6.2 新增操作

mapper文件:

<!--
	添加操作使用insert标签
	增删改操作没有resultType,只有查询有resultType;
	因为增删改操作返回值都是int类型,所以我们不需要指明

	注意:给占位符赋值,#{}中编写的内容为实体类型参数中的成员变量名称
		#{empno}    Mybatis会从传递过来的参数对象里面得到emono字段的值
-->
<insert id="insert" parameterType="com.newcapec.entity.Emp">
    insert into emp(ename,job,mgr,hiredate,sal,comm,deptno)
    values(#{ename},#{job},#{mgr},#{hiredate},#{sal},#{comm},#{deptno})
</insert>

Java代码:

@Test
public void testInsert() throws IOException {
    InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
    SqlSession session = factory.openSession();

    Emp emp = new Emp();
    emp.setEname("TOM");
    emp.setJob("CLARK");
    emp.setMgr(1);
    emp.setHiredate(new Date());
    emp.setSal(6500.0);
    emp.setComm(1200.0);

    System.out.println("新增之前的主键值为:" + emp.getEmpno());
    
    int result = session.insert("emp.insert", emp);
    
    System.out.println("影响数据库的条数为:" + result);
    /**
     * mybatis中的事务是jdbc的事务机制,mybatis里面默认是手动提交
     */
    session.commit();
    
    System.out.println("新增之后的主键值为:" + emp.getEmpno());

    session.close();
}

插入数据的主键返回:

  • select last_insert_id(),表示得到刚insert进去记录的主键值,适用与自增主键的数据库;

  • select seq_demo.nextval from dual,表示获取下一个序列生成的值,适用于存在序列的数据库;

  • keyProperty:将查询到主键值设置到parameterType指定的对象的哪个属性;

  • order:selectKey标签中Sql语句,相对于insert语句来说的执行顺序;

  • resultType:指定selectKey标签中Sql语句的结果类型;

Oracle数据库:

<insert id="insert" parameterType="com.newcapec.entity.Emp">
    <selectKey resultType="java.lang.Integer" keyProperty="empno" order="BEFORE">
        select seq_demo.nextval from dual
    </selectKey>
    insert into emp(empno,ename,job,mgr,hiredate,sal,comm,deptno)
    values(#{empno},#{ename},#{job},#{mgr},#{hiredate},#{sal},#{comm},#{deptno})
</insert>

MySQL数据库:

<insert id="insert" parameterType="com.newcapec.entity.Emp">
    <selectKey resultType="java.lang.Integer" keyProperty="empno" order="AFTER">
        select last_insert_id()
    </selectKey>
    insert into emp(ename,job,mgr,hiredate,sal,comm,deptno)
    values(#{ename},#{job},#{mgr},#{hiredate},#{sal},#{comm},#{deptno})
</insert>

如果是主键自增型数据库(MySQL)还可以使用:

  • useGeneratedKeys: 是否开启主键返回,默认不开启false;

  • keyProperty: 获取到主键值后存放在实体类哪个成员变量中;

<insert id="insert" parameterType="com.newcapec.entity.Emp" useGeneratedKeys="true" keyProperty="empno">
    insert into emp(ename,job,mgr,hiredate,sal,comm,deptno)
    values(#{ename},#{job},#{mgr},#{hiredate},#{sal},#{comm},#{deptno})
</insert>

1.6.3 修改操作

mapper文件:

<update id="update" parameterType="com.newcapec.entity.Emp">
    update emp set ename=#{ename},job=#{job},mgr=#{mgr},hiredate=#{hiredate},sal=#{sal},comm=#{comm},deptno=#{deptno} where empno=#{empno}
</update>

Java代码:

@Test
public void testUpdate() throws IOException {
    InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
    SqlSession session = factory.openSession();

    Emp emp = new Emp();
    emp.setEmpno(7936);
    emp.setEname("JERRY");
    emp.setJob("MANAGER");
    emp.setMgr(7698);
    emp.setHiredate(new Date(new Date().getTime() + 1000*60*60*24));
    emp.setSal(7800.0);
    emp.setComm(800.0);

    int result = session.update("emp.update", emp);
    System.out.println("影响数据库的条数为:" + result);
    
    session.commit();

    session.close();
}

1.6.4 删除操作

mapper文件:

<delete id="delete" parameterType="java.lang.Integer">
    delete from emp where empno=#{empno}
</delete>

Java代码:

@Test
public void testDelete() throws IOException {
    InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
    SqlSession session = factory.openSession();

    int result = session.delete("emp.delete", 7935);
    System.out.println("影响数据库的条数为:" + result);

    session.commit();

    session.close();
}

1.6.5 模糊查询

mapper文件:

<!--
	条件查询:模糊查询
    1、#{}占位符,防止sql注入
    需要在Java中将传入数据的前后拼接%符号
    where ename like #{ename}
    
	2、使用字符串拼接函数
    where ename like concat('%',#{ename},'%')

    3、${}拼接符号,实现sql的拼接
    where ename like '%${value}%'
    注意:${}不是占位符,如果输入参数为简单类型,${}中的内容必须为value
-->
<select id="selectByEname1" parameterType="java.lang.String" resultType="com.newcapec.entity.Emp">
    select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp
    where ename like #{ename}
</select>

Java代码:

@Test
public void testSelectByEname() throws IOException {
    InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
    SqlSession session = factory.openSession();
    String ename = "S";
    List<Emp> list = session.selectList("emp.selectByEname1", "%"+ename+"%");
    for (Emp emp : list) {
        System.out.println(emp);
    }
    session.close();
}

mapper文件:

<select id="selectByEname2" parameterType="java.lang.String" resultType="com.newcapec.entity.Emp">
    select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp
    where ename like concat('%',#{ename},'%')
</select>

Java代码:

@Test
public void testSelectByEname2() throws IOException {
    InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
    SqlSession session = factory.openSession();
    String ename = "S";
    List<Emp> list = session.selectList("emp.selectByEname2", ename);
    for (Emp emp : list) {
        System.out.println(emp);
    }
    session.close();
}

mapper文件:

<select id="selectByEname3" parameterType="java.lang.String" resultType="com.newcapec.entity.Emp">
    select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp
    where ename like '%${value}%'
</select>

Java代码:

@Test
public void testSelectByEname3() throws IOException {
    InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
    SqlSession session = factory.openSession();
    String ename = "S";
    List<Emp> list = session.selectList("emp.selectByEname3", ename);
    for (Emp emp : list) {
        System.out.println(emp);
    }
    session.close();
}

1.6.6 总结

parameterType和resultType:

    parameterType:指定输入参数类型,MyBatis通过ognl从输入对象中获取参数值设置在Sql中;

    resultType:指定输出结果类型,MyBatis将Sql查询结果的一行记录数据映射为resultType指定类型的对象;

#{} 和 ${}:

    #{}表示一个占位符号,#{}接收输入参数,类型可以是简单类型、实体类类型、HashMap;
    #{}接收简单类型,#{}中可以写成value或其它名称;
    #{}接收实体类对象值,通过OGNL读取对象中的属性值,通过属性.属性.属性...的方式获取对象属性值;
  
    ${}表示一个拼接符号,会引用Sql注入,所以不建议使用${};
    ${}接收输入参数,类型可以是简单类型、实体类类型、HashMap;
    ${}接收简单类型,${}中只能写成value;
    ${}接收实体类对象值,通过OGNL读取对象中的属性值,通过属性.属性.属性...的方式获取对象属性值;

selectOne和selectList:

    selectOne表示查询出一条记录进行映射;
    如果使用selectOne可以实现使用selectList也可以实现(list中只有一个对象);
    
    selectList表示查询出一个列表(多条记录)进行映射;
    如果使用selectList查询多条记录,不能使用selectOne ;
    如果使用selectOne报错:org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 4  

1.7 MyBatis和Hibernate区别

    Hibernate:是一个标准ORM框架(对象关系映射)。
        入门门槛较高的,不需要程序写Sql,Sql语句自动生成,对sql语句进行优化、修改比较困难的;
        应用场景:适用与需求变化不多的中小型项目,比如:后台管理系统,erp、crm、oa...;

    MyBatis:专注于Sql本身,需要程序员自己编写Sql语句,Sql修改、优化比较方便;
        MyBatis是一个不完全的ORM框架,虽然程序员自己写Sql,MyBatis也可以实现映射(输入映射、输出映射);
        应用场景:适用与需求变化较多的项目,比如:互联网项目  

    企业进行技术选型,以低成本高回报作为技术选型的原则,根据项目组的技术力量进行选择。

    Mybatis和hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句,不过mybatis可以通过XML或注解方式灵活配置要运行的sql语句,并将java对象和sql语句映射生成最终执行的sql,最后将sql执行的结果再映射生成java对象。

    Mybatis学习门槛低,简单易学,程序员直接编写原生态sql,可严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,例如互联网软件、企业运营类软件等,因为这类软件需求变化频繁,一但需求变化要求成果输出迅速。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件则需要自定义多套sql映射文件,工作量大。

    Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件(例如需求固定的定制化软件)如果用hibernate开发可以节省很多代码,提高效率。但是Hibernate的学习门槛高,要精通门槛更高,而且怎么设计O/R映射,在性能和对象模型之间如何权衡,以及怎样用好Hibernate需要具有很强的经验和能力才行。

    总之,按照员工的需求在有限的资源环境下只要能做出维护性、扩展性良好的软件架构都是好架构,所以框架只有适合才是最好。

1.8 Mybatis解决JDBC编程的问题

    1、数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
        解决:在mybatis-config.xml中配置数据链接池,使用连接池管理数据库链接。

    2、Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。
        解决:将Sql语句配置在XXXXmapper.xml文件中与java代码分离。

    3、向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。
        解决:Mybatis自动将java对象映射至sql语句,通过statement中的parameterType定义输入参数的类型。

    4、对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。
        解决:Mybatis自动将sql执行结果映射至java对象,通过statement中的resultType定义输出结果的类型。

二、Mybatis DAO开发

使用Mybatis开发Dao,通常有两个方法,即原始Dao开发方法和Mapper接口开发方法。

2.1 Mybatis API

2.1.1 SqlSessionFactoryBuilder

    SqlSessionFactoryBuilder用于创建SqlSessionFacoty,SqlSessionFacoty一旦创建完成就不需要SqlSessionFactoryBuilder了,因为SqlSession是通过SqlSessionFactory生产,所以可以将SqlSessionFactoryBuilder当成一个工具类使用,最佳使用范围是方法范围即方法体内局部变量。

2.1.2 SqlSessionFactory

    SqlSessionFactory是一个接口,接口中定义了openSession的不同重载方法,SqlSessionFactory的最佳使用范围是整个应用运行期间,一旦创建后可以重复使用,通常以单例模式管理SqlSessionFactory。

    注:openSession(true),true为自动提交事务,false则相反。

2.1.3 SqlSession

    SqlSession是一个面向用户(程序员)的接口,其中提供了很多操作数据库的方法。如:selectOne(返回单个对象)、selectList(返回单个或多个对象)、insert、update、delete。

    SqlSession的实例不能共享使用,它是线程不安全的,每个线程都应该有它自己的SqlSession实例,因此最佳的范围是请求或方法范围。绝对不能将SqlSession实例的引用放在一个类的静态字段或实例字段中。

2.2 Mybatis工具类

    为了简化MyBatis的开发,可将MyBatis进一步封装。

package com.newcapec.util;

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 java.io.IOException;
import java.io.InputStream;

/**
 * Mybatis工具类
 */
public class MybatisUtil {
    /**
     * 不让用户在外界创建工具类对象
     */
    private MybatisUtil() {
    }

    /**
     * 初始化SqlSessionFactory对象
     */
    private static SqlSessionFactory factory;

    static {
        try {
            InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
            factory = new SqlSessionFactoryBuilder().build(in);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取SqlSession对象的方法
     */
    public static SqlSession getSession() {
        return factory.openSession();
    }
}

测试:

public class MybatisUtilTest {
    @Test
    public void test() {
        SqlSession sqlSession = MybatisUtil.getSession();
        System.out.println(sqlSession);
        sqlSession.close();
    }
}

2.3 原始DAO开发方式

    原始Dao开发方法需要程序员编写Dao接口和Dao实现类,无非就是Dao实现类里面调用映射文件里面定义的sql而已。

2.3.1 实体类

package com.newcapec.entity;

/**
 * Dept实体类
 */
public class Dept {

    private Integer deptno;
    private String dname;
    private String loc;

    public Integer getDeptno() {
        return deptno;
    }

    public void setDeptno(Integer deptno) {
        this.deptno = deptno;
    }

    public String getDname() {
        return dname;
    }

    public void setDname(String dname) {
        this.dname = dname;
    }

    public String getLoc() {
        return loc;
    }

    public void setLoc(String loc) {
        this.loc = loc;
    }

    @Override
    public String toString() {
        return "Dept{" +
                "deptno=" + deptno +
                ", dname='" + dname + '\'' +
                ", loc='" + loc + '\'' +
                '}';
    }
}

2.3.2 接口

package com.newcapec.dao;

import com.newcapec.entity.Dept;

import java.util.List;

public interface DeptDao {
    List<Dept> select();

    Dept selectById(Integer deptno);

    int insert(Dept dept);

    int update(Dept dept);

    int delete(Integer deptno);
}

2.3.3 实现类

package com.newcapec.dao.impl;

import com.newcapec.dao.DeptDao;
import com.newcapec.entity.Dept;
import com.newcapec.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;

import java.util.List;

/**
 * DeptDao实现类
 */
public class DeptDaoImpl implements DeptDao {
    @Override
    public List<Dept> select() {
        SqlSession sqlSession = MybatisUtil.getSession();
        List<Dept> list = sqlSession.selectList("dept.select");
        sqlSession.close();
        return list;
    }

    @Override
    public Dept selectById(Integer deptno) {
        SqlSession sqlSession = MybatisUtil.getSession();
        Dept dept = sqlSession.selectOne("dept.selectById", deptno);
        sqlSession.close();
        return dept;
    }

    @Override
    public int insert(Dept dept) {
        SqlSession sqlSession = MybatisUtil.getSession();
        int result = sqlSession.insert("dept.insert", dept);
        sqlSession.commit();
        sqlSession.close();
        return result;
    }

    @Override
    public int update(Dept dept) {
        SqlSession sqlSession = MybatisUtil.getSession();
        int result = sqlSession.update("dept.update", dept);
        sqlSession.commit();
        sqlSession.close();
        return result;
    }

    @Override
    public int delete(Integer deptno) {
        SqlSession sqlSession = MybatisUtil.getSession();
        int result = sqlSession.delete("dept.delete", deptno);
        sqlSession.commit();
        sqlSession.close();
        return result;
    }
}

2.3.4 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="dept">

    <select id="select" resultType="com.newcapec.entity.Dept">
        select deptno,dname,loc from dept
    </select>

    <select id="selectById" parameterType="java.lang.Integer" resultType="com.newcapec.entity.Dept">
        select deptno,dname,loc from dept where deptno=#{deptno}
    </select>

    <insert id="insert" parameterType="com.newcapec.entity.Dept">
        insert into dept(dname,loc) values(#{dname},#{loc})
    </insert>

    <update id="update" parameterType="com.newcapec.entity.Dept">
        update dept set dname=#{dname},loc=#{loc} where deptno=#{deptno}
    </update>

    <delete id="delete" parameterType="java.lang.Integer">
        delete from dept where deptno=#{deptno}
    </delete>
</mapper>

加载mapper文件:

<!-- 加载映射文件的位置 -->
<mappers>
    <mapper resource="mapper/Dept.xml"/>
</mappers>

2.3.5 测试

package com.newcapec;

import com.newcapec.dao.DeptDao;
import com.newcapec.dao.impl.DeptDaoImpl;
import com.newcapec.entity.Dept;
import org.junit.Test;

import java.util.List;

public class DaoTest {

    private DeptDao deptDao = new DeptDaoImpl();

    @Test
    public void testSelect() {
        List<Dept> list = deptDao.select();
        for (Dept dept : list) {
            System.out.println(dept);
        }
    }

    @Test
    public void testSelectById() {
        Dept dept = deptDao.selectById(20);
        System.out.println(dept);
    }

    @Test
    public void testInsert() {
        Dept dept = new Dept();
        dept.setDname("企划部");
        dept.setLoc("深圳");
        int result = deptDao.insert(dept);
        System.out.println("影响数据库的条数:" + result);
    }

    @Test
    public void testUpdate() {
        Dept dept = new Dept();
        dept.setDeptno(41);
        dept.setDname("生产部");
        dept.setLoc("杭州");
        int result = deptDao.update(dept);
        System.out.println("影响数据库的条数:" + result);
    }

    @Test
    public void testDelete() {
        int result = deptDao.delete(41);
        System.out.println("影响数据库的条数:" + result);
    }
}

2.3.6 原始DAO开发问题

    dao接口实现类方法中存在大量模板方法,设想能否将这些代码提取出来,大大减轻程序员的工作量。
    
    调用sqlSession的数据库操作方法需要指定statement的id,这里存在硬编码,不利于开发维护。
    
    调用SqlSession方法时传入的变量,由于SqlSession方法使用泛型,即使变量类型传入错误,在编译阶段也不报错,不利于程序员开发。  
    注:原始Dao开发和我们Web阶段讲解的Dao开发基本类似,都是有Dao接口和Dao实现类,无非Web阶段的Dao实现类中通过DBUtils来操作SQL;现在Mybatis的原始Dao开发,把SQL分离出去了,写在的XML映射文件里面而已。

2.4 Mapper代理方式(重点)

    Mapper代理开发方式只需要程序员编写Mapper接口(相当于Dao接口),由MyBatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法。
    
    程序员编写Mapper接口需要遵循一些开发规范,MyBatis可以自动生成Mapper接口实现类代理对象。

2.4.1 开发规范

    1、Mapper.xml文件中的namespace与mapper接口的类路径相同。

    2、Mapper接口方法名和Mapper.xml中定义的每个标签的id相同。

    3、Mapper接口方法的参数类型和mapper.xml中定义的每个sql的parameterType的类型相同。

    4、Mapper接口方法返回值类型和mapper.xml中定义的每个sql的resultType的类型相同。

注:Mapper.xml映射文件最好和Mapper接口名称一致;

2.4.2 实体类

package com.newcapec.entity;

import java.util.Date;

/**
 * Emp实体类
 */
public class Emp {

    private Integer empno;
    private String ename;
    private String job;
    private Integer mgr;
    private Date hiredate;
    private Double sal;
    private Double comm;
    private Integer deptno;

    public Integer getEmpno() {
        return empno;
    }

    public void setEmpno(Integer empno) {
        this.empno = empno;
    }

    public String getEname() {
        return ename;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    public String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }

    public Integer getMgr() {
        return mgr;
    }

    public void setMgr(Integer mgr) {
        this.mgr = mgr;
    }

    public Date getHiredate() {
        return hiredate;
    }

    public void setHiredate(Date hiredate) {
        this.hiredate = hiredate;
    }

    public Double getSal() {
        return sal;
    }

    public void setSal(Double sal) {
        this.sal = sal;
    }

    public Double getComm() {
        return comm;
    }

    public void setComm(Double comm) {
        this.comm = comm;
    }

    public Integer getDeptno() {
        return deptno;
    }

    public void setDeptno(Integer deptno) {
        this.deptno = deptno;
    }

    @Override
    public String toString() {
        return "Emp{" +
                "empno=" + empno +
                ", ename='" + ename + '\'' +
                ", job='" + job + '\'' +
                ", mgr=" + mgr +
                ", hiredate=" + hiredate +
                ", sal=" + sal +
                ", comm=" + comm +
                ", deptno=" + deptno +
                '}';
    }
}

2.4.3 Mapper接口

package com.newcapec.mapper;

import com.newcapec.entity.Emp;

import java.util.List;

/*
 * Mapper接口相当于我们之前写的Dao接口,只是在Mybatis里面我们习惯这么写而已。
 */
public interface EmpMapper {
    List<Emp> select();

    Emp selectById(Integer empno);

    void insert(Emp emp);

    int update(Emp emp);

    boolean delete(Integer empno);
}
  • 批量查询:方法返回值为List类型,表示SqlSession对象将调用selectList()方法。

  • 单条查询:方法返回值为单个实体对象,表示SqlSession对象将调用selectOne()方法。

  • 增删改:

    1. 方法返回值为void,表示SqlSession对象中insert,update,delete方法的返回值不做任何处理。

    2. 方法返回值为int类型,表示SqlSession对象中insert,update,delete方法的返回值直接返回。

    3. 方法返回值为boolean类型,表示根据SqlSession对象中的insert,update,delete方法返回值(影响数据库的条数)判断操作是否成功,如果影响数据库的条数大于0条,表示成功,否则表示失败。

2.4.4 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">
<!--namespace:与Mapper接口的全限定名保持一致-->
<mapper namespace="com.newcapec.mapper.EmpMapper">

    <!--
        statementId与Mapper接口的方法名称保持一致
        parameterType的类型必须与方法的参数类型保持一致
        resultType的类型必须与方法的返回值类型保持一致
    -->
    <select id="select" resultType="com.newcapec.entity.Emp">
        select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp
    </select>

    <select id="selectById" parameterType="java.lang.Integer" resultType="com.newcapec.entity.Emp">
        select empno,ename,job,hiredate,mgr,sal,comm,deptno from emp where empno=#{empno}
    </select>

    <insert id="insert" parameterType="com.newcapec.entity.Emp">
        insert into emp(ename,job,mgr,hiredate,sal,comm,deptno)
        values(#{ename},#{job},#{mgr},#{hiredate},#{sal},#{comm},#{deptno})
    </insert>

    <update id="update" parameterType="com.newcapec.entity.Emp">
        update emp set
        ename=#{ename},job=#{job},mgr=#{mgr},hiredate=#{hiredate},sal=#{sal},comm=#{comm},deptno=#{deptno}
        where empno=#{empno}
    </update>

    <delete id="delete" parameterType="java.lang.Integer">
        delete from emp where empno=#{empno}
    </delete>
</mapper>

加载mapper文件:

<!-- 加载映射文件的位置 -->
<mappers>
    <mapper resource="mapper/EmpMapper.xml"/>
</mappers>

2.4.5 测试

package com.newcapec;

import com.newcapec.entity.Emp;
import com.newcapec.mapper.EmpMapper;
import com.newcapec.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.util.Date;
import java.util.List;

public class MapperTest {

    private SqlSession sqlSession;
    private EmpMapper empMapper;

    @Before
    public void before() {
        sqlSession = MybatisUtil.getSession();
        //获取Mapper接口的代理对象
        empMapper = sqlSession.getMapper(EmpMapper.class);
    }

    @After
    public void after() {
        sqlSession.commit();
        sqlSession.close();
    }

    @Test
    public void test() {
        System.out.println(sqlSession);
        System.out.println(empMapper);
    }

    @Test
    public void testSelect() {
        List<Emp> list = empMapper.select();
        for (Emp emp : list) {
            System.out.println(emp);
        }
    }

    @Test
    public void testSelectById() {
        Emp emp = empMapper.selectById(7938);
        System.out.println(emp);
    }

    @Test
    public void testInsert() {
        Emp emp = new Emp();
        emp.setEname("小明");
        emp.setJob("职员");
        emp.setSal(4500.0);
        emp.setComm(1000.0);
        emp.setMgr(1);
        emp.setHiredate(new Date());

        empMapper.insert(emp);
    }

    @Test
    public void testUpdate() {
        Emp emp = new Emp();
        emp.setEmpno(7940);
        emp.setEname("小李");
        emp.setJob("秘书");
        emp.setSal(5300.0);
        emp.setComm(1300.0);
        emp.setMgr(1);
        emp.setHiredate(new Date());

        int result = empMapper.update(emp);
        System.out.println("方法的返回值:" + result);
    }

    @Test
    public void testDelete() {
        boolean result = empMapper.delete(7940);
        System.out.println("方法的返回值:" + result);
    }
}

    Mybatis官方推荐使用mapper代理方法开发mapper接口,程序员不用编写mapper接口实现类,使用mapper代理方法时,输入参数可以使用pojo包装对象或map对象,保证dao的通用性。

 三、Mybatis 核心配置文件

MyBatis的全局配置文件mybatis-config.xml,配置内容如下:

  • properties(属性)

  • settings(全局配置参数)

  • typeAliases(类型别名)

  • typeHandlers(类型处理器)

  • objectFactory(对象工厂)

  • plugins(插件)

  • environments(环境集合属性对象)

    • environment(环境子属性对象)

      • transactionManager(事务管理)

      • dataSource(数据源)

  • mappers(映射器)

3.1 properties属性

    将数据库连接参数单独配置在db.properties中,只需要在mybatis-config.xml中加载db.properties的属性值。在mybatis-config.xml中就不需要对数据库连接参数硬编码。

# Oracle相关配置
jdbc.oracle.driver=oracle.jdbc.driver.OracleDriver
jdbc.oracle.url=jdbc:oracle:thin:@localhost:1521:orcl
jdbc.oracle.username=scott
jdbc.oracle.password=tiger

# Mysql相关配置
jdbc.mysql.driver=com.mysql.jdbc.Driver
jdbc.mysql.url=jdbc:mysql://localhost:3306/ssm?characterEncoding=utf8&useSSL=false
jdbc.mysql.username=root
jdbc.mysql.password=root

使用 <properties> 标签加载属性文件:

  • resource: 从classpath下读取资源文件

    • classpath为maven项目中的src/main/resources目录 + src/main/java目录,编译之后的目录为target/classes;

  • url: 从当前系统环境的文件系统中读取资源

    • windows: d:/conf/test.properties

    • linux: /home/tom/conf/test.properties

<!-- 加载外部资源文件 -->
<properties resource="db.properties">
    <!--properties中还可以配置一些属性-->
    <!--<property name="mysql.url" value="jdbc:mysql://localhost:3306/ssm"/>-->
</properties>

在环境中使用:

<environments default="mysql">
    <environment id="mysql">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="${jdbc.mysql.driver}"/>
            <property name="url" value="${jdbc.mysql.url}"/>
            <property name="username" value="${jdbc.mysql.username}"/>
            <property name="password" value="${jdbc.mysql.password}"/>
        </dataSource>
    </environment>
</environments>

注意:MyBatis将按照下面的顺序来加载属性

  1. 在properties标签体内定义的属性首先被读取;

  2. 然后会读取properties标签中resource或url加载的属性,它会覆盖已读取的同名属性;

  3. 最后读取parameterType传递的属性,它会覆盖已读取的同名属性;

建议:

  1. 不要在properties标签体内添加任何属性值,只将属性值定义在properties文件中;

  2. 在properties文件中定义属性名要有一定的特殊性,如:XXXXX.XXXXX.XXXX;

3.2 settings全局配置参数

    MyBatis框架在运行时可以调整一些运行参数,比如:开启二级缓存、开启延迟加载。

可配置参数如下:

设置描述有效值默认值
cacheEnabled在全局范围内启用或禁用缓存配置任何映射器在此配置下。true, falsetrue
lazyLoadingEnabled在全局范围内启用或禁用延迟加裁。禁用时,所有将会将热加载。true, falsefalse
aggressiveLazyLoading当开启时,任何方法的调用都会加载该对象的所有属性。true, falsefalse
multipleResultSetsEnabled是否允许单一语句返回多结果集(需要驱动支持)。true, falsetrue
useColumnLabel使用列标签代替列名。不同的驱动在这方面会有不同的表现,具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。true, falsetrue
useGeneratedKeys允许 JDBC 支持自动生成主键,需要驱动支持。 如果设置为 true 则这个设置强制使用自动生成主键,尽管一些驱动不能支持但仍可正常工作(比如 Derby)。true, falsefalse
autoMappingBehavior指定 MyBatis 应如何自动映射列到字段或属性。NONE 表示取消自动映射;PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。 FULL 会自动映射任意复杂的结果集(无论是否嵌套)。NONE, PARTIAL, FULLPARTIAL
autoMappingUnknownColumnBehavior指定发现自动映射目标未知列(或者未知属性类型)的行为。NONE: 不做任何反应;WARNING: 输出提醒日志 ('AutoMappingUnknownColumnBehavior'的日志等级必须设置为 WARN);FAILING: 映射失败 (抛出 SqlSessionException)。NONE, WARNING, FAILINGNONE
defaultExecutorType配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。SIMPLE, REUSE, BATCHSIMPLE
defaultStatementTimeout设置超时时间,它决定驱动等待数据库响应的秒数。任意正整数未设置
defaultFetchSize为驱动的结果集获取数量(fetchSize)设置一个提示值。此参数只可以在查询设置中被覆盖。任意正整数未设置
safeRowBoundsEnabled允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为 false。true, falsefalse
safeResultHandlerEnabled允许在嵌套语句中使用分页(ResultHandler)。如果允许使用则设置为 false。true, falsetrue
mapUnderscoreToCamelCase是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。true, falsefalse
localCacheScopeMyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。SESSION, STATEMENTSESSION
jdbcTypeForNull当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。 某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。JdbcType 常量,常用值:NULL, VARCHAR 或 OTHEROTHER
lazyLoadTriggerMethods指定哪个对象的方法触发一次延迟加载。用逗号分隔的方法列表equals,clone,hashCode,toString
defaultScriptingLanguage指定动态 SQL 生成的默认语言。一个类型别名或完全限定类名org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
defaultEnumTypeHandler指定 Enum 使用的默认 TypeHandler 。一个类型别名或完全限定类名org.apache.ibatis.type.EnumTypeHandler
callSettersOnNulls指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这在依赖于 Map.keySet() 或 null 值初始化的时候比较有用。注意基本类型(int、boolean 等)是不能设置成 null 的。true, falsefalse
returnInstanceForEmptyRow当返回行的所有列都是空时,MyBatis默认返回 null。 当开启这个设置时,MyBatis会返回一个空实例。 请注意,它也适用于嵌套的结果集 (如集合或关联)。true, falsefalse
logPrefix指定 MyBatis 增加到日志名称的前缀。任何字符串未设置
logImpl指定 MyBatis 所用日志的具体实现,未指定时将自动查找。SLF4J, LOG4J, LOG4J2, JDK_LOGGING, COMMONS_LOGGING, STDOUT_LOGGING, NO_LOGGING未设置
proxyFactory指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具。CGLIB, JAVASSISTJAVASSIST
vfsImpl指定 VFS 的实现。自定义 VFS 的实现的类全限定名,以逗号分隔未设置
useActualParamName允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 -parameters 选项。true, falsetrue
configurationFactory指定一个提供 Configuration 实例的类。 这个被返回的 Configuration 实例用来加载被反序列化对象的延迟加载属性值。 这个类必须包含一个签名为static Configuration getConfiguration() 的方法。类型别名或者全类名未设置

开启下划线与驼峰命名的转换:

<settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

3.3 typeAliases类型别名

    在mapper.xml中,定义很多的statement。statement需要parameterType指定输入参数的类型、需要resultType指定输出结果的映射类型。如果在指定类型时输入类型全路径,不方便进行开发,可以针对parameterType或resultType指定的类型定义一些别名,在mapper.xml中通过别名定义,方便开发。

3.3.1 MyBatis默认支持别名

别名映射的类型别名映射的类型
_bytebytebytejava.lang.Byte
_shortshortshortjava.lang.Short
_intintintjava.lang.Integer
_integerintintegerjava.lang.Integer
_longlonglongjava.lang.Long
_floatfloatfloatjava.lang.Float
_doubledoubledoublejava.lang.Double
_booleanbooleanbooleanjava.lang.Boolean
stringjava.lang.Stringdatejava.util.Date
mapjava.util.Maphashmapjava.util.HashMap
listjava.util.Listarraylistjava.util.ArrayList
objectjava.lang.Object

3.3.2 自定义别名

单个定义别名:

    单个类型的别名配置:typeAlias。
        type:类型,java中类的全限定名;
        alias:别名;

<typeAliases>
    <typeAlias type="com.newcapec.entity.Dept" alias="dd"/>
</typeAliases>

批量定义别名:

    批量类型别名的配置:package。
        name:需要配置别名的类所在的包;
        设置包名之后,此包下所有的类都拥有类型别名,其别名为:该类的类名,首字母大小均可。

<typeAliases>
    <!--
        <package> 批量别名配置
        name:需要配置别名的实体类所在包的包名
        默认别名为该类的类名,其首字母大小均可
    -->
    <package name="com.newcapec.entity"/>
</typeAliases>

注解定义别名:

    在实体类上可以使用 @Alias("name") 注解来标识该类的别名。

@Alias("depart")
public class Dept {
    //...
}

注意:配置和注解仅能使用一种方式,当注解存在时,则其别名为其注解值。

3.3.3 应用

实体类:

public class Dept {

    private Integer deptno;
    private String dname;
    private String loc;

    public Integer getDeptno() {
        return deptno;
    }

    public void setDeptno(Integer deptno) {
        this.deptno = deptno;
    }

    public String getDname() {
        return dname;
    }

    public void setDname(String dname) {
        this.dname = dname;
    }

    public String getLoc() {
        return loc;
    }

    public void setLoc(String loc) {
        this.loc = loc;
    }

    @Override
    public String toString() {
        return "Dept{" +
            "deptno=" + deptno +
            ", dname='" + dname + '\'' +
            ", loc='" + loc + '\'' +
            '}';
    }
}

接口:

package com.newcapec.mapper;

import com.newcapec.entity.Dept;

import java.util.List;

public interface DeptMapper {

    List<Dept> select();

    Dept selectById(Integer deptno);

    List<Dept> selectByDname(String dname);
}

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.newcapec.mapper.DeptMapper">

    <select id="select" resultType="dd">
        select deptno,dname,loc from dept
    </select>

    <select id="selectById" parameterType="int" resultType="dept">
        select deptno,dname,loc from dept where deptno=#{deptno}
    </select>

    <select id="selectByDname" parameterType="string" resultType="Dept">
        select deptno,dname,loc from dept where dname=#{dname}
    </select>
</mapper>

测试:

package com.newcapec;

import com.newcapec.entity.Dept;
import com.newcapec.mapper.DeptMapper;
import com.newcapec.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

/**
 * 别名测试
 */
public class TypeAliasTest {

    @Test
    public void testSelect() {
        SqlSession sqlSession = MybatisUtil.getSession();
        DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class);
        List<Dept> list = deptMapper.select();
        for (Dept dept : list) {
            System.out.println(dept);
        }
        sqlSession.close();
    }

    @Test
    public void testSelectById() {
        SqlSession sqlSession = MybatisUtil.getSession();
        DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class);
        Dept dept = deptMapper.selectById(10);
        System.out.println(dept);
        sqlSession.close();
    }

    @Test
    public void testSelectByDname() {
        SqlSession sqlSession = MybatisUtil.getSession();
        DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class);
        List<Dept> list = deptMapper.selectByDname("ACCOUNTING");
        System.out.println(list);
        sqlSession.close();
    }
}

3.4 typeHandlers类型处理器

    MyBatis中通过typeHandlers完成jdbc类型和Java类型的转换,MyBatis自带的类型处理器基本上满足日常需求,不需要单独定义。

MyBatis支持类型处理器:

类型处理器Java类型JDBC类型
BooleanTypeHandlerBoolean,boolean任何兼容的布尔值
ByteTypeHandlerByte,byte任何兼容的数字或字节类型
ShortTypeHandlerShort,short任何兼容的数字或短整型
IntegerTypeHandlerInteger,int任何兼容的数字和整型
LongTypeHandlerLong,long任何兼容的数字或长整型
FloatTypeHandlerFloat,float任何兼容的数字或单精度浮点型
DoubleTypeHandlerDouble,double任何兼容的数字或双精度浮点型
BigDecimalTypeHandlerBigDecimal任何兼容的数字或十进制小数类型
StringTypeHandlerStringCHAR和VARCHAR类型
ClobTypeHandlerStringCLOB和LONGVARCHAR类型
NStringTypeHandlerStringNVARCHAR和NCHAR类型
NClobTypeHandlerStringNCLOB类型
ByteArrayTypeHandlerbyte[]任何兼容的字节流类型
BlobTypeHandlerbyte[]BLOB和LONGVARBINARY类型
DateTypeHandlerjava.util.DateTIMESTAMP类型
DateOnlyTypeHandlerjava.util.DateDATE类型
TimeOnlyTypeHandlerjava.util.DateTIME类型
SqlTimestampTypeHandlerjava.sql.TimestampTIMESTAMP类型
SqlDateTypeHandlerjava.sql.DateDATE类型
SqlTimeTypeHandlerjava.sql.TimeTIME类型
ObjectTypeHandler任意其他或未指定类型
EnumTypeHandlerEnumeration类型VARCHAR-任何兼容的字符串类型,作为代码存储(而不是索引)

3.5 mappers映射器

    mappers:映射器,加载mapper文件。

3.5.1 单个加载映射文件

    单个映射文件的加载:mapper。

  • resource: 从classpath下加载mapper文件

<mappers>
    <!--
        <mapper>单个加载映射文件
        resource:通过相对路径加载文件(项目源目录下的文件)
        url:通过绝对路径加载文件(文件系统中文件)
    -->
    <mapper resource="mapper/DeptMapper.xml"/>
    <mapper resource="mapper/EmpMapper.xml"/>
</mappers>
  • class: 配置dao接口的全限定名,通过Java中的dao接口的名称加载mapper.xml文件

<mappers>
    <!--
        <mapper>单个加载映射文件
        class : 配置mapper接口的全限定名,通过Java中的Mapper接口来加载映射文件
    -->
    <mapper class="com.newcapec.mapper.DeptMapper"/>
    <mapper class="com.newcapec.mapper.EmpMapper"/>
</mappers>

要求:

  1. 必须使用mapper代理的开发方式;

  2. mapper.xml文件的名称必须与dao接口的名称保持一致;

  3. mapper.xml文件必须与dao接口放在同一个目录下;

    注意:同一个目录是指编译之后的目录,并非开发时的目录。

3.5.2 批量加载映射文件

    批量映射文件的加载:package。

  • name:dao接口与mapper文件存放的共同的目录名称

    此种配置使mapper扫描指定包,并在此包下获取所有的接口以及与接口名称相同mapper文件,并加载;

<mappers>
    <!--
        <package>批量加载映射文件
        name:存放Mapper接口与mapper.xml文件的包名
    -->
    <package name="com.newcapec.mapper"/>
</mappers>

要求:与mapper接口加载单个映射文件(class方式)一致。

3.5.3 应用

实体类:

public class Emp {

    private Integer empno;
    private String ename;
    private String job;
    private Integer mgr;
    private Date hiredate;
    private Double sal;
    private Double comm;
    private Integer deptno;
    private String gender;

    public Integer getEmpno() {
        return empno;
    }

    public void setEmpno(Integer empno) {
        this.empno = empno;
    }

    public String getEname() {
        return ename;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    public String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }

    public Integer getMgr() {
        return mgr;
    }

    public void setMgr(Integer mgr) {
        this.mgr = mgr;
    }

    public Date getHiredate() {
        return hiredate;
    }

    public void setHiredate(Date hiredate) {
        this.hiredate = hiredate;
    }

    public Double getSal() {
        return sal;
    }

    public void setSal(Double sal) {
        this.sal = sal;
    }

    public Double getComm() {
        return comm;
    }

    public void setComm(Double comm) {
        this.comm = comm;
    }

    public Integer getDeptno() {
        return deptno;
    }

    public void setDeptno(Integer deptno) {
        this.deptno = deptno;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    @Override
    public String toString() {
        return "Emp{" +
                "empno=" + empno +
                ", ename='" + ename + '\'' +
                ", job='" + job + '\'' +
                ", mgr=" + mgr +
                ", hiredate=" + hiredate +
                ", sal=" + sal +
                ", comm=" + comm +
                ", deptno=" + deptno +
                ", gender='" + gender + '\'' +
                '}';
    }
}

接口:

public interface EmpMapper {

    List<Emp> select();
}

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.newcapec.mapper.EmpMapper">

    <select id="select" resultType="Emp">
        select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp
    </select>
</mapper>

测试:

/**
 * Mappers测试
 */
public class MappersTest {
    @Test
    public void testSelectEmp() {
        SqlSession sqlSession = MybatisUtil.getSession();
        EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);
        List<Emp> list = empMapper.select();
        for (Emp emp : list) {
            System.out.println(emp);
        }
        sqlSession.close();
    }
}

3.6 Mybatis配置文件的标签顺序

Mybatis配置文件中各标签的位置顺序如下:

	properties?,
    settings?, 
    typeAliases?, 
    typeHandlers?, 
    objectFactory?, 
    objectWrapperFactory?, 
    reflectorFactory?, 
    plugins?, 
    environments?, 
    databaseIdProvider?, 
    mappers?
        
    具体可以参考 http://mybatis.org/dtd/mybatis-3-config.dtd 文件。

四、Mybatis Mapper配置文件

mapper.xml映射文件中定义了操作数据库的Sql,每个Sql是一个statement,映射文件是MyBatis的核心。

4.1 parameterType输入映射

    parameterType配置输入参数的类型。

4.1.1 表结构

CREATE TABLE `users`  (
  `id` int(11) PRIMARY KEY AUTO_INCREMENT,
  `username` varchar(20),
  `password` varchar(50),
  `realname` varchar(20)
);

INSERT INTO `users` VALUES (1, 'admin', '123456', '管理员');
INSERT INTO `users` VALUES (2, 'tom', '123', '汤姆');
INSERT INTO `users` VALUES (3, 'jerry', '456', '杰瑞');
INSERT INTO `users` VALUES (4, 'zhangsan', '111', '张三');
INSERT INTO `users` VALUES (5, 'lisi', '222', '李四');

4.1.2 实体类

package com.newcapec.entity;

public class Users {

    private Integer id;
    private String username;
    private String password;
    private String realname;

    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;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getRealname() {
        return realname;
    }

    public void setRealname(String realname) {
        this.realname = realname;
    }

    @Override
    public String toString() {
        return "Users{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", realname='" + realname + '\'' +
                '}';
    }
}

4.1.3 简单类型

    Java基本数据类型以及包装类,String字符串类型。

mapper接口:

package com.newcapec.mapper;

import com.newcapec.entity.Users;

import java.util.List;

public interface UsersMapper {
    List<Users> selectByRealname(String realname);
}

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.newcapec.mapper.UsersMapper">

    <select id="selectByRealname" parameterType="java.lang.String" resultType="com.newcapec.entity.Users">
        select id,username,password,realname from users where realname like concat('%',#{realname},'%')
    </select>
</mapper>

测试:

package com.newcapec;

import com.newcapec.entity.Users;
import com.newcapec.mapper.UsersMapper;
import com.newcapec.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class ParameterTypeTest {

    @Test
    public void testSimpleParam() {
        SqlSession sqlSession = MybatisUtil.getSession();
        UsersMapper usersMapper = sqlSession.getMapper(UsersMapper.class);
        List<Users> list = usersMapper.selectByRealname("张");
        for (Users users : list) {
            System.out.println(users);
        }
        sqlSession.close();
    }
}

4.1.4 实体类或自定义类型

    开发中通过实体类或pojo类型传递查询条件,查询条件是综合的查询条件,不仅包括实体类中查询条件还包括其它的查询条件,这时可以使用包装对象传递输入参数。

  • 自定义类型

分页类:

package com.newcapec.entity;

/**
 * 分页类
 */
public class Page {

    //当前页码
    private Integer pageNum = 1;
    //每页条数
    private Integer pageSize = 3;
    //总页数: 总记录数/每页条数,除不尽+1
    private Integer pages;
    //总记录数
    private Integer total;

    /**
     * mysql
     * 起始偏移量:(当前页码-1)*每页条数
     */
    private Integer offset;

    /**
     * oracle
     * 起始条数:(当前页码-1)*每页条数+1
     * 结束条数: 当前页码*每页条数
     */
    private Integer start;
    private Integer end;

    public Integer getPageNum() {
        return pageNum;
    }

    public void setPageNum(Integer pageNum) {
        this.pageNum = pageNum;
    }

    public Integer getPageSize() {
        return pageSize;
    }

    public void setPageSize(Integer pageSize) {
        this.pageSize = pageSize;
    }

    public Integer getPages() {
        return getTotal() % getPageSize() == 0 ? getTotal() / getPageSize() : getTotal() / getPageSize() + 1;
    }

    public void setPages(Integer pages) {
        this.pages = pages;
    }

    public Integer getTotal() {
        return total;
    }

    public void setTotal(Integer total) {
        this.total = total;
    }

    public Integer getOffset() {
        return (getPageNum() - 1) * getPageSize();
    }

    public void setOffset(Integer offset) {
        this.offset = offset;
    }

    public Integer getStart() {
        return (getPageNum() - 1) * getPageSize() + 1;
    }

    public void setStart(Integer start) {
        this.start = start;
    }

    public Integer getEnd() {
        return getPageNum() * getPageSize();
    }

    public void setEnd(Integer end) {
        this.end = end;
    }
}

复合类:UsersQuery

package com.newcapec.entity;

/**
 * 多条件查询复合类
 */
public class UsersQuery {

    private Users users;
    private Page page;

    public Users getUsers() {
        return users;
    }

    public void setUsers(Users users) {
        this.users = users;
    }

    public Page getPage() {
        return page;
    }

    public void setPage(Page page) {
        this.page = page;
    }
}
  • mapper接口
List<Users> selectByPage(Page page);

List<Users> selectByRealnameAndPage(UsersQuery usersQuery);
  • mapper文件
<select id="selectByPage" parameterType="com.newcapec.entity.Page" resultType="com.newcapec.entity.Users">
    select id,username,password,realname from users order by id limit #{offset}, #{pageSize}
</select>

<select id="selectByRealnameAndPage" parameterType="com.newcapec.entity.UsersQuery" resultType="com.newcapec.entity.Users">
    select id,username,password,realname from users
    where realname like concat('%',#{users.realname},'%')
    order by id limit #{page.offset}, #{page.pageSize}
</select>
  • 测试
@Test
public void testClassParam1() {
    SqlSession sqlSession = MybatisUtil.getSession();
    UsersMapper usersMapper = sqlSession.getMapper(UsersMapper.class);
    Page page = new Page();
    page.setPageNum(1);

    System.out.println("mysql起始偏移量:" + page.getOffset());
    System.out.println("起始条数:" + page.getStart());
    System.out.println("结束条数:" + page.getEnd());
    List<Users> list = usersMapper.selectByPage(page);
    for (Users users : list) {
        System.out.println(users);
    }
    sqlSession.close();
}

@Test
public void testPojoParam2() {
    SqlSession sqlSession = MybatisUtil.getSession();
    UsersMapper usersMapper = sqlSession.getMapper(UsersMapper.class);
    Page page = new Page();
    page.setPageNum(1);

    Users users = new Users();
    users.setRealname("张");
    UsersQuery usersQuery = new UsersQuery();
    usersQuery.setPage(page);
    usersQuery.setUsers(users);

    List<Users> list = usersMapper.selectByRealnameAndPage(usersQuery);
    for (Users u : list) {
        System.out.println(u);
    }
    sqlSession.close();
}

4.1.5 Map类型

mapper接口:

List<Users> selectUseMap(Map<String, Object> map);

mapper文件:

<select id="selectUseMap" parameterType="java.util.HashMap" resultType="com.newcapec.entity.Users">
    select id,username,password,realname from users
    where realname like concat('%',#{name},'%')
    order by id limit #{begin}, #{size}
</select>

测试:

@Test
public void testMapParam() {
    SqlSession sqlSession = MybatisUtil.getSession();
    UsersMapper usersMapper = sqlSession.getMapper(UsersMapper.class);
    Map<String, Object> map = new HashMap<>();
    map.put("name", "李");
    map.put("size", 5);
    map.put("begin", 0);

    List<Users> list = usersMapper.selectUseMap(map);
    for (Users u : list) {
        System.out.println(u);
    }
    sqlSession.close();
}

4.1.6 多输入参数

    MyBatis中允许有多个输入参数,可使用@Param注解。

    这种做法类似与Map类型的输入参数,其中@Param注解的value属性值为Map的key,在映射文件中通过ognl可获取对应的value,并且parameterType可以不指定类型。

mapper接口:

Users login(@Param("uname") String username, @Param("pwd") String password);

mapper文件:

<select id="login" parameterType="java.util.HashMap" resultType="com.newcapec.entity.Users">
    select id,username,password,realname from users
    where username=#{uname} and password=#{pwd}
</select>

测试:

@Test
public void testMultiParam() {
    SqlSession sqlSession = MybatisUtil.getSession();
    UsersMapper usersMapper = sqlSession.getMapper(UsersMapper.class);

    Users users = usersMapper.login("jerry", "456");
    System.out.println(users);
    sqlSession.close();
}

4.2 resultType输出映射

4.2.1 表结构

CREATE TABLE `person`  (
  `id` int(11) PRIMARY KEY AUTO_INCREMENT,
  `person_name` varchar(20),
  `person_age` int(4),
  `person_address` varchar(50)
);

INSERT INTO `person` VALUES (1, '曹操', 40, '洛阳');
INSERT INTO `person` VALUES (2, '刘备', 38, '成都');
INSERT INTO `person` VALUES (3, '孙权', 29, '杭州');
INSERT INTO `person` VALUES (4, '关羽', 35, '荆州');
INSERT INTO `person` VALUES (5, '张飞', 32, '成都');
INSERT INTO `person` VALUES (6, '曹仁', 28, '许都');

4.2.2 实体类

package com.newcapec.entity;

public class Person {

    private Integer id;
    private String personName;
    private Integer personAge;
    private String personAddress;

    public Integer getId() {
        return id;
    }

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

    public String getPersonName() {
        return personName;
    }

    public void setPersonName(String personName) {
        this.personName = personName;
    }

    public Integer getPersonAge() {
        return personAge;
    }

    public void setPersonAge(Integer personAge) {
        this.personAge = personAge;
    }

    public String getPersonAddress() {
        return personAddress;
    }

    public void setPersonAddress(String personAddress) {
        this.personAddress = personAddress;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", personName='" + personName + '\'' +
                ", personAge=" + personAge +
                ", personAddress='" + personAddress + '\'' +
                '}';
    }
}

4.2.3 简单类型

    查询出来的结果集只有一行且一列,可以使用简单类型进行输出映射。

mapper接口:

package com.newcapec.mapper;

public interface PersonMapper {
    // 查询Person的总数量
    Integer selectCount();
}

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.newcapec.mapper.PersonMapper">

    <select id="selectCount" resultType="java.lang.Integer">
        select count(1) from person
    </select>
</mapper>

测试:

public class ResultTypeTest {

    @Test
    public void testSimpleResult() {
        SqlSession sqlSession = MybatisUtil.getSession();
        PersonMapper personMapper = sqlSession.getMapper(PersonMapper.class);

        int total = personMapper.selectCount();
        System.out.println("总记录数:" + total);
        sqlSession.close();
    }
}

4.2.4 实体类对象和列表

    不管是输出的实体类是单个对象还是一个列表(list中包括实体类对象),在mapper.xml中resultType指定的类型是一样的
在原始Dao的方式中,通过selectOne和selectList方法来区分返回值为单个对象或集合列表,而在mapper代理中,则通过接口中定义的方法返回值来区分。

mapper接口:

Person selectById(Integer id);

List<Person> selectAll();

mapper文件:

<select id="selectById" parameterType="java.lang.Integer" resultType="com.newcapec.entity.Person">
    select id,person_name personName,person_age personAge,person_address personAddress from person where id=#{id}
</select>

<select id="selectAll" resultType="com.newcapec.entity.Person">
    select id,person_name personName,person_age personAge,person_address personAddress from person
</select>

测试:

@Test
public void testResultType1() {
    SqlSession sqlSession = MybatisUtil.getSession();
    PersonMapper personMapper = sqlSession.getMapper(PersonMapper.class);

    Person person = personMapper.selectById(1);
    System.out.println(person);
    sqlSession.close();
}

@Test
public void testResultType2() {
    SqlSession sqlSession = MybatisUtil.getSession();
    PersonMapper personMapper = sqlSession.getMapper(PersonMapper.class);

    List<Person> list = personMapper.selectAll();
    for (Person person : list) {
        System.out.println(person);
    }
    sqlSession.close();
}

4.2.5 resultMap

  • resultType可以指定将查询结果映射为实体类,但需要实体类的属性名和SQL查询的列名一致方可映射成功,当然如果开启下划线转驼峰 或 Sql设置列别名,也可以自动映射。

  • 如果SQL查询字段名和实体类的属性名不一致,可以通过resultMap将字段名和属性名作一个对应关系,resultMap实质上还会将查询结果映射到实体类对象中。

  • resultMap可以实现将查询结果映射为复合型的实体类,比如在查询结果映射对象中包括实体类和list实现一对一查询和一对多查询。

mapper接口:

List<Person> select();

mapper文件:

    使用resultMap作为statement的输出映射类型。

<resultMap id="selectResultMap" type="com.newcapec.entity.Person">
    <id property="id" column="id"/>
    <result property="personName" column="person_name"/>
    <result property="personAge" column="person_age"/>
    <result property="personAddress" column="person_address"/>
</resultMap>
<select id="select" resultMap="selectResultMap">
    select id,person_name,person_age,person_address from person
</select>
  • resultType: 自动映射

  • resultMap: 手动映射

    • id: 唯一标识,名称;

    • type: 手动映射的java类型

    • 子标签 <id/> 配置数据库表中的主键和实体类中属性的对应关系

    • 子标签 <result/> 配置数据库表中的普通字段和实体类中属性的对应关系

      • property:实体类中的成员变量名

      • column:结果集中的字段名称

      • javaType:实体类成员变量的类型,由mybaits自动识别,可不配置

      • jdbcType:表字段的类型,由mybaits自动识别,可不配置

      • typeHandler:自定义类型处理器,用的相对比较少

测试:

@Test
public void testResultMap() {
    SqlSession sqlSession = MybatisUtil.getSession();
    PersonMapper personMapper = sqlSession.getMapper(PersonMapper.class);

    List<Person> list = personMapper.select();
    for (Person person : list) {
        System.out.println(person);
    }
    sqlSession.close();
}

4.3 动态SQL

4.3.1 什么是动态SQL

    动态Sql是指MyBatis核心对Sql语句进行灵活操作,通过表达式进行判断,对Sql进行灵活拼接、组装。
    
    比如:
        我们要查询姓名中带 M 和 高于 1000的员工信息;
        可能有时候我们需要不带条件查询;
        可能有时候我们需要模糊查询;
        可能有时候需要根据多条件查询;
        动态SQL可以帮助我们解决这些问题;

    通过mybatis提供的各种标签方法实现动态拼接sql。

4.3.2 if标签

    判断标签,当参数符合判断条件拼接SQL语句。

实体类:

package com.newcapec.entity;

import java.util.Date;

public class Emp {

    private Integer empno;
    private String ename;
    private String job;
    private Integer mgr;
    private Date hiredate;
    private Double sal;
    private Double comm;
    private Integer deptno;
    private String gender;

    public Integer getEmpno() {
        return empno;
    }

    public void setEmpno(Integer empno) {
        this.empno = empno;
    }

    public String getEname() {
        return ename;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    public String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }

    public Integer getMgr() {
        return mgr;
    }

    public void setMgr(Integer mgr) {
        this.mgr = mgr;
    }

    public Date getHiredate() {
        return hiredate;
    }

    public void setHiredate(Date hiredate) {
        this.hiredate = hiredate;
    }

    public Double getSal() {
        return sal;
    }

    public void setSal(Double sal) {
        this.sal = sal;
    }

    public Double getComm() {
        return comm;
    }

    public void setComm(Double comm) {
        this.comm = comm;
    }

    public Integer getDeptno() {
        return deptno;
    }

    public void setDeptno(Integer deptno) {
        this.deptno = deptno;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    @Override
    public String toString() {
        return "Emp{" +
                "empno=" + empno +
                ", ename='" + ename + '\'' +
                ", job='" + job + '\'' +
                ", mgr=" + mgr +
                ", hiredate=" + hiredate +
                ", sal=" + sal +
                ", comm=" + comm +
                ", deptno=" + deptno +
                ", gender='" + gender + '\'' +
                '}';
    }
}

mapper接口:

public interface EmpMapper {
    
    List<Emp> selectUseIf(Emp emp);
}

mapper文件:

<select id="selectUseIf" parameterType="com.newcapec.entity.Emp" resultType="com.newcapec.entity.Emp">
    select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp
    where
    <!--
		注意:判断条件中使用的变量为实体类或输入参数的属性
			 空字符串的判断仅能使用在字符串类型的属性中
	-->
    <if test="ename != null and ename != ''">
        ename like concat('%',#{ename},'%')
    </if>
    <if test="sal != null">
        and sal=#{sal}
    </if>
    <if test="deptno != null">
        and deptno=#{deptno}
    </if>
</select>

测试:

/*
 * 动态sql测试
 */
public class DynamicSqlTest {

    @Test
    public void testIf() {
        SqlSession sqlSession = MybatisUtil.getSession();
        EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);

        Emp emp = new Emp();
        emp.setEname("S");
        emp.setSal(1300.0);
        emp.setDeptno(20);

        List<Emp> list = empMapper.selectUseIf(emp);
        for (Emp e : list) {
            System.out.println(e);
        }
        sqlSession.close();
    }
}

4.3.3 where标签

    where标签,替代where关键字。
        1、当where标签内所有的条件都不成立,不会拼接where关键字,只要有一个条件成立就会在SQL语句中拼接where关键字。
        2、where标签会自动剔除条件头部的and或者or关键字。

mapper接口:

List<Emp> selectUseWhere(Emp emp);

mapper文件:

<select id="selectUseWhere" parameterType="com.newcapec.entity.Emp" resultType="com.newcapec.entity.Emp">
    select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp
    <where>
        <if test="ename != null and ename != ''">
            ename like concat('%',#{ename},'%')
        </if>
        <if test="sal != null">
            and sal=#{sal}
        </if>
        <if test="deptno != null">
            and deptno=#{deptno}
        </if>
    </where>
</select>

测试:

@Test
public void testWhere() {
    SqlSession sqlSession = MybatisUtil.getSession();
    EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);

    Emp emp = new Emp();
    emp.setEname("S");
    emp.setSal(1300.0);
    emp.setDeptno(20);

    List<Emp> list = empMapper.selectUseWhere(emp);
    for (Emp e : list) {
        System.out.println(e);
    }
    sqlSession.close();
}

4.3.4 set标签

    set标签,替代set关键字。
        1、当set标签内所有的条件都不成立,不会拼接set关键字,只要有一个条件成立就会在SQL语句中拼接set关键字。
        2、注意:如果set包含的内容为空SQL语句会出错。
        3、set标签会自动剔除条件末尾的任何不相关的逗号。

mapper接口:

void updateUseSet(Emp emp);

mapper文件:

<update id="updateUseSet" parameterType="com.newcapec.entity.Emp">
    update emp
    <set>
        <if test="ename != null">
            ename=#{ename},
        </if>
        <if test="job != null">
            job=#{job},
        </if>
        <if test="mgr != null">
            mgr=#{mgr},
        </if>
        <if test="hiredate != null">
            hiredate=#{hiredate},
        </if>
        <if test="sal != null">
            sal=#{sal},
        </if>
        <if test="comm != null">
            comm=#{comm},
        </if>
        <if test="deptno != null">
            deptno=#{deptno},
        </if>
    </set>
    where empno=#{empno}
</update>

测试:

@Test
public void testSet() {
    SqlSession sqlSession = MybatisUtil.getSession();
    EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);

    Emp emp = new Emp();
    emp.setEmpno(7938);
    emp.setEname("JACK");
    emp.setJob("MANAGER");
    emp.setMgr(7844);
    emp.setSal(5600.0);
    emp.setComm(1200.0);
    emp.setHiredate(new Date());
    emp.setDeptno(30);
    empMapper.updateUseSet(emp);
    sqlSession.commit();
    sqlSession.close();
}

4.3.5 trim标签

trim标签属性解析:

  • prefix:前缀,包含内容前加上某些字符。

  • suffix:后缀,包含内容后加上某些字符。

  • prefixOverrides:剔除包含内容前的某些字符。

  • suffixOverrides:剔除包含内容后的某些字符。

mapper接口:

void insertUseTrim(Emp emp);

mapper文件:

<insert id="insertUseTrim" parameterType="com.newcapec.entity.Emp">
    insert into emp
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="ename != null">
            ename,
        </if>
        <if test="job != null">
            job,
        </if>
        <if test="mgr != null">
            mgr,
        </if>
        <if test="hiredate != null">
            hiredate,
        </if>
        <if test="sal != null">
            sal,
        </if>
        <if test="comm != null">
            comm,
        </if>
        <if test="deptno != null">
            deptno,
        </if>
    </trim>
    <trim prefix=" values(" suffix=")" suffixOverrides=",">
        <if test="ename != null">
            #{ename},
        </if>
        <if test="job != null">
            #{job},
        </if>
        <if test="mgr != null">
            #{mgr},
        </if>
        <if test="hiredate != null">
            #{hiredate},
        </if>
        <if test="sal != null">
            #{sal},
        </if>
        <if test="comm != null">
            #{comm},
        </if>
        <if test="deptno != null">
            #{deptno},
        </if>
    </trim>
</insert>

测试:

@Test
public void testTrim() {
    SqlSession sqlSession = MybatisUtil.getSession();
    EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);

    Emp emp = new Emp();
    emp.setEname("CHRIS");
    emp.setJob("CLERK");
    emp.setMgr(1);
    emp.setSal(3400.0);
    emp.setComm(800.0);
    emp.setHiredate(new Date());
    emp.setDeptno(10);

    empMapper.insertUseTrim(emp);
    sqlSession.commit();
    sqlSession.close();
}

代替where标签:

<select id="selectUseTrim" parameterType="com.newcapec.entity.Emp" resultType="com.newcapec.entity.Emp">
    select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp
    <trim prefix="where" prefixOverrides="and|or">
        <if test="ename != null and ename != ''">
            ename like concat('%',#{ename},'%')
        </if>
        <if test="sal != null">
            and sal=#{sal}
        </if>
        <if test="deptno != null">
            and deptno=#{deptno}
        </if>
    </trim>
</select>

代替set标签:

<update id="updateUseTrim" parameterType="com.newcapec.entity.Emp">
    update emp
    <trim prefix="set" suffixOverrides=",">
        <if test="ename != null">
            ename=#{ename},
        </if>
        <if test="job != null">
            job=#{job},
        </if>
        <if test="mgr != null">
            mgr=#{mgr},
        </if>
        <if test="hiredate != null">
            hiredate=#{hiredate},
        </if>
        <if test="sal != null">
            sal=#{sal},
        </if>
        <if test="comm != null">
            comm=#{comm},
        </if>
        <if test="deptno != null">
            deptno=#{deptno},
        </if>
    </trim>
    where empno=#{empno}
</update>

4.3.6 foreach标签

    向SQL传递数组或list,MyBatis使用foreach解析。

属性解析:

  • collection: 遍历的数组或集合对象名称。

    • SQL只接收一个数组参数,这时SQL解析参数的名称MyBatis固定为array。

    • SQL只接收一个List参数,这时SQL解析参数的名称MyBatis固定为list。

    • 如果是通过一个实体类或自定义类型的属性传递到SQL的数组或List集合,则参数的名称为实体类或自定义类型中的属性名。

  • index: 为数组的下标。

  • item: 每个遍历生成对象中。

  • open: 开始遍历时拼接的串。

  • close: 结束遍历时拼接的串。

  • separator: 遍历的两个对象中需要拼接的串。

mapper接口:

void deleteUseForeach(Integer[] ids);

void insertUseForeach(List<Emp> empList);

mapper文件:

<delete id="deleteUseForeach" parameterType="java.lang.Integer">
    <!--delete from emp where empno in (1,2,3,4)-->
    delete from emp where empno in
    <foreach collection="array" open="(" close=")" separator="," item="id">
        #{id}
    </foreach>
</delete>

<insert id="insertUseForeach" parameterType="com.newcapec.entity.Emp">
    insert into emp(ename,job,mgr,hiredate,sal,comm,deptno) values
    <foreach collection="list" separator="," item="emp">
        (#{emp.ename},#{emp.job},#{emp.mgr},#{emp.hiredate},#{emp.sal},#{emp.comm},#{emp.deptno})
    </foreach>
</insert>

测试:

@Test
public void testForeach() {
    SqlSession sqlSession = MybatisUtil.getSession();
    EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);

    empMapper.deleteUseForeach(new Integer[]{1, 2, 3, 4});
    sqlSession.commit();
    sqlSession.close();
}

@Test
public void testForeach2() {
    SqlSession sqlSession = MybatisUtil.getSession();
    EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);

    List<Emp> empList = new ArrayList<>();
    for (int i = 1; i <= 3; i++) {
        Emp emp = new Emp();
        emp.setEname("TOM" + i);
        emp.setJob("CLERK" + i);
        emp.setMgr(1);
        emp.setSal(4567.0);
        emp.setComm(123.0);
        emp.setHiredate(new Date());
        emp.setDeptno(10);
        empList.add(emp);
    }
    empMapper.insertUseForeach(empList);
    //sqlSession.commit();
    sqlSession.close();
}

4.3.7 choose标签

    choose标签、when标签、otherwise标签的组合,类似于if-else-if判断。

<select id="">
    select...
    <choose>
        <when test="">

        </when>
        <when test="">

        </when>
        <otherwise>

        </otherwise>
    </choose>
</select>

4.3.8 SQL片段

    将实现的动态SQL判断代码块抽取出来,组成一个SQL片段,其它的statement中就可以引用SQL片段,方便程序员进行开发。

    注意:在SQL片段中不要包括where标签。

<sql id="feildSql">
    empno,ename,job,mgr,hiredate,sal,comm,deptno
</sql>

<sql id="whereSql">
    <if test="ename != null and ename != ''">
        ename like concat('%',#{ename},'%')
    </if>
    <if test="sal != null">
        and sal=#{sal}
    </if>
    <if test="deptno != null">
        and deptno=#{deptno}
    </if>
</sql>

<select id="selectUseSql" parameterType="com.newcapec.entity.Emp" resultType="com.newcapec.entity.Emp">
    select
    <include refid="feildSql"></include>
    from emp
    <where>
        <include refid="whereSql"></include>
    </where>
</select>

五、Mybatis 关联查询

5.1 数据模型分析

5.1.1 表功能介绍

  • 用户表: 记录用户的基本信息。

  • 订单表: 记录用户所创建的订单(购买商品的订单)。

  • 订单详情表: 记录订单的详细信息,即购买商品的信息。

  • 商品表: 记录商品的基本信息。

5.1.2 表之间的业务关系

用户表和订单表:

  • 用户表 ---> 订单表: 一个用户可以创建多个订单,一对多关系;

  • 订单表 ---> 用户表: 一个订单只由一个用户创建,一对一关系;

订单表和订单详情表:

  • 订单表 ---> 订单详情表: 一个订单可以包含多个订单详情,因为一个订单可以购买多个商品,每个商品的购买信息在订单详情表中记录,一对多关系;

  • 订单详情表 ---> 订单表: 一个订单详情只能包括在一个订单中,一对一关系;

订单详情表和商品表:

  • 订单详情表 ---> 商品表: 一个订单详情只对应一个商品信息,一对一关系;

  • 商品表 ---> 订单详情表: 一个商品可以包括在多个订单详情,一对多关系;

订单表和商品表:

  • 订单表 <---> 商品表: 一个订单中包含多个商品,一个商品可以添加在多个订单中,两者是通过订单详情表建立关系,多对多关系;

注意:

  • 如果两张表有主外键关联关系,那么他们的业务关系是一对一/一对多,或者是双向一对一(比如用户表和用户详情表)。

  • 如果两张表是双向一对多关系,那么他们是多对多关系,并且必然存在一张关系描述表作为中间表。

5.1.3 表结构

用户表:

CREATE TABLE `users`  (
  `id` int(11) PRIMARY KEY AUTO_INCREMENT,
  `username` varchar(20),
  `password` varchar(50),
  `realname` varchar(20)
);

INSERT INTO `users` VALUES (1, 'admin', '123456', '管理员');
INSERT INTO `users` VALUES (2, 'tom', '123', '汤姆');
INSERT INTO `users` VALUES (3, 'jerry', '456', '杰瑞');
INSERT INTO `users` VALUES (4, 'zhangsan', '111', '张三');
INSERT INTO `users` VALUES (5, 'lisi', '222', '李四');

订单表:

CREATE TABLE `orders`  (
  `id` int(11) PRIMARY KEY AUTO_INCREMENT,
  `order_number` varchar(30),
  `total_price` double,
  `status` varchar(5),
  `user_id` int(11)
);
​
INSERT INTO `orders` VALUES (1, '201812290838001', 2535, '已评价', 2);
INSERT INTO `orders` VALUES (2, '201812290838002', 4704.6, '已签收', 2);
INSERT INTO `orders` VALUES (3, '201812290838003', 3620, '已支付', 2);
INSERT INTO `orders` VALUES (4, '201812290840001', 600, '已发货', 3);
INSERT INTO `orders` VALUES (5, '201812290840002', 280, '未支付', 3);

订单详情表:

CREATE TABLE `orders_detail`  (
  `id` int(11) PRIMARY KEY AUTO_INCREMENT,
  `amount` int(11),
  `goods_id` int(11),
  `orders_id` int(11)
);
​
INSERT INTO `orders_detail` VALUES (1, 1, 1, 1);
INSERT INTO `orders_detail` VALUES (2, 3, 8, 1);
INSERT INTO `orders_detail` VALUES (3, 1, 2, 2);
INSERT INTO `orders_detail` VALUES (4, 2, 7, 2);
INSERT INTO `orders_detail` VALUES (5, 1, 3, 3);
INSERT INTO `orders_detail` VALUES (6, 6, 6, 3);
INSERT INTO `orders_detail` VALUES (7, 2, 4, 4);
INSERT INTO `orders_detail` VALUES (8, 1, 5, 5);

商品表:

CREATE TABLE `goods`  (
  `id` int(11) PRIMARY KEY AUTO_INCREMENT,
  `goods_name` varchar(50),
  `description` varchar(500),
  `price` double
);
​
INSERT INTO `goods` VALUES (1, '手机', '手机', 2499);
INSERT INTO `goods` VALUES (2, '笔记本电脑', '笔记本电脑', 4699);
INSERT INTO `goods` VALUES (3, 'IPAD', 'IPAD', 3599);
INSERT INTO `goods` VALUES (4, '运动鞋', '运动鞋', 300);
INSERT INTO `goods` VALUES (5, '外套', '外套', 280);
INSERT INTO `goods` VALUES (6, '可乐', '可乐', 3.5);
INSERT INTO `goods` VALUES (7, '辣条', '辣条', 2.8);
INSERT INTO `goods` VALUES (8, '水杯', '水杯', 12);

5.2 一对一查询

5.2.1 需求

    查询订单信息。关联如下:    
        1、关联查询其相关用户信息。

5.2.2 通过resultType方式实现

实体类:

    实体类Orders类不能映射全部字段,需要新创建的实体类,创建一个包括查询字段较多的实体类。OrdersQuery中包含了Orders以及Users需要查询的属性。

package com.newcapec.vo;

/**
 * OrdersQuery值对象,不是entity/po,因为它和数据库中表的字段不是对应关系
 */
public class OrdersQuery {

    //订单属性
    private Integer id;
    private String orderNumber;
    private Double totalPrice;
    private String status;
    private Integer userId;
    //用户属性
    private String username;
    private String password;
    private String realname;

    public Integer getId() {
        return id;
    }

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

    public String getOrderNumber() {
        return orderNumber;
    }

    public void setOrderNumber(String orderNumber) {
        this.orderNumber = orderNumber;
    }

    public Double getTotalPrice() {
        return totalPrice;
    }

    public void setTotalPrice(Double totalPrice) {
        this.totalPrice = totalPrice;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getRealname() {
        return realname;
    }

    public void setRealname(String realname) {
        this.realname = realname;
    }

    @Override
    public String toString() {
        return "OrdersQuery{" +
            "id=" + id +
            ", orderNumber='" + orderNumber + '\'' +
            ", totalPrice=" + totalPrice +
            ", status='" + status + '\'' +
            ", userId=" + userId +
            ", username='" + username + '\'' +
            ", password='" + password + '\'' +
            ", realname='" + realname + '\'' +
            '}';
    }
}

mapper接口:

package com.newcapec.mapper;

import com.newcapec.vo.OrdersQuery;

import java.util.List;

public interface OrdersMapper {

    List<OrdersQuery> selectUseResultType();
}

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.newcapec.mapper.OrdersMapper">

    <select id="selectUseResultType" resultType="com.newcapec.vo.OrdersQuery">
        select a.id,a.order_number,a.total_price,a.status,a.user_id,b.username,b.password,b.realname
        from orders a, users b where a.user_id=b.id
    </select>
</mapper>

测试:

public class QueryTest {

    @Test
    public void testOneToOneResultType() {
        SqlSession sqlSession = MybatisUtil.getSession();
        OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class);
        List<OrdersQuery> list = ordersMapper.selectUseResultType();
        for (OrdersQuery ordersQuery : list) {
            System.out.println(ordersQuery);
        }
        sqlSession.close();
    }
}

5.2.3 通过resultMap方式实现

  • 5.2.3.1 实体类

用户类:

package com.newcapec.entity;

public class Users {

    private Integer id;
    private String username;
    private String password;
    private String realname;

    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;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getRealname() {
        return realname;
    }

    public void setRealname(String realname) {
        this.realname = realname;
    }

    @Override
    public String toString() {
        return "Users{" +
            "id=" + id +
            ", username='" + username + '\'' +
            ", password='" + password + '\'' +
            ", realname='" + realname + '\'' +
            '}';
    }
}

订单类:

    在Orders类中加入Users属性,Users属性用于存储关联查询的用户信息。

    因为订单关联查询用户是一对一关系,所以这里使用单个Users对象存储关联查询的用户信息。

package com.newcapec.entity;

public class Orders {

    private Integer id;
    private String orderNumber;
    private Double totalPrice;
    private String status;
    private Integer userId;
    /**
     * 一对一关系属性
     */
    private Users users;

    public Integer getId() {
        return id;
    }

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

    public String getOrderNumber() {
        return orderNumber;
    }

    public void setOrderNumber(String orderNumber) {
        this.orderNumber = orderNumber;
    }

    public Double getTotalPrice() {
        return totalPrice;
    }

    public void setTotalPrice(Double totalPrice) {
        this.totalPrice = totalPrice;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public Users getUsers() {
        return users;
    }

    public void setUsers(Users users) {
        this.users = users;
    }

    @Override
    public String toString() {
        return "Orders{" +
                "id=" + id +
                ", orderNumber='" + orderNumber + '\'' +
                ", totalPrice=" + totalPrice +
                ", status='" + status + '\'' +
                ", userId=" + userId +
                ", users=" + users +
                '}';
    }
}
  • 5.2.3.2 mapper接口
List<Orders> selectUseResultMap();
  • 5.2.3.3 mapper文件

    association标签: 一对一关系映射描述。
        property: 关系属性名称
        javaType: 关系属性类型

<resultMap id="selectResultMap" type="com.newcapec.entity.Orders">
    <id column="id" property="id"/>
    <result column="order_number" property="orderNumber"/>
    <result column="total_price" property="totalPrice"/>
    <result column="status" property="status"/>
    <result column="user_id" property="userId"/>
    <association property="users" javaType="com.newcapec.entity.Users">
        <id column="user_id" property="id"/>
        <result column="username" property="username"/>
        <result column="password" property="password"/>
        <result column="realname" property="realname"/>
    </association>
</resultMap>
<select id="selectUseResultMap" resultMap="selectResultMap">
    select a.id,a.order_number,a.total_price,a.status,a.user_id,b.username,b.password,b.realname
    from orders a, users b where a.user_id=b.id
</select>
  • 5.2.3.4 测试
@Test
public void testOneToOneResultMap() {
    SqlSession sqlSession = MybatisUtil.getSession();
    OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class);
    List<Orders> list = ordersMapper.selectUseResultMap();
    for (Orders orders : list) {
        System.out.println(orders);
    }
    sqlSession.close();
}

5.2.4 resultType和resultMap实现一对一查询小结

  • resultType:使用resultType实现较为简单,如果实体类中没有包括查询出来的列名,需要增加列名对应的属性,即可完成映射。如果查询结果没有特殊要求,建议使用resultType。

  • resultMap:需要单独定义resultMap,实现有点麻烦,如果对查询结果有特殊的要求,使用resultMap可以完成将关联查询映射到实体类的属性中。

  • resultMap可以实现延迟加载,resultType无法实现延迟加载。

5.3 一对多查询

5.3.1 需求

    查询订单信息。关联如下:
        1、关联查询其相关用户信息
        2、关联查询其相关订单详情信息。

5.3.2 实体类

订单详情类:

public class OrdersDetail {
    
    private Integer id;
    private Integer amount;
    private Integer ordersId;
    private Integer goodsId;

    public Integer getId() {
        return id;
    }

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

    public Integer getAmount() {
        return amount;
    }

    public void setAmount(Integer amount) {
        this.amount = amount;
    }

    public Integer getOrdersId() {
        return ordersId;
    }

    public void setOrdersId(Integer ordersId) {
        this.ordersId = ordersId;
    }

    public Integer getGoodsId() {
        return goodsId;
    }

    public void setGoodsId(Integer goodsId) {
        this.goodsId = goodsId;
    }

    @Override
    public String toString() {
        return "OrdersDetail{" +
                "id=" + id +
                ", amount=" + amount +
                ", ordersId=" + ordersId +
                ", goodsId=" + goodsId +
                '}';
    }
}

订单类:

    在Order类中加入List<OrdersDetail> detailList属性,details属性用于存储关联查询的订单详情。

    因为订单关联查询订单详情是一对多关系,所以这里使用集合对象存储关联查询的订单详情信息。

public class Orders {

    private Integer id;
    private String orderNumber;
    private Double totalPrice;
    private String status;
    private Integer userId;
    /**
     * 一对一关系属性
     */
    private Users users;
    /**
     * 一对多关系属性
     */
    private List<OrdersDetail> detailList;

    public Integer getId() {
        return id;
    }

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

    public String getOrderNumber() {
        return orderNumber;
    }

    public void setOrderNumber(String orderNumber) {
        this.orderNumber = orderNumber;
    }

    public Double getTotalPrice() {
        return totalPrice;
    }

    public void setTotalPrice(Double totalPrice) {
        this.totalPrice = totalPrice;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public Users getUsers() {
        return users;
    }

    public void setUsers(Users users) {
        this.users = users;
    }

    public List<OrdersDetail> getDetailList() {
        return detailList;
    }

    public void setDetailList(List<OrdersDetail> detailList) {
        this.detailList = detailList;
    }

    @Override
    public String toString() {
        return "Orders{" +
                "id=" + id +
                ", orderNumber='" + orderNumber + '\'' +
                ", totalPrice=" + totalPrice +
                ", status='" + status + '\'' +
                ", userId=" + userId +
                ", users=" + users +
                ", detailList=" + detailList +
                '}';
    }
}

5.3.3 mapper接口

List<Orders> selectOrdersAndDetail();

5.3.4 mapper文件

    collection标签: 一对多关系映射描述。
        property: 关系属性名称
        ofType: 关系属性是一个List集合,集合中存放的元素类型

<resultMap id="detailResultMap" type="com.newcapec.entity.Orders">
    <id column="id" property="id"/>
    <result column="order_number" property="orderNumber"/>
    <result column="total_price" property="totalPrice"/>
    <result column="status" property="status"/>
    <result column="user_id" property="userId"/>
    <association property="users" javaType="com.newcapec.entity.Users">
        <id column="user_id" property="id"/>
        <result column="username" property="username"/>
        <result column="password" property="password"/>
        <result column="realname" property="realname"/>
    </association>
    <collection property="detailList" ofType="com.newcapec.entity.OrdersDetail">
        <id column="detail_id" property="id"/>
        <result column="amount" property="amount"/>
        <result column="goods_id" property="goodsId"/>
        <result column="id" property="ordersId"/>
    </collection>
</resultMap>
<select id="selectOrdersAndDetail" resultMap="detailResultMap">
    select a.id,a.order_number,a.total_price,a.status,a.user_id,b.username,b.password,b.realname,
    c.id detail_id,c.amount,c.goods_id
    from orders a
    join users b on a.user_id=b.id
    join orders_detail c on a.id=c.orders_id
</select>

5.3.5 测试

@Test
public void testOneToMany() {
    SqlSession sqlSession = MybatisUtil.getSession();
    OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class);
    List<Orders> list = ordersMapper.selectOrdersAndDetail();
    for (Orders orders : list) {
        System.out.println(orders);
    }
    sqlSession.close();
}

5.4 多对多查询

5.4.1 订单与商品

  • 5.4.1.1 需求

    查询订单信息。关联如下:
        1、关联查询其相关用户信息
        2、关联查询其相关订单详情信息
        3、关联查询订单详情中的商品信息

  • 5.4.1.2 实体类

    将OrderDetail类中Integer类型的goods_id属性修改为Goods类型属性,goods属性用于存储关联查询的商品信息。

    订单与订单详情是一对多关系,订单详情与商品是一对一关系,反之商品与订单详情是一对多关系,订单详情与订单是一对一关系,所以订单与商品之前为多对多关系。

商品类:

public class Goods {

    private Integer id;
    private String goodsName;
    private String description;
    private Double price;

    public Integer getId() {
        return id;
    }

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

    public String getGoodsName() {
        return goodsName;
    }

    public void setGoodsName(String goodsName) {
        this.goodsName = goodsName;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Goods{" +
                "id=" + id +
                ", goodsName='" + goodsName + '\'' +
                ", description='" + description + '\'' +
                ", price=" + price +
                '}';
    }
}

订单详情类:

public class OrdersDetail {

    private Integer id;
    private Integer amount;
    private Integer ordersId;
    private Integer goodsId;
    /**
     * 一对一关系
     */
    private Goods goods;

    public Integer getId() {
        return id;
    }

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

    public Integer getAmount() {
        return amount;
    }

    public void setAmount(Integer amount) {
        this.amount = amount;
    }

    public Integer getOrdersId() {
        return ordersId;
    }

    public void setOrdersId(Integer ordersId) {
        this.ordersId = ordersId;
    }

    public Integer getGoodsId() {
        return goodsId;
    }

    public void setGoodsId(Integer goodsId) {
        this.goodsId = goodsId;
    }

    public Goods getGoods() {
        return goods;
    }

    public void setGoods(Goods goods) {
        this.goods = goods;
    }

    @Override
    public String toString() {
        return "OrdersDetail{" +
                "id=" + id +
                ", amount=" + amount +
                ", ordersId=" + ordersId +
                ", goodsId=" + goodsId +
                ", goods=" + goods +
                '}';
    }
}
  • 5.4.1.3 mapper接口
List<Orders> selectOrdersAndGoods();
  • 5.4.1.4 mapper文件
<resultMap id="goodsResultMap" type="com.newcapec.entity.Orders">
    <id column="id" property="id"/>
    <result column="order_number" property="orderNumber"/>
    <result column="total_price" property="totalPrice"/>
    <result column="status" property="status"/>
    <result column="user_id" property="userId"/>
    <association property="users" javaType="com.newcapec.entity.Users">
        <id column="user_id" property="id"/>
        <result column="username" property="username"/>
        <result column="password" property="password"/>
        <result column="realname" property="realname"/>
    </association>
    <collection property="detailList" ofType="com.newcapec.entity.OrdersDetail">
        <id column="detail_id" property="id"/>
        <result column="amount" property="amount"/>
        <result column="goods_id" property="goodsId"/>
        <result column="id" property="ordersId"/>
        <association property="goods" javaType="com.newcapec.entity.Goods">
            <id column="goods_id" property="id"/>
            <result column="goods_name" property="goodsName"/>
            <result column="description" property="description"/>
            <result column="price" property="price"/>
        </association>
    </collection>
</resultMap>
<select id="selectOrdersAndGoods" resultMap="goodsResultMap">
    select a.id,a.order_number,a.total_price,a.status,a.user_id,b.username,b.password,b.realname,
    c.id detail_id,c.amount,c.goods_id,d.goods_name,d.description,d.price
    from orders a
    join users b on a.user_id=b.id
    join orders_detail c on a.id=c.orders_id
    join goods d on c.goods_id=d.id
</select>
  • 5.4.1.5 测试
@Test
public void testManyToMany1() {
    SqlSession sqlSession = MybatisUtil.getSession();
    OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class);
    List<Orders> list = ordersMapper.selectOrdersAndGoods();
    for (Orders orders : list) {
        System.out.println(orders);
    }
    sqlSession.close();
}

5.4.2 学生与课程之间的多对多关系

  • 5.4.2.1 需求

    查询学生信息,并关联查询学生相应的课程信息。

  • 5.4.2.2 表结构
CREATE TABLE `course`  (
  `id` int(11) PRIMARY KEY AUTO_INCREMENT,
  `cname` varchar(20)
);

INSERT INTO `course` VALUES (1, '大学语文');
INSERT INTO `course` VALUES (2, '大学英语');
INSERT INTO `course` VALUES (3, '高等数学');
INSERT INTO `course` VALUES (4, 'JAVA语言');
INSERT INTO `course` VALUES (5, '网络维护');
INSERT INTO `course` VALUES (6, '通信原理');

CREATE TABLE `student`  (
  `id` int(11) PRIMARY KEY AUTO_INCREMENT,
  `name` varchar(20),
  `gender` varchar(20),
  `major` varchar(20)
);

INSERT INTO `student` VALUES (1, '小明', '男', '软件工程');
INSERT INTO `student` VALUES (2, '小红', '女', '网络工程');
INSERT INTO `student` VALUES (3, '小丽', '女', '物联网');

CREATE TABLE `student_course`  (
  `id` int(11) PRIMARY KEY AUTO_INCREMENT,
  `student_id` int(11),
  `course_id` int(11)
);

INSERT INTO `student_course` VALUES (1, 1, 1);
INSERT INTO `student_course` VALUES (2, 1, 3);
INSERT INTO `student_course` VALUES (3, 1, 4);
INSERT INTO `student_course` VALUES (4, 2, 1);
INSERT INTO `student_course` VALUES (5, 2, 2);
INSERT INTO `student_course` VALUES (6, 2, 5);
INSERT INTO `student_course` VALUES (7, 3, 2);
INSERT INTO `student_course` VALUES (8, 3, 3);
INSERT INTO `student_course` VALUES (9, 3, 6);
  • 5.4.2.3 实体类

课程类:

public class Course {

    private Integer id;
    private String cname;

    public Integer getId() {
        return id;
    }

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

    public String getCname() {
        return cname;
    }

    public void setCname(String cname) {
        this.cname = cname;
    }

    @Override
    public String toString() {
        return "Course{" +
                "id=" + id +
                ", cname='" + cname + '\'' +
                '}';
    }
}

学生类:

public class Student {
    private Integer id;
    private String name;
    private String gender;
    private String major;

    /**
     * 一对多
     */
    private List<Course> courseList;

    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 String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getMajor() {
        return major;
    }

    public void setMajor(String major) {
        this.major = major;
    }

    public List<Course> getCourseList() {
        return courseList;
    }

    public void setCourseList(List<Course> courseList) {
        this.courseList = courseList;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                ", major='" + major + '\'' +
                ", courseList=" + courseList +
                '}';
    }
}
  • 5.4.2.4 mapper接口
public interface StudentMapper {
    
    List<Student> select();
}
  • 5.4.2.5 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.newcapec.mapper.StudentMapper">

    <resultMap id="selectResultMap" type="com.newcapec.entity.Student">
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="gender" property="gender"/>
        <result column="major" property="major"/>
        <collection property="courseList" ofType="com.newcapec.entity.Course">
            <id column="course_id" property="id"/>
            <result column="cname" property="cname"/>
        </collection>
    </resultMap>
    <select id="select" resultMap="selectResultMap">
        select a.id,a.name,a.gender,a.major,b.course_id,c.cname
        from student a
                 join student_course b on a.id=b.student_id
                 join course c ON b.course_id=c.id
    </select>
</mapper>
  • 5.4.2.6 测试
@Test
public void testManyToMany2() {
    SqlSession sqlSession = MybatisUtil.getSession();
    StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
    List<Student> list = studentMapper.select();
    for (Student student : list) {
        System.out.println(student);
    }
    sqlSession.close();
}

5.5 关联查询总结

5.5.1 resultType

    作用:将查询结果按照SQL列名与实体类属性名一致性映射到实体类对象中。
    
    场合:常见一些明细记录的展示,比如用户购买商品明细,将关联查询信息全部展示在页面时,此时可直接使用resultType将每一条记录映射到实体类中,在前端页面遍历list(list中是实体类)即可。

5.5.2 resultMap

    使用association和collection完成一对一和一对多高级映射(对结果有特殊的映射要求)。

association

    作用:将关联查询信息映射到一个实体类对象中。

    场合:为了方便查询关联信息可以使用association将关联信息映射为当前对象的一个属性,比如:查询订单以及关联用户信息。

collection

    作用:将关联查询信息映射到一个list集合中。

    场合:为了方便查询遍历关联信息可以使用collection将关联信息映射到list集合中,比如:  查询用户权限范围模块及模块下的菜单,可使用collection将模块映射到模块list中,将菜单列表映射到模块对象的菜单list属性中,这样的作的目的也是方便对查询结果集进行遍历查询。如果使用resultType无法将查询结果映射到list集合中。

resultMap的继承

     resultMap标签可以通过extends属性来继承一个已有的或公共的resultMap,避免重复配置的出现,减少配置量。

例子如下:

<!-- 父resultMap标签-->
<resultMap id="baseResultMap" type="com.newcapec.entity.Orders">
    <id column="id" property="id"/>
    <result column="order_number" property="orderNumber"/>
    <result column="total_price" property="totalPrice"/>
    <result column="status" property="status"/>
    <result column="user_id" property="userId"/>
</resultMap>
<!-- 继承父resultMap标签中的配置,避免重复配置 -->
<resultMap id="subResultMap" type="com.newcapec.entity.Orders" extends="baseResultMap">
    <association property="users" javaType="com.newcapec.entity.Users">
        <id column="id" property="id"/>
        <result column="username" property="username"/>
        <result column="password" property="password"/>
        <result column="realname" property="realname"/>
    </association>
</resultMap>

六、Mybatis 延迟加载

6.1 什么是延迟加载

    需要查询关联信息时,使用MyBatis延迟加载特性可有效的减少数据库压力,首次查询只查询主要信息,关联信息等用户获取时再加载。
    
    懒加载针对级联使用的,懒加载的目的是减少内存的浪费和减轻系统负担。你可以理解为按需加载,当我调用到关联的数据时才与数据库交互否则不交互。
    
    resultMap可以实现高级映射(使用association、collection实现一对一和一对多映射),association、collection具备延迟加载功能。

6.2 打开延迟加载开关

    在MyBatis核心配置文件中配置:lazyLoadingEnabled、aggressiveLazyLoading。

设置项描述允许值默认值
lazyLoadingEnabled全局性设置懒加载。如果设为‘false’,则所有相关联的都会被初始化加载true, falsefalse
aggressiveLazyLoading当设置为‘true’的时候,懒加载的对象可能被任何懒属性全部加载。否则,每个属性都按需加载true, falsetrue
<!-- 全局参数设置 -->
<settings>
    <!-- 开启延迟加载 -->
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

6.3 实体类

6.3.1 部门类

public class Dept {

    private Integer deptno;
    private String dname;
    private String loc;
    /**
     * 关系属性
     */
    private List<Emp> empList;

    public Integer getDeptno() {
        return deptno;
    }

    public void setDeptno(Integer deptno) {
        this.deptno = deptno;
    }

    public String getDname() {
        return dname;
    }

    public void setDname(String dname) {
        this.dname = dname;
    }

    public String getLoc() {
        return loc;
    }

    public void setLoc(String loc) {
        this.loc = loc;
    }

    public List<Emp> getEmpList() {
        return empList;
    }

    public void setEmpList(List<Emp> empList) {
        this.empList = empList;
    }

    @Override
    public String toString() {
        return "Dept{" +
                "deptno=" + deptno +
                ", dname='" + dname + '\'' +
                ", loc='" + loc + '\'' +
                '}';
    }
}

6.3.2 员工类

public class Emp {

    private Integer empno;
    private String ename;
    private String job;
    private Integer mgr;
    private Date hiredate;
    private Double sal;
    private Double comm;
    private Integer deptno;
    /**
     * 关系属性
     */
    private Dept dept;

    public Integer getEmpno() {
        return empno;
    }

    public void setEmpno(Integer empno) {
        this.empno = empno;
    }

    public String getEname() {
        return ename;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    public String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }

    public Integer getMgr() {
        return mgr;
    }

    public void setMgr(Integer mgr) {
        this.mgr = mgr;
    }

    public Date getHiredate() {
        return hiredate;
    }

    public void setHiredate(Date hiredate) {
        this.hiredate = hiredate;
    }

    public Double getSal() {
        return sal;
    }

    public void setSal(Double sal) {
        this.sal = sal;
    }

    public Double getComm() {
        return comm;
    }

    public void setComm(Double comm) {
        this.comm = comm;
    }

    public Integer getDeptno() {
        return deptno;
    }

    public void setDeptno(Integer deptno) {
        this.deptno = deptno;
    }

    public Dept getDept() {
        return dept;
    }

    public void setDept(Dept dept) {
        this.dept = dept;
    }

    @Override
    public String toString() {
        return "Emp{" +
                "empno=" + empno +
                ", ename='" + ename + '\'' +
                ", job='" + job + '\'' +
                ", mgr=" + mgr +
                ", hiredate=" + hiredate +
                ", sal=" + sal +
                ", comm=" + comm +
                ", deptno=" + deptno +
                '}';
    }
}

6.4 使用association实现延迟加载

    查询员工以及相关联的部门信息。

6.4.1 mapper接口

public interface EmpMapper {

    /*
     * 查询所有员工的信息,并关联查询部门信息
     */
    List<Emp> select();
}

public interface DeptMapper {

    /*
     * 根据部门编号查询部门信息
     */
    Dept selectById(Integer deptno);
}

6.4.2 mapper文件

    懒加载的前提是需要分离Sql,不再使用关联查询Sql。
    
    比如我们要查询所有员工的信息,并关联查询部门信息;想要实现部门信息懒加载,那么查询员工信息是一条独立的Sql,根据部门编号查询部门信息也是一条独立的Sql,当查询员工信息的时候,如果需要用到部门信息,那么就调用根据部门编号查询部门信息的Sql。

根据部门编号查询部门信息:

<?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.newcapec.mapper.DeptMapper">

    <select id="selectById" parameterType="java.lang.Integer" resultType="com.newcapec.entity.Dept">
        select deptno,dname,loc from dept where deptno=#{deptno}
    </select>
</mapper>

查询员工信息(单表查询),并通过上边的查询去关联部门信息:

    association标签的属性:
        select:执行延迟加载时关联数据查询的sql对应的statementId。
            执行的关联查询语句在同一mapper文件中:直接填入statementId即可;
            执行的关联查询语句在不同的mapper文件中:namespace.statementId;
        column:在执行关系表信息查询时,与其关联的字段名称。

<?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.newcapec.mapper.EmpMapper">

    <resultMap id="baseResultMap" type="com.newcapec.entity.Emp">
        <id column="empno" property="empno"/>
        <result column="ename" property="ename"/>
        <result column="job" property="job"/>
        <result column="mgr" property="mgr"/>
        <result column="hiredate" property="hiredate"/>
        <result column="sal" property="sal"/>
        <result column="comm" property="comm"/>
        <result column="deptno" property="deptno"/>
        <association property="dept" javaType="com.newcapec.entity.Dept"
                     select="com.newcapec.mapper.DeptMapper.selectById" column="deptno">
        </association>
    </resultMap>
    <select id="select" resultMap="baseResultMap">
        select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp
    </select>
</mapper>

6.4.3 测试

public class LazyLoadingTest {

    @Test
    public void testOneToOne() {
        SqlSession sqlSession = MybatisUtil.getSession();
        EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);

        List<Emp> list = empMapper.select();
        for (Emp emp : list) {
            System.out.println(emp.getEmpno() + "--" + emp.getEname() + "--" + emp.getJob());
            System.out.println("----------");
            // 需要使用关联信息
            Dept dept = emp.getDept();
            if (dept != null) {
                System.out.println(dept.getDeptno() + "--" + dept.getDname() + "--" + dept.getLoc());
            }
            System.out.println("-----------------分割线----------------");
        }

        sqlSession.close();
    }
}

6.5 使用collection实现延迟加载

6.5.1 mapper接口

public interface DeptMapper {
    
    /*
     * 查询所有部门信息,并关联部门对应的员工信息
     */
    List<Dept> select();
}

public interface EmpMapper {

    /*
     * 根据部门编号查询对应的员工信息
     */
    List<Emp> selectByDeptno(Integer deptno);
}

6.5.2 mapper文件

根据部门编号查询员工信息:

<select id="selectByDeptno" parameterType="java.lang.Integer" resultType="com.newcapec.entity.Emp">
    select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp where deptno=#{deptno}
</select>

查询部门信息(单表查询),并通过上边的查询去关联部门中的员工信息:

<resultMap id="baseResultMap" type="com.newcapec.entity.Dept">
    <id column="deptno" property="deptno"/>
    <result column="dname" property="dname"/>
    <result column="loc" property="loc"/>
    <!--关联信息描述-->
    <collection property="empList" ofType="com.newcapec.entity.Emp"
                select="com.newcapec.mapper.EmpMapper.selectByDeptno" column="deptno">
    </collection>
</resultMap>
<select id="select" resultMap="baseResultMap">
    select deptno,dname,loc from dept
</select>

6.5.3 测试

@Test
public void testOneToMany() {
    SqlSession sqlSession = MybatisUtil.getSession();

    DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class);
    List<Dept> list = deptMapper.select();
    for (Dept dept : list) {
        System.out.println(dept.getDeptno() + "--" + dept.getDname() + "--" + dept.getLoc());

        //查询关联信息
        List<Emp> empList = dept.getEmpList();
        for (Emp emp : empList) {
            System.out.println("员工姓名:" + emp.getEname());
        }
    }
    sqlSession.close();
}

七、Mybatis 查询缓存

    缓存:将数据临时存储在存储介质(内存,文件)中,关系型数据库的缓存目的就是为了减轻数据库的压力。
    
    数据库的数据实际是存储在硬盘中,如果我们程序需要用到数据,那么就需要频繁的从磁盘中读取数据,效率低,数据库压力大。我们可以把查询到的数据缓存起来,这样就减少了频繁操作磁盘数据,提高查询效率,减轻服务器压力。
    
    Mybatis提供了查询缓存,用于减轻数据库压力,提高数据库性能。但是在实际项目开发中,很少使用Mybatis的缓存机制,现在主流的缓存机制是redis。

7.1 什么是查询缓存

  • MyBatis提供查询缓存,用于减轻数据库压力,提高数据库性能;

  • MyBatis提供一级缓存,和二级缓存;

  • 一级缓存是SqlSession级别的缓存。在操作数据库时需要构造SqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的SqlSession之间的缓存数据区域(HashMap)是互相不影响的;

  • 二级缓存是mapper级别的缓存,多个SqlSession去操作同一个mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的;

7.2 一级缓存

7.2.1 一级缓存工作原理

  • 第一次查询id为1的数据,先去找一级缓存中查找是否有id为1的数据,如果没有,从数据库中查询该数据,并将该数据存储到一级缓存中;

  • 第二次查询id为1的数据,也先去找一级缓存中查找是否有id为1的数据,缓存中有,直接从缓存中获取该数据,不再查询数据库;

  • 如果SqlSession去执行commit操作(执行插入、更新、删除),将清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的数据,避免脏读;

7.2.2 实体类

public class Person {

    private Integer id;
    private String personName;
    private Integer personAge;
    private String personAddress;

    public Integer getId() {
        return id;
    }

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

    public String getPersonName() {
        return personName;
    }

    public void setPersonName(String personName) {
        this.personName = personName;
    }

    public Integer getPersonAge() {
        return personAge;
    }

    public void setPersonAge(Integer personAge) {
        this.personAge = personAge;
    }

    public String getPersonAddress() {
        return personAddress;
    }

    public void setPersonAddress(String personAddress) {
        this.personAddress = personAddress;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", personName='" + personName + '\'' +
                ", personAge=" + personAge +
                ", personAddress='" + personAddress + '\'' +
                '}';
    }
}

7.2.3 mapper接口

public interface PersonMapper {

    Person selectById(Integer id);

    List<Person> select();
}

7.2.4 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.newcapec.mapper.PersonMapper">

    <select id="selectById" parameterType="java.lang.Integer" resultType="com.newcapec.entity.Person">
        select id,person_name,person_age,person_address from person where id=#{id}
    </select>

    <select id="select" resultType="com.newcapec.entity.Person">
        select id,person_name,person_age,person_address from person
    </select>
</mapper>

7.2.5 测试

public class CacheTest {

    @Test
    public void testSqlSessionCache() {
        SqlSession sqlSession = MybatisUtil.getSession();
        PersonMapper personMapper = sqlSession.getMapper(PersonMapper.class);

        System.out.println("----------第一次使用id为1的person数据-----------");
        Person p1 = personMapper.selectById(1);
        System.out.println(p1);

        /**
         * 一级缓存自带缓存,不可不用的,缓存介质为内存
         * commit()提交方法可以请求缓存
         */
        //sqlSession.commit();

        System.out.println("----------第二次使用id为1的person数据-----------");
        Person p2 = personMapper.selectById(1);
        System.out.println(p2);

        sqlSession.close();
    }
}

7.3 二级缓存

7.3.1 二级缓存工作原理

  • SqlSession1去查询id为1的数据,查询到后会将该数据存储到二级缓存中;

  • SqlSession2去查询id为1的数据,去缓存中找是否存在数据,如果存在直接从缓存中取出数据;

  • 如果SqlSession3去执行相同mapper下sql,执行commit提交,清空该mapper下的二级缓存区域的数据;

  • 二级缓存与一级缓存区别,二级缓存的范围更大,多个SqlSession可以共享一个Mapper的二级缓存区域;

  • 每个mapper有一个二级缓存区域,按namespace分;

  • 如果两个mapper的namespace如果相同,这两个mapper执行sql查询到数据将存在相同的二级缓存区域中;

7.3.2 开启二级缓存

在mybatis核心配置文件中配置:cacheEnabled

设置项描述允许值默认值
cacheEnabled对在此配置文件下的所有cache 进行全局性开/关设置true \ falsetrue
<!-- 全局参数设置 -->
<settings>
    <!-- 开启二级缓存-->
    <setting name="cacheEnabled" value="true"/>
</settings>

在映射文件中开启二缓存,mapper.xml下的SQL执行完成会存储到它的缓存区域HashMap。

<mapper namespace="com.newcapec.mapper.PersonMapper">
    <!-- 配置当前mapper文件中所有查询语句都放入二级缓存中 -->
    <cache/>
    
    <select>
        ...
    </select>
</mapper>

7.3.3 实体类

    二级缓存中存储数据的实体类必须实现可序列化接口java.io.Serializable。

public class Person implements Serializable {
    
}

7.3.4 二级缓存测试

@Test
public void testMapperCache(){
    /**
     * 二级缓存,可插拔式缓存,缓存介质:内存+磁盘
     */

    SqlSession sqlSession1 = MybatisUtil.getSession();
    PersonMapper personMapper1 = sqlSession1.getMapper(PersonMapper.class);
    System.out.println("-------------第一次查询--------------");
    Person p1 = personMapper1.selectById(2);
    System.out.println(p1);
    sqlSession1.close();

    System.out.println("----------------sqlSession1关闭,建立sqlSession2连接-------------------");

    SqlSession sqlSession2 = MybatisUtil.getSession();
    PersonMapper personMapper2 = sqlSession2.getMapper(PersonMapper.class);
    System.out.println("-------------第二次查询--------------");
    Person p2 = personMapper2.selectById(2);
    System.out.println(p2);
    sqlSession2.close();
}

7.3.5 useCache配置

    在statement中设置useCache="false"可以禁用当前select语句的二级缓存,即每次查询都会发出sql去查询。默认情况是true,即该sql使用二级缓存。

<?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.newcapec.mapper.PersonMapper">

    <!-- 配置当前mapper文件中所有查询语句都放入二级缓存中 -->
    <cache/>

    <select id="selectById" parameterType="java.lang.Integer" resultType="com.newcapec.entity.Person" useCache="true">
        select id,person_name,person_age,person_address from person where id=#{id}
    </select>

    <!-- select标签中的useCache属性: 决定了当前sql是否使用二级缓存,默认为true -->
    <select id="select" resultType="com.newcapec.entity.Person" useCache="false">
        select id,person_name,person_age,person_address from person
    </select>
</mapper>

测试:

@Test
public void testMapperCache2() {
    SqlSession sqlSession1 = MybatisUtil.getSession();
    PersonMapper personMapper1 = sqlSession1.getMapper(PersonMapper.class);
    System.out.println("-------------第一次查询--------------");
    List<Person> list1 = personMapper1.select();
    System.out.println(list1);
    sqlSession1.close();

    System.out.println("----------------sqlSession1关闭,建立sqlSession2连接-------------------");

    SqlSession sqlSession2 = MybatisUtil.getSession();
    PersonMapper personMapper2 = sqlSession2.getMapper(PersonMapper.class);
    System.out.println("-------------第二次查询--------------");
    List<Person> list2 = personMapper2.select();
    System.out.println(list2);
    sqlSession2.close();
}

八、Mybatis PageHelper分页插件

8.1 导入分页插件jar包

方式一:导入jar包

 方式二:配置maven依赖

<!-- PageHelper -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.1.10</version>
</dependency>

8.2 配置分页插件

8.2.1 在MyBatis全局配置文件中配置拦截器插件

<!-- 分页插件 -->
<plugins>
    <!--  
		interceptor属性:配置PageHelper插件中的核心拦截器类;
		PageInterceptor拦截器类(类似于JavaWeb阶段的过滤器):
			该拦截器的作用是在查询SQL执行之前,将编写的SQL语句改造成分页查询语句;
	-->
    <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>

8.2.2 配置插件属性

属性名称默认值描述
helperDialect分页插件会自动检测当前的数据库链接,自动选择合适的分页方式你可以配置helperDialect属性来指定分页插件使用哪种方言。配置时,可以使用下面的缩写值:oracle,mysql,mariadb,sqlite,hsqldb,postgresql,db2,sqlserver,informix,h2,sqlserver2012,derby
offsetAsPageNumfalse该参数对使用 RowBounds作为分页参数时有效当该参数设置为 true时,会将 RowBounds中的 offset参数当成 pageNum使用,可以用页码和页面大小两个参数进行分页
rowBoundsWithCountfalse该参数对使用 RowBounds作为分页参数时有效当该参数设置为true时,使用 RowBounds分页会进行 count查询
pageSizeZerofalse当该参数设置为 true时,如果 pageSize=0 或者 RowBounds.limit=0 就会查询出全部的结果,相当于没有执行分页查询,但是返回结果仍然是 Page类型
reasonablefalse分页合理化参数启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据
paramspageNum=pageNum; pageSize=pageSize; count=countSql; reasonable=reasonable; pageSizeZero=pageSizeZero为了支持startPage(Object params)方法,增加了该参数来配置参数映射,用于从对象中根据属性名取值, 可以配置 pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值
supportMethodsArgumentsfalse支持通过 Mapper接口参数来传递分页参数分页插件会从查询方法的参数值中,自动根据上面 params配置的字段中取值,查找到合适的值时就会自动分页
autoRuntimeDialectfalse设置为 true 时,允许在运行时根据多数据源自动识别对应方言的分页
closeConntrue当使用运行时动态数据源或没有设置 helperDialect属性自动获取数据库类型时,会自动获取一个数据库连接, 通过该属性来设置是否关闭获取的这个连接,默认true关闭,设置为 false后,不会关闭获取的连接,这个参数的设置要根据自己选择的数据源来决定
<!-- 分页插件 -->
<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
        <!-- 开启合理化分页-->
        <property name="reasonable" value="true"/>
    </plugin>
</plugins>

8.3 在程序中的使用

8.3.1 实体类

public class Emp {

    private Integer empno;
    private String ename;
    private String job;
    private Integer mgr;
    private Date hiredate;
    private Double sal;
    private Double comm;
    private Integer deptno;

    public Integer getEmpno() {
        return empno;
    }

    public void setEmpno(Integer empno) {
        this.empno = empno;
    }

    public String getEname() {
        return ename;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    public String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }

    public Integer getMgr() {
        return mgr;
    }

    public void setMgr(Integer mgr) {
        this.mgr = mgr;
    }

    public Date getHiredate() {
        return hiredate;
    }

    public void setHiredate(Date hiredate) {
        this.hiredate = hiredate;
    }

    public Double getSal() {
        return sal;
    }

    public void setSal(Double sal) {
        this.sal = sal;
    }

    public Double getComm() {
        return comm;
    }

    public void setComm(Double comm) {
        this.comm = comm;
    }

    public Integer getDeptno() {
        return deptno;
    }

    public void setDeptno(Integer deptno) {
        this.deptno = deptno;
    }

    @Override
    public String toString() {
        return "Emp{" +
                "empno=" + empno +
                ", ename='" + ename + '\'' +
                ", job='" + job + '\'' +
                ", mgr=" + mgr +
                ", hiredate=" + hiredate +
                ", sal=" + sal +
                ", comm=" + comm +
                ", deptno=" + deptno +
                '}';
    }
}

8.3.2 mapper接口

public interface EmpMapper {

    List<Emp> selectByPage();
}

8.3.3 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.newcapec.mapper.EmpMapper">

    <!--使用PageHelper插件实现分页,SQL语句无需编写任何和分页相关的内容-->
    <select id="selectByPage" resultType="com.newcapec.entity.Emp">
        select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp order by empno
    </select>
</mapper>

8.3.4 测试

public class PageHelperTest {

    @Test
    public void testPageHelper() {
        SqlSession sqlSession = MybatisUtil.getSession();
        EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);

        //开启分页拦截器,设置分页的基本属性(当前页面数,每页条数)
        PageHelper.startPage(1, 5);
        List<Emp> empList = empMapper.selectByPage();
        for (Emp emp : empList) {
            System.out.println(emp);
        }

        //分页信息对象
        PageInfo<Emp> pageInfo = new PageInfo<>(empList);
        System.out.println("当前页数:" + pageInfo.getPageNum());
        System.out.println("每页条数:" + pageInfo.getPageSize());
        System.out.println("总记录数:" + pageInfo.getTotal());
        System.out.println("总页数:" + pageInfo.getPages());
        System.out.println("上一页:" + pageInfo.getPrePage());
        System.out.println("下一页:" + pageInfo.getNextPage());
        System.out.println("是否有上一页:" + pageInfo.isHasPreviousPage());
        System.out.println("是否有下一页:" + pageInfo.isHasNextPage());
        System.out.println("是否为首页:" + pageInfo.isIsFirstPage());
        System.out.println("是否为末页:" + pageInfo.isIsLastPage());
        System.out.println("存放页码的数据:" + Arrays.toString(pageInfo.getNavigatepageNums()));
        System.out.println("获取当前页数据:" + pageInfo.getList());
        sqlSession.close();
    }
}

九、Mybatis Generator代码生成

    虽然MyBatis是一个简单易学的框架,但是配置XML文件也是一件相当繁琐的一个过程,而且会出现很多不容易定位的错误。当在工作中需要生成大量对象的时候,有太多的重复劳动,简直是生无可恋。
    
    为此官方开发了MyBatis Generator。它只需要很少量的简单配置,就可以完成大量的表到Java对象的生成工作,拥有零出错和速度快的优点,让开发人员解放出来更专注于业务逻辑的开发。

9.1 生成文件介绍

    MyBatis Generator生成的文件包含三类:
        1、Model实体文件,一个数据库表对应生成一个 Model 实体;
        2、Mapper接口文件,数据数操作方法都在此接口中定义;
        3、Mapper XML配置文件;

9.2 配置依赖

<dependencies>
    <!--引入mybatis的依赖-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.4.6</version>
    </dependency>
    <!--  mysql -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.49</version>
    </dependency>
    <!-- log4j -->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <!--mybatis代码生成器-->
    <dependency>
        <groupId>org.mybatis.generator</groupId>
        <artifactId>mybatis-generator-core</artifactId>
        <version>1.3.7</version>
    </dependency>
</dependencies>

9.3 引入相关配置

    我们只需引入log4j.properties即可,无需引入mybatis-config.xml。

9.4 生成配置文件

generator.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:必选,上下文id,用于在生成错误时提示
        targetRuntime:
            1,MyBatis3:默认的值,生成基于MyBatis3.x以上版本的内容,包括XXXBySample;
            2,MyBatis3Simple:类似MyBatis3,只是不生成XXXBySample;
    -->
    <context id="testTables" targetRuntime="MyBatis3">
        <!-- 为模型生成序列化方法-->
        <plugin type="org.mybatis.generator.plugins.SerializablePlugin"/>
        <!-- 为生成的Java模型创建一个toString方法 -->
        <plugin type="org.mybatis.generator.plugins.ToStringPlugin"/>
        <!--生成mapper.xml时覆盖原文件-->
        <plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin" />

        <commentGenerator>
            <!-- 是否去除自动生成的注释 true:是 : false:否 -->
            <property name="suppressAllComments" value="true" />
        </commentGenerator>

        <!--数据库连接的信息:驱动类、连接地址、用户名、密码 -->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/ssm"
                        userId="root"
                        password="root">
        </jdbcConnection>

        <!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true时把JDBC DECIMAL 和
            NUMERIC 类型解析为java.math.BigDecimal -->
        <javaTypeResolver>
            <property name="forceBigDecimals" value="false" />
        </javaTypeResolver>

        <!--
            Java模型创建器,是必须要的元素
            targetPackage:生成的类要放的包,真实的包受enableSubPackages属性控制;
            targetProject:目标项目,指定一个存在的目录下,生成的内容会放到指定目录中,如果目录不存在,MBG不会自动建目录
        -->
        <javaModelGenerator targetPackage="com.newcapec.entity" targetProject=".\src\main\java">
            <!--  for MyBatis3/MyBatis3Simple
                自动为每一个生成的类创建一个构造方法,构造方法包含了所有的field;而不是使用setter;
             -->
            <property name="constructorBased" value="true"/>

            <!-- 在targetPackage的基础上,根据数据库的schema再生成一层package,最终生成的类放在这个package下 -->
            <property name="enableSubPackages" value="false" />
            <!-- 从数据库返回的值被清理前后的空格 -->
            <property name="trimStrings" value="true" />
        </javaModelGenerator>

        <!--
            生成SQL map的XML文件生成器
            targetPackage/targetProject:同javaModelGenerator
        -->
        <sqlMapGenerator targetPackage="mapper" targetProject=".\src\main\resources">
            <property name="enableSubPackages" value="false" />
        </sqlMapGenerator>

        <!--
            对于mybatis来说,即生成Mapper接口,注意,如果没有配置该元素,那么默认不会生成Mapper接口
            targetPackage/targetProject:同javaModelGenerator
            type:选择怎么生成mapper接口(在MyBatis3/MyBatis3Simple下):
            1,ANNOTATEDMAPPER:会生成使用Mapper接口+Annotation的方式创建(SQL生成在annotation中),不会生成对应的XML;
            2,MIXEDMAPPER:使用混合配置,会生成Mapper接口,并适当添加合适的Annotation,但是XML会生成在XML中;
            3,XMLMAPPER:会生成Mapper接口,接口完全依赖XML;
        -->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.newcapec.mapper" targetProject=".\src\main\java">
            <property name="enableSubPackages" value="false" />
        </javaClientGenerator>


        <!-- 选择一个table来生成相关文件,可以有一个或多个table,必须要有table元素
        选择的table会生成一下文件:
        1,SQL map文件
        2,生成一个主键类;
        3,除了BLOB和主键的其他字段的类;
        4,包含BLOB的类;
        5,一个用户生成动态查询的条件类(selectByExample, deleteByExample),可选;
        6,Mapper接口(可选)

        tableName(必要):要生成对象的表名;
        注意:大小写敏感问题。正常情况下,MBG会自动的去识别数据库标识符的大小写敏感度,在一般情况下,MBG会
            根据设置的schema,catalog或tablename去查询数据表,按照下面的流程:
            1,如果schema,catalog或tablename中有空格,那么设置的是什么格式,就精确的使用指定的大小写格式去查询;
            2,否则,如果数据库的标识符使用大写的,那么MBG自动把表名变成大写再查找;
            3,否则,如果数据库的标识符使用小写的,那么MBG自动把表名变成小写再查找;
            4,否则,使用指定的大小写格式查询;
        另外的,如果在创建表的时候,使用的""把数据库对象规定大小写,就算数据库标识符是使用的大写,在这种情况下也会使用给定的大小写来创建表名;
        这个时候,请设置delimitIdentifiers="true"即可保留大小写格式;

        可选:
        1,schema:数据库的schema;
        2,catalog:数据库的catalog;
        3,alias:为数据表设置的别名,如果设置了alias,那么生成的所有的SELECT SQL语句中,列名会变成:alias_actualColumnName
        4,domainObjectName:生成的domain类的名字,如果不设置,直接使用表名作为domain类的名字;可以设置为somepck.domainName,那么会自动把domainName类再放到somepck包里面;
        5,enableInsert(默认true):指定是否生成insert语句;
        6,enableSelectByPrimaryKey(默认true):指定是否生成按照主键查询对象的语句(就是getById或get);
        7,enableSelectByExample(默认true):MyBatis3Simple为false,指定是否生成动态查询语句;
        8,enableUpdateByPrimaryKey(默认true):指定是否生成按照主键修改对象的语句(即update);
        9,enableDeleteByPrimaryKey(默认true):指定是否生成按照主键删除对象的语句(即delete);
        10,enableDeleteByExample(默认true):MyBatis3Simple为false,指定是否生成动态删除语句;
        11,enableCountByExample(默认true):MyBatis3Simple为false,指定是否生成动态查询总条数语句(用于分页的总条数查询);
        12,enableUpdateByExample(默认true):MyBatis3Simple为false,指定是否生成动态修改语句(只修改对象中不为空的属性);
        13,modelType:参考context元素的defaultModelType,相当于覆盖;
        14,delimitIdentifiers:参考tableName的解释,注意,默认的delimitIdentifiers是双引号,如果类似MYSQL这样的数据库,使用的是`(反引号,那么还需要设置context的beginningDelimiter和endingDelimiter属性)
        15,delimitAllColumns:设置是否所有生成的SQL中的列名都使用标识符引起来。默认为false,delimitIdentifiers参考context的属性

        注意,table里面很多参数都是对javaModelGenerator,context等元素的默认属性的一个复写;
     -->
        <!--逆向工程不生成example类-->
        <!-- 列出要生成代码的所有表,这里配置的是不生成Example文件 -->
        <table tableName="users" domainObjectName="Users"
               enableCountByExample="false"
               enableUpdateByExample="false"
               enableDeleteByExample="false"
               enableSelectByExample="false"
               selectByExampleQueryId="false">
            <!-- 如果设置为true,生成的model类会直接使用column本身的名字,而不会再使用驼峰命名方法 -->
            <property name="useActualColumnNames" value="false"/>
        </table>
    </context>
</generatorConfiguration>

    Mybatis Generator最完整配置详解:
        https://blog.csdn.net/qq_33326449/article/details/105930655

9.5 生成文件代码

public class Generator {

    public static void main(String[] args) throws Exception {
        //是否覆盖已有文件
        boolean overwirte = true;
        DefaultShellCallback callback = new DefaultShellCallback(overwirte);
        List<String> warnings = new ArrayList<>();

        //创建配置解析类
        ConfigurationParser configurationParser = new ConfigurationParser(warnings);
        InputStream in = Generator.class.getClassLoader().getResourceAsStream("generator.xml");
        Configuration configuration = configurationParser.parseConfiguration(in);
        MyBatisGenerator myBatisGenerator = new MyBatisGenerator(configuration, callback, warnings);
        myBatisGenerator.generate(null);
        System.out.println("代码生成成功...");
    }
}

9.6 第三方插件

9.6.1 Free Mybatis Tool

安装插件

    File -> Settings -> Plugins -> 搜索框输入:Free Mybatis Tool,点击Install安装。
    
    注意:安装完成之后,最好重启IDEA。

逆向生成mapper、类

    通过这个插件不用使用官方的mybatis逆向生成包,写配置文件等等,仅需连接对应数据库就可以实现逆向生成对应的类、mapper文件等。

第一步:连接数据库

 第二步:配置Driver(首次使用)

    点击Driver:Mysql -> Go to Driver,配置MySQL驱动;

第三步:找到需要逆向生成的表右键选择Mybatis-Generator

第四步:配置

跳转功能

    在使用mybatis框架的时候,你还在一个类一个类的点开寻找对应mapper或者dao程序的位置吗?那样就会显得特别麻烦且浪费时间。而这个Free Mybatis Tool插件提供了跳转的功能。通过点击箭头就可以跳转到相应的地方。

9.6.2 Easy Code

    EasyCode是idea的一个插件,可以采用图形化的方式对数据的表生成entity,controller,service,dao,mapper……无需任何编码,简单而强大。

安装插件

    File -> Settings -> Plugins -> 搜索框输入:EasyCode,点击Install安装。
    
    注意:安装完成之后,最好重启IDEA。

逆向生成mapper、类

    找到需要逆向生成的表右键选择EasyCode。
        1、Generate Code,代码生成;
        2、Config Table,配置表信息;

十、Mybatis 注解开发

10.1 什么是注解开发

    Mybatis最初配置信息是基于XML,映射语句(SQL)也是定义在 XML 中的。而到了 MyBatis 3提供了新的基于注解的配置。使用注解开发方式,可以减少编写 Mapper 映射文件。

10.2 常用注解说明

注解描述
@Insert配置新增
@Update配置更新
@Delete配置删除
@Select配置查询
@Options配置主键返回,关闭二级缓存等功能
@Result结果集封装
@Results与@Result 一起使用,封装多个结果集
@ResultMap引用@Results 定义的封装
@One一对一结果集封装
@Many一对多结果集封装
@SelectProvider动态 SQL 映射
@CacheNamespace二级缓存
@Param输入多参数
@Mapper把mapper这个DAO交給Spring管理,整合用到

10.3 实体类

10.3.1 部门类

public class Dept {

    private Integer deptno;
    private String dname;
    private String loc;

    public Integer getDeptno() {
        return deptno;
    }

    public void setDeptno(Integer deptno) {
        this.deptno = deptno;
    }

    public String getDname() {
        return dname;
    }

    public void setDname(String dname) {
        this.dname = dname;
    }

    public String getLoc() {
        return loc;
    }

    public void setLoc(String loc) {
        this.loc = loc;
    }

    @Override
    public String toString() {
        return "Dept{" +
                "deptno=" + deptno +
                ", dname='" + dname + '\'' +
                ", loc='" + loc + '\'' +
                '}';
    }
}

10.3.2 员工类

public class Emp {

    private Integer empno;
    private String ename;
    private String job;
    private Integer mgr;
    private Date hiredate;
    private Double sal;
    private Double comm;
    private Integer deptno;
    private Dept dept;

    public Integer getEmpno() {
        return empno;
    }

    public void setEmpno(Integer empno) {
        this.empno = empno;
    }

    public String getEname() {
        return ename;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    public String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }

    public Integer getMgr() {
        return mgr;
    }

    public void setMgr(Integer mgr) {
        this.mgr = mgr;
    }

    public Date getHiredate() {
        return hiredate;
    }

    public void setHiredate(Date hiredate) {
        this.hiredate = hiredate;
    }

    public Double getSal() {
        return sal;
    }

    public void setSal(Double sal) {
        this.sal = sal;
    }

    public Double getComm() {
        return comm;
    }

    public void setComm(Double comm) {
        this.comm = comm;
    }

    public Integer getDeptno() {
        return deptno;
    }

    public void setDeptno(Integer deptno) {
        this.deptno = deptno;
    }

    public Dept getDept() {
        return dept;
    }

    public void setDept(Dept dept) {
        this.dept = dept;
    }

    @Override
    public String toString() {
        return "Emp{" +
                "empno=" + empno +
                ", ename='" + ename + '\'' +
                ", job='" + job + '\'' +
                ", mgr=" + mgr +
                ", hiredate=" + hiredate +
                ", sal=" + sal +
                ", comm=" + comm +
                ", deptno=" + deptno +
                ", dept=" + dept +
                '}';
    }
}

10.4 单表增删改差

10.4.1 mapper接口

public interface DeptMapper {
    @Select("select deptno,dname,loc from dept")
    List<Dept> select();

    @Select("select deptno,dname,loc from dept where deptno = #{deptno}")
    Dept selectById(Integer deptno);

    @Insert("insert into dept(dname,loc) values (#{dname}, #{loc})")
    @Options(useGeneratedKeys = true, keyProperty = "deptno", keyColumn = "deptno")
    void insert(Dept dept);

    @Update("update dept set dname = #{dname},loc=#{loc} where deptno = #{deptno}")
    void update(Dept dept);

    @Delete("delete from dept where deptno=#{deptno}")
    void delete(Integer deptno);
}

10.4.2 测试

public class AnnotationTest {

    @Test
    public void testSelect() {
        SqlSession sqlSession = MybatisUtil.getSession();

        DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class);
        List<Dept> list = deptMapper.select();
        for (Dept dept : list) {
            System.out.println(dept);
        }

        sqlSession.close();
    }

    @Test
    public void testSelectById() {
        SqlSession sqlSession = MybatisUtil.getSession();

        DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class);
        Dept dept = deptMapper.selectById(10);
        System.out.println(dept);

        sqlSession.close();
    }

    @Test
    public void testInsert() {
        SqlSession sqlSession = MybatisUtil.getSession();

        DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class);
        Dept dept = new Dept();
        dept.setDname("aa");
        dept.setLoc("aa");
        deptMapper.insert(dept);
        sqlSession.commit();
        System.out.println("主键:" + dept.getDeptno());

        sqlSession.close();
    }

    @Test
    public void testUpdate() {
        SqlSession sqlSession = MybatisUtil.getSession();

        DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class);
        Dept dept = new Dept();
        dept.setDeptno(41);
        dept.setDname("bb");
        dept.setLoc("bb");
        deptMapper.update(dept);
        sqlSession.commit();

        sqlSession.close();
    }

    @Test
    public void testDelete() {
        SqlSession sqlSession = MybatisUtil.getSession();

        DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class);
        deptMapper.delete(41);
        sqlSession.commit();

        sqlSession.close();
    }
}

10.5 一对一关系映射

10.5.1 mapper接口

public interface EmpMapper {
    /*
     * 手动映射resultMap标签
     * @Results + @Result注解替代
     * @Results = resultMap标签
     * @Result = resultMap标签的子标签id和result
     */
    @Select("select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp")
    @Results(id = "selectResultMap",
             value = {
                 @Result(id = true, column = "empno", property = "empno"),
                 @Result(column = "ename", property = "ename"),
                 @Result(column = "job", property = "job"),
                 @Result(column = "mgr", property = "mgr"),
                 @Result(column = "hiredate", property = "hiredate"),
                 @Result(column = "sal", property = "sal"),
                 @Result(column = "comm", property = "comm"),
                 @Result(column = "deptno", property = "deptno"),
                 @Result(column = "deptno", property = "dept", javaType = Dept.class,
                         one = @One(select = "com.newcapec.dao.DeptDao.selectById", fetchType = FetchType.LAZY))
             })
    List<Emp> select();

    @Select("select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp where empno=#{empno}")
    @ResultMap("selectResultMap")
    Emp selectById(Integer empno);
}

10.5.2 测试

@Test
public void testSelectEmp() {
    SqlSession sqlSession = MybatisUtil.getSession();

    EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);

    List<Emp> empList = empMapper.select();
    for (Emp emp : empList) {
        System.out.println(emp);
    }
    sqlSession.close();
}

@Test
public void testSelectEmpById() {
    SqlSession sqlSession = MybatisUtil.getSession();

    EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);

    Emp emp = empMapper.selectById(7369);
    System.out.println(emp);
    sqlSession.close();
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我是波哩个波

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值