MyBatis

MyBatis入门

什么是MyBatis?

MyBatis是一款优秀的持久层框架,用于简化JDBC开发

MyBatis 本是 Apache 的一个开源项目 iBatis,2010年这个项目由 apache software foundation 迁移到了 google code,并且改名为 MyBatis,2013年11月迁移到Github

持久层

负责将数据保持到数据库的那一层代码

JavaEE三层架构:表现层、业务层、持久层

框架

框架就是一个半成品软件,是一套可重用的、通用的、软件基础代码模型

在框架的基础之上构建软件编写更加高效、规范、通用、可扩展

JDBC缺点

硬编码

  • 注册驱动,获取连接
  • SQL语句

操作繁琐

  • 手动设置参数
  • 手动封装结果集

MyBatis简化

  • 将硬编码抽取到配置文件当中
  • 操作繁琐的部分自动完成
  • 免除了几乎所有的JDBC代码以及设置参数和获取结果集的工作

MyBatis快速入门

查询user表中所有数据

  • 创建user表,添加数据
create database mybatis;
use mybatis;

drop table if exists tb_user;

create table tb_user(
	id int primary key auto_increment,
	username varchar(20),
	password varchar(20),
	gender char(1),
	addr varchar(30)
);

INSERT INTO tb_user VALUES (1, 'zhangsan', '123', '男', '北京');
INSERT INTO tb_user VALUES (2, '李四', '234', '女', '天津');
INSERT INTO tb_user VALUES (3, '王五', '11', '男', '西安');
  • 创建模块,导入坐标
<dependencies>
    <!-- mybatis -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.16</version>
    </dependency>
    <!-- mysql 驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.30</version>
    </dependency>
    <!--junit单元测试-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13</version>
        <scope>test</scope>
    </dependency>
    <!-- 添加slf4j日志api -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.20</version>
    </dependency>
    <!-- 添加logback-classic依赖 -->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.3</version>
    </dependency>
    <!-- 添加logback-core依赖 -->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-core</artifactId>
        <version>1.2.3</version>
    </dependency>
</dependencies>

注意:需要在项目的resources目录下创建logback.xml的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!--
        CONSOLE :表示当前的日志信息是可以输出到控制台的。
    -->
    <appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>[%level] %blue(%d{HH:mm:ss.SSS}) %cyan([%thread]) %boldGreen(%logger{15}) - %msg %n</pattern>
        </encoder>
    </appender>

    <logger name="com.ling" level="DEBUG" additivity="false">
        <appender-ref ref="Console"/>
    </logger>


    <!--

      level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF
     , 默认debug
      <root>可以包含零个或多个<appender-ref>元素,标识这个输出位置将会被本日志级别控制。
      -->
    <root level="DEBUG">
        <appender-ref ref="Console"/>
    </root>
</configuration>
  • 编写MyBatis核心配置文件 --> 替换连接信息,解决硬编码问题
    • 在模块下的resources目录下创建mybatis-config.xml,内容如下:
<?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>
    <!--
    environments: 配置数据库连接环境信息,可以配置多个environment,通过default属性切换不同的environment
    -->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <!--数据库连接信息-->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql:///mybatis?useSSL=false"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>

        <environment id="test">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <!--数据库连接信息-->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql:///mybatis?useSSL=false"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!--加载sql映射文件-->
        <mapper resource="UserMapper.xml"/>

        <!--Mapper代理方式-->
<!--        <package name="com.ling.mapper"/>-->
    </mappers>
</configuration>
  • 编写SQL映射文件 --> 统一管理sql语句,解决硬编码问题
    • 在模块的resorces目录下创建映射配置文件UserMapping.xml,内容如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="test">
    <select id="selectAll" resultType="com.itheima.pojo.User">
        select * from tb_user;
    </select>
</mapper>
  • 编码
    • 定义POJO类
    • package com.ling.pojo;
      
      public class User {
      
          private Integer id;
          private String username;
          private String password;
          private String gender;
          private String addr;
      
          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 getGender() {
              return gender;
          }
      
          public void setGender(String gender) {
              this.gender = gender;
          }
      
          public String getAddr() {
              return addr;
          }
      
          public void setAddr(String addr) {
              this.addr = addr;
          }
      
          @Override
          public String toString() {
              return "User{" +
                      "id=" + id +
                      ", username='" + username + '\'' +
                      ", password='" + password + '\'' +
                      ", gender='" + gender + '\'' +
                      ", addr='" + addr + '\'' +
                      '}';
          }
      }
      
    • 加载核心配置文件,获取SqlSessionFactory对象
    • // 1.加载mybatis核心配置文件,获取SqlSessionFactory
      String resource = "mybatis-config.xml";
      InputStream inputStream = Resources.getResourceAsStream(resource);
      SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    • 获取SqlSession对象,执行SQL语句
    • // 2.获取SqlSession对象,可以用它来执行sql
      SqlSession sqlSession = sqlSessionFactory.openSession();
      
      // 3.执行sql
      List<Object> users = sqlSession.selectList("test.selectAll");
      
    • 释放资源
    • // 4.释放资源
      sqlSession.close();

Mapper代理开发

目的

  • 解决原生方式中的硬编码
  • 简化后期执行SQL

Mapper代理开发概述

之前我们写的代码是基本使用方式,它也存在硬编码的问题,如下:

