MyBatis使用Demo

文章目录

当前项目里面的 CRUD 接口都是基于前人已经封装好的 API、或者直接使用 mybatis-generator 插件生成的,将其底层和数据库打交道的关键层 Mybatis 进行了封装。但是我认为了解持久层原理还是很有必要的,因此决定从 Mybatis 基本用法出发,进一步地剖析其底层的一些关键源码,对 Mybatis 有个比较清晰的把握。

01、Mybatis 意义

02、Mybatis 快速入门

先写了一堆 pom.xml 依赖

xml 复制代码
<?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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.jxz</groupId>
    <artifactId>mybatis-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.5</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.2.3</version>
        </dependency>
    </dependencies>

</project>

每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。

从 XML 文件中构建 SqlSessionFactory 的实例非常简单,建议使用类路径下的资源文件进行配置。 但也可以使用任意的输入流(InputStream)实例,比如用文件路径字符串或 file:// URL 构造的输入流。MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,使得从类路径或其它位置加载资源文件更加容易。

Mybatis-config.xml

java 复制代码
<?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>
    <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:///mybatis?useSSL=false"/>
                <property name="username" value="xxx"/>
                <property name="password" value="xxx"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="org/mybatis/example/BlogMapper.xml"/>
    </mappers>
</configuration>
  1. 编写 SQL 映射文件 UserMapper.xml

    java 复制代码
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!--namespace 命名空间-->
    <mapper namespace="test">
        <select id="selectAll" resultType="com.jxz.pojo.User">
            select * from tb_user;
        </select>
    </mapper>
  2. 定义和 SQL 映射文件有关的实体 User

  3. Mybatis-config.xml 加载映射文件

java 复制代码
<!--        加载 sql 映射文件-->
  <mappers>
      <mapper resource="UserMapper.xml"/>
  </mappers>
  1. 创建 SqlSession 对象,执行 sqlSession 查询方法
java 复制代码
package com.jxz;

import com.jxz.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;

/**
 * @Author jiangxuzhao
 * @Description Hello World Demo
 * @Date 2024/6/14
 */
public class MyBatisDemo {
    public static void main(String[] args) 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. 执行 sql
        List<User> result = sqlSession.selectList("test.selectAll");
        System.out.println(result);

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

04、Mapper 代理开发

上面执行 sql 的时候还是碰到了硬编码

改成 Mapper 代理可以基于接口开发,但是有些约定需要遵循

  1. 定义同名的 Mapper Interface 并和 SQL 映射文件放在同一目录下(java 包和 resources 包最后编译都会放在一块),我项目中放在 com/jxz/mapper 目录下
  2. 修改 UserMapper.xml 中的 namespace = 接口全限定名 com.jxz.mapper.UserMapper
java 复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace 命名空间-->
<mapper namespace="com.jxz.mapper.UserMapper">
    <select id="selectAll" resultType="com.jxz.pojo.User">
        select * from tb_user;
    </select>
</mapper>
  1. Mapper 接口中定义方法,与SQL 映射文件 id、参数类型、返回值一致。
java 复制代码
package com.jxz.mapper;

import com.jxz.pojo.User;

import java.util.List;

/**
 * @Author jiangxuzhao
 * @Description
 * @Date 2024/6/14
 */
public interface UserMapper {
    List<User> selectAll();
}
  1. Mybatis-config 配置文件加载 SQL 映射文件的路径需要修改,改成 UserMapper 接口以及 SQL 映射文件共同所在的包路径

     <mappers>
    
         <package name="com.jxz.mapper"/>
     </mappers>
    
