One article teaches you how to play with Mybatis, super detailed code explanation and actual combat

1. Getting started with Mybatis

1.1 What is MyBatis

    MyBatis was originally iBatis, an open source project of Apache. In 2010, the project was migrated from Apache Software Foundation to Google Code and renamed MyBatis. Migrated to Github in November 2013.
    
    The word iBATIS comes from the combination of "internet" and "abatis", which is a Java-based persistence layer framework. The persistence layer framework provided by iBATIS includes SQL Maps and Data Access Objects (DAOs).

    A software framework (software framework) usually refers to a software component specification to implement a certain industry standard or complete a specific basic task, and also refers to a software product that provides the basic functions required by the specification in order to implement a certain software component specification. It can be simply understood that the framework is a semi-finished project, and we can develop projects based on the framework to improve development efficiency. Internet companies now put efficiency first. If the development cycle is too long, similar products may appear in the market soon, and the opportunity will be lost.
    
    Currently, the latest version is MyBatis 3.5.9, and its release date is December 26, 2021.
    
    MyBatis is an excellent persistence layer framework. It encapsulates the process of JDBC to operate the database, so that developers only need to pay attention to SQL itself, without spending energy on processing such as registering drivers, creating connections, creating statements, manually setting parameters, and results. JDBC complex process code such as set retrieval.
    
    MyBatis configures various statements to be executed through xml files or annotations, and generates the final executed sql statement through mapping between Java objects and the dynamic parameters of sql in the statement, and finally the mybatis framework executes sql and maps the results into Java objects and return.

1.2 Advantages of MyBatis

    Easy to learn: itself is small and simple. Without any third-party dependencies, the simplest installation only needs two jar files + configuration of several sql mapping files. Easy to learn, easy to use. Through the documentation and source code, you can fully grasp its design ideas and implementation.

    Flexible: Mybatis does not impose any impact on the existing design of the application or database. SQL is written in xml, which is convenient for unified management and optimization. All requirements for operating the database can be met through sql statements.

    Uncoupling sql and program code: By providing a DAO layer, business logic and data access logic are separated, making the system design clearer, easier to maintain, and easier to unit test. The separation of sql and code improves maintainability.

    Provides mapping tags and supports ORM field relationship mapping between objects and databases.

    Object-relational mapping tags are provided to support object-relational building and maintenance.

    Provide xml tags and support writing dynamic sql.

1.3 Problems in JDBC programming

    In the actual development process, we generally use the ORM framework to replace the traditional JDBC, such as Mybatis or Hibernate, but JDBC is the basis for Java to achieve data access, mastering it is very helpful for us to understand the data operation process of Java.

    ORM: Object Relational Mapping (ORM) mode is a technology to solve the mismatch between object-oriented and relational databases. Simply put, ORM automatically persists the objects in the program to the relational database by using the metadata describing the mapping between the object and the database.

    Typical ORM middleware for Java include: Hibernate, Mybatis, speedframework.

    ORM technical features:
        1. Improved development efficiency. Since ORM can automatically map fields and attributes between Entity objects and Tables in the database, we may not actually need a dedicated and huge data access layer. 

        2. ORM provides a mapping to the database, without directly coding with sql, and can obtain data from the database like an operation object. 

    Then why do we use some ORM frameworks instead of traditional JDBC in actual development? Next, let's take a look at a simple implementation of a JDBC program and analyze its shortcomings, as follows:

1.3.1 JDBC program

@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 programming steps

    1. Load the database driver;

    2. Create and obtain the database connection Connection;

    3. Create a PreparedStatement object that executes the SQL statement;

    4. Set the placeholder parameters in the SQL statement;

    5. Execute Sql through PreparedStatement and get the result set;

    6. Analyze and process the Sql execution results;

    7. Release resources (Connection, Preparedstatement, ResultSet).

1.3.3 JDBC problems are summarized as follows