// 3.执行sql
List<Object> users = sqlSession.selectList("test.selectAll");
System.out.println(users);

这里调用 selectList() 方法传递的参数是映射配置文件中的 namespace.id值。这样写也不便于后期的维护。如果使用 Mapper 代理方式(如下图)则不存在硬编码问题。

// 3.1 使用mapper代理方法
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> users = userMapper.selectAll();

通过上面的描述可以看出 Mapper 代理方式的目的:

  • 解决原生方式中的硬编码
  • 简化后期执行SQL

Mybatis 官网也是推荐使用 Mapper 代理的方式。下图是截止官网的图片

使用Mapper代理方式完成入门案例

  • 定义与SQL映射文件同名的Mapper接口,并且将Mapper接口和SQL映射文件放置在同一目录下

  • 设置SQL映射文件的namespace属性为Mapper接口的全限定名

  • 在Mapper接口中定义方法,方法名就是SQL映射文件中sql语句的id,并保持参数类型和返回值类型一致

  • 编码
    • 在 com.ling.mapper 包下创建 UserMapper接口,代码如下:
    • package com.ling.mapper;
      
      import com.ling.pojo.User;
      
      import java.util.List;
      
      public interface UserMapper {
      
          List<User> selectAll();
      }
      
    • 在 resources 下创建 com/ling/mapper 目录,并在该目录下创建 UserMapper.xml 映射配置文件
    • <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE mapper
              PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
              "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
      
      <!--
          namespace: 名称空间
      -->
      <mapper namespace="com.ling.mapper.UserMapper">
          <select id="selectAll" resultType="com.ling.pojo.User">
              select * from tb_user;
          </select>
      </mapper>
    • 通过SqlSession的getMapper方法获取Mapper接口的代理对象
    • 调用对应方法完成sql的执行
package com.ling;


import com.ling.mapper.UserMapper;
import com.ling.pojo.User;
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;
import java.util.List;

/*
MyBatis 快速入门代码
 */
public class MyBatisDemo {

    public static void main(String[] args) throws IOException {
        // 1.加载mybatis核心配置文件,获取SqlSessionFactory
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        // 2.获取SqlSession对象,可以用它来执行sql
        SqlSession sqlSession = sqlSessionFactory.openSession();

        // 3.执行sql
        // List<Object> users = sqlSession.selectList("test.selectAll");

        // 3.1 使用mapper代理方法
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = userMapper.selectAll();

        System.out.println(users);

        // 4.释放资源
        sqlSession.close();

    }
}

细节:如果Mapper接口名称和SQL映射文件名称相同,并在同一目录下,则可以使用包扫描的方式简化SQL映射文件的加载

核心配置文件

核心配置文件中现有的配置之前已经给大家进行了解释,而核心配置文件中还可以配置很多内容。我们可以通过查询官网看可以配置的内容

接下来我们先对里面的一些配置进行讲解。

多环境配置

在核心配置文件的 environments 标签中其实是可以配置多个 environment ,使用 id 给每段环境起名,在 environments 中使用 default='环境id' 来指定使用哪段配置。我们一般就配置一个 environment 即可。

    <!--
    environments: 配置数据库连接环境信息,可以配置多个environment,通过default属性切换不同的environment
    -->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <!--数据库连接信息-->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql:///mybatis?useSSL=false"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>

        <environment id="test">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <!--数据库连接信息-->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql:///mybatis?useSSL=false"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

类型别名

在映射文件中的resultType属性需要配置数据封装的类型(类的全限定名),而每次这样写是特别麻烦的,Mybatis提供了类型别名(typeAliaser)可以简化这部分的书写。

首先需要在核心配置文件中配置类型别名,也就意味着给pojo包下的所有的类起了别名(别名就是类名),不区分大小写。内容如下:

<typeAliases>
    <!--name属性的值是实体类所在包-->
    <package name="com.ling.pojo"/>
</typeAliases>

通过上述的配置,我们就可以简化映射配置文件中 resultType 属性值的编写

<mapper namespace="com.ling.mapper.UserMapper">
    <select id="selectAll" resultType="User">
        select * from tb_user;
    </select>
</mapper>

配置文件完成增删改查

准备环境

数据库表 tb_brand

-- 删除tb_brand表
drop table if exists tb_brand;
-- 创建tb_brand表
create table tb_brand
(
    -- id 主键
    id           int primary key auto_increment,
    -- 品牌名称
    brand_name   varchar(20),
    -- 企业名称
    company_name varchar(20),
    -- 排序字段
    ordered      int,
    -- 描述信息
    description  varchar(100),
    -- 状态:0:禁用  1:启用
    status       int
);
-- 添加数据
insert into tb_brand (brand_name, company_name, ordered, description, status)
values ('三只松鼠', '三只松鼠股份有限公司', 5, '好吃不上火', 0),
       ('华为', '华为技术有限公司', 100, '华为致力于把数字世界带入每个人、每个家庭、每个组织,构建万物互联的智能世界', 1),
       ('小米', '小米科技有限公司', 50, 'are you ok', 1);


SELECT * FROM tb_brand;

实体类 Brand

package com.ling.pojo;

/**
 * 品牌
 *
 * alt + 鼠标左键:整列编辑
 *
 * 在实体类中,基本数据类型建议使用其对应的包装类型
 */