  2. 从 SqlSession 中拿到代理对象查询

java 复制代码
package com.jxz;

import com.jxz.mapper.UserMapper;
import com.jxz.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;

/**
 * @Author jiangxuzhao
 * @Description Mapper 代理方式
 * @Date 2024/6/14
 */
public class MybatisDemo2 {
    public static void main(String[] args) 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. 获取 UserMapper 代理对象
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = userMapper.selectAll();
        System.out.println(users);

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

执行路径就是 SqlSession 根据 mybatis-config.xml 中 下配置的包路径找到 UserMapper Interface,相同目录下找到 UserMapper.xml SQL 配置文件;在调用 selectAll 方法时对应到 SQL 配置文件中的 id = ,也就找到了配置的 SQL 语句。

05、Mybatis 配置文件

就是前面为了生成 SqlSessionFactoryBuilder 的 mybatis-config.xml 文件

我在这里配置了个别名

java 复制代码
<typeAliases>
    <package name="com.jxz.pojo"/>
</typeAliases>

这样子在 UserMapper.xml SQL 配置文件中,resultType 就可以直接使用别名了,即 Pojo 类的首字母小写的非限定类名

java 复制代码
<!--Mappper 代理模式为接口全限定名-->
<mapper namespace="com.jxz.mapper.UserMapper">
    <select id="selectAll" resultType="user">
        select * from tb_user;
    </select>
</mapper>

07、查询所有&结果映射

像 04 的 UserMapper 代理开发一样,这章使用 BrandMapper 进行更复杂场景的开发,包含结果映射的相关知识。

数据库表的字段名称 和 实体类的属性名称不一样,不能自动封装数据。

java 复制代码
<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>
resultMap:
    1. 定义 <resultMap> 标签
    2. 在<select> 标签中,使用 resultMap 属性替换 resultType 属性

08、查询-查看详情

java 复制代码
Brand selectById(int id);
jav 复制代码
<select id="selectById" resultMap="brandResultMap">
        select *
        from tb_brand
        where id = #{id}
</select>

09、查询-条件查询

多条件查询

总共有3种传参方式

java 复制代码
<!--
    条件查询
-->
    <select id="selectByCondition" resultMap="brandResultMap">
        select *
        from tb_brand
        where status = #{status}
        and company_name like #{companyName}
        and brand_name like #{brandName}
    </select>
java 复制代码
/**
 *  条件查询
 *  参数接收
 *  1. 散装参数:如果方法中有多个参数,需要使用 @Param("SQL占位符名称")
 *  2. 对象参数:对象的属性名称要和参数的占位符一致
 *  3. map 集合参数
 * @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);

10、查询-动态条件查询

多条件动态查询

上面的条件查询在 SQL 配置文件中写死了,如果 status,companyName,brandName任意一个条件没传递,类似 status = null,最终就会查询不出来东西,因此需要配置动态 SQL 查询语句,判断 status 是否为 null。

java 复制代码
<select id="selectByCondition" resultMap="brandResultMap">
    select *
    from tb_brand
    where
        <if test="status != null">
            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>
</select>

但这样有个问题,如果仅用到了 companyName 这个条件,拼接的查询语句就是 "where and company_name like #{companyName}",不符合 SQL 语法了。

为了解决这个问题,可以使用两种方法

1. 恒等式
2. <where> 替换 where 关键字

用 标签比较优雅,替换掉 SQL 语句中的 where 关键字

java 复制代码
<select id="selectByCondition" resultMap="brandResultMap">
    select *
    from tb_brand
    <where>
        <if test="status != null">
            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>

单条件动态查询

和多条件实现的结果差不多,相当于是用户去选了某个 where 属性来生效

11、添加&修改功能

添加功能

void add(Brand brand);

这里并没有 insert into id 这个属性

<insert id="add">
    insert into tb_brand(brand_name, company_name, ordered, description, status)
    values (#{brandName}, #{companyName}, #{ordered}, #{description}, #{status})
</insert>
java 复制代码
    @Test
    public void testAdd() throws IOException {
        int status = 1;
        String companyName = "阿里巴巴";
        String brandName = "阿里巴巴";
        int ordered = 1;
        String description = "des";

        Brand brand = new Brand();
        brand.setStatus(status);
        brand.setCompanyName(companyName);
        brand.setBrandName(brandName);
        brand.setOrder(ordered);
        brand.setDescription(description);

        // 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. 获取代理对象
        BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
        brandMapper.add(brand);

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

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

其中别忘了手动提交事务

主键返回:

添加订单以后,想继续往订单里面添加对应的订单项,就需要拿到添加订单的主键 id

设置 useGeneratedKeys 和 keyProperty 两个属性进行回绑

java 复制代码
 <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>

也不需要再去回查 brand 对象了,直接拿到原来构造的插入对象 brand.getId() 获取就可以了

java 复制代码
// 3. 获取代理对象
BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
brandMapper.add(brand);
Integer id = brand.getId();
System.out.println(id);

修改功能

修改全部字段:

将需要修改的对象作为入参,传入 id 就是 SQL 配置文件中的 where id = #{id},最终返回修改成功的行数

java 复制代码
int update(Brand brand);
java 复制代码
<update id="update" >
        update tb_brand
        set
            brand_name = #{brandName},
            company_name = #{companyName},
            ordered = #{ordered},
            description = #{description},
            status = #{status}
        where id = #{id}
    </update>
java 复制代码
  @Test
  public void testUpdate() throws IOException {
      // id
      int id = 6;
      int status = 1;
      String companyName = "阿里巴巴n";
      String brandName = "阿里巴巴";
      int ordered = 1;
      String description = "des牛逼牛逼";

      Brand brand = new Brand();
      // 直接将 id 填充进 brand
      brand.setId(id);
      brand.setStatus(status);
      brand.setCompanyName(companyName);
      brand.setBrandName(brandName);
      brand.setOrder(ordered);
      brand.setDescription(description);

      // 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. 获取代理对象
      BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
      // 会根据 id 去填充 SQL 语句
      int count = brandMapper.update(brand);
      System.out.println(count);

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

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

12、删除功能

删除一个

int deleteById(int id);

<delete id="deleteById">
    delete from tb_brand
    where id = #{id}
</delete>

@Test
public void testDeleteById() throws IOException {
    // id
    int id = 7;

    // 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. 获取代理对象
    BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
    int count = brandMapper.deleteById(id);
    System.out.println(count);

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

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

批量删除

delete from tb_brand

where id

in (?,?,?)

int deleteByIds(@Param("ids") int[] ids);
java 复制代码
<!--  mybatis 会将数组参数,封装为一个 Map 集合
 * 默认: key = array  value = 数组
 * 使用 @Param 注解改变 Map 集合的默认 key 的名称

-->
<delete id="deleteByIds">
    delete from tb_brand
    where id in
    <foreach collection="array" item = "id" separator="," open="(" close=")">
        #{id}
    </foreach>
</delete>

<delete id="deleteByIds">
    delete from tb_brand
    where id in
    <foreach collection="ids" item = "id" separator="," open="(" close=")">
        #{id}
    </foreach>
</delete>
java 复制代码
@Test
public void testDeleteByIds() throws IOException {
    // id
    int[] ids = {10};

    // 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. 获取代理对象
    BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
    int count = brandMapper.deleteByIds(ids);
    System.out.println(count);

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

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

13、参数传递

多个参数进行传递查询的时候

List<User> selectByCondition(String username, String password);

如果不通过 @Param 注解对其进行标注,直接使用类似下面的语句进行查询

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

会抛出如下异常:

Cause: org.apache.ibatis.binding.BindingException: Parameter 'username' not found. Available parameters are [arg1, arg0, param1, param2]

简单来说就是找不到传递入参的映射关系了

但是 SQL 配置文件查询语句改成如下就可以查询出来的东西

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

其主要原因在于多个参数传递的时候,Mabatis 在进行参数解析的时候,会将入参封装为 Map 集合,比如两个入参则会做如下操作

java 复制代码
map.put("arg0", 参数值1)
map.put("param1", 参数值1) 
map.put("arg1", 参数值2)
map.put("param2", 参数值2) 

源代码在 org.apache.ibatis.reflection.ParamNameResolver#getNamedParams 中

java 复制代码
public Object getNamedParams(Object[] args) {
    final int paramCount = names.size();
    if (args == null || paramCount == 0) {
      return null;
    } else if (!hasParamAnnotation && paramCount == 1) {
      Object value = args[names.firstKey()];
      return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
    } else {
      final Map<String, Object> param = new ParamMap<>();
      int i = 0;
      for (Map.Entry<Integer, String> entry : names.entrySet()) {
        // map.put("arg0", 参数值1)
        param.put(entry.getValue(), args[entry.getKey()]);
        // add generic param names (param1, param2, ...)
        final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
        // ensure not to overwrite parameter named with @Param
        if (!names.containsValue(genericParamName)) {
          // map.put("param1", 参数值1) 
          param.put(genericParamName, args[entry.getKey()]);
        }
        i++;
      }
      return param;
    }
  }

如果使用 @Param 注解

java 复制代码
List<User> selectByCondition(@Param("username") String username, String password);

arg0 会被替换为 username

java 复制代码
map.put("username", 参数值1)
map.put("param1", 参数值1) 
map.put("arg1", 参数值2)
map.put("param2", 参数值2) 
相关推荐
Q_19284999064 分钟前
基于Spring Boot的九州美食城商户一体化系统
java·spring boot·后端
张国荣家的弟弟22 分钟前
【Yonghong 企业日常问题 06】上传的文件不在白名单,修改allow.jar.digest属性添加允许上传的文件SH256值?
java·jar·bi
ZSYP-S33 分钟前
Day 15:Spring 框架基础
java·开发语言·数据结构·后端·spring
yuanbenshidiaos40 分钟前
C++----------函数的调用机制
java·c++·算法
是小崔啊1 小时前
开源轮子 - EasyExcel01(核心api)
java·开发语言·开源·excel·阿里巴巴
黄公子学安全1 小时前
Java的基础概念(一)
java·开发语言·python
liwulin05061 小时前
【JAVA】Tesseract-OCR截图屏幕指定区域识别0.4.2
java·开发语言·ocr
jackiendsc1 小时前
Java的垃圾回收机制介绍、工作原理、算法及分析调优
java·开发语言·算法
Yuan_o_1 小时前
Linux 基本使用和程序部署
java·linux·运维·服务器·数据库·后端
Oneforlove_twoforjob1 小时前
【Java基础面试题027】Java的StringBuilder是怎么实现的?
java·开发语言