From the above procedures, the summary is as follows:

    1. The database connection is created when it is used, and released immediately when it is not in use, so that the database is frequently operated, resulting in waste of resources and affecting performance; optimization idea: use the database
        connection pool to manage database objects;

    2. sql is hard-coded into the Java program. If you change sql, you have to recompile the Java code, which is not conducive to the maintenance of the system in the later stage; optimization idea: configure the sql statement
        in xml, even if you change the sql, you don’t need to recompile the source code ;

    3. Setting parameters to PreparedStatement is also hard-coded into the Java program, which is not conducive to later maintenance;
        optimization idea: all the sql statements, placeholders and parameters are configured in xml, and the source code does not need to be recompiled for changes;

    4. When traversing the result set data from the resultset, there is also hard coding, which is not conducive to the maintenance of the later system;
        optimization idea: automatically map the query result set into a Java object;

    In response to the above problems, many solutions to optimize JDBC have naturally appeared, that is, the ORM persistence layer framework that appeared later, such as Mybatis and Hibernate, etc.; this is why ORM frameworks are preferred in actual development. up.
    
    With this concept, then we will start to learn about Mybatis...

    NOTE: Hardcoding is the software development practice of embedding data directly into the source code of a program or other executable object, as opposed to obtaining data externally or generating it at runtime. Hardcoded data can usually only be modified by editing the source code and recompiling the executable.

1.4 MyBatis Architecture

    1. MyBatis configuration:
        mybatis-config.xml (the name is not fixed), this file is used as the global (core) configuration file of MyBatis, and configures the operating environment of MyBatis and other information.
        The mapper.xml file is the Sql mapping file, and the Sql statement for operating the database is configured in the file. This file needs to be loaded in mybatis-config.xml.

    2. Construct SqlSessionFactory, that is, the session factory, through configuration information such as the MyBatis environment.

    3. The SqlSession is created by the session factory, that is, the session, and the operation of the database needs to be performed through the SqlSession.

    4. The bottom layer of MyBatis customizes the Executor interface to operate the database. The Executor interface has two implementations, one is the basic executor and the other is the cache executor.

    5. MappedStatement is also an underlying encapsulation object of MyBatis. Mybatis loads SQL configuration information into MappedStatement objects (including incoming parameter mapping configuration, executed SQL statement, and result mapping configuration) and stores them in memory. A Sql in the mapper.xml file corresponds to a MappedStatement object, and the id of the Sql is the id of the MappedStatement.

    6. MappedStatement defines the input parameters for Sql execution, including HashMap, basic type, string type, and entity class type. Executor maps the input Java object to Sql through MappedStatement before executing Sql. The input parameter mapping is JDBC programming. PreparedStatement sets parameters.

    7. MappedStatement defines the output results of Sql execution, including HashMap, basic types, string types, and entity class types. Executor maps the output results to Java objects after executing SQL through MappedStatement. The output result mapping process is equivalent to JDBC programming. The process of parsing the results.

Principle analysis:

    1. Load configuration: The configuration comes from two places, one is the configuration file, the other is the annotation of the Java code, and the SQL configuration information is loaded into each MappedStatement object (including the incoming parameter mapping configuration and the executed SQL statement , result mapping configuration), stored in memory.

    2. SQL parsing: When the API interface layer receives a call request, it will receive the ID of the incoming SQL and the incoming object (which can be a Map, JavaBean or basic data type), and Mybatis will find the corresponding MappedStatement according to the ID of the SQL. Then the MappedStatement is parsed according to the incoming parameter object, and the final SQL statement and parameters to be executed can be obtained after parsing.

    3. SQL execution: take the final SQL and parameters to the database for execution, and obtain the result of operating the database.

    4. Result mapping: convert the result of operating the database according to the mapping configuration, which can be converted into HashMap, JavaBean or basic data type, and return the final result.

1.5 Build MyBatis project

1.5.1 Create a Java project

    MyBatis is a persistence layer framework, which is used when operating the database. There is no need to create a JavaWEB project, just create a Maven Java project.

1.5.2 Import MyBatis framework jar package

    The code of Mybaits is managed by github.com, download address: https://github.com/mybatis/mybatis-3/releases

mybatis directory structure:

    lib -> mybatis accessory toolkit

    LICENSE -> License

    mybatis-3.4.6.jar -> mybatis core package
    
    mybatis-3.4.6.pdf -> mybatis manual

    NOTICE -> Legal notice

Method 1: Import the jar package

    mybatis-3.4.6.jar is the core jar package and must be imported.
    
    The jar package in the lib directory is a toolkit, which is optional. We generally introduce log4j as the mybatis log output component.

    The driver package of mysql or oracle database must be imported.  

Method 2: maven dependency configuration