public class Brand {
    // id 主键
    private Integer id;
    // 品牌名称
    private String brandName;
    // 企业名称
    private String companyName;
    // 排序字段
    private Integer ordered;
    // 描述信息
    private String description;
    // 状态:0:禁用  1:启用
    private Integer status;


    public Integer getId() {
        return id;
    }

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

    public String getBrandName() {
        return brandName;
    }

    public void setBrandName(String brandName) {
        this.brandName = brandName;
    }

    public String getCompanyName() {
        return companyName;
    }

    public void setCompanyName(String companyName) {
        this.companyName = companyName;
    }

    public Integer getOrdered() {
        return ordered;
    }

    public void setOrdered(Integer ordered) {
        this.ordered = ordered;
    }

    public String getDescription() {
        return description;
    }

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

    public Integer getStatus() {
        return status;
    }

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

    @Override
    public String toString() {
        return "Brand{" +
                "id=" + id +
                ", brandName='" + brandName + '\'' +
                ", companyName='" + companyName + '\'' +
                ", ordered=" + ordered +
                ", description='" + description + '\'' +
                ", status=" + status +
                '}';
    }
}

测试用例

安装 MyBatisX 插件

MyBatisX 是一款基于IDEA的快读开发插件,为效率而生

主要功能:
  • XML和接口方法相互跳转
  • 模糊接口方法生成statement
安装

查询——查询所有数据

编写接口方法
package com.ling.mapper;

import com.ling.pojo.Brand;

import java.util.List;

public interface BrandMapper {

    /*
    查询所有
     */
    List<Brand> selectAll();
}

编写SQL语句:SQL映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!--
    namespace: 名称空间
-->
<mapper namespace="com.ling.mapper.BrandMapper">

    <!--
        数据库表的字段名称 和 实体类的属性名称不一样,就不能自动封装数据
        1.起别名: 对不一样的列名起别名,让别名和实体类的属性名一样
            缺点:每次查询都需要定义一次别名
                sql片段
                    缺点:不灵活
        2.使用resultMap
            定义resultMap标签
            在 <select> 标签中,使用resultMap属性替换resultType属性
    -->

    <!--
        id: 唯一标识
        type: 返回值类型
    -->
    <resultMap id="brandResultMap" type="brand">
        <!--
            id:完成主键字段的映射
                column:表的列名
                property:实体类的属性名
            result:完成一般字段的映射
                column:表的列名
                property:实体类的属性名
        -->
        <result column="brand_name" property="brandName"/>
        <result column="company_name" property="companyName"/>
    </resultMap>

    <select id="selectAll" resultMap="brandResultMap">
        select
        *
        from tb_brand;
    </select>


    <!--
        sql片段:可重用的sql语句
    -->
<!--    <sql id="brand_column">-->
<!--            id, brand_name as brandName, company_name as companyName, ordered, description, status-->
<!--    </sql>-->

<!--    <select id="selectAll" resultType="Brand">-->
<!--        select-->
<!--            <include refid="brand_column"/>-->
<!--        from tb_brand;-->
<!--    </select>-->

<!--    <select id="selectAll" resultType="Brand">-->
<!--        select * from tb_brand;-->
<!--    </select>-->
</mapper>
执行方法,测试
package com.ing.test;

import com.ling.mapper.BrandMapper;
import com.ling.pojo.Brand;
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;
import java.util.List;

public class MyBatisTest {

    @Test
    public void testSelectAll() throws IOException {

        // 1. 获取sqlSessionFactory对象
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory =  new SqlSessionFactoryBuilder().build(inputStream);

        // 2. 获取sqlSession对象
        SqlSession sqlSession = sqlSessionFactory.openSession();

        // 3. 获取Mapper接口的代理对象
        BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);

        // 4. 执行方法
        List<Brand> brands = mapper.selectAll();

        // 5. 处理结果
        System.out.println(brands);


        // 6. 释放资源
        sqlSession.close();

    }
}

实体类属性名和数据库表列名不一致,不能自动封装数据

起别名:
  • 在SQL语句中,对不一样的列名起别名,别名和实体类属性名一样
    • 可以定义<sql>片段,提升复用性
resultMap:
  • 定义<resultMap>完成不一致的属性名和列名的映射

查看详情

编写接口方法
Brand selectById(int id);

编写SQL语句:SQL映射文件

<!--
    参数占位符:
        #{}:相当于PreparedStatement中的?,防止SQL注入
        ${}:字符串拼接,会直接把参数进行拼接,存在SQL注入问题
        使用时机:
            参数传递的时候:#{}
            表名或者列名不固定的时候:${}
    参数类型:parameterType 可以省略
    特殊字符类型:
        1、转义字符:比如<、>、&等,可以使用转义字符 &lt;、&gt;、&amp;
        2、CDATA区:把特殊字符当成普通字符处理 <![CDATA[<]]>
-->
<select id="selectById" parameterType="int" resultMap="brandResultMap">
    select * from tb_brand where id = #{id};
</select>
执行方法,测试
@Test
public void testSelectById() throws IOException {
    // 接收参数
    int id = 1;
    // 1. 获取sqlSessionFactory对象
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory =  new SqlSessionFactoryBuilder().build(inputStream);
    // 2. 获取sqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // 3. 获取Mapper接口的代理对象
    BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
    // 4. 执行方法
    Brand brand = mapper.selectById(id);
    // 5. 处理结果
    System.out.println(brand);
    // 6. 释放资源
    sqlSession.close();
}

