Mybatis


一、简介

1.1 什么是MyBatis

MyBatis

  • Mybatis是apache的一个开源项目iBatis。2010年这个项目由 apache software foundation 迁移到了 google code ,并且改名为 MyBatis。2013年11月迁移到 Github
  • Mybatis是一款优秀的持久层矿框架
  • 它支持定制化 SQL、存储过程以及高级映射
  • Mybatis避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集
  • Mybatis可以使用简单的 XML 或者注解来配置和映射原生类型、接口和 JAVA 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录

1.2 如何获得Mybatis

  • maven仓库:
<!--mybatis 依赖-->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.5</version>
</dependency>

1.3 什么是持久化

数据持久化

  • 持久化就是将程序的数据在持久状态和瞬时状态转化的过程
  • 内存:断电即失
  • 数据库(jdbc),io文件持久化
  • 生活中:冷藏、罐头

为什么要持久化?

  • 有些对象,不能让他丢失
  • 内存贵

1.4 持久层

  • 完成持久化工作的代码块
  • 层的界限十分明显

1.5 为什么需要Mybatis

  • 帮助程序员将数据存入到数据库中
  • 方便、简化、框架
  • 传统的 JDBC 代码太复杂
  • 自动化
  • Mybatis 并不是必须的,只是更容易上手
  • 使用的人多

优点:

  • 简单易学
  • 灵活
  • sql 和代码分离,提高了可维护性
  • 提供映射标签,支持对象与数据库的 orm 字段关系映射
  • 提供对象关系映射标签,支持对象关系组建维护
  • 提供 xml 标签,支持编写动态 sql

二、构建Mybatis程序

思路:搭建环境 --> 导入Mybatis --> 编写代码 --> 测试

2.1 搭建环境

  • 搭建数据库
-- 删除同名数据库
DROP DATABASE IF EXISTS `mybatis`;

-- 建库
CREATE DATABASE IF NOT EXISTS `mybatis`;

USE `mybatis`;

-- 建表
CREATE TABLE `user`(
`id` INT(20) NOT NULL PRIMARY KEY,
`name` VARCHAR(30) DEFAULT NULL,
`pwd` VARCHAR(30) DEFAULT NULL
)ENGINE=INNODB DEFAULT CHARSET=utf8;

-- 插入数据
INSERT INTO `user` VALUES
(1,'张三','123456'),
(2,'李四','123457'),
(3,'王五','123458')

  • 新建项目
    1、新建一个普通的 maven 项目
    2、删除 src
    3、导入 maven 依赖
<dependencies>
    <!--mysql驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    <!--mybatis-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.5</version>
    </dependency>
    <!--junit-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>

2.2 创建一个模块

  • 编写mybatis核心配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<!--configuration核心配置-->
<configuration>
    <environments default="development">
        <!--开发环境-->
        <environment id="development">
            <!--事务管理-->
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="数据库用户名"/>
                <property name="password" value="数据库密码"/>
            </dataSource>
        </environment>
    </environments>
</configuration>
  • 编写mybatis工具类
/**
 * 会话工厂的创建类->会话工厂->会话
 */
public class MybatisUtils {

    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            //使用Mybatis第一步:获取sqlSessionFactory对象
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //有了sqlSessionFactory,顾名思义,我们就可以从中获取SqlSession的实例了
    //SqlSession包含了面向数据库执行sql命令所需的所有方法
    public static SqlSession getSqlSession() {
        return sqlSessionFactory.openSession();
    }
}

2.3 编写代码

  • 实体类
public class User {

    private Integer id;

    private String name;

    private String pwd;

    public User() {
    }

    public User(Integer id, String name, String pwd) {
        this.id = id;
        this.name = name;
        this.pwd = pwd;
    }

    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 getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }
}
  • Dao接口
public interface UserMapper {

    //查询所有用户
    List<User> getUserList();
}
  • 接口实现类由原来的impl转换为一个Mapper.xml配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<!--namespace=绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.cary.mapper.UserMapper">
    <!--查询所有用户-->
    <select id="getUserList" resultType="com.cary.pojo.User">
        select * from user
    </select>