<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 relies on error resolution: https://blog.csdn.net/qq_38000902/article/details/82759268

    There is ojdbc6.jar in the oracle installation directory: D:\Tools\product\11.2.0\dbhome_1\jdbc\lib (my path)

    Mybatis uses log4j as the output log information by default, and you can also import log4j related files.

1.5.3 Write the global configuration file in MyBatis

    Create mybatis-config.xml in the src/main/resources directory, function: configure the MyBatis operating environment such as data sources, transactions, etc.

    Note: If there is no resources directory under src/main, then we manually create one and make it 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 configuration file reports an error:

    When writing an xml configuration file to introduce DTD constraints, this error may appear indicating that URI is not registered (Settings | Languages ​​& Frameworks | Schemas and DTDs). At this time, use the shortcut key to select the Fetch external resource or Ignore external resource option.

    Or directly add the problematic path in settings -> languages&frameworks -> schemas and dtds.

1.5.4 Database 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 Writing Entity Classes

    The entity class is used as Mybatis for sql mapping. The entity class usually corresponds to the database table. Emp.java is as follows:

    Note: Entity classes are used to correspond to database tables, we'd better use all reference types.

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 Writing a mapping file

    Create a mapper directory under src/main/resources, and create a sql mapping file Emp.xml in this directory.

<?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 Loading the mapping file

    Add the mapping file location in the global configuration file of MyBatis.

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

1.5.8 log4j configuration

    Mybatis log output: log4j.properties configuration file.

#井号表示注释,配置内容为键值对格式,每行只能有一个键值对,键值对之间以=连接
#指定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 Writing test programs

    It is recommended to use Junit unit tests.

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 Basic operation of adding, deleting, modifying and checking

    Realize the following functions:
        query all employee information

        add staff

        update staff

        delete employee

        Fuzzy query based on employee name

1.6.1 Query operation

mapper file:

<!--
	查询到数据返回多条记录,每一条封装在一个实体类对象中,所有的实体类对象封装在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 code:

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 New operation

mapper file:

<!--
	添加操作使用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 code:

@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();
}

The primary key of the inserted data returns:

  • select last_insert_id(), which means to get the primary key value of the record just inserted, which is applicable to the database with self-increasing primary key;

  • select seq_demo.nextval from dual, which means to get the value generated by the next sequence, which is suitable for databases with sequences;

  • keyProperty: Set the query to the primary key value to which attribute of the object specified by parameterType;

  • order: the execution order of the Sql statement in the selectKey tag relative to the insert statement;

  • resultType: Specify the result type of the Sql statement in the selectKey tag;

Oracle database:

<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 database:

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

If it is a primary key self-incrementing database (MySQL), you can also use:

  • useGeneratedKeys: Whether to enable primary key return, false is not enabled by default;

  • keyProperty: Which member variable of the entity class is stored in the obtained primary key value;

<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 Modify operation

mapper file:

<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 code:

@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 Delete operation

mapper file:

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

Java code:

@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 Fuzzy query

mapper file:

<!--
	条件查询:模糊查询
    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 code:

@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 file:

<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 code:

@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 file:

<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 code:

@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 Summary

parameterType and resultType:

    parameterType: Specify the input parameter type, MyBatis obtains the parameter value from the input object through ognl and sets it in Sql;

    resultType: Specifies the output result type, and MyBatis maps a row of record data of the Sql query result to an object of the type specified by resultType;

#{} and ${}:

    #{} represents a placeholder symbol, #{} receives input parameters, the type can be simple type, entity class type, HashMap; #{}
    receives simple type, #{} can be written as value or other names;
    #{} receives Entity class object value, read the attribute value in the object through OGNL, and obtain the object attribute value through attribute.attribute.attribute...;
  
    ${} represents a splicing symbol, which will refer to Sql injection, so it is not recommended to use ${ };
    ${} receives input parameters, the type can be simple type, entity class type, HashMap;
    ${} receives simple type, ${} can only be written as value;
    ${} receives entity class object value, read through OGNL The attribute value in the object, get the attribute value of the object through attribute.attribute.attribute...;

selectOne和selectList:

    selectOne means querying a record for mapping;
    if you use selectOne, you can use selectList (there is only one object in the list);
    
    selectList means querying a list (multiple records) for mapping;
    if you use selectList to query multiple records, you cannot Use selectOne;
    if you use selectOne to report an error: org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 4  