参数占位符

  • #{}:执行SQL时,会将#{}字符替换成?,将来自动设置参数值
  • ${}:拼SQL,会存在SQL注入问题
  • 使用时机:
    • 参数传递都是用#{}
    • 如果要对表名、列名进行动态设置,只能使用${}进行sql拼接

parameterType

  • 用于设置参数类型,该参数可以省略

SQL语句中特殊字符处理

  • 转义字符
  • <![CDATA[内容]]>

条件查询

多条件查询

编写接口方法
/**
 * 条件查询
 * 参数接收
 *      1、散装参数:如果方法中有多个参数,需要使用@Param("SQL参数占位符名称")
 *      2、对象参数: 对象的属性的名称要和参数占位符名称一致
 *      3、map集合参数
 * @param status
 * @param companyName
 * @param brandName
 * @return
 */
List<Brand> selectByCondition(@Param("status") int status, @Param("companyName") String companyName, @Param("brandName") String brandName);
List<Brand> selectByCondition(Brand brand);
List<Brand> selectByCondition(Map map);
编写SQL语句
<!--
    条件查询
-->
<select id="selectByCondition" resultMap="brandResultMap">
    select *
    from tb_brand
    where status = #{status}
    and company_name like #{companyName}
    and brand_name like #{brandName};
</select>
执行方法,测试
@Test
public void testSelectByCondition() throws IOException {
    // 接收参数
    int status = 1;
    String companyName = "华为";
    String brandName = "华为";

    // 处理参数
    companyName = "%" + companyName + "%";
    brandName = "%" + brandName + "%";

    // 封装对象
    /*Brand brand = new Brand();
    brand.setStatus(status);
    brand.setCompanyName(companyName);
    brand.setBrandName(brandName);*/

    Map map = new HashMap();
    map.put("status", status);
    map.put("companyName", companyName);
    map.put("brandName", brandName);

    // 1. 获取sqlSessionFactory对象
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory =  new SqlSessionFactoryBuilder().build(inputStream);

    // 2. 获取sqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 3. 获取Mapper接口的代理对象
    BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);

    // 4. 执行方法
    // List<Brand> brands = mapper.selectByCondition(status, companyName, brandName);
    // List<Brand> brands = mapper.selectByCondition(brand);
    List<Brand> brands = mapper.selectByCondition(map);

    // 5. 处理结果
    System.out.println(brands);

    // 6. 释放资源
    sqlSession.close();
}

SQL语句设置多个参数有几种方式
散装参数:
  • 需要使用@Param("SQL中的参数占位符名称")
实体类封装参数
  • 只需要保证SQL中的参数名 和 实体类属性名对应上,即可设置成功
map集合
  • 只需要保证SQL中的参数名 和 map集合的键的名称对应上,即可设置成功

多条件的动态条件查询

编写接口方法

在 BrandMapper 接口中定义多条件查询的方法。

而该功能有三个参数,我们就需要考虑定义接口时,参数应该如何定义。Mybatis针对多参数有多种实现

  • 使用 @Param("参数名称") 标记每一个参数,在映射配置文件中就需要使用 #{参数名称} 进行占位
List<Brand> selectByCondition(@Param("status") int status, @Param("companyName") String companyName,@Param("brandName") String brandName);
  • 将多个参数封装成一个 实体对象 ,将该实体对象作为接口的方法参数。该方式要求在映射配置文件的SQL中使用 #{内容} 时,里面的内容必须和实体类属性名保持一致。
List<Brand> selectByCondition(Brand brand);
  • 将多个参数封装到map集合中,将map集合作为接口的方法参数。该方式要求在映射配置文件的SQL中使用 #{内容} 时,里面的内容必须和map集合中键的名称一致。
List<Brand> selectByCondition(Map map);
/**
 * 条件查询
 * 参数接收
 *      1、散装参数:如果方法中有多个参数,需要使用@Param("SQL参数占位符名称")
 *      2、对象参数: 对象的属性的名称要和参数占位符名称一致
 *      3、map集合参数
 * @param status
 * @param companyName
 * @param brandName
 * @return
 */
List<Brand> selectByCondition(@Param("status") int status, @Param("companyName") String companyName, @Param("brandName") String brandName);
List<Brand> selectByCondition(Brand brand);
List<Brand> selectByCondition(Map map);
编写SQL语句
<!--
    动态条件查询
        if: 条件判断
            test: 判断表达式,OGNL表达式
        问题:
            恒等式
            <where>替换 where 关键字
-->
<select id="selectByCondition" resultMap="brandResultMap">
    select *
    from tb_brand
    <!--where 1 = 1-->
    where
        <if test="status != null">
            and status = #{status}
        </if>
        <if test="companyName != null and companyName != ''">
            and company_name like #{companyName}and company_name like #{companyName}
        </if>
        <if test="brandName != null and brandName != ''">
            and brand_name like #{brandName}
        </if>
    </where>
</select>

但是它也存在问题,如果此时给的参数值是

Map map = new HashMap();
// map.put("status" , status);
map.put("companyName", companyName);
map.put("brandName" , brandName);

拼接的SQL语句就变成了