</mapper>
  • 整个项目结构图
    项目结构图

2.4 测试

  • 编写测试类(注意包的层级)
package com.cary.test;

import com.cary.mapper.UserMapper;
import com.cary.pojo.User;
import com.cary.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class UserTest {

    @Test
    public void testGetUserList(){

        //第一步:获得sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        //方式一:
        //执行sql,通过class对象调用其内查询方法
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        //方式二:
        //根据方法的返回值调用方法
        //List<User> userList = sqlSession.selectList("com.cary.mapper.UserMapper.getUserList");

        List<User> userList = mapper.getUserList();

        userList.forEach(System.out::println);

        //关闭sqlSession
        sqlSession.close();
    }
}

  • 易报错:org.apache.ibatis.binding.BindingException: Type interface com.cary.mapper.UserMapper is not known to the MapperRegistry
    解决:应该在Mybatis核心配置文件mybatis-config.xml中加载sql映射文件
<!--每一个Mapper.xml都需要在Mybatis核心配置文件中注册-->
    <mappers>
        <package name="com.cary.mapper"/>
    </mappers>
  1. 运行结果
    运行结果

2.5 容易遇到的问题

  1. 配置文件没有注册
  2. 绑定接口错误
  3. 方法名不对
  4. 返回类型不对
  5. Maven 导出资源问题

三、CRUD

  1. 编写接口
public interface UserMapper {

    //查询所有用户
    List<User> getUserList();

    //根据ID查询用户
    User getUserById(Integer id);

    //添加一个用户
    int addUser(User user);

    //修改一个用户
    int updateUser(User user);

    //删除一个用户
    int deleteUserById(Integer id);
}
  1. 编写对应的 mapper 中的 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" >
<!--namespace=绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.cary.mapper.UserMapper">
    <!--添加一个用户,对象中的属性可以直接取出来-->
    <insert id="addUser" parameterType="com.cary.pojo.User">
        insert into `user` values (#{id},#{name},#{pwd})
    </insert>

    <!--修改一个用户-->
    <update id="updateUser" parameterType="com.cary.pojo.User">
        update `user` set `name`=#{name},pwd=#{pwd} where id=#{id}
    </update>

    <!--根据ID删除一个用户-->
    <delete id="deleteUserById" parameterType="Integer">
        delete from `user` where id=#{id}
    </delete>

    <!--查询所有用户-->
    <select id="getUserList" resultType="com.cary.pojo.User">
        select * from user
    </select>

    <!--根据ID查询用户-->
    <select id="getUserById" resultType="com.cary.pojo.User" parameterType="Integer">
        select * from user where id=#{id}
    </select>
</mapper>
  1. 测试
public class UserTest {


    //查询所有
    @Test
    public void testGetUserList(){

        //第一步:获得sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        //方式一:
        //执行sql,通过class对象调用其内查询方法
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        //方式二:
        //根据方法的返回值调用方法
        //List<User> userList = sqlSession.selectList("com.cary.mapper.UserMapper.getUserList");

        List<User> userList = mapper.getUserList();

        userList.forEach(System.out::println);

        //关闭sqlSession
        sqlSession.close();

    }


    //根据ID查询
    @Test
    public void testGetUserById(){

        SqlSession sqlSession = MybatisUtils.getSqlSession();

        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        User user = mapper.getUserById(1);

        sqlSession.close();

        System.out.println(user);

    }


    //添加一个用户,增删改需要提交事务
    @Test
    public void testAddUser(){

        SqlSession sqlSession = MybatisUtils.getSqlSession();

        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        int row = mapper.addUser(new User(4, "赵四", "152346"));


        System.out.println(row+"行受到影响");


        //提交事务
        sqlSession.commit();

        sqlSession.close();
    }


    //修改一个用户,增删改要提交事务
    @Test
    public void testUpdateUser(){

        SqlSession sqlSession = MybatisUtils.getSqlSession();

        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        int row = mapper.updateUser(new User(4, "赵三", "456789"));


        System.out.println(row+"行受到影响");


        //提交事务
        sqlSession.commit();

        sqlSession.close();
    }


    //根据ID删除一个用户,增删改要提交事务
    @Test
    public void testDeleteUserById(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        int row = mapper.deleteUserById(4);


        System.out.println(row+"行受到影响");


        //提交事务
        sqlSession.commit();

        sqlSession.close();
    }
}

3.1 namespace

namespace 中的包名要和 Dao/Mapper 接口的包名一致!!!

3.2 select

选择,查询语句;

  • id:就是对应的 namespace 中的方法名
  • resultType:sql 语句执行的返回值
  • parameterType:参数类型

3.3 Insert

插入语句;

  • id:就是对应的 namespace 中的方法名
  • resultType:sql 语句执行的返回值
  • parameterType:参数类型

3.4 Update

更新,修改语句;

  • id:就是对应的 namespace 中的方法名
  • resultType:sql 语句执行的返回值
  • parameterType:参数类型

3.5 Delete

删除语句;

  • id:就是对应的 namespace 中的方法名
  • resultType:sql 语句执行的返回值
  • parameterType:参数类型

注意:增删改需要提交事务

3.6 万能Map

关于万能Map的使用本人另外整理了一篇文章:Mybatis中的万能Map

3.7 模糊查询

<select id="xxx" resultType="com.cary.pojo.User">
	select * from `user` where name like "%"#{name}"%"
</select>

四、配置解析

4.1 核心配置文件

  • mybatis-config.xml
  • MyBatis 的配置文件包含了影响 MyBatis 行为的设置和属性信息
configura(配置)√
properties(属性)√
settings(设置)√
typeAliases(类型别名)√
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)