1.7 Difference between MyBatis and Hibernate

    Hibernate: is a standard ORM framework (object-relational mapping).
        The entry threshold is high, no need to program to write Sql, Sql statement is automatically generated, it is difficult to optimize and modify the sql statement; application
        scenario: applicable to small and medium-sized projects with little change in demand, such as: background management system, erp, crm, oa...;

    MyBatis: Focus on Sql itself, requiring programmers to write Sql statements themselves. Sql modification and optimization are more convenient;
        MyBatis is an incomplete ORM framework. Although programmers write Sql themselves, MyBatis can also implement mapping (input mapping, output mapping) ;
        Application scenario: Applicable and demanding projects, such as: Internet projects  

    The enterprise selects technology, takes low cost and high return as the principle of technology selection, and selects according to the technical strength of the project team.

    Mybatis is different from hibernate, it is not completely an ORM framework, because MyBatis requires programmers to write Sql statements themselves, but mybatis can flexibly configure the sql statements to be run through XML or annotations, and map java objects and sql statements to generate final execution sql, and finally map the results of sql execution to generate java objects.

    Mybatis has a low learning threshold and is easy to learn. Programmers can directly write native sql, which can strictly control sql execution performance and has high flexibility. It is very suitable for software development that does not require high requirements for relational data models, such as Internet software, enterprise operation software, etc. Because the requirements of this type of software change frequently, once the requirements change, the results are required to be output quickly. But the premise of flexibility is that mybatis cannot be database-independent. If you need to implement software that supports multiple databases, you need to customize multiple sets of sql mapping files, which is a heavy workload.

    Hibernate has strong object/relational mapping capabilities and good database independence. If you use hibernate to develop software with high requirements for relational models (such as customized software with fixed requirements), you can save a lot of code and improve efficiency. However, Hibernate has a high learning threshold and a higher threshold for proficiency, and how to design O/R mapping, how to trade off between performance and object model, and how to make good use of Hibernate requires strong experience and ability.

    In short, as long as a software architecture with good maintainability and scalability can be made according to the needs of employees in a limited resource environment, it is a good architecture, so the best framework is only suitable.

1.8 Mybatis solves the problem of JDBC programming

    1. The frequent creation and release of database links causes waste of system resources and affects system performance. If you use a database link pool, this problem can be solved.
        Solution: Configure the data link pool in mybatis-config.xml, and use the connection pool to manage the database link.

    2. Sql statements are written in the code, which makes the code difficult to maintain. The actual application of sql may change a lot, and the change of sql needs to change the java code.
        Solution: Configure the Sql statement in the XXXXmapper.xml file and separate it from the java code.

    3. It is troublesome to pass parameters to the SQL statement, because the where condition of the SQL statement is not necessarily certain, there may be more or less, and the placeholders need to correspond to the parameters one by one.
        Solution: Mybatis automatically maps java objects to sql statements, and defines the type of input parameters through the parameterType in the statement.

    4. It is troublesome to parse the result set. Changes in sql lead to changes in the parsing code, and it needs to be traversed before parsing. It is more convenient to parse the database records into pojo objects.
        Solution: Mybatis automatically maps the sql execution result to the java object, and defines the type of the output result through the resultType in the statement.

2. Mybatis DAO development

There are usually two methods for developing Dao using Mybatis, namely the original Dao development method and the Mapper interface development method.

2.1 Mybatis API

2.1.1 SqlSessionFactoryBuilder

    SqlSessionFactoryBuilder is used to create SqlSessionFactoryBuilder. Once SqlSessionFactoryBuilder is created, SqlSessionFactoryBuilder is not needed. Because SqlSession is produced through SqlSessionFactory, SqlSessionFactoryBuilder can be used as a tool class. The best use range is the method scope, that is, the local variables in the method body.

2.1.2 SqlSessionFactory

    SqlSessionFactory is an interface that defines different overloading methods of openSession. The best use of SqlSessionFactory is during the entire application run. Once created, it can be reused. SqlSessionFactory is usually managed in a singleton mode.

    Note: openSession (true), true is to automatically commit the transaction, false is the opposite.

2.1.3 SqlSession

    SqlSession is a user-oriented (programmer) interface, which provides many methods for operating the database. Such as: selectOne (returns a single object), selectList (returns a single or multiple objects), insert, update, delete.

    Instances of SqlSession cannot be shared, it is not thread safe, each thread should have its own instance of SqlSession, so the best scope is request or method scope. Never put a reference to a SqlSession instance in a static or instance field of a class.

