《Mybatis使用篇》——只为使用,一篇足以

目录

快速入门系列 

概述mybatis是什么?有什么特点?

前置工作

一,引入log4j 

1.1,为什么要引入log4j

​编辑 1.2,如何引入log4j

1.2.1,导入log4j依赖

1.2.2,创建log4j.properties配置文件详解 (不要改文件名字)

1.2.3,在mybatis全局配置文件中引入

二,mybatis的输入输出 

2.1,mybatis的SQL传参

2.1.1,#{}(使用的最多,有利于防止sql注入)

2.1.2,${}(不安全)

2.2,mybatis输入 

2.2.1,单个参数

2.2.2,实体类型参数 

2.2.3,两个以上的简单类型的数据

2.2.4,Map类型的数据输入

总结

2.3,mybatis输入

2.3.1,返回单个简单类型数据

2.3.2,返回实体类型

2.3.3,返回Map类型

2.3.4,返回List数据

2.3.5,返回自增主键

2.3.6,数据库表字段和实体类属性对应关系

总结

三,使用Mybatis映射关联关系

3.1,关联关系概念说明

3.1.1,数量关系

3.1.2,关联关系的方向

3.2,建模

3.2.1,order类

3.2.2,customer类

3.2,对一

3.2.1,创建orderMapper接口

3.2.2,创建orderMapper配置文件 

3.3,对多

3.3.1,创建customerMapper

3.3.2,创建customerMapper配置文件

3.4,分步查询

3.4.1,概念

3.4.2,编写sql文件

3.4.3,配置文件各元素之间的对应关系

3.5,延迟查询 

3.5.1,概念

3.5.2,较低版本

3.5.3,较高版本

四,动态SQL 

4.1,if和where

4.2,set

4.3,trim

4.4,choose/when/otherwise

4.5,foreach

 五,缓存简介(由于是使用篇,这里就不多介绍了)

六,逆向工程 

6.1,简介

6.2,配置pom

 6.3,MEG配置文件

6.4,执行MBG插件

6.5,QBC查询 

6.5.1,版本选择

6.5.2,QBC查询 

6.6,分页插件 

6.6.1,在pom文件中引入依赖

6.6.2,使用 

最后



快速入门系列 

https://blog.csdn.net/weixin_64972949/article/details/130945007?spm=1001.2014.3001.5502

概述
mybatis是什么?有什么特点?

它是一款半自动的ORM持久层框架,具有较高的SQL灵活性,支持高级映射(一对一,一对多),动态SQL,延迟加载和缓存等特性,但它的数据库无关性较低

什么是ORM?

Object Relation Mapping,对象关系映射。对象指的是Java对象,关系指的是数据库中的关系模型,对象关系映射,指的就是在Java对象和数据库的关系模型之间建立一种对应关系,比如用一个Java的Student类,去对应数据库中的一张student表,类中的属性和表中的列一一对应。Student类就对应student表,一个Student对象就对应student表中的一行数据

为什么mybatis是半自动的ORM框架?

用mybatis进行开发,需要手动编写SQL语句。而全自动的ORM框架,如hibernate,则不需要编写SQL语句。用hibernate开发,只需要定义好ORM映射关系,就可以直接进行CRUD操作了。由于mybatis需要手写SQL语句,所以它有较高的灵活性,可以根据需要,自由地对SQL进行定制,也因为要手写SQL,当要切换数据库时,SQL语句可能就要重写,因为不同的数据库有不同的方言(Dialect),所以mybatis的数据库无关性低。虽然mybatis需要手写SQL,但相比JDBC,它提供了输入映射和输出映射,可以很方便地进行SQL参数设置,以及结果集封装。并且还提供了关联查询和动态SQL等功能,极大地提升了开发的效率。并且它的学习成本也比hibernate低很多

前置工作

首先,准备三张表,分别为emp3,t_customer,t_order。

emp3:

DROP TABLE IF EXISTS `emp3`;
CREATE TABLE `emp3`  (
  `emp_id` int(0) NOT NULL AUTO_INCREMENT,
  `wkname` varchar(10) CHARACTER SET utf32 COLLATE utf32_general_ci NULL DEFAULT NULL,
  `address` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT 'Unknown',
  `job_id` int(0) NULL DEFAULT 0,
  PRIMARY KEY (`emp_id`) USING BTREE,
  INDEX `emp_name_index`(`wkname`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic;
 
-- ----------------------------
-- Records of emp3
-- ----------------------------
INSERT INTO `emp3` VALUES (1, '张三', '衡阳', 12);
INSERT INTO `emp3` VALUES (2, '李四', '郴州', 13);
INSERT INTO `emp3` VALUES (3, '王五', '岳阳', 14);
 
SET FOREIGN_KEY_CHECKS = 1;

t_order&t_customer 

CREATE TABLE `t_customer` (`customer_id` INT NOT NULL AUTO_INCREMENT, `customer_name` CHAR(100), PRIMARY KEY (`customer_id`) );

CREATE TABLE `t_order` ( `order_id` INT NOT NULL AUTO_INCREMENT, `order_name` CHAR(100), `customer_id` INT, PRIMARY KEY (`order_id`) ); 

INSERT INTO `t_customer` (`customer_name`) VALUES ('c01');

INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o1', '1');
INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o2', '1');
INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o3', '1'); 

一,引入log4j 

1.1,为什么要引入log4j

首先为什么要引入log4j,先给大家看一个例子,在mybatis全局配置文件中,我将引入log4j的

<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

先注释掉。

然后,我们再随便执行我们的一个测试方法 ,我们会发现,控制台打印了一堆我们看不懂的东西(这是因为我之前引入了SLF4J和logback日志显示,这两个东西有利于我们知道是哪里在报错,如果不引入,信息会更少)。 

之后,我们在将注释取消,再执行同样的测试方法。我们会发现在控制台上有许多对我们有用的信息,最重要的就是它可以显示你当前执行的sql语句,和你传入的参数。这两个信息在我们调试方法的时候是非常有用的。所以我们引入log4j日志是十分有必要的。

 1.2,如何引入log4j

1.2.1,导入log4j依赖

            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>1.2.12</version>
            </dependency>

1.2.2,创建log4j.properties配置文件详解 (不要改文件名字)

#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义
log4j.rootLogger=DEBUG,console,file

#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n

#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/fanlan.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yyyy-MM-dd HH:mm:ss}][%c]%m%n

#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG

1.2.3,在mybatis全局配置文件中引入

     <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

 注意:在全局配置文件中配置信息的顺序是固定的,不可以随便改动。如下图,按照这张图的顺序去书写我们的配置信息。不然的话,就会报错。

大家可以参考一下,我的全局配置文件。注意:这里我导入了外部的jdbc.properties配置文件,至于properties配置文件我就不多讲了。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
    <properties resource="jdbc.properties"/>

    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
<!--    Cause: org.apache.ibatis.builder.BuilderException: Error creating document instance.-->
<!--    Cause: org.xml.sax.SAXParseException; lineNumber: 58; columnNumber: 17; -->
<!--    元素类型为 "configuration" 的内容必须匹配 "(properties?,settings?,typeAliases?,-->
<!--    typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,-->
<!--    environments?,databaseIdProvider?,mappers?)"。-->
<!--    文件摆放的顺序是固定的,不可以改变-->

    <!-- environments表示配置Mybatis的开发环境,可以配置多个环境,在众多具体环境中,使用default属性指定实际运行时使用的环境。default属性的取值是environment标签的id属性的值。 -->
    <environments default="development">

        <!-- environment表示配置Mybatis的一个具体的环境,id:表示连接数据库环境的唯一标识,不可以重复 -->
        <environment id="development">

            <!-- Mybatis的内置的事务管理器 -->
<!--            type: JDBC/MANAGED-->
<!--            JDBC:表示当前环境中,执行SQL时,使用的是JDBC原生的事务管理方式,事务的提交和回滚是手动的-->
<!--            MANAGED:被管理,例如spring-->
            <transactionManager type="JDBC"/>

            <!-- 配置数据源 -->
            <dataSource type="POOLED">
<!--                type:设置数据源的类型-->
<!--                type=POOLED|UNPOOLED|JNDI-->
<!--                POOLED:表示使用数据库连接池缓存数据库连接-->
<!--                UNPOOLED:不使用数据库连接池-->
<!--                JNDI:表示使用上下文中的数据源-->
                <!-- 建立数据库连接的具体信息 -->
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${user}"/>
                <property name="password" value="${password}"/>
            </dataSource>

        </environment>
    </environments>



    <mappers>
        <!-- Mapper注册:指定Mybatis映射文件的具体位置 -->
        <!-- mapper标签:配置一个具体的Mapper映射文件 -->
        <!-- resource属性:指定Mapper映射文件的实际存储位置,这里需要使用一个以类路径根目录为基准的相对路径 -->
        <!--    对Maven工程的目录结构来说,resources目录下的内容会直接放入类路径,所以这里我们可以以resources目录为基准 -->
        <mapper resource="mappers/EmpolyeeMapper.xml"/>
        <!-- 在mapper标签的resource属性中指定Mapper配置文件以“类路径根目录”为基准的相对路径 -->
        <mapper resource="mappers/OrderMapper.xml"/>

        <mapper resource="mappers/CustomerMapper.xml"/>
    </mappers>


</configuration>

工程目录结构如下图:

  

二,mybatis的输入输出 

2.1,mybatis的SQL传参

2.1.1,#{}(使用的最多,有利于防止sql注入)

首先,调用查询方法,查询empId=1的员工。 

对应的sql语句。

    <select id="selectEmpolyee" resultType="com.zhuzhu.Empolyee">
        <!-- Mybatis负责把SQL语句中的#{}部分替换成“?”占位符,在#{}内部还是要声明一个见名知意的名称 -->
        select emp_id empId,wkname name,address address,job_id jobId from emp3 where emp_id=#{empId}
    </select>

可以看到,#{}在mybatis中,最后会被解析为?,其实就是Jdbc的PreparedStatement中的?占位符,它有预编译的过程,会对输入参数进行类型解析(如果入参是String类型,设置参数时会自动加上引号),可以防止SQL注入,如果parameterType属性指定的入参类型是简单类型的话(简单类型指的是8种java原始类型再加一个String),#{}中的变量名可以任意。

2.1.2,${}(不安全)

将上面的sql语句改为。

 <select id="selectEmpolyee" resultType="com.zhuzhu.Empolyee">
        <!-- Mybatis负责把SQL语句中的#{}部分替换成“?”占位符,在#{}内部还是要声明一个见名知意的名称 -->
        select emp_id empId,wkname name,address address,job_id jobId from emp3 where emp_id=#{empId}
    </select>

然后,在执行相同的方法,我们发现${}在底层为我们做的是字符串拼接 ,熟悉sql注入的就知道,字符串拼接就是一个大坑。

2.2,mybatis输入 

2.2.1,单个参数

  int deleteEmpolyeeById(Integer empId);

对应sql

    <delete id="deleteEmpolyeeById">
        delete from emp3 where emp_id=#{empId}
    </delete>

当只有一个参数的时候,直接传就好,但是注意 :sql语句中写字段名,#{}中写对应的属性名

单个简单类型参数,在#{}中可以随意命名,但是没有必要。通常还是使用和接口方法参数同名。

2.2.2,实体类型参数 

 int updateEmpolyeeById(Empolyee empolyee);

对应sql

<update id="updateEmpolyeeById">
        update emp3 set wkname=#{wkname},address=#{address}
        where emp_id=#{empId}
    </update>

Mybatis会根据#{}中传入的数据,加工成getXxx()方法,通过反射在实体类对象中调用这个方法,从而获取到对应的数据。填充到#{}解析后的问号占位符这个位置。所以,使用实体类型数据的时候,#{}中的名字不可以自定义,不然无法通过getXxx()方法获取的值。

2.2.3,两个以上的简单类型的数据

int updateEmployee(@Param("empId") Integer empId,@Param("empSalary") Double empSalary);

sql

<update id="updateEmployee">

  update t_emp set emp_salary=#{empSalary} where emp_id=#{empId}

</update>

 零散的多个简单类型参数,如果没有特殊处理,那么Mybatis无法识别自定义名称,所以我们需要使用@Param注解去标记。

2.2.4,Map类型的数据输入

int updateEmployeeByMap(Map<String, Object> paramMap);

sql

<update id="updateEmployeeByMap">

  update t_emp set emp_salary=#{empSalaryKey} where emp_id=#{empIdKey}

</update>

 #{}写上Map中的Key值,就可以取出数据给到sql,语句中。

总结

1.对于单个简单类型的参数,在#{}中我们可以自定义他的名字,但是通常还是使用和接口方法参数同名。

2.对于实体类型参数,Mybatis会根据#{}中传入的数据,加工成getXxx()方法,通过反射在实体类对象中调用这个方法,从而获取到对应的数据。填充到#{}解析后的问号占位符这个位置

3.对于两个以上的简单的参数,我们需要使用@Param注解,不然mybatis无法识别

4.对于Map类型的数据,#{}中写Map中的key

2.3,mybatis输入

2.3.1,返回单个简单类型数据

int selectEmpCount();

sql

<select id="selectEmpCount" resultType="int">

  select count(*) from t_emp

</select>

 Mybatis 内部给常用的数据类型设定了很多别名。 以 int 类型为例,可以写的名称有:int、integer、Integer、java.lang.Integer、Int、INT、INTEGER 等等。

2.3.2,返回实体类型

Empolyee selectEmpolyee(Integer empId);

sql

<!-- 编写具体的SQL语句,使用id属性唯一的标记一条SQL语句 --> 
<!-- resultType属性:指定封装查询结果的Java实体类的全类名 -->
 <select id="selectEmpolyee" resultType="com.zhuzhu.Empolyee"> 
<!-- Mybatis负责把SQL语句中的#{}部分替换成“?”占位符,在#{}内部还是要声明一个见名知意的名称 -->                     
      select emp_id empId,wkname name,address address,job_id jobId 
      from emp3 where emp_id=${empId}
 </select>

返回实体类型的时候,我们只需要将resultType设置成对应的实体类的全类名就好。

2.3.3,返回Map类型

Map<String,Object> selectEmpNameAndMaxJobId();

sql

   <select id="selectEmpNameAndMaxJobId" resultType="map">
        SELECT
            wkname ,
            job_id ,
            (SELECT AVG(job_id) FROM emp3)
        FROM emp3 WHERE job_id=(
            SELECT MAX(job_id) FROM emp3
        )
    </select>

如下图,通过下面运行产生的日志文件,我们可以知道,返回的Map数据的Key值是字段名,而Value值就是查询出来的内容,比如说(”job_id“,"16")。

2.3.4,返回List数据

适用于SQL查询返回的各个字段综合起来并不和任何一个现有的实体类对应,没法封装到实体类对象中。能够封装成实体类类型的,就不使用Map类型。

List<Empolyee> selectAll();

sql

<select id="selectAll" resultType="com.zhuzhu.Empolyee">
    select emp_id empId,wkname name,address address,job_id jobId from emp3 
</select>

这里的resultType照样是写我们List集合中的元素的全类名

2.3.5,返回自增主键

通常我们会将数据库表的主键id设为自增。在插入一条记录时,我们不设置其主键id,而让数据库自动生成该条记录的主键id,那么在插入一条记录后,如何得到数据库自动生成的这条记录的主键id呢?有两种方式

1.使用useGeneratedKeys和keyProperty属性(要求数据库支持自增主键,比如说Mysql)

<!-- int insertEmployee(Employee employee); -->
<!-- useGeneratedKeys属性字面意思就是“使用生成的主键” -->
<!-- keyProperty属性可以指定主键在实体类对象中对应的属性名,Mybatis会将拿到的主键值存入这个属性 -->
<insert id="insertEmployee" useGeneratedKeys="true" keyProperty="empId">

  insert into t_emp(emp_name,emp_salary)
  values(#{empName},#{empSalary})

</insert>

注意:Mybatis是将自增主键的值设置到实体类对象中,而不是以Mapper接口方法返回值的形式返回。

2.使用子标签(用于不支持自增主键的数据库,比如说Oracle)

<insert id="insertEmployee" parameterType="com.atguigu.mybatis.beans.Employee"  databaseId="oracle">

  <selectKey order="BEFORE" keyProperty="id" resultType="integer">

    select employee_seq.nextval from dual 

  </selectKey>  

  insert into orcl_employee(id,last_name,email,gender) values(#{id},#{lastName},#{email},#{gender})

</insert>

2.3.6,数据库表字段和实体类属性对应关系

1.别名

将字段的别名设置成和实体类属性一致。

就比如我们在写select语句的时候,我们在字段名后面写上实体类的属性名。

<select id="selectAll" resultType="com.zhuzhu.Empolyee"> 
    select emp_id empId,wkname name,address address,job_id jobId from emp3 
</select>

2.全局配置自动识别驼峰式命名规则

在Mybatis全局配置文件加入如下配置:

注意:加的时候依然要注意写的顺序

<!-- 使用settings对Mybatis全局进行设置 --> 
<settings> 
    <!-- 将xxx_xxx这样的列名自动映射到xxXxx这样驼峰式命名的属性名 --> 
    <setting name="mapUnderscoreToCamelCase" value="true"/> 
</settings>

如果加了这个配置,那么你的select语句中就不用再去写别名了,但是前提是你的属性名和数据库中的字段名都需要符合命名规则:

比如说在数据库中字段名是emp_id,而属性名就是empId,其实就是将下划线后面的一个字母大写就好了。

3.使用resultMap

使用resultMap标签定义对应关系,再在后面的SQL语句中引用这个对应关系

 <resultMap id="selectEmployeeByRMResultMap" type="com.zhuzhu.Empolyee">

        <!-- 使用id标签设置主键列和主键属性之间的对应关系 -->
        <!-- column属性用于指定字段名;property属性用于指定Java实体类属性名 -->
        <id column="emp_id" property="empId"/>

        <!-- 使用result标签设置普通字段和Java实体类属性之间的关系 -->
        <result column="wkname" property="wkname"/>

        <result column="address" property="address"/>

    </resultMap>

    <!-- Employee selectEmployeeByRM(Integer empId); -->
    <select id="selectEmployeeByRM" resultMap="selectEmployeeByRMResultMap">

        select emp_id,wkname,address from emp3 where emp_id=#{empId}

    </select>

总结

1.返回简单类型的数据时,比如说int数据类型,你可以在resultType中写名称有:int、integer、Integer、java.lang.Integer、Int、INT、INTEGER。

2.返回实体类型的数据的时候,直接在resultType中写上实体类型的全类名。

3.返回Map类型的数据的时候,返回的Map数据的Key值是字段名,而Value值就是查询出来的内容,比如说(”job_id“,"16")。

4.返回List数据的时候,resultType照样是写我们List集合中的元素的全类名。

5.返回自增主键时,有两种方式,一种使用useGeneratedKeys和keyProperty属性适用于可自增主键的数据库——Mysql,还有一种使用selectKey标签适用于不可自增主键的数据库——Oracle。

6.数据库表字段和实体类属性对应关系,有三种方式——1.别名,2.自动匹配,3.使用resltMap进行绑定,只要实现其中的一种方式就可以了。

三,使用Mybatis映射关联关系

3.1,关联关系概念说明

3.1.1,数量关系

主要体现在数据库表中

一对一

    夫妻关系,人和身份证号
一对多

    用户和用户的订单,锁和钥匙

 多对多

    老师和学生,部门和员工

3.1.2,关联关系的方向

主要体现在Java实体类中

- 双向:双方都可以访问到对方
    - Customer:包含Order的集合属性
    - Order:包含单个Customer的属性
- 单向:双方中只有一方能够访问到对方
    - Customer:不包含Order的集合属性,访问不到Order
    - Order:包含单个Customer的属性

3.2,建模

3.2.1,order类

package com.zhuzhu;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order {

    private Integer orderId;

    private String orderName;

    private Customer customer;// 体现的是对一的关系
}

3.2.2,customer类

package com.zhuzhu;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Customer {

    private Integer customerId;

    private String customerName;

    private List<Order> orderList;// 体现的是对多的关系
}

注意:表在前面我已经建好了。 

3.2,对一

3.2.1,创建orderMapper接口

package com.zhuzhu.Dao;

import com.zhuzhu.Order;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface OrderMapper {
    Order selectOrderWithCustomer(Integer orderId);
}

3.2.2,创建orderMapper配置文件 

<?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属性:在Mybatis全局范围内找到一个具体的Mapper配置 -->
<!-- 引入接口后,为了方便通过接口全类名来找到Mapper配置文件,所以通常将namespace属性设置为接口全类名 -->
<mapper namespace="com.zhuzhu.Dao.OrderMapper">

    <!-- 创建resultMap实现“对一”关联关系映射 -->
    <!-- id属性:通常设置为这个resultMap所服务的那条SQL语句的id加上“ResultMap” -->
    <!-- type属性:要设置为这个resultMap所服务的那条SQL语句最终要返回的类型 -->
    <resultMap id="selectOrderWithCustomerResultMap" type="com.zhuzhu.Order">

        <!-- 先设置Order自身属性和字段的对应关系 -->
        <!-- 主键使用id标签,其他键使用result标签       -->
        <id column="order_id" property="orderId"/>

        <result column="order_name" property="orderName"/>

        <!-- 使用association标签配置“对一”关联关系 -->
        <!-- property属性:在Order类中对一的一端进行引用时使用的属性名 -->
        <!-- javaType属性:一的一端类的全类名 -->
        <association property="customer" javaType="com.zhuzhu.Customer">

            <!-- 配置Customer类的属性和字段名之间的对应关系 -->
            <id column="customer_id" property="customerId"/>
            <result column="customer_name" property="customerName"/>

        </association>

    </resultMap>

    <!-- Order selectOrderWithCustomer(Integer orderId); -->
    <select id="selectOrderWithCustomer" resultMap="selectOrderWithCustomerResultMap">

        SELECT order_id,order_name,c.customer_id,customer_name
        FROM t_order o
                 LEFT JOIN t_customer c
                           ON o.customer_id=c.customer_id
        WHERE o.order_id=#{orderId}

    </select>
</mapper>

在这里,我们使用 association标签去绑定order与customer之间的对一关系。

使用association标签配置“对一”关联关系
property属性:在Order类中对一的一端进行引用时使用的属性名
 javaType属性:一的一端类的全类名

3.3,对多

3.3.1,创建customerMapper

package com.zhuzhu.Dao;

import com.zhuzhu.Customer;

public interface CustomerMapper {
    Customer selectCustomerWithOrderList(Integer customerId);

}

3.3.2,创建customerMapper配置文件

<!-- 配置resultMap实现从Customer到OrderList的“对多”关联关系 -->
<resultMap id="selectCustomerWithOrderListResultMap"

  type="com.atguigu.mybatis.entity.Customer">

  <!-- 映射Customer本身的属性 -->
  <id column="customer_id" property="customerId"/>

  <result column="customer_name" property="customerName"/>

  <!-- collection标签:映射“对多”的关联关系 -->
  <!-- property属性:在Customer类中,关联“多”的一端的属性名 -->
  <!-- ofType属性:集合属性中元素的类型 -->
  <collection property="orderList" ofType="com.zhuzhu.Order">

    <!-- 映射Order的属性 -->
    <id column="order_id" property="orderId"/>

    <result column="order_name" property="orderName"/>

  </collection>

</resultMap>

<!-- Customer selectCustomerWithOrderList(Integer customerId); -->
<select id="selectCustomerWithOrderList" resultMap="selectCustomerWithOrderListResultMap">

  SELECT c.customer_id,c.customer_name,o.order_id,o.order_name
  FROM t_customer c
  LEFT JOIN t_order o
  ON c.customer_id=o.customer_id
  WHERE c.customer_id=#{customerId}

</select>

collection标签:映射“对多”的关联关系
property属性:在Customer类中,关联“多”的一端的属性名 
ofType属性:集合属性中元素的类型

3.4,分步查询

3.4.1,概念

为了实现延迟加载,对Customer和Order的查询必须分开,分成两步来做,才能够实现。为此,我们需要单独查询Order,也就是需要在Mapper配置文件中,单独编写查询Order集合数据的SQL语句。

3.4.2,编写sql文件

在customerMapper配置文件中实现分步查询,配置文件如下:

<?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属性:在Mybatis全局范围内找到一个具体的Mapper配置 -->
<!-- 引入接口后,为了方便通过接口全类名来找到Mapper配置文件,所以通常将namespace属性设置为接口全类名 -->
<mapper namespace="com.zhuzhu.Dao.CustomerMapper">

    <!-- 配置resultMap实现从Customer到OrderList的“对多”关联关系 -->
    <resultMap id="selectCustomerWithOrderListResultMap"

               type="com.zhuzhu.Customer">

        <!-- 映射Customer本身的属性 -->
        <id column="customer_id" property="customerId"/>

        <result column="customer_name" property="customerName"/>

       
<!--        分步查询-->
        <!-- orderList集合属性的映射关系,使用分步查询 -->
        <!-- 在collection标签中使用select属性指定要引用的SQL语句 -->
        <!-- select属性值的格式是:Mapper配置文件的名称空间.SQL语句id -->
        <!-- column属性:指定Customer和Order之间建立关联关系时所依赖的字段 -->
        <collection

                property="orderList"

                select="com.zhuzhu.Dao.CustomerMapper.selectOrderList"

                column="customer_id"/>

    </resultMap>
    <select id="selectCustomerWithOrderList" resultMap="selectCustomerWithOrderListResultMap">

        select customer_id,customer_name from t_customer
        where customer_id=#{customerId}

    </select>
<!--    注意select语句要是没有resultmap去指定字段对应属性,
就需要在select语句中起别名去对应字段order_id orderId,order_name orderName-->
    <select id="selectOrderList" resultType="com.zhuzhu.Order">

        select order_id orderId,order_name orderName from t_order where customer_id=#{customer_id}

    </select>
</mapper>

3.4.3,配置文件各元素之间的对应关系

 

3.5,延迟查询 

3.5.1,概念

查询到Customer的时候,不一定会使用Order的List集合数据。如果Order的集合数据始终没有使用,那么这部分数据占用的内存就浪费了。对此,我们希望不一定会被用到的数据,能够在需要使用的时候再去查询。  

例如:对Customer进行1000次查询中,其中只有15次会用到Order的集合数据,那么就在需要使用时才去查询能够大幅度节约内存空间。  

延迟加载的概念:对于实体类关联的属性到需要使用时才查询。也叫懒加载。

3.5.2,较低版本

<!-- 使用settings对Mybatis全局进行设置 -->
<settings>

  <!-- 开启延迟加载功能:需要配置两个配置项 -->
  <!-- 1、将lazyLoadingEnabled设置为true,开启懒加载功能 -->
  <setting name="lazyLoadingEnabled" value="true"/>

  <!-- 2、将aggressiveLazyLoading设置为false,关闭“积极的懒加载” -->
  <setting name="aggressiveLazyLoading" value="false"/>

</settings>

3.5.3,较高版本

<!-- Mybatis全局配置 -->
<settings>

  <!-- 开启延迟加载功能 -->
  <setting name="lazyLoadingEnabled" value="true"/>

</settings>

四,动态SQL 

4.1,if和where

    <select id="selectEmpolyeeByCondition" resultType="com.zhuzhu.Empolyee">
        select emp_id empId,wkname wkname,address address,job_id jobId from emp3
        <!-- where标签会自动去掉“标签体内前面多余的and/or” -->
        <where>
            <!-- 使用if标签,让我们可以有选择的加入SQL语句的片段。这个SQL语句片段是否要加入整个SQL语句,就看if标签判断的结果是否为true -->
            <!-- 在if标签的test属性中,可以访问实体类的属性,不可以访问数据库表的字段 -->
            <if test="wkname!=null and wkname!=''">
                and job_id=#{jobId}
            </if>
<!--            <if test="jobId=null">-->
<!--                or address=#{address}-->
<!--            </if>-->
        </where>
    </select>

4.2,set

    <update id="updateEmpolyeeDynamic">
        update emp3
        <set>
            <if test="wkname!=null and wkname!=''">
<!--             注意这里后面有个”,“逗号-->
                wkname=#{wkname},
            </if>
<!--            <if test="address=null">-->
<!--                address="未知",-->
<!--            </if>-->
        </set>
        where job_id=#{jobId}
    </update>

4.3,trim

<select id="selectEmpolyeeByConditionByTrim" resultType="com.zhuzhu.Empolyee">
        select emp_id empId,wkname wkname,address address,job_id jobId from emp3
        <!-- prefix属性指定要动态添加的前缀 -->
        <!-- suffix属性指定要动态添加的后缀 -->
        <!-- prefixOverrides属性指定要动态去掉的前缀,使用“|”分隔有可能的多个值 -->
        <!-- suffixOverrides属性指定要动态去掉的后缀,使用“|”分隔有可能的多个值 -->
        <!-- 当前例子用where标签实现更简洁,但是trim标签更灵活,可以用在任何有需要的地方 -->
        <trim prefix="where" suffixOverrides="and|or">
            <if test="jobId &gt; 10">
                job_id>#{jobId} and
            </if>
<!--            <if test="empAge &lt;= 20">-->
<!--                emp_age=#{empAge} or-->
<!--            </if>-->
<!--            <if test="wkname!=null and wkname!=''">-->
<!--                wkname=#{wkname} or -->
<!--            </if>-->
        </trim>
    </select>

4.4,choose/when/otherwise

    <select id="selectEmployeeByConditionByChoose" resultType="com.zhuzhu.Empolyee">
        select emp_id empId,wkname wkname,address address,job_id jobId from emp3
        where
<!--        - 从上到下依次执行条件判断
        - 遇到的第一个满足条件的分支会被采纳
        - 被采纳分支后面的分支都将不被考虑
        - 如果所有的when分支都不满足,那么就执行otherwise分支 -->
        <choose>
            <when test="wkname!=null and wkname!=''"> job_id=#{jobId}</when>
            <otherwise>1=1</otherwise>
        </choose>
    </select>

4.5,foreach

collection属性:要遍历的集合
    item属性:遍历集合的过程中能得到每一个具体对象,在item属性中设置一个名字,将来通过这个名字引用遍历出来的对象
    separator属性:指定当foreach标签的标签体重复拼接字符串时,各个标签体字符串之间的分隔符
    open属性:指定整个循环把字符串拼好后,字符串整体的前面要添加的字符串
    close属性:指定整个循环把字符串拼好后,字符串整体的后面要添加的字符串
    index属性:这里起一个名字,便于后面引用
        遍历List集合,这里能够得到List集合的索引值
        遍历Map集合,这里能够得到Map集合的key

    <insert id="insertEmployeeByConditionByForeach">
        insert into emp3 value 
        <foreach collection="emps" item="emp" separator=",">
            (#{emp.empId},#{emp.wkname},#{emp.address},#{emp.jobId})
        </foreach>
    </insert>

测试代码:

    @Test
    public void TestForeach(){
        List<Empolyee> list = new ArrayList<>();
        list.add(new Empolyee(10,"厦门","张三丰",15));
        list.add(new Empolyee(11,"深圳","张无忌",16));
        EmpolyeeMapper empolyeeMapper = session.getMapper(EmpolyeeMapper.class);
        int i = empolyeeMapper.insertEmployeeByConditionByForeach(list);
    }

 可以看到,批量插入的本质其实是执行一条sql语句

批量更新,实现批量更新则需要多条SQL语句拼起来,用分号分开。也就是一次性发送多条SQL语句让数据库执行。此时需要在数据库连接信息的URL地址中设置: 

url=jdbc:mysql://localhost:3306/h3?allowMultiQueries=true

 五,缓存简介(由于是使用篇,这里就不多介绍了)

一级缓存

默认开启,同一个SqlSesion级别共享的缓存,在一个SqlSession的生命周期内,执行2次相同的SQL查询,则第二次SQL查询会直接取缓存的数据,而不走数据库,当然,若第一次和第二次相同的SQL查询之间,执行了DML(INSERT/UPDATE/DELETE),则一级缓存会被清空,第二次查询相同SQL仍然会走数据库

一级缓存在下面情况会被清除

在同一个SqlSession下执行增删改操作时(不必提交),会清除一级缓存
SqlSession提交或关闭时(关闭时会自动提交),会清除一级缓存
对mapper.xml中的某个CRUD标签,设置属性flushCache=true,这样会导致该MappedStatement的一级缓存,二级缓存都失效(一个CRUD标签在mybatis中会被封装成一个MappedStatement)
在全局配置文件中设置 <setting name="localCacheScope" value="STATEMENT"/>,这样会使一级缓存失效,二级缓存不受影响
二级缓存

默认关闭,可通过全局配置文件中的<settings name="cacheEnabled" value="true"/>开启二级缓存总开关,然后在某个具体的mapper.xml中增加<cache />,即开启了该mapper.xml的二级缓存。二级缓存是mapper级别的缓存,粒度比一级缓存大,多个SqlSession可以共享同一个mapper的二级缓存。注意开启二级缓存后,SqlSession需要提交,查询的数据才会被刷新到二级缓存当中
 

六,逆向工程 

6.1,简介

正向:先创建Java实体类,由框架负责根据实体类生成数据库表。Hibernate是支持正向工程的。

逆向:先创建数据库表,由框架负责根据数据库表,反向生成实体类,mapper接口,mapper配置文件

6.2,配置pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>testMybatis</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>MybatisDemo2</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <!-- Mybatis核心 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
        </dependency>
        <!-- junit测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- MySQL驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>RELEASE</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
        </dependency>

    </dependencies>
    <!-- 控制Maven在构建过程中相关配置 -->
    <build>

        <!-- 构建过程中用到的插件 -->
        <plugins>

            <!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 -->
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.0</version>

                <!-- 插件的依赖 -->
                <dependencies>

                    <!-- 逆向工程的核心依赖 -->
                    <dependency>
                        <groupId>org.mybatis.generator</groupId>
                        <artifactId>mybatis-generator-core</artifactId>
                        <version>1.3.2</version>
                    </dependency>

                    <!-- 数据库连接池 -->
                    <dependency>
                        <groupId>com.mchange</groupId>
                        <artifactId>c3p0</artifactId>
                        <version>0.9.2</version>
                    </dependency>

                    <!-- MySQL驱动 -->
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>8.0.20</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>

</project>

 6.3,MEG配置文件

在generatorConfig.xml(这个配置文件名就是这样约定的,最好别改),配置如下信息;

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <!--
            targetRuntime: 执行生成的逆向工程的版本
                    MyBatis3Simple: 生成基本的CRUD(清新简洁版)
                    MyBatis3: 生成带条件的CRUD(奢华尊享版)
     -->
    <context id="DB2Tables" targetRuntime="MyBatis3">
        <!-- 数据库的连接信息 -->
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/h3?serverTimezone=UTC&amp;useSSL=false&amp;rewriteBatchedStatements=true"
                        userId="root"
                        password="123456">
        </jdbcConnection>
        <!-- javaBean的生成策略-->
        <javaModelGenerator targetPackage="com.zhuzhu.mybatis.pojo" targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true" />
            <property name="trimStrings" value="true" />
        </javaModelGenerator>
        <!-- SQL映射文件的生成策略 -->
        <sqlMapGenerator targetPackage="mapper"  targetProject=".\src\main\resources">
            <property name="enableSubPackages" value="true" />
        </sqlMapGenerator>
        <!-- Mapper接口的生成策略 -->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.zhuzhu.mybatis.mapper"  targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true" />
        </javaClientGenerator>
        <!-- 逆向分析的表 -->
        <!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName -->
        <!-- domainObjectName属性指定生成出来的实体类的类名 -->
        <table tableName="emp3" domainObjectName="Employee"/>
<!--        <table tableName="t_customer" domainObjectName="Customer"/>-->
<!--        <table tableName="t_order" domainObjectName="Order"/>-->
    </context>
</generatorConfiguration>

 <property name="enableSubPackages" value="true" />

在targetPackage的基础上,根据数据库的schema再生成一层package,最终生成的类放在这个package下,默认为false。

如下图: 

6.4,执行MBG插件

配置好上述信息后,双击如下的插件,就可以逆向生成pojo,mapper接口,mapper.xml配置文件

6.5,QBC查询 

6.5.1,版本选择

我们在配置文件的这个位置,指定是使用奢华版还是简洁版,版本的选择在生成pojo,mapper接口,mapper.xml配置文件之前就已经指定了,选择版本后才可以,生成逆向工程。

<!--
            targetRuntime: 执行生成的逆向工程的版本
                    MyBatis3Simple: 生成基本的CRUD(清新简洁版)
                    MyBatis3: 生成带条件的CRUD(奢华尊享版)
     -->
    <context id="DB2Tables" targetRuntime="MyBatis3">

奢华版:查询方式更多,基本不需要再手写sql了。

简洁版:查询方式不多,有时候还需要自己手写sql。

6.5.2,QBC查询 

在test包下,编写测试类testMBG类。

package com.zhuzhu;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.zhuzhu.mybatis.mapper.EmployeeMapper;
import com.zhuzhu.mybatis.pojo.Employee;
import com.zhuzhu.mybatis.pojo.EmployeeExample;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

public class TestMBG {
    private SqlSession session;
    private Logger log = LoggerFactory.getLogger(TestMBG.class);
    // junit会在每一个@Test方法前执行@Before方法
    @Before
    public void init() throws IOException {
        session = new SqlSessionFactoryBuilder()
                .build(
                        Resources.getResourceAsStream("mybatis-config.xml"))
                .openSession();
        //当openSession中的值是true的时候,就会自动提交事务
    }

    // junit会在每一个@Test方法后执行@After方法
    @After
    public void clear() {
        //由于使用的是JDBC,所以不会自动提交事务,所以这里手动提交
        session.commit();
        session.close();
    }
    @Test
    public  void testMBG(){
        EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);

        //条件查询
        EmployeeExample employeeExample1 = new EmployeeExample();
        employeeExample1.createCriteria().andAddressEqualTo("长沙");
        employeeExample1.or().andJobIdBetween(12,14);
        List<Employee> list = employeeMapper.selectByExample(employeeExample1);
        list.forEach(employee -> System.out.println(employee));
        //根据条件修改
        //下面的意思就是说,将id为7的元素,修改为Employee(7,null,null,12)
        EmployeeExample employeeExample2 = new EmployeeExample();
        employeeExample2.createCriteria().andEmpIdEqualTo(7);
        employeeMapper.updateByExampleSelective(new Employee(7,null,null,12),employeeExample);
    }
}

        EmployeeExample employeeExample1 = new EmployeeExample();
        employeeExample1.createCriteria().andAddressEqualTo("长沙");
        employeeExample1.or().andJobIdBetween(12,14); 

由这里,我们可以知道,设置employeeExample就类似于编写我们的sql语句,就是条件。 

6.6,分页插件 

6.6.1,在pom文件中引入依赖

        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
        </dependency>

6.6.2,使用 

package com.zhuzhu;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.zhuzhu.mybatis.mapper.EmployeeMapper;
import com.zhuzhu.mybatis.pojo.Employee;
import com.zhuzhu.mybatis.pojo.EmployeeExample;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

public class TestMBG {
    private SqlSession session;
    private Logger log = LoggerFactory.getLogger(TestMBG.class);
    // junit会在每一个@Test方法前执行@Before方法
    @Before
    public void init() throws IOException {
        session = new SqlSessionFactoryBuilder()
                .build(
                        Resources.getResourceAsStream("mybatis-config.xml"))
                .openSession();
        //当openSession中的值是true的时候,就会自动提交事务
    }

    // junit会在每一个@Test方法后执行@After方法
    @After
    public void clear() {
        //由于使用的是JDBC,所以不会自动提交事务,所以这里手动提交
        session.commit();
        session.close();
    }
    @Test
    public  void testMBG(){
        EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
        //查询所有数据
        PageHelper.startPage(1,3);
        List<Employee> list = employeeMapper.selectByExample(null);
        list.forEach(employee -> System.out.println(employee));
        PageInfo<Employee> pageInfo = new PageInfo<>(list);
        System.out.println(pageInfo);
        //获取总条数
        System.out.println("总条数:"+pageInfo.getTotal()); //获取总页数
        System.out.println("总页数"+pageInfo.getPages()); //获取当前页
        System.out.println("当前页"+pageInfo.getPageNum()); //获取每页显示的条数
        System.out.println("每页条数"+pageInfo.getSize());
       
    }
}

最后

本篇博客最Mybatis使用的介绍就到这里了,如果本篇博客对你的有帮助的话,请点一个小赞支持一下,谢谢!咱们下篇博客再见!

猜你喜欢

转载自blog.csdn.net/weixin_64972949/article/details/131032979