environments(环境配置)√
	environment(环境变量)
		transactionManager(事务管理器)
		dataSource(数据源)

databaseIdProvider(数据库厂商表示)
mappers(映射器)√

4.2 环境配置(environments)

MyBatis 可以配置成适应多种环境
要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境
学会使用配置多套运行环境!
Mybatis默认的事务管理器就是JDBC,连接池:POOLED

4.3 属性(properties)

可以通过 properties 属性来实现引用配置文件
这些属性都是可以外部配置且可以动态替换的,既可以在典型的 Java 属性文件中配置,亦可通过 properties 元素的子元素来传递【db.properties】

  • 编写一个配置文件 db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/数据库名?useSSL=true&useUnicode=true&characterEncoding=UTF-8
username=数据库用户名
password=数据库密码
  • 在核心配置文件中引入外部配置文件
    标签顺序
<!--引入配置文件-->
<properties resource="db.properties"/>

可以直接引入外部文件
可以在其中增加一些属性配置
如果两个文件有同一个字段,优先使用外部配置文件的!!!

4.4 类型别名(typeAliases)

  • 类型别名是为 Java 类型设置一个短的名字
  • 存在的意义仅在于用来减少类完全限定名的冗余
  1. 可以给实体类起别名
<!--给实体类起别名-->
<typeAliases>
    <typeAlias type="com.cary.pojo.User" alias="User"/>
</typeAliases>
  1. 也可以指定一个报名,Mybatis 会在包名下面搜索需要的 JavaBean ,扫描实体类的包,他的默认别名就为这个类的类名首字母小写!!!
<!--指定包名-->
<typeAliases>
    <package name="com.cary.pojo"/>
</typeAliases>

如果实体类少的时候,使用第一种
如果实体类多的时候,建议使用第二种
第一种可以DIY别名,第二种如果非要改,需要在实体类上增加注解 @Alias

4.5 设置(settings)

设置名描述有效值默认值
cacheEnabled全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。true/falsetrue
lazyLoadingEnabled延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。true/falsefalse
mapUnderscoreToCamelCase是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。true/falsefalse
logImpl指定 MyBatis 所用日志的具体实现,未指定时将自动查找。SLF4J、LOG4J(3.5.9 起废弃)、LOG4J2、JDK_LOGGING、COMMONS_LOGGING、STDOUT_LOGGING、NO_LOGGING未设置

4.6 映射器(mappers)

MapperRegistry:注册绑定我们的Mapper文件

  1. 方式一:【推荐使用】