select * from tb_brand where and company_name like ? and brand_name like ?

而上面的语句中 where 关键后直接跟 and 关键字,这就是一条错误的SQL语句。这个就可以使用 <where> 标签解决

<select id="selectByCondition" resultMap="brandResultMap">
    select *
    from tb_brand
    <where>
        <if test="status != null">
            and status = #{status}
        </if>
        <if test="companyName != null and companyName != '' ">
            and company_name like #{companyName}
        </if>
        <if test="brandName != null and brandName != '' ">
            and brand_name like #{brandName}
        </if>
    </where>
</select>

注意:需要给每个条件前都加上 and 关键字。 

执行方法,测试
@Test
public void testSelectByCondition() throws IOException {
    // 接收参数
    int status = 1;
    String companyName = "华为";
    String brandName = "华为";

    // 处理参数
    companyName = "%" + companyName + "%";
    brandName = "%" + brandName + "%";

    // 封装对象
    /*Brand brand = new Brand();
    brand.setStatus(status);
    brand.setCompanyName(companyName);
    brand.setBrandName(brandName);*/

    Map map = new HashMap();
    map.put("status", status);
    map.put("companyName", companyName);
    map.put("brandName", brandName);

    // 1. 获取sqlSessionFactory对象
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory =  new SqlSessionFactoryBuilder().build(inputStream);

    // 2. 获取sqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 3. 获取Mapper接口的代理对象
    BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);

    // 4. 执行方法
    // List<Brand> brands = mapper.selectByCondition(status, companyName, brandName);
    // List<Brand> brands = mapper.selectByCondition(brand);
    List<Brand> brands = mapper.selectByCondition(map);

    // 5. 处理结果
    System.out.println(brands);

    // 6. 释放资源
    sqlSession.close();
}

注意这里的模糊查询两边加上%,但是这个%不能在映射文件中的#{}中去加,而是只能在传入形参之前对数据进行处理,再进行传入操作。

如上的程序有一个小bug,那就是如果用户没有填写所有的搜索条件,那么结果是查询不到的。

动态SQL

if:用于判断参数是否优质,使用test属性进行条件判断

  • 存在的问题:第一个条件不需要逻辑运算符
  • 解决方案
    • 使用恒等式让所有条件的格式都一样         where 1 = 1 (and ...)
    • <where>标签替换where关键字
      • 替换where关键字
      • 会动态的去掉第一个条件前的 and
      • 如果所有的参数没有值则不加where关键字

单条件的动态条件查询

编写接口方法
/**
 * 单条件动态查询
 * @param brand
 * @return
 */
List<Brand> selectByConditionSingle(Brand brand);

这个地方一般使用对象的方法来传递参数,因为你不知道用户会选择哪个参数。 

编写SQL语句
<select id="selectByConditionSingle" resultMap="brandResultMap">
    select *
    from tb_brand
    <where>
        <choose><!--相当于switch-->
            <when test="status != null"><!--相当于case-->
                status = #{status}
            </when>
            <when test="companyName != null and companyName != ''">
                company_name like #{companyName}
            </when>
            <when test="brandName != null and brandName != ''">
                brand_name like #{brandName}
            </when>
        </choose>
    </where>
</select>

注意这个地方使用<where>是为了处理用户不进行选择就进行查询的情况。这种情况同样有第二种解决办法:恒等式
在这里插入图片描述

执行方法,测试
@Test
public void testSelectByConditionSingle() throws IOException {
    // 接收参数
    int status = 1;
    String companyName = "华为";
    String brandName = "华为";

    // 处理参数
    companyName = "%" + companyName + "%";
    brandName = "%" + brandName + "%";

    // 封装对象
    Brand brand = new Brand();
    // brand.setStatus(status);
    brand.setCompanyName(companyName);
    // brand.setBrandName(brandName);

    /*Map map = new HashMap();
    map.put("status", status);
    map.put("companyName", companyName);
    map.put("brandName", brandName);*/

    // 1. 获取sqlSessionFactory对象
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory =  new SqlSessionFactoryBuilder().build(inputStream);

    // 2. 获取sqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 3. 获取Mapper接口的代理对象
    BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);

    // 4. 执行方法
      List<Brand> brands = mapper.selectByCondition(status, companyName, brandName);
      List<Brand> brands = mapper.selectByCondition(brand);
      List<Brand> brands = mapper.selectByCondition(map);
    List<Brand> brands = mapper.selectByConditionSingle(brand);

    // 5. 处理结果
    System.out.println(brands);

    // 6. 释放资源
    sqlSession.close();
}
动态SQL

choose(when,otherwise)

  • 存在的问题:如果多个<when>标签的判断都不满足,就会报错
  • 解决方案:
    • 添加<otherwise>里面的内容,一般写作恒等式
    • 将where关键字改成<where>标签

添加

编写接口方法

由于添加操作不需要返回值,所以返回值类型写void即可

/**
 * 添加
 * @param brand
 */
void add(Brand brand);

编写SQL语句

这里就不需要调用之前写的resultMap的内容了