2.2 Mybatis tool class

    In order to simplify the development of MyBatis, MyBatis can be further packaged.

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

test:

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

2.3 The original DAO development method

    The original Dao development method requires programmers to write the Dao interface and the Dao implementation class, which is nothing more than calling the sql defined in the mapping file in the Dao implementation class.

2.3.1 Entity class

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 Interface

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 Implementation class

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 file

<?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>

Load mapper file:

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

2.3.5 Testing

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 Original DAO development issues

    There are a large number of template methods in the dao interface implementation class method. Imagine whether these codes can be extracted to greatly reduce the workload of programmers.
    
    Calling the database operation method of sqlSession needs to specify the id of the statement, which is hard-coded here, which is not conducive to development and maintenance.
    
    The variable passed in when calling the SqlSession method, because the SqlSession method uses generics, even if the variable type is wrongly passed in, no error will be reported during the compilation stage, which is not conducive to programmer development.  
    Note: The original Dao development is basically similar to the Dao development we explained in the Web stage. They all have Dao interfaces and Dao implementation classes, but the Dao implementation classes in the Web stage use DBUtils to operate SQL; now the original Dao development of Mybatis separates SQL It's out, it's just written in the XML mapping file.

2.4 Mapper proxy method (key point)

    The Mapper proxy development method only requires programmers to write the Mapper interface (equivalent to the Dao interface), and the MyBatis framework creates the dynamic proxy object of the interface according to the interface definition. The method body of the proxy object is the same as the above Dao interface implementation class method.
    
    Programmers need to follow some development specifications when writing the Mapper interface, and MyBatis can automatically generate a proxy object of the Mapper interface implementation class.

2.4.1 Development Specifications

    1. The namespace in the Mapper.xml file is the same as the classpath of the mapper interface.

    2. The Mapper interface method name is the same as the id of each tag defined in Mapper.xml.

    3. The parameter type of the Mapper interface method is the same as the parameterType of each sql defined in mapper.xml.

    4. The return value type of the Mapper interface method is the same as the resultType of each sql defined in mapper.xml.

Note: The Mapper.xml mapping file should preferably be consistent with the name of the Mapper interface;

2.4.2 Entity class

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 interface

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);
}
  • Batch query: The return value of the method is List type, which means that the SqlSession object will call the selectList() method.

  • Single query: The return value of the method is a single entity object, which means that the SqlSession object will call the selectOne() method.

  • Additions, deletions and modifications:

    1. The return value of the method is void, which means that the return value of the insert, update, and delete methods in the SqlSession object will not be processed.

    2. The return value of the method is int type, indicating that the return value of the insert, update, and delete methods in the SqlSession object is returned directly.

    3. The return value of the method is boolean type, indicating whether the operation is successful or not according to the return value of the insert, update, and delete methods in the SqlSession object (the number of entries affecting the database). If the number of entries affecting the database is greater than 0, it means success, otherwise it means failure.

2.4.4 mapper file

<?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>

Load mapper file:

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

2.4.5 Testing

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 officially recommends using the mapper proxy method to develop the mapper interface. Programmers do not need to write a mapper interface implementation class. When using the mapper proxy method, the input parameters can use pojo packaging objects or map objects to ensure the versatility of dao.

 3. Mybatis core configuration file

The global configuration file mybatis-config.xml of MyBatis, the configuration content is as follows:

  • properties

  • settings (global configuration parameters)

  • typeAliases (type aliases)

  • typeHandlers (type handlers)

  • objectFactory (object factory)

  • plugins

  • environments (environment collection attribute object)

    • environment (environment sub-property object)

      • transactionManager (transaction management)

      • dataSource (data source)

  • mappers

3.1 properties attribute

    To configure the database connection parameters separately in db.properties, you only need to load the property values ​​of db.properties in mybatis-config.xml. There is no need to hardcode the database connection parameters in 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

Load the properties file using <properties>the tag :

  • resource: read resource files from the classpath

    • The classpath is the src/main/resources directory + src/main/java directory in the maven project, and the compiled directory is target/classes;

  • url: read resources from the file system of the current system environment

    • 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>

In the environment use:

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