<!--每一个Mapper.xml都需要在Mybatis核心配置文件中注册-->
<mappers>
	<mapper resource="com/cary/mapper/UserMapper.xml" />
</mappers>
  1. 使用class文件绑定注册
<!--每一个Mapper.xml都需要在Mybatis核心配置文件中注册-->
<mappers>
	<mapper class="com.cary.mapper.UserMapper" />
</mappers>

注意点:

  • 接口和它的 Mapper 配置文件必须同名!
  • 接口和它的 Mapper 配置文件必须在同一个包下!
  1. 方式三:使用扫描包进行注入绑定
<!--每一个Mapper.xml都需要在Mybatis核心配置文件中注册-->
<mappers>
	<package name="com.cary.mapper" />
</mappers>

注意点:

  • 接口和它的 Mapper 配置文件必须同名!
  • 接口和它的 Mapper 配置文件必须在同一个包下!

五、解决属性名和字段名不一致的问题

  • 数据库中字段
    数据库中字段
  • 实体类中的字段不一致
    实体类中字段
  • 测试出现问题
    测试
  • 原因
    查询语句:select * from user where id=#{id}
    实质是:select id,name,pwd from user where id=#{id}
    数据库中字段和实体类字段无法对应传值

起别名(不推荐)

  • 给字段名取别名,让别名与实体类的属性名一致
    select id,name,pwd as password from user where id=#{id}

手动创建表的字段名与实体类的属性名之间的映射(字段多、麻烦)

  • resultMap结果集映射
  • < id >:映射表的主键
  • < result >:映射表中其他的字段
  • column 数据库中的字段
  • property实体类中的属性
  • 查询结果使用 resultMap ,而不是 resultType
<!--结果集映射-->
<resultMap id="UserMap" type="User">
	<!--column数据库中的字段,property实体类中的属性-->
    <!--result column="id" property="id"/-->
    <!--result column="name" property="name"/-->
    <result column="pwd" property="password"/>
</resultMap>

<!--根据ID查询用户-->
<select id="getUserById" resultMap="UserMap">
    select * from user where id=#{id}
</select>
  • resultMap 元素是MyBatis中最重要最强大的元素
  • resultMap 的设计思想,对于简单的语句根本不需要配置显式的结果映射,而对于复杂一点的语句只需要描述它们的关系就好了
  • 什么不一样转什么

在核心配置文件中直接将下划线映射成驼峰命名法(强烈推荐)

前提是实体类的属性名和字段名有对应关系,如 user_name 和 userName

  • 在核心配置文件中设置命名映射关系
<settings>
    <!--映射下划线为驼峰命名法-->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

六、多条件查询

  • < where >标签
    有条件的时候,相当于 where 关键字
    没有条件的时候,会去掉多余的 where 关键字
    还会去掉多余的 and 和 or 关键字
  • < if >标签
    test 属性:逻辑判断

方式一:参数都写在方法的形参上

如果有多个参数作为占位符来使用,必须给每个参数通过注解 @Param 起名字

List<User> findByCondition(@Param("status") Integer status, 
						   @Param("age") Integer age, 
						   @Param("name") String userName);
<!-- sql语句中的变量和形参名字无关 -->
<select id="findByCondition" resultType="User">
	select * from `user` where `status`=#{status} and `age`=#{age} and `name` like "%"#{name}"%"
</select>

方式二:将多个参数封装到实体类中

实体类中的属性名字与条件查询的名字相同即可

List<User> findByCondition(User user);

方式三:万能Map

Map中键的名字和条件查询的名字相同

List<User> findByCondition(Map<StringObject> map);

多分支选择其中一个

  • < choose >标签
    多条件选择的父标签,包含以下两个子标签
    < when > 多个条件之一,可以出现多个,如果条件为真则执行,其他的就不执行
    < otherwise> 如果上面所有条件都不满足,这执行这个分支,类似于switch里的default
<choose>
	<when test="...">
	...
	</when>
	<otherwise></otherwise>
</choose>

七、复杂查询处理

关系图

  • 多个学生对应一个老师
  • 对于学生这边而言,关联…多个学生,关联一个老师【多对一】
  • 对于老师这边而言,集合…一个老师,有很多个学生【一对多】