<insert id="add">
    insert into tb_brand (brand_name, company_name, ordered, description, status)
    values (#{brandName}, #{companyName}, #{ordered}, #{description}, #{status})
</insert>

编写测试方法

@Test
public void testAdd() throws IOException {
    // 接收参数
    int status = 1;
    String companyName = "波导手机";
    String brandName = "波导";
    String description = "手机中的战斗机";
    int ordered = 100;

    // 封装对象
    Brand brand = new Brand();
    brand.setStatus(status);
    brand.setCompanyName(companyName);
    brand.setBrandName(brandName);
    brand.setDescription(description);
    brand.setOrdered(ordered);

    // 1. 获取sqlSessionFactory对象
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory =  new SqlSessionFactoryBuilder().build(inputStream);

    // 2. 获取sqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();
    
    // 3. 获取Mapper接口的代理对象
    BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);

    // 4. 执行方法
    mapper.add(brand);

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

    // 6. 释放资源
    sqlSession.close();
}

注意:

使用openSession是默认开启事务的,如果不提交事务的话,数据库不会发生变化。在这里有两种解决办法:

  • openSession():默认开启事务,但是再进行增删改操作后需要手动提交事务,使用sqlSession.commit(); 手动提交事务
  • openSession(true):可以设置为自动提交事务(关闭事务)

添加——主键返回

有时候我们需要返回添加数据的主键
比如:添加订单和订单项

  • 添加订单
  • 添加订单项,订单项中需要设置所属订单的id

做法:
在 insert 标签上添加如下属性:

  • useGeneratedKeys:是否获取自动增长的主键值。true表示获取
  • keyProperty :指定将获取到的主键值封装到哪个属性里
<insert id="add" useGeneratedKeys="true" keyProperty="id">
    insert into tb_brand (brand_name, company_name, ordered, description, status)
    values (#{brandName}, #{companyName}, #{ordered}, #{description}, #{status})
</insert>

再直接使用对象的get方法就可以获得当前的主键值了

@Test
public void testAdd2() throws IOException {
    // 接收参数
    int status = 1;
    String companyName = "波导手机";
    String brandName = "波导";
    String description = "手机中的战斗机";
    int ordered = 100;

    // 封装对象
    Brand brand = new Brand();
    brand.setStatus(status);
    brand.setCompanyName(companyName);
    brand.setBrandName(brandName);
    brand.setDescription(description);
    brand.setOrdered(ordered);

    // 1. 获取sqlSessionFactory对象
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory =  new SqlSessionFactoryBuilder().build(inputStream);

    // 2. 获取sqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // SqlSession sqlSession = sqlSessionFactory.openSession(true);

    // 3. 获取Mapper接口的代理对象
    BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);

    // 4. 执行方法
    mapper.add(brand);
    Integer id = brand.getId();
    System.out.println(id);

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

    // 6. 释放资源
    sqlSession.close();
}

修改——修改全部字段

编写接口方法
/**
 * 修改
 * @param brand
 * @return
 */
int update(Brand brand);

编写SQL语句

<update id="update">
    update tb_brand
    set
        brand_name = #{brandName},
        company_name = #{companyName},
        ordered = #{ordered},
        description = #{description},
        status = #{status}
    where id = #{id}
</update>
执行方法,测试
@Test
public void testUpdate() throws IOException {
    // 接收参数
    int status = 1;
    String companyName = "波导手机";
    String brandName = "波导";
    String description = "波导手机,手机中的战斗机";
    int ordered = 200;
    int id = 5;

    // 封装对象
    Brand brand = new Brand();
    brand.setStatus(status);
    brand.setCompanyName(companyName);
    brand.setBrandName(brandName);
    brand.setDescription(description);
    brand.setOrdered(ordered);
    brand.setId(id);

    // 1. 获取sqlSessionFactory对象
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory =  new SqlSessionFactoryBuilder().build(inputStream);

    // 2. 获取sqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // SqlSession sqlSession = sqlSessionFactory.openSession(true);

    // 3. 获取Mapper接口的代理对象
    BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);

    // 4. 执行方法
    int rows = mapper.update(brand);
    System.out.println("受影响的行数:" + rows);

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

    // 6. 释放资源
    sqlSession.close();
}

修改动态字段

假设一个场景,我们仅仅只需要修改某个字段,但是在这个条件下,其他没有修改的属性就会被设置成null。

我们的基本思路就是用if标签去包含每一项

这个地方我们又会遇到前面遇到过问题,就是如果用户什么都没有修改,那么只有一个set在那里肯定会报错。或者用户修改的其中一项而尾部有一个逗号,那么也会报错。这个时候我们就需要用到MyBatis给我们提供的set标签

set 标签可以用于动态包含需要更新的列,忽略其它不更新的列。

我们可以将sql语句改写为:

<update id="update">
    update tb_brand
    <set>
        <if test="brandName != null and brandName != '' ">
            brand_name = #{brandName},
        </if>
        <if test="companyName != null and companyName != '' ">
            company_name = #{companyName},
        </if>
        <if test="ordered != null and ordered != '' ">
            ordered = #{ordered},
        </if>
        <if test="description != null and description != '' ">
            description = #{description},
        </if>
        <if test="status != null">
            status = #{status}
        </if>
    </set>
    where id = #{id}
</update>

删除一个

编写接口方法
/**
 * 根据id删除
 * @param id
 */
void deleteById(int id);

编写SQL方法

<delete id="deleteById">
    delete from tb_brand where id = #{id}
