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) 
相关推荐
扎瓦11 分钟前
ThreadLocal 线程变量
java·后端
BillKu30 分钟前
Java后端检查空条件查询
java·开发语言
jackson凌35 分钟前
【Java学习笔记】String类(重点)
java·笔记·学习
刘白Live1 小时前
【Java】谈一谈浅克隆和深克隆
java
一线大码1 小时前
项目中怎么确定线程池的大小
java·后端
要加油哦~1 小时前
vue · 插槽 | $slots:访问所有命名插槽内容 | 插槽的使用:子组件和父组件如何书写?
java·前端·javascript
crud1 小时前
Spring Boot 3 整合 Swagger:打造现代化 API 文档系统(附完整代码 + 高级配置 + 最佳实践)
java·spring boot·swagger
天天摸鱼的java工程师1 小时前
从被测试小姐姐追着怼到运维小哥点赞:我在项目管理系统的 MySQL 优化实战
java·后端·mysql
周某某~1 小时前
四.抽象工厂模式
java·设计模式·抽象工厂模式
异常君2 小时前
高并发数据写入场景下 MySQL 的性能瓶颈与替代方案
java·mysql·性能优化