测试环境搭建

  • 新建工程、导入lombok依赖
    <dependencies>
        <!-- mysql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.22</version>
        </dependency>

        <!-- mybatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.6</version>
        </dependency>

        <!-- junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
  • 数据库
DROP TABLE IF EXISTS `user`;

-- 创建用户基本表
CREATE TABLE `user` (
  id INT PRIMARY KEY AUTO_INCREMENT,
  username VARCHAR(20) NOT NULL,
  birthday DATE,
  sex CHAR(1) DEFAULT '男',
  address VARCHAR(50)
);

INSERT INTO `user` VALUES (NULL, '孙悟空','1980-10-24','男','花果山水帘洞');
INSERT INTO `user` VALUES (NULL, '白骨精','1992-11-12','女','白虎岭白骨洞');
INSERT INTO `user` VALUES (NULL, '猪八戒','1983-05-20','男','福临山云栈洞');
INSERT INTO `user` VALUES (NULL, '蜘蛛精','1995-03-22','女','盤丝洞');
 
SELECT * FROM `user`; 
 
-- 用户扩展信息表,一个用户对应一条用户扩展信息
CREATE TABLE user_info (
   id INT PRIMARY KEY,   -- 既是主键又是外键
   height DOUBLE,  -- 身高厘米
   weight DOUBLE,   -- 体重公斤
   married TINYINT,    -- 是否结婚,1为结婚,0为未婚
   FOREIGN KEY (id) REFERENCES `user`(id)
);
 
-- 插入用户扩展信息表  
INSERT INTO user_info VALUES(1,185,90,1),(2,170,60,0);
SELECT * FROM user_info;
  • 实体类