</delete>
执行方法,测试
@Test
public void testDelete() throws IOException {
    // 接收参数
    int id = 5;
    // 1. 获取sqlSessionFactory对象
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory =  new SqlSessionFactoryBuilder().build(inputStream);
    // 2. 获取sqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();
      SqlSession sqlSession = sqlSessionFactory.openSession(true);
    // 3. 获取Mapper接口的代理对象
    BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
    // 4. 执行方法
    mapper.deleteById(id);
    // 5. 提交事务
    sqlSession.commit();
    // 6. 释放资源
    sqlSession.close();
}

批量删除

编写接口方法
/**
 * 批量删除
 * @param ids
 */
void delete(int[] ids);

编写SQL方法

批量删除我们不知道要删除几个,所以编写SQL时需要遍历数组来拼接SQL语句。Mybatis 提供了 foreach 标签供我们使用。

foreach 标签

用来迭代任何可迭代的对象(如数组,集合)。

  • collection 属性:要遍历的可迭代对象
    • mybatis会将数组参数,封装为一个Map集合。
    • 默认:array = 数组
    • 使用@Param注解改变map集合的默认key的名称
      • /**
         * 批量删除
         * @param ids
         */
        void delete(@Param("ids") int[] ids);
        <delete id="delete">
            delete from tb_brand
            where id in
            <foreach collection="ids" item="id" separator="," open="(" close=")" >
                #{id}
            </foreach>
        </delete>
  • item 属性:本次迭代获取到的元素。
  • separator 属性:集合项迭代之间的分隔符。foreach 标签不会错误地添加多余的分隔符。也就是最后一次迭代不会加分隔符。
  • open 属性:该属性值是在拼接SQL语句之前拼接的语句,只会拼接一次
  • close 属性:该属性值是在拼接SQL语句拼接后拼接的语句,只会拼接一次
<delete id="delete">
    delete from tb_brand
    where id in
    <foreach collection="array" item="id" separator="," open="(" close=")" >
        #{id}
    </foreach>
</delete>

假如数组中的id数据是{1,2,3},那么拼接后的sql语句就是:

delete from tb_brand where id in (1,2,3);

编写测试方法

@Test
public void testDeleteByIds() throws IOException {
    // 接收参数
    int[] ids = {3, 5, 6};

    // 1. 获取sqlSessionFactory对象
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory =  new SqlSessionFactoryBuilder().build(inputStream);

    // 2. 获取sqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // SqlSession sqlSession = sqlSessionFactory.openSession(true);

    // 3. 获取Mapper接口的代理对象
    BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);

    // 4. 执行方法
    mapper.delete(ids);

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

    // 6. 释放资源
    sqlSession.close();
}

MyBatis参数传递

MyBatis中的参数传递

MyBatis接口方法中可以接受各种各样的参数,如下:

  • 单个参数
    • POJO类型
    • Map集合类型
    • Collection集合嘞
    • List集合类型
    • Array类型
    • 其他类型
  • 多个参数

多个参数

如下面的代码,就是接收两个参数,而接收多个参数需要使用 @Param 注解,那么为什么要加该注解呢?这个问题要弄明白就必须来研究Mybatis 底层对于这些参数是如何处理的。

User select(@Param("username") String username,@Param("password") String password);
<select id="select" resultType="user">
	select *
    from tb_user
    where 
    	username=#{username}
    	and password=#{password}
</select>

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

  • 以 arg 开头 :第一个参数就叫 arg0,第二个参数就叫 arg1,以此类推。如:
    • map.put(“arg0”,参数值1);
    • map.put(“arg1”,参数值2);
  • 以 param 开头 : 第一个参数就叫 param1,第二个参数就叫 param2,依次类推。如:
    • map.put(“param1”,参数值1);
    • map.put(“param2”,参数值2);

代码验证

在UserMapper接口中定义如下方法

User select(String username,String password);

在UserMapper.xml映射配置文件中定义SQL

<select id="select" resultType="user">
	select *
    from tb_user
    where 
    	username=#{arg0}
    	and password=#{arg1}
</select>

或者

<select id="select" resultType="user">
	select *
    from tb_user
    where 
    	username=#{param1}
    	and password=#{aram2}
</select>

运行代码结果如下

但是,在映射配合文件的SQL语句中使用用 arg 开头的和 param 书写,代码的可读性会变的特别差,此时可以使用 @Param 注解。

在接口方法参数上使用 @Param 注解,MyBatis 会将 arg 开头的键名替换为对应注解的属性值。

代码验证

在UserMapper接口中定义如下方法,在user那么参数前加上@param注解

User select(@Param("username") String username, String password);

Mybatis 在封装 Map 集合时,键名就会变成如下:

map.put(“username”,参数值1);

map.put(“arg1”,参数值2);

map.put(“param1”,参数值1);

map.put(“param2”,参数值2);

在 UserMapper.xml 映射配置文件中定义SQL

<select id="select" resultType="user">
	select *
    from tb_user
    where 
    	username=#{username}
    	and password=#{param2}
</select>

运行程序结果没有报错。而如果将 #{} 中的 username 还是写成 arg0

<select id="select" resultType="user">
	select *
    from tb_user
    where 
    	username=#{arg0}
    	and password=#{param2}
</select>
  • 运行程序则可以看到错误

结论:以后接口参数是多个时,在每个参数上都使用 @Param 注解。这样代码的可读性更高。