Note: MyBatis will load properties in the following order

  1. The properties defined in the properties tag body are read first;

  2. Then the property loaded by resource or url in the properties tag will be read, and it will overwrite the read property of the same name;

  3. Finally, read the attribute passed by parameterType, which will overwrite the read attribute of the same name;

suggestion:

  1. Do not add any attribute values ​​in the properties tag body, only define the attribute values ​​in the properties file;

  2. There must be some particularity in defining property names in the properties file, such as: XXXXX.XXXXX.XXXX;

3.2 settings global configuration parameters

    The MyBatis framework can adjust some operating parameters during runtime, such as: enable secondary cache, enable lazy loading.

The configurable parameters are as follows:

set up describe effective value Defaults
cacheEnabled Globally enable or disable cache configuration for any mappers under this configuration. true, false true
lazyLoadingEnabled Globally enable or disable latency scaling. When disabled, all will be hot reloaded. true, false false
aggressiveLazyLoading When enabled, any method call will load all properties of the object. true, false false
multipleResultSetsEnabled Whether to allow a single statement to return multiple result sets (requires driver support). true, false true
useColumnLabel Use column labels instead of column names. Different drivers have different performances in this regard. For details, please refer to the relevant driver documentation or test the two different modes to observe the results of the driver used. true, false true
useGeneratedKeys Allowing JDBC to support automatic generation of primary keys requires driver support. If set to true this setting forces the use of auto-generated primary keys, although some drivers do not support this but still work (eg Derby). true, false false
autoMappingBehavior Specifies how MyBatis should automatically map columns to fields or properties. NONE disables automapping; PARTIAL will only automap result sets that do not define nested result set mappings. FULL automatically maps arbitrarily complex result sets (whether nested or not). NONE, PARTIAL, FULL PARTIAL
autoMappingUnknownColumnBehavior Specifies the behavior of discovering unknown columns (or unknown attribute types) of automap targets. NONE: No response; WARNING: Output reminder log (the log level of 'AutoMappingUnknownColumnBehavior' must be set to WARN); FAILING: Mapping failed (throws SqlSessionException). NONE, WARNING, FAILING NONE
defaultExecutorType Configure the default executor. SIMPLE is a normal executor; REUSE executor will reuse prepared statements; BATCH executor will reuse statements and perform batch updates. SIMPLE, REUSE, BATCH SIMPLE
defaultStatementTimeout Sets the timeout, which determines the number of seconds the driver waits for a response from the database. any positive integer not set
defaultFetchSize Sets a hint value for the driver's result set fetch size (fetchSize). This parameter can only be overridden in query settings. any positive integer not set
safeRowBoundsEnabled Allows pagination (RowBounds) in nested statements. Set to false if allowed. true, false false
safeResultHandlerEnabled Allows pagination (ResultHandler) in nested statements. Set to false if allowed. true, false true
mapUnderscoreToCamelCase Whether to enable automatic camel case mapping, that is, a similar mapping from the classic database column name A_COLUMN to the classic Java property name aColumn. true, false false
localCacheScope MyBatis uses the local cache mechanism (Local Cache) to prevent circular references (circular references) and accelerate repeated nested queries. The default value is SESSION, in which case all queries executed in a session are cached. If the setting value is STATEMENT, the local session is only used for statement execution, and different calls to the same SqlSession will not share data. SESSION, STATEMENT SESSION
jdbcTypeForNull Specify the JDBC type for the null value when no specific JDBC type is provided for the parameter. Some drivers need to specify the JDBC type of the column. In most cases, just use the general type, such as NULL, VARCHAR or OTHER. JdbcType constant, common values: NULL, VARCHAR or OTHER OTHER
lazyLoadTriggerMethods Specifies which object's method triggers a lazy load. comma separated list of methods equals,clone,hashCode,toString
defaultScriptingLanguage Specifies the default language for dynamic SQL generation. a type alias or fully qualified class name org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
defaultEnumTypeHandler Specifies the default TypeHandler used by the Enum. a type alias or fully qualified class name org.apache.ibatis.type.EnumTypeHandler
callSettersOnNulls Specifies whether to call the map object's setter (or put) method when the value in the result set is null, which is useful when relying on Map.keySet() or null value initialization. Note that primitive types (int, boolean, etc.) cannot be set to null. true, false false
returnInstanceForEmptyRow MyBatis returns null by default when all columns of the returned row are empty. When this setting is turned on, MyBatis will return an empty instance. Note that it also works with nested result sets (such as collections or associations). true, false false
logPrefix Specifies the prefix that MyBatis adds to log names. any string not set
logImpl Specify the specific implementation of the log used by MyBatis, and will automatically find it if it is not specified. SLF4J, LOG4J, LOG4J2, JDK_LOGGING, COMMONS_LOGGING, STDOUT_LOGGING, NO_LOGGING 未设置
proxyFactory 指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具。 CGLIB, JAVASSIST JAVASSIST
vfsImpl 指定 VFS 的实现。 自定义 VFS 的实现的类全限定名,以逗号分隔 未设置
useActualParamName 允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 -parameters 选项。 true, false true
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默认支持别名