/**
 * 用户实体类
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;

    // 建立一对一的关联关系
    private UserInfo userInfo;

    //只输出用户的基本信息
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", birthday=" + birthday +
                ", sex='" + sex + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}
/**
 * 用户信息表
 * 与用户表是1对1的关系
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserInfo {

    private Integer id;
    private Double height;
    private Double weight;
    private Boolean married;

    @Override
    public String toString() {
        return "UserInfo{" +
                "id=" + id +
                ", height=" + height +
                ", weight=" + weight +
                ", married=" + married +
                '}';
    }

}
  • 项目整体结构
    项目结构

7.1 表连接查询

一对一查询

  • 接口
// 一对一:通过用户ID查找用户和扩展信息
User findUserAndInfo(Integer id);
  • mapper.xml
    <!--用户表的映射
        id:唯一标识,给映射起个名字,只要不重复就可以了
        type:指定要映射的实体类的名字
        autoMapping:自动映射,只要字段名和属性名相同则自动映射

        表连接查询无论字段名是否相同,都要进行映射,否则没有值

        <association>:创建一对一的关系映射
                       property:指定另一方对象的属性名字
                       resultMap:指定另一方的映射
    -->
    <resultMap id="userMap" type="com.cary.pojo.User" autoMapping="true">
        <association property="userInfo" resultMap="userInfoMap"/>
    </resultMap>


    <!--用户扩展信息表映射-->
    <resultMap id="userInfoMap" type="com.cary.pojo.UserInfo" autoMapping="true"/>

    <!-- 一对一表连接查询
         使用resulMap指定映射的名字
    -->
    <select id="findUserAndInfo" resultMap="userMap">
        select * from user u inner join user_info i on u.id=i.id where u.id=#{id}
    </select>

映射关系

  • 表连接查询无论字段名是否相同,都要进行映射,否则没有值
  • resultMap
    id:唯一标识,给映射起个名字,只要不重复就可以
    type:指定要映射的实体类的名字
    autoMapping:自动映射,只要字段名和属性名相同则自动映射
  • 子标签 < association >:创建一对一的关系映射
    property:指定另一方的属性名字
    resultMap:指定另一方的映射

一对多映射

  • 数据准备
-- 创建订单表
CREATE TABLE `order`(
    oid INT PRIMARY KEY AUTO_INCREMENT ,   -- 主键
    user_id INT NOT NULL,   -- 用户id,外键
    number VARCHAR(20),   -- 订单编号
    create_time DATETIME,  -- 下单时间
    note VARCHAR(100)   -- 备注
);

-- 添加订单数据
INSERT INTO `order` VALUES(NULL, 1,'10001001', NOW(), '小米9代'),(NULL, 1,'10001002', NOW(), '9代小米'),(NULL, 1,'10001003', NOW(), '小米9手机');
INSERT INTO `order` VALUES(NULL, 2,'10001004', NOW(), '逃生锤'),(NULL, 2,'10001005', NOW(), '安全带');

SELECT * FROM `order`;
  • 实体类
/**
 * 用户实体类
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;

    //建立一对多的关联系统
    private List<Order> orders;

    // 建立一对一的关联关系
    private UserInfo userInfo;

    //只输出用户的基本信息
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", birthday=" + birthday +
                ", sex='" + sex + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}
/**
 * 订单实体类
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {

    private Integer oid;   // 主键
    private Integer userId;   // 用户id,外键
    private String number;   // 订单编号
    private Timestamp createTime;  // 下单时间,既有日期又有时间
    private String note;   // 备注

    @Override
    public String toString() {
        return "OrderForm{" +
                "oid=" + oid +
                ", userId=" + userId +
                ", number='" + number + '\'' +
                ", createTime=" + createTime +
                ", note='" + note + '\'' +
                '}';
    }
}
  • 接口
    // 一对多:通过用户id查询用户和他的订单信息
    User findUserAndOrders(Integer userId);
  • mapper.xml
 <!--用户表的映射
        id:唯一标识,给映射起个名字,只要不重复就可以了
        type:指定要映射的实体类的名字
        autoMapping:自动映射,只要字段名和属性名相同则自动映射

        表连接查询无论字段名是否相同,都要进行映射,否则没有值

        <collection>:创建一对多的关系映射
                       property:指定另一方对象的属性名字
                       resultMap:指定另一方的映射
    -->
    <resultMap id="userMap" type="com.cary.pojo.User" autoMapping="true">
        <!--一对多必须添加主键的配置,通过主键去查询另一张表的数据,封装到集合中去-->
        <id column="id" property="id"/>
        <!--一对多-->
        <collection property="orders" resultMap="orderMap"/>
    </resultMap>

    <!--订单映射-->
    <resultMap id="orderMap" type="com.cary.pojo.Order" autoMapping="true"/>

    <!--一对多:通过用户id查询用户和他的订单信息-->
    <select id="findUserAndOrders" resultMap="userMap">
        select * from user u inner join `order` o on o.user_id=u.id where u.id=#{userId}
    </select>

  • 表连接查询无论字段名是否相同,都要进行映射,否则没有值
  • resultMap
    id:唯一标识,给映射起个名字,只要不重复就可以
    type:指定要映射的实体类的名字
    autoMapping:自动映射,只要字段名和属性名相同则自动映射
  • 子标签 < collection>:创建一对多的关系映射
    property:指定另一方的属性名字
    resultMap:指定另一方的映射
  • 一对多必须添加主键的配置,通过主键去查询另一张表的数据,封装到集合中去
    < id column=“id” property=“id”/>
    column:数据表的列名或者标签别名
    property:需要映射到实体类的属性名称

小结

与一对一的关联映射是一样的,唯一的区别是

  1. 一对一:association
  2. 一对多:collection

扩展:其实上面2个标签的功能是一样的,只是语义上区别

表连接查询总结

7.2 级联查询

表连接查询是直接查出两张表的数据
级联查询是指先查询一张表,再查询另一张表,分开查询

  • 接口写两个方法
public interface UserMapper {

    // 通过用户ID查询用户的基本信息
    User findUserById(Integer id);

    // 通过用户ID查询用户的扩展信息
    UserInfo findUserInfoById(Integer id);
}
  • mapper.xml
<mapper namespace="com.cary.mapper.UserMapper">

    <resultMap id="userMap" type="com.cary.pojo.User" autoMapping="true">
        <!-- 主键的映射 -->
        <id property="id" column="id"/>
        <!-- 一对一级联查询 -->
        <association property="userInfo" select="findUserInfoById" column="id"/>
    </resultMap>

    <!-- 通过用户ID查询用户的基本信息 -->
    <select id="findUserById" resultMap="userMap">
        select * from `user` where id=#{id}
    </select>


    <!-- 通过用户ID查询用户的扩展信息 -->
    <select id="findUserInfoById" resultType="com.cary.pojo.UserInfo">
        select * from `user_info` where id=#{id}
    </select>
</mapper>

一对一级联查询
一对一级联查询

延迟加载

  • 在mybatis核心配置文件mybatis-config.xml进行配置
    <settings>
        <!--开启延迟加载-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!--不使用积极加载-->
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>
  • 开启延迟加载时,级联查询表现为第二张表的查询当需要时才会执行
  • 注意:不要直接输出user对象,因为会触发toString()方法,导致立即加载userInfo对象

7.2 一对多级联查询

  • 接口
public interface UserMapper {

    // 通过用户ID查询用户的基本信息
    User findUserById(Integer id);

    // 通过用户ID查询用户的所有订单
    List<Order> findOrders(Integer userId);

    // 通过用户ID查询用户的扩展信息
    UserInfo findUserInfoById(Integer id);

}
  • mapper.xml
<mapper namespace="com.cary.mapper.UserMapper">

    <resultMap id="userMap" type="com.cary.pojo.User" autoMapping="true">
        <!-- 主键的映射 -->
        <id property="id" column="id"/>
        <!-- 一对一级联查询 -->
        <association property="userInfo" select="findUserInfoById" column="id"/>
        <collection property="orders" select="findOrders" column="id"/>
    </resultMap>

    <!-- 通过用户ID查询用户的基本信息 -->
    <select id="findUserById" resultMap="userMap">
        select * from `user` where id=#{id}
    </select>


    <!-- 通过用户ID查询用户的扩展信息 -->
    <select id="findUserInfoById" resultType="com.cary.pojo.UserInfo">
        select * from `user_info` where id=#{id}
    </select>

    <!-- 通过用户ID查询用户的所有订单 -->
    <select id="findOrders" resultType="com.cary.pojo.Order">
        select * from `order` where user_id=#{ids}
    </select>
</mapper>

一对多级联查询

一对多级联查询

一级缓存

  • 一级缓存是默认打开的,会话级别缓存,只在一个会话中起作用
  • 测试
public class CacheTest {
	
	// 测试一级缓存
    @Test
    public void testFirst(){
        // 获取会话对象
        SqlSession session = MyBatisUtils.getSession();
        // 得到接口的代理对象
        UserMapper userMapper = session.getMapper(UserMapper.class);
        // 调用接口中方法
        UserInfo info1 = userMapper.findUserInfoById(1);
        System.out.println(info1);

        // 第二次查询:从一级缓存中获取数据,没有再次发送SQL语句
        UserInfo info2 = userMapper.findUserInfoById(1);
        System.out.println(info2);

       session.close();

    }
}
  • 结果
    一级缓存
  • 一级缓存的清空
    清空的目的:如果进行了增删改的操作,表中的记录有可能发生变化,缓存中的数据就是脏数据
    清空的方式:只要执行了增删改的操作,提交事务,关闭会话操作会就自动清空一级缓存的数据

二级缓存

  • 二级缓存是mapper映射级别缓存,作用范围跨越SqlSession,即可以在多个SqlSession之间共享二级缓存数据
  • 二级缓存关键点:实体类需要实现Serializable接口
  1. 实体类实现Serializable接口
  2. 在核心配置文件开启二级缓存
<settings>
        <!--在控制台显示SQL语句-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        <!--映射下划线为驼峰命名法-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <!--开启延迟加载-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!--不使用积极加载-->
        <setting name="aggressiveLazyLoading" value="false"/>
        <!-- 开启二级缓存 -->
        <setting name="cacheEnabled" value="true"/>
    </settings>
  1. 在mapper.xml映射文件中
<mapper namespace="com.cary.mapper.UserMapper">
    <!--开启二级缓存:对当前配置文件中所有的查询进行二级缓存-->
    <cache/>
</mapper>
  • 测试
public class CacheTest {

    // 测试二级缓存:在同一个会话工厂中,在不同的会话中起作用
    @Test
    public void testSecond(){

        // 获取会话对象
        SqlSession session1 = MyBatisUtils.getSession();
        // 得到接口的代理对象
        UserMapper userMapper1 = session1.getMapper(UserMapper.class);

        UserInfo userInfo1 = userMapper1.findUserInfoById(1);
        System.out.println(userInfo1);

        session1.close();


        // 获取会话对象
        SqlSession session2 = MyBatisUtils.getSession();
        // 得到接口的代理对象
        UserMapper userMapper2 = session2.getMapper(UserMapper.class);

        UserInfo userInfo2 = userMapper2.findUserInfoById(1);
        System.out.println(userInfo2);

        session2.close();


    }
}
  • 结果
    二级缓存

八、添加记录

< insert > 标签的属性

  • useGeneratedKeys:获取 mysql 新添加的主键,前提是逐渐自动增长
  • keyColumn:表中主键字段名字
  • keyProperty:实体类中主键的属性名字

九、修改记录

< set > 标签

  • 生成 set 关键字
  • 根据条件去掉多余的逗号

十、删除记录

批量删除sql如:

delete from 表名 where id in (id,id,id...)  

< delete > 标签

用于遍历数组或集合
子标签 < foreach > 属性

  • collection:指定要遍历的数组或集合名字,需要使用 @Param 指定名字
  • open:遍历前添加的字符,如:" ( "
  • item:指定遍历中每个元素的变量名字,如上述的 " id "
  • separator:每遍历一个元素添加的符号,如:" , "
  • close:便利结束后添加的字符,如:" ) "
void deleteByIds(@Param("aaa") int[] ids)
<delete id="...">
    delete from 表名 where id
    in
    <foreach collection="aaa" item="id" separator="," open="(" close=")">
        #{id}
    </foreach>
</delete>

十一、mybatis参数传递

多个参数传递

我们 在接口方法中定义多个参数,Mybatis 会将这些参数封装成 Map 集合对象,值就是参数值,而键在没有使用 @Param 注解起别名的情况下我们可以有一下命名规则:

  • 以 arg 开头 :第一个参数就叫 arg0,第二个参数就叫 arg1,以此类推。
  • 以 param 开头 : 第一个参数就叫 param1,第二个参数就叫 param2,依次类推。
    arg从0开始,param从1开始

扩展

  1. SqlSessionFactoryBuilder
    这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。因此 SqlSessionFactoryBuilder 实例的最佳作用域(也就是局部方法的变量)。你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但是最好还是不要让其一直存在,以保证所有的XML解析资源可以被释放给更重要的事情。

    • 一旦创建就不再需要
    • 局部变量
  2. SqlSessionFactory
    SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。使用 SqlSessionFactory 最佳实践实在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码的“坏味道(bad smell)”。因此 SqlSessionFactory 的最佳作用域是应用作用域。有很多方法可以做到,最简单的就是使用单例模式或静态单例模式。

    • 类似于数据库连接池
    • 一旦被创建就应该在应用的运行期间一直存在, 没有任何理由丢弃它或重新创建另一个实例
    • 因此 SqlSessionFactory 的最佳作用域是应用作用域
    • 最简单的就是使用 单例模式 或者静态单例模式
  3. SqlSession
    每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法的作用域。绝不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如Servlet框架中的 HttpSession。如果你现在正在使用一种 Web 框架,要考虑 SqlSession 放在一个和 HTTP 请求对象相似的作用域中。换句话说,每次收到的 HTTP 请求,就可以打开一个 SqlSession ,返回一个响应,就关闭它。这个关闭操作是很重要的,你应该把这个关闭操作放到 finally 块中以确保每次都能执行关闭。

    • 连接到连接池的一个请求!
    • SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳作用域是请求或方法作用域
    • 用完后应及时关闭,否则占用资源!

    以下示例就是一个确保 SqlSession 关闭的标准模式:

try (SqlSession sqlSession=sqlSessionFactory.openSession){
            //你的逻辑代码
        }finally {
            sqlSession.close();
        }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值