单个参数

  • POJO 类型
    • 直接使用。要求 属性名 和 参数占位符名称 一致
  • Map 集合类型
    • 直接使用。要求 map集合的键名 和 参数占位符名称 一致
  • Collection 集合类型
    • Mybatis 会将集合封装到 map 集合中,如下:

map.put(“arg0”,collection集合);

map.put(“collection”,collection集合;

可以使用 @Param 注解替换map集合中默认的 arg 键名。

  • List 集合类型
    • Mybatis 会将集合封装到 map 集合中,如下:

map.put(“arg0”,list集合);

map.put(“collection”,list集合);

map.put(“list”,list集合);

可以使用 @Param 注解替换map集合中默认的 arg 键名。

  • Array 类型
    • Mybatis 会将集合封装到 map 集合中,如下:

map.put(“arg0”,数组);

map.put(“array”,数组);

可以使用 @Param 注解替换map集合中默认的 arg 键名。

  • 其他类型
    • 比如int类型,参数占位符名称 叫什么都可以。尽量做到见名知意

注解完成增删改查

使用注解开发会比配置文件开发更加方便。如下就是使用注解进行开发

@Select(value = "select * from tb_user where id = #{id}")
public User select(int id);

注意:

  • 注解是用来替换映射配置文件方式配置的,所以使用了注解,就不需要再映射配置文件中书写对应的 statement

Mybatis 针对 CURD 操作都提供了对应的注解,已经做到见名知意。如下:

  • 查询 :@Select
  • 添加 :@Insert
  • 修改 :@Update
  • 删除 :@Delete

接下来我们做一个案例来使用 Mybatis 的注解开发

代码实现

  • 将之前案例中 UserMapper.xml 中的 根据id查询数据 的 statement 注释掉

  • 在 UserMapper 接口的 selectById 方法上添加注解

  • 运行测试程序也能正常查询到数据

注意:在官方文档中 入门 中有这样的一段话:

而我们之前写的动态 SQL 就是复杂的功能,如果用注解使用的话,就需要使用到 Mybatis 提供的SQL构建器来完成,而对应的代码如下:

上述代码将java代码和SQL语句融到了一块,使得代码的可读性大幅度降低。

参数映射

对于以下一个简单的select语句:

<select id="selectPerson" parameterType="int" resultType="hashmap">
  SELECT * FROM PERSON WHERE ID = #{id}
</select>

我们可以发现一个参数符号:

#{id}

这就告诉 MyBatis 创建一个预处理语句(PreparedStatement)参数,在 JDBC 中,这样的一个参数在 SQL 中会由一个“?”来标识,并被传递到一个新的预处理语句中,就像这样:

// 近似的 JDBC 代码,非 MyBatis 代码...
String selectPerson = "SELECT * FROM PERSON WHERE ID=?";
PreparedStatement ps = conn.prepareStatement(selectPerson);
ps.setInt(1,id);

上面的这个示例说明了一个非常简单的命名参数映射。鉴于参数类型(parameterType)会被自动设置为 int,这个参数可以随意命名。原始类型或简单数据类型(比如 Integer 和 String)因为没有其它属性,会用它们的值来作为参数。 然而,如果传入一个复杂的对象,行为就会有点不一样了。比如:

<insert id="insertUser" parameterType="User">
  insert into users (id, username, password)
  values (#{id}, #{username}, #{password})
</insert>

如果 User 类型的参数对象传递到了语句中,会查找 id、username 和 password 属性,然后将它们的值传入预处理语句的参数中。

对传递语句参数来说,这种方式真是干脆利落。

字符串替换

默认情况下,使用 #{} 参数语法时,MyBatis 会创建 PreparedStatement 参数占位符,并通过占位符安全地设置参数(就像使用 ? 一样)。 这样做更安全,更迅速,通常也是首选做法,不过有时你就是想直接在 SQL 语句中直接插入一个不转义的字符串。 比如 ORDER BY 子句,这时候你可以:

ORDER BY ${columnName}

这样,MyBatis 就不会修改或转义该字符串了。

当 SQL 语句中的元数据(如表名或列名)是动态生成的时候,字符串替换将会非常有用 。 举个例子,如果你想 select 一个表任意一列的数据时,不需要这样写:

@Select("select * from user where id = #{id}")
User findById(@Param("id") long id);

@Select("select * from user where name = #{name}")
User findByName(@Param("name") String name);

@Select("select * from user where email = #{email}")
User findByEmail(@Param("email") String email);


// 其它的 "findByXxx" 方法

而是可以只写这样一个方法:

@Select("select * from user where ${column} = #{value}")
User findByColumn(@Param("column") String column, @Param("value") String value);

因为#{}其本质也是赋值替换预处理语句中相应占位符的操作,所以这个地方你传入的id是string类型也可以

其中 ${column} 会被直接替换,而 #{value} 会使用 ? 预处理。 这样,就能完成同样的任务:

User userOfId1 = userMapper.findByColumn("id", 1L);
User userOfNameKid = userMapper.findByColumn("name", "kid");
User userOfEmail = userMapper.findByColumn("email", "noone@nowhere.com");


这种方式也同样适用于替换表名的情况。

提示 :用这种方式接受用户的输入,并用作语句参数是不安全的,会导致潜在的 SQL 注入攻击。因此,要么不允许用户输入这些字段,要么自行转义并检验这些参数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值