别名 映射的类型 别名 映射的类型
_byte byte byte java.lang.Byte
_short short short java.lang.Short
_int int int java.lang.Integer
_integer int integer java.lang.Integer
_long long long java.lang.Long
_float float float java.lang.Float
_double double double java.lang.Double
_boolean boolean boolean java.lang.Boolean
string java.lang.String date java.util.Date
map java.util.Map hashmap java.util.HashMap
list java.util.List arraylist java.util.ArrayList
object java.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类型
BooleanTypeHandler Boolean,boolean 任何兼容的布尔值
ByteTypeHandler Byte,byte 任何兼容的数字或字节类型
ShortTypeHandler Short,short 任何兼容的数字或短整型
IntegerTypeHandler Integer,int 任何兼容的数字和整型
LongTypeHandler Long,long 任何兼容的数字或长整型
FloatTypeHandler Float,float 任何兼容的数字或单精度浮点型
DoubleTypeHandler Double,double 任何兼容的数字或双精度浮点型
BigDecimalTypeHandler BigDecimal 任何兼容的数字或十进制小数类型
StringTypeHandler String CHAR和VARCHAR类型
ClobTypeHandler String CLOB和LONGVARCHAR类型
NStringTypeHandler String NVARCHAR和NCHAR类型
NClobTypeHandler String NCLOB类型
ByteArrayTypeHandler byte[] 任何兼容的字节流类型
BlobTypeHandler byte[] BLOB和LONGVARBINARY类型
DateTypeHandler java.util.Date TIMESTAMP类型
DateOnlyTypeHandler java.util.Date DATE类型
TimeOnlyTypeHandler java.util.Date TIME类型
SqlTimestampTypeHandler java.sql.Timestamp TIMESTAMP类型
SqlDateTypeHandler java.sql.Date DATE类型
SqlTimeTypeHandler java.sql.Time TIME类型
ObjectTypeHandler 任意 其他或未指定类型
EnumTypeHandler Enumeration类型 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, false false
aggressiveLazyLoading 当设置为‘true’的时候,懒加载的对象可能被任何懒属性全部加载。否则,每个属性都按需加载 true, false true
<!-- 全局参数设置 -->
<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 \ false true
<!-- 全局参数设置 -->
<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
offsetAsPageNum false 该参数对使用 RowBounds作为分页参数时有效当该参数设置为 true时,会将 RowBounds中的 offset参数当成 pageNum使用,可以用页码和页面大小两个参数进行分页
rowBoundsWithCount false 该参数对使用 RowBounds作为分页参数时有效当该参数设置为true时,使用 RowBounds分页会进行 count查询
pageSizeZero false 当该参数设置为 true时,如果 pageSize=0 或者 RowBounds.limit=0 就会查询出全部的结果,相当于没有执行分页查询,但是返回结果仍然是 Page类型
reasonable false 分页合理化参数启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据
params pageNum=pageNum; pageSize=pageSize; count=countSql; reasonable=reasonable; pageSizeZero=pageSizeZero 为了支持startPage(Object params)方法,增加了该参数来配置参数映射,用于从对象中根据属性名取值, 可以配置 pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值
supportMethodsArguments false 支持通过 Mapper接口参数来传递分页参数分页插件会从查询方法的参数值中,自动根据上面 params配置的字段中取值,查找到合适的值时就会自动分页
autoRuntimeDialect false 设置为 true 时,允许在运行时根据多数据源自动识别对应方言的分页
closeConn true 当使用运行时动态数据源或没有设置 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();
}

Guess you like

Origin blog.csdn.net/ligonglanyuan/article/details/124443117