MyBatis使用

MyBatis

面试题:mybatis与hibernate区别?

  • Hibernate 和 MyBatis 都是目前业界中主流的对象关系映射(ORM)框架

  • sql优化方面

    • Hibernate 使用 HQL(Hibernate Query Language)语句,独立于数据库。不需要编写大量的 SQL,就可以完全映射,但会多消耗性能,且开发人员不能自主的进行 SQL 性能优化。提供了日志、缓存、级联(级联比 MyBatis 强大)等特性。
    • MyBatis 需要手动编写 SQL,所以灵活多变。支持动态 SQL、处理列表、动态生成表名、支持存储过程。工作量相对较大。
  • 开发方面

    • MyBatis 是一个半自动映射的框架,因为 MyBatis 需要手动匹配 POJO 和 SQL 的映射关系。

      Hibernate 是一个全表映射的框架,只需提供 POJO 和映射关系即可。

  • 针对简单逻辑,Hibernate和MyBatis都有相应的代码生成工具,可以生成简单基本的DAO层方法。

    针对高级查询,Mybatis需要手动编写SQL语句,以及ResultMap。而Hibernate有良好的映射机制,开发者无需关心SQL的生成与结果映射,可以更专注于业务流程。

  • Hibernate 有更好的二级缓存机制,可以使用第三方缓存。MyBatis 本身提供的缓存机制不佳。

  • MyBatis 适合需求多变的互联网项目,Hibernate 适合需求明确、业务固定的项目。

1、Mybatis概述

——MyBatis 是一款优秀的持久层框架,用于简化 JDBC 开发,内部封装了 jdbc

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

  • 官网:https://mybatis.org/mybatis-3/zh/index.html

问题提出:

(1)什么是持久层?

  • JavaEE三层架构:表现层、业务层、持久层
  • 持久层就是负责操作数据库的那一层代码,以后开发我们会将操作数据库的Java代码作为持久层,而Mybatis就是对jdbc代码进行了封装。

(2)什么是框架?

  • 框架就是一个半成品软件,是一套可重用的、通用的、软件基础代码模型
  • 在框架的基础之上构建软件编写更加高效、规范、通用、可扩展
1.1、 JDBC 缺点

下面是 JDBC 代码,我们通过该代码分析都存在什么缺点:

在这里插入图片描述

  • 硬编码
    • 注册驱动、获取连接存在硬编码问题,如果换成别的关系型数据库,需要更改源代码参数。
  • 代码冗余
    • 业务操作和数据库操作混在一起,开发效率很低。
  • 操作繁琐
    • 需要我们手动设置参数

    • ResultSet 不能帮我们完成数据库和实体对象的自动转换,我们还要手动赋值。

1.2、Mybatis 优化
  • MyBatis内部封装了 jdbc,开发人员只需要关注 sql 语句,不需要处理加载驱动、创建连接等繁琐的过程(配置xml文件,交给mybatis处理即可)。

  • MyBatis 通过 xml 或 注解 两种方式配置 sql 语句,然后执行 sql 并将结果映射为 java 对象并返回。

2、Mapper代理开发(重点掌握)

需求:查询user表中所有的数据

步骤1:创建user表,添加数据
create database mybatisTest collate utf8_general_ci character set utf8;
use mybatisTest;

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', '男', '西安');
步骤2:创建User实体类

——一般根据数据库创建

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private int id;
    private String username;
    private String password;
    private String gender;
    private String addr;
}
步骤3:创建UserMapper接口,并编写方法
  • com.itheima.mapper 包下创建 UserMapper接口,代码如下:

    public interface UserMapper {
        //查询所有用户数据
        List<User> selectAll();
    }
    
步骤4:创建模块,导入坐标
<dependencies>
    <!--mybatis 依赖-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.5</version>
    </dependency>

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

    <!--junit 单元测试-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13</version>
        <scope>test</scope>
    </dependency>
    
    <!--mybatis分页插件-->
    <dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper</artifactId>
        <version>5.2.0</version>
    </dependency>
 
    <!--解决控制台乱码-->
    <dependency>
        <groupId>org.jboss</groupId>
        <artifactId>jboss-vfs</artifactId>
        <version>3.2.16.Final</version>
    </dependency>

    <!-- 添加这一层依赖可以在控制台打印sql语句执行信息-->
    <!-- 添加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>
    
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.12</version>
    </dependency>
</dependencies>
步骤5:编写 MyBatis 核心配置文件

在模块下的 resources 目录下创建mybatis的配置文件 mybatis-config.xml

<?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>
    <settings>
        <!--是否开启全局二级缓存-->
        <setting name="cacheEnabled" value="true"/>
        <!-- 是否在控制台打印日志 -->
        <setting name="logImpl" value="STDOUT_LOGGING" />
    </settings>

    <!--给全路径名起一个简单的别名,一般是实体类的类名,简化映射文件的resultType编写-->
    <typeAliases>
        <package name="com.haoyu.pojo"/>
    </typeAliases>

    <plugins>
        <!--分页插件-->
        <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
    </plugins>

    <!--
    environments:配置数据库连接环境信息。可以配置多个environment,通过default属性切换不同的environment
    -->
    <environments default="development">
        <!-- 配置 mysql 的环境-->
        <environment id="development">
            <!--事务的类型,JDBC类型-->
            <transactionManager type="JDBC"/>
            <!-- 配置数据源 POOLED:表示使用连接池 -->
            <dataSource type="POOLED">
                <!--数据库连接信息-->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url"
                          value="jdbc:mysql:///mybatisTest?useSSL=false&amp;useServerPrepStmts=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="232104"/>
            </dataSource>
        </environment>
    </environments>


    <!-- 指定映射配置文件的位置-->
    <mappers>
        <!--Mapper代理模式加载sql映射文件-->
        <package name="com.haoyu.mapper"/>
    </mappers>


</configuration>
步骤6:编写 SQL 映射文件(统一管理sql语句)
  • 规定:文件名称要和接口保持一致,Mapper接口需要和SQL映射文件放置在同一目录下。(userMapper接口对应userMapper.xml)
    在这里插入图片描述

  • 一般来说一个表对应一个xml

在模块的 resources 目录下创建映射配置文件 UserMapper.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:名称空间,必须是对应接口的全限定名
    id:唯一标识,在 Mapper 接口中定义方法,方法名就是SQL映射文件中sql语句的id,并保持参数类型和返回值类型一致
    resultType:返回结果的类型
 -->
<mapper namespace="com.haoyu.mapper.UserMapper">
    <select id="selectAll" resultType="User">
        select * from tb_user;
    </select>
</mapper>

特别注意:

1、因为mybatis核心配置文件取了别名,resultType可以直接简写成resultType=“User”>,如果未取应该resultType=“com.itheima.pojo.User”>

2、resultType一般值为实体类类型,如果方法没有返回值可以不写。(语句的返回结果就是以这个为准)

步骤7:创建UserMapper接口,并编写方法
  • com.itheima.mapper 包下创建 UserMapper接口,代码如下:

    public interface UserMapper {
        //查询所有用户数据
        List<User> selectAll();
    }
    
步骤8:创建测试类
/**
 * 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
        //3.1 获取UserMapper接口的代理对象
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = userMapper.selectAll();
        for (User user : users) {
            System.out.println(user.toString());
        }
        
        //4. 释放资源
        sqlSession.close();
    }
}
代码优化:抽取工具类

——因为获取sqlSessionFactory的代码可以重复利用,抽取出来当作工具类

public class SqlSessionFactoryUtils {

    private static SqlSessionFactory sqlSessionFactory;

    static {
        //静态代码块会随着类的加载而自动执行,且只执行一次
        try {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static SqlSessionFactory getSqlSessionFactory(){
        return sqlSessionFactory;
    }
}

优化步骤8

public class MybatisDemo {
    //通过工具类方法加载mybatis的核心配置文件,获取SqlSessionFactory
    public static SqlSessionFactory factory= SqlSessionFactoryUtils.getSqlSessionFactory();

    public static void main(String[] args) throws IOException {

        //获取SqlSession对象,用它来执行sql
        SqlSession sqlSession = factory.openSession();
        
        //获取UserMapper接口的代理对象
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        //执行sql
        List<User> users = userMapper.selectAll();

        for (User user : users) {
            System.out.println(user.toString());
        }
        
        //释放资源
        sqlSession.close();
    }
}

扩展:SqlSessionFactory的作用(重点)

——SqlSessionFactory是MyBatis中的一个工厂类,用于创建SqlSession对象。它是MyBatis框架中最重要的对象之一,负责管理MyBatis的配置信息和映射信息,并根据这些信息创建SqlSession对象,进而与数据库进行交互。

SqlSessionFactory的创建和使用流程一般如下所示:

  • 读取MyBatis的配置文件:SqlSessionFactory从MyBatis的配置文件中读取配置信息。
  • 创建数据库连接池:SqlSessionFactory根据配置信息创建数据库连接池,以提高数据库操作的性能。
  • 创建SqlSessionFactory对象:SqlSessionFactory根据配置信息创建SqlSessionFactory对象,用于创建SqlSession对象。
  • 创建SqlSession对象:应用程序通过SqlSessionFactory创建SqlSession对象,用于执行SQL语句、提交事务等操作。
  • 执行数据库操作:通过SqlSession对象执行数据库操作,如查询、插入、更新、删除等。
  • 提交事务或回滚事务:如果应用程序需要提交事务或回滚事务,则调用SqlSession的commit或rollback方法。
  • 关闭SqlSession:操作完成后,应用程序需要调用SqlSession的close方法关闭SqlSession对象,释放数据库连接等资源。

扩展:关于mybatis的事务控制

SqlSession sqlSession = sqlSessionFactory.openSession(false);  //关闭自动提交事务
try {
    // 执行数据库操作
    sqlSession.insert("insertUser", user);
    sqlSession.update("updateUser", user);
    
    // 提交事务
    sqlSession.commit();
} catch (Exception e) {
    // 回滚事务
    sqlSession.rollback();
} finally {
    // 关闭 SqlSession
    sqlSession.close();
}

3、使用Mapper代理要求

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

在这里插入图片描述

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

    在这里插入图片描述

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

    在这里插入图片描述

4、Mybatis深入了解

扩展:安装 MyBatisX 插件

在这里插入图片描述

功能:

  • 红色头绳的表示映射配置文件,蓝色头绳的表示mapper接口,点击小鸟可以实现相互跳转。
  • 自动生成statement

在这里插入图片描述

4.1、实现CRUD操作(重点)

——特别注意:涉及到增删改操作时,一定要提交事务。

4.1.1、数据准备

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);

实体类Brand

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

4.1.2、数据库字段名与实体类不匹配问题

——因为mybatis底层获取数据后,会通过反射创建对应对象(如Brand)并封装数据,如果数据库字段名与实体类不匹配会导致封装失败,从而导致不对应数据为null。

在这里插入图片描述

在这里插入图片描述

方式1:使用SQL片段
  • 将需要复用的SQL片段抽取到 sql 标签中

    <sql id="brand_column">
    	id, brand_name as brandName, company_name as companyName, ordered, description, status
    </sql>
    

    id属性值是唯一标识,引用时也是通过该值进行引用。

  • 在原sql语句中进行引用

    使用 include 标签引用上述的 SQL 片段,而 refid 指定上述 SQL 片段的id值。

    <select id="selectAll" resultType="brand">
        select
        <include refid="brand_column" />
        from tb_brand;
    </select>
    
方式2:使用resultMap解决(推荐使用)
  • 在映射配置文件中使用resultMap定义 字段 和 属性 的映射关系

    案例:

    <mapper namespace="com.haoyu.mapper.BrandMapper">
        <!-- type表示返回值类型 -->
        <resultMap id="brandResultMap" type="brand">
            <!--
                    id:完成主键字段的映射
                        column:表的列名
                        property:实体类的属性名
                    result:完成一般字段的映射
                        column:表的列名
                        property:实体类的属性名
             -->  
        <!--也就是告诉mybatis将查询到的brand_name的值赋值给 反射创建的对象Brand的属性brandName -->
            <result column="brand_name" property="brandName"/>  
            <result column="company_name" property="companyName"/>
        </resultMap>
    
        <select id="selectAll" resultMap="brandResultMap"> //特别注意:这个时候应该使用resultMap
            select * from tb_brand;
        </select>
    </mapper>
    
    • 配置后SQL语句正常编写即可
    • 特别注意:这个时候resultType=“Brand"应该改为resultMap=” resultMap的id名 ">

4.1.3、提供id查询数据

1、编写接口方法
//根据Id查询
Brand selectById(int id);

2、编写SQL语句
<select id="selectById"  resultMap="brandResultMap">
    select *
    from tb_brand where id = #{id};
</select>
//注意:因为使用了resultMap映射关系,需使用'resultMap'而不是使用'resultType'
4.1.3.1、参数占位符

mybatis提供了两种参数占位符:

  • #{} :执行SQL时,会将 #{} 占位符替换为?,将来自动设置参数值,底层使用的是PreparedStatement
  • ${}:直接将数据拼接到SQL语句中,有注入风险,不推荐使用。
4.1.3.2、parameterType使用

对于有参数的mapper接口方法,我们在映射配置文件中应该配置 ParameterType指定参数类型。只不过该属性都可以省略

<select id="selectById" parameterType="int" resultMap="brandResultMap">
    select *
    from tb_brand where id = ${id};
</select>
4.1.3.3、SQL语句中特殊字段处理

在这里插入图片描述

在这里插入图片描述

4.1.4、多条件查询(重点)

在这里插入图片描述

步骤1:编写接口方法

而该功能有三个参数,我们就需要考虑定义接口时,参数应该如何定义。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);
    
步骤2:编写SQL语句
<select id="selectByCondition" resultMap="brandResultMap">
    select *
    from tb_brand
    where status = #{status}
    and company_name like #{companyName}
    and brand_name like #{brandName}
</select>

步骤3:编写测试方法

//通过工具类方法加载mybatis的核心配置文件,获取SqlSessionFactory
public static SqlSessionFactory factory= SqlSessionFactoryUtils.getSqlSessionFactory();

//多条件查询
@Test
public void testSelectByCondition() throws IOException {
    //接收参数
    int status = 1;
    String companyName = "华为";
    String brandName = "华为";

    //处理参数
    companyName = "%" + companyName + "%";  //因为是模糊查询,所以要作处理
    brandName = "%" + brandName + "%";

    //获取SqlSession对象
    SqlSession sqlSession = factory.openSession();
    //获取Mapper接口的代理对象
    BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);

    //执行方法
    方式一 :接口方法参数使用 @Param 方式调用的方法
    List<Brand> brands = brandMapper.selectByCondition(status, companyName, brandName);
    
    方式二 :接口方法参数是 实体类对象 方式调用的方法
    //封装对象
    Brand brand = new Brand();
        brand.setStatus(status);
        brand.setCompanyName(companyName);
        brand.setBrandName(brandName);
    List<Brand> brands = brandMapper.selectByCondition(brand);

    方式三 :接口方法参数是 map集合对象 方式调用的方法
    Map map = new HashMap();
    map.put("status" , status);
    map.put("companyName", companyName);
    map.put("brandName" , brandName);
    List<Brand> brands = brandMapper.selectByCondition(map);
    System.out.println(brands);

    //5. 释放资源
    sqlSession.close();
}
扩展:@Param注解的作用

——给参数命名。

案例:

mapper中的方法

public User selectUser(@Param("userName") String name,@Param("password") String pwd);

映射到xml中的标签

<select id="selectUser" resultMap="User">  
   select * from user  where user_name = #{userName} and user_password=#{password}  
</select>
  • 其中where user_name = #{userName} and user_password = #{password} 中的userName和password都是从注解@Param()里面取出来的,取出来的值就是方法中形式参数 String name 和 String pwd的值。`

  • 也就是说,SQL语句占位符中名称要与@Param注解命名保持一致。

4.1.4.1、动态SQL(重点)
情况1:多个条件

——需要使用 if和where 标签实现

上述功能实现存在很大的问题:用户在输入条件时,可能输入条件不会都填写,这个时候SQL语句就不能那么填写:

用户只输入当前状态时,SQL语句就是:

select * from tb_brand where status = #{status}

而用户如果只输入企业名称时,SQL语句就是:

select * from tb_brand where company_name like #{companName}

而用户如果输入了 当前状态企业名称 时,SQL语句又不一样:

select * from tb_brand where status = #{status} and company_name like #{companName}

针对上述的需要,Mybatis对动态SQL有很强大的支撑:

  • if

  • choose (when, otherwise)

  • trim (where, set)

  • foreach

  • if 标签:条件判断

    • test 属性:逻辑表达式
  • where 标签

    • 作用:
      • 替换where关键字
      • 会动态的去掉第一个条件前的 and
      • 如果所有的参数没有值则不加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>
    

    注意:if标签与where标签结合使用时,需要给每个条件前都加上 and 关键字。

如上的这种SQL语句就会根据传递的参数值进行动态的拼接,如果此时status和companyName有值那么就会值拼接这两个条件。

在这里插入图片描述

  • choose标签
    • 作用:相当于if,不过可以配合 when标签 和 otherwrise标签(相当于else)使用。

例子:

<choose>
<when test="status != null">
    and status = #{status}
</when>
<otherwrise></otherwrise>
</choose>

//相当于java的
if(test="status != null"){
    and status = #{status}
}else{

}
情况2:单个条件

——需要使用choose(when,otherwise)标签实现

在这里插入图片描述

如上图所示,在查询时只能选择 品牌名称当前状态企业名称 这三个条件中的一个,但是用户到底选择哪儿一个,我们并不能确定。这种就属于单个条件的动态SQL语句。

编写接口方法

//单条件查询
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 != '' "><!--相当于case-->
                company_name like #{companyName}
            </when>
            <when test="brandName != null and brandName != ''"><!--相当于case-->
                brand_name like #{brandName}
            </when>
        </choose>
    </where>
</select>

编写测试方法

test/java 下的 com.itheima.mapper 包下的 MybatisTest类中 定义测试方法

//通过工具类方法加载mybatis的核心配置文件,获取SqlSessionFactory
public static SqlSessionFactory factory= SqlSessionFactoryUtils.getSqlSessionFactory();

@Test
public void testSelectByConditionSingle() throws IOException {
    //接收参数
    String companyName = "华为";

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

    //封装对象
    Brand brand = new Brand();
    brand.setCompanyName(companyName);  //只有单一条件

    //获取SqlSession对象
    SqlSession sqlSession = factory.openSession();
    //获取Mapper接口的代理对象
    BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
    //4. 执行方法
    List<Brand> brands = brandMapper.selectByConditionSingle(brand);
    System.out.println(brands);

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

执行测试方法结果如下:

在这里插入图片描述

4.1.5、添加数据

编写接口方法

//添加数据
void add(Brand brand);

编写SQL语句

——因为id一般是自增,所以不用添加

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

编写测试方法

//通过工具类方法加载mybatis的核心配置文件,获取SqlSessionFactory
public static SqlSessionFactory factory= SqlSessionFactoryUtils.getSqlSessionFactory();

@Test
public void testAdd() throws IOException {
    //接收数据并封装对象
    Brand brand = new Brand("豪与她","豪与有限公司",20,"这是一家无敌的公司",1);
    
    //获取SqlSession对象
    SqlSession sqlSession = factory.openSession(true);  //设置自动提交事务,这种情况不需要手动提交事务了
    //获取Mapper接口的代理对象
    BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
    //执行方法
    brandMapper.add(brand);
    
    //释放资源
    sqlSession.close();
    }
}

在这里插入图片描述

4.1.5.1、主键返回

——有时候我们添加完数据后会使用到其主键。

编写接口方法

//添加数据
int add(Brand brand);

编写SQL语句

在 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>

这个时候执行完方法后brand对象中就有其id值了

brandMapper.addData(brand);
int id=brand.getId();

4.1.6、修改数据

在前端页面修改数据时,如果输入框没有输入内容时,应该保留字段之前的值。

编写接口方法

//修改数据
void update(Brand brand);

编写SQL语句

<update id="update">
    update tb_brand
    <set>
        <trim suffixOverrides=",">   <!--用于去除多余的","-->
        <if test="brandName != null and brandName != ''">
            brand_name = #{brandName},
        </if>
        <if test="companyName != null and companyName != ''">
            company_name = #{companyName},
        </if>
        <if test="ordered != null">
            ordered = #{ordered},
        </if>
        <if test="description != null and description != ''">
            description = #{description},
        </if>
        <if test="status != null">
            status = #{status}
        </if>
        </trim>
    </set>
    where id = #{id}
</update>

编写测试方法

//通过工具类方法加载mybatis的核心配置文件,获取SqlSessionFactory
public static SqlSessionFactory factory= SqlSessionFactoryUtils.getSqlSessionFactory();

@Test
public void testUpdate(){
    int id=4;

    //封装对象
    Brand brand = new Brand(null,null,10,"这是一家无敌的公司",2);
    brand.setId(id);
    //获取SqlSession对象
    SqlSession sqlSession = factory.openSession(true);
    //获取Mapper接口的代理对象
    BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
    //4. 执行方法
    brandMapper.updateDnata(brand);

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

4.1.7、批量删除数据

——主要使用的是foreach标签

foreach标签的主要属性:

  • collection:
    • collection=“list” 传入的参数是一个list集合
    • collection=“array” 传入的参数是一个array集合
    • collection=“map” 传入的参数是一个map集合
  • **item:**循环的临时变量,相当于foreach遍历的临时变量名
  • **separator:**每次循环的分隔符
  • **open:**整个循环内容开头的字符串。
  • **close:**整个循环内容结尾的字符串。

编写接口方法

void delete(Integer[] ids);

编写SQL语句

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

注意:这条语句就相当于 delete from tb_brand where id in( , , , , );

编写测试方法

 @Test
    public void testDelete(){
        Integer[] ids={1,2,3};

        //获取SqlSession对象
        SqlSession sqlSession = factory.openSession(true);
        //获取Mapper接口的代理对象
        BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
        //当有数据时才调用方法,避免无数据而出错
        if(ids.length>0)   brandMapper.delete(ids);
        sqlSession.close();
    }

5、使用注解开发

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

Mybatis 针对 CURD 操作都提供了对应的注解:

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

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

@Select("select * from tb_brand where id=#{id}")
Brand selectById(int id);

测试方法和之前一样,直接使用即可。

总结:注解用于完成简单的SQL语句,对于复杂的操作,最好用xml来映射语句

6、mybatis分页操作

6.1、使用mybatis分页插件

添加依赖:

<!--mybatis分页插件-->
<dependency>
	<groupId>com.github.pagehelper</groupId>
	<artifactId>pagehelper</artifactId>
	<version>5.2.0</version>
</dependency>
 
<!--解决控制台乱码-->
<dependency>
    <groupId>org.jboss</groupId>
    <artifactId>jboss-vfs</artifactId>
    <version>3.2.16.Final</version>
</dependency>

在MyBatis的核心配置文件中配置插件:

<plugins>
    <plugin interceptor="com.github.pagehelper.PageHelper">
    </plugin>
</plugins>

在查询功能之前使用PageHelper.startPage(int pageNum,int pageSize)开启分页功能:

pageNum:当前页的页码,从1开始

pageSize:每页显示的条数

@Test
public void selectAll() {
    //获取SqlSession对象,用它来执行sql
    SqlSession sqlSession = factory.openSession();
    //获取UserMapper接口的代理对象
    BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);

    //开启分页拦截器,拦截器会自动在sql后面追加limit
    PageHelper.startPage(1, 3);
    //执行SQL方法
    List<Brand> brands = brandMapper.selectAll();
    brands.forEach(brand -> System.out.println(brand));
    
    //获取分页相关数据
    PageInfo<Brand> page = new PageInfo<>(brands);
    System.out.println(page.getPageNum());  //获取当前页码
    System.out.println(page.getSize());  //获取每页显示的条数

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

在查询获取list集合之后,使用PageInfo pageInfo = new PageInfo<>(list list,int navigatePages)获取分页相关数据.

list:分页之后的数据

navigatePages:导航分页的页码数

常用数据:

pageNum:当前页的页码

pageSize:每页显示的条数

size:当前页显示的真实条数

total:总记录数

pages:总页数

prePage:上一页的页码

nextPage:下一页的页码

isFirstPage/isLastPage:是否为第一页/最后一页

hasPreviousPage/hasNextPage:是否存在上一页/下一页

6.2、SpringBoot使用分页

  • 导入坐标

    <!--PageHelper坐标-->
    <dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper-spring-boot-starter</artifactId>
        <version>1.2.5</version>
    </dependency>
    
  • 编写代码

    //分页查询文章
    @Override
    public Result<PageBean<Article>> list(Integer pageNum, Integer pageSize,
                                          Integer categoryId, String state) {
        //创建PageBean对象
        PageBean<Article> pageBean=new PageBean<>();
    
        //开启分页拦截器,拦截器会自动在sql后面追加limit
        PageHelper.startPage(pageNum,pageSize);
    
        //调用mapper
        Map<String, Object> map=ThreadLocalUtil.get();
        Integer userId = (Integer) map.get("id");
        List<Article> lists= articleMapper.list(userId,categoryId,state);
        //Page中提供了方法,可以获取PageHelper分页查询后,得到的总记录条数和当前页数据
        Page<Article> page= (Page<Article>) lists;
    
        pageBean.setTotal(page.getTotal());
        pageBean.setItems(lists);
    
        return Result.success(pageBean);
    }
    

7、mybatis完成复杂查询(重点)

7.1、数据准备

#创建班级表
create table class
(
class_id int not null PRIMARY KEY AUTO_INCREMENT ,
class_name VARCHAR(15) not null
);

#创建学生表
create table student
(
stu_id int not null PRIMARY KEY AUTO_INCREMENT,
stu_name VARCHAR(15) not null,
class_id int not null
);
#创建一些数据
insert into class VALUES (1,'101班');
insert into class VALUES (2,'102班');
insert into student VALUES(1,'haoyu',1);
insert into student VALUES(2,'sijia',1);
insert into student VALUES(3,'tanlin',2);
insert into student VALUES(4,'xiaocui',2);

创建实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class StuClass {
    private Integer id;
    private String  name;
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
    private Integer id;
    private String name;
    private Integer classId;
}

7.2、多对一(association)

——多个学生对应一个教室,多对一情况下,在Stdent类添加ClassRoom属性

public class Student {
    private Integer id;
    private String name;
    private Integer classId;
    //添加一方对象属性
    private StuClass stuClass;
}

定义StudentMapper接口

public interface StudentMapper {
    List<Student> selectAllStudent();
}

编写xml方法

方式一:

在StudentMapper.xml编写

<mapper namespace="com.haoyu.mapper.StudentMapper">

    <select id="selectAllStudent" resultMap="selectAllRes">
    select * from student s left join class c on s.class_id=c.class_id
    </select>

    <resultMap id="selectAllRes" type="Student">
        <id column="stu_id" property="id"/>
        <result column="stu_name" property="name"/>
        <result column="class_id" property="classId"/>
        
        <!-- property表示实体类的属性名,javaType表示属性的类型 -->
        <association property="stuClass" javaType="StuClass">
            <id column="class_id" property="id"/>
            <result column="class_name" property="name"/>
        </association>
    </resultMap>
        
</mapper>

缺点:需要手动配置所有字段映射,不然会为null,比较适用于字段较少的查询。

结果:

在这里插入图片描述

方式二:

在StudentMapper.xml编写

<mapper namespace="com.haoyu.mapper.StudentMapper">
    
    <select id="selectAllStudent" resultMap="selectAllRes">
          select * from student;
    </select>

    <resultMap id="selectAllRes" type="Student">
                <id column="stu_id" property="id"/>
                <result column="stu_name" property="name"/>
                <result column="class_id" property="classId"/>
 <!-- 查询到学生信息时,会用column的值传递给select指定的查询selectClass,selectClass查询到的结果会赋值给property指定的成员变量 -->
        <association property="stuClass" column="class_id"  
        <!-- 使用外部的语句-->
        select="com.haoyu.mapper.ClassMapper.selectClass"></association>
    </resultMap>
        
</mapper>

在ClassMapper.xml编写

<mapper namespace="com.haoyu.mapper.ClassMapper">
    
    <select id="selectClass" resultMap="selectClassRes">
          select * from class where class_id=#{class_id}
    </select>

    <resultMap id="selectClassRes" type="StuClass">
        <id column="class_id" property="id"/>
        <result column="class_name" property="name"/>
    </resultMap>
        
</mapper>

结果:

在这里插入图片描述

7.2、一对多(collection)

——一个教师有多个学生,一对多情况下,在StuClass类添加List属性

public class StuClass {
    private Integer id;
    private String  name;
    
    //添加多方集合对象属性
    private List<Student> students;
}

编写接口方法

public interface StudentMapper {
    List<StuClass> selectAllClass();
}

编写xml方法

方式一:

在ClassMapper.xml编写

<mapper namespace="com.haoyu.mapper.ClassMapper">
    
<select id="selectAllClass" resultMap="selectAllClassRes">
        select * from class c left join student s on s.class_id=c.class_id
 </select>

 <resultMap id="selectAllClassRes" type="StuClass">
     <id column="class_id" property="id"/>
     <result column="class_name" property="name"/>
        <collection property="students" ofType="Student">
           <id column="stu_id" property="id"/>
           <result column="stu_name" property="name"/>
           <result column="class_id" property="classId"/>
        </collection>
 </resultMap>
            
</mapper>

结果:

在这里插入图片描述

方式二:

在ClassMapper.xml编写

<mapper namespace="com.haoyu.mapper.ClassMapper">
    
<select id="selectAllClass" resultMap="selectAllClassRes">
     select * from class
</select>

<resultMap id="selectAllClassRes" type="StuClass">
     <id column="class_id" property="id"/>
     <result column="class_name" property="name"/>
     <collection property="students" column="class_id" select="com.haoyu.mapper.StudentMapper.selectAllStu"></collection>
</resultMap>
         
</mapper>

在StudentMapper.xml编写

<mapper namespace="com.haoyu.mapper.StudentMapper">
  
<select id="selectAllStu" resultMap="selectAllStuRes">
     select * from student where class_id = #{class_id}
</select>

<resultMap id="selectAllStuRes" type="Student">
      <id column="stu_id" property="id"/>
      <result column="stu_name" property="name"/>
      <result column="class_id" property="classId"/>
</resultMap>
    
</mapper>

7.3、模糊查询

编写接口方法

List<Student> selectStu(String str);

编写xml方法

<select id="selectStu" resultMap="selectAllStuRes">
    select * from t_user where username like '%${username}%';
    select * from student where stu_name like "%"#{str}"%"
    select * from student where stu_name like concat('%',concat(#{str},'%'))
</select>

扩展:mybatis日期比较

编写接口方法

List<New> selectByDate(@Param("startTime") String startTime, @Param("endTime") String endTime);

xml语句格式

SELECT * FROM NEWS WHERE <![CDATA[ create_time >= #{startTime} AND create_time < #{endTime} ]]>

8、mybatis缓存

——缓存是指将数据保存在内存中,读取时无需再从磁盘读入,因此比较快。

——mybatis为减轻数据库压力,提高数据库性能,提供了两级缓存机制:

——https://blog.csdn.net/weixin_45486926/article/details/124883009

8.1、一级缓存

  • mybatis默认开启,SqlSession级别的缓存(只在同一个SqlSession内有效)
    • SqlSession一旦刷新或关闭,都会清除一级缓存。
  • 底层基于HashMap
    • 在操作数据库时需要构造sqlSession对象,在对象中有一个HashMap本地缓存数据结构,用于缓存数据。不同的SQL会话(sqlSession)之间的缓存数据区域(HashMap)是互不影响的。

工作流程:

  • 第一次查询用户id为1的用户信息,先去缓存中查询是否有id为1的用户信息,如果没有,从数据库中查询用户信息。得到用户信息后再将用户信息储存到一级缓存中。

  • 第二次查询用户id为1的用户信息,先去缓存中查询是否有id为1的用户信息,如缓存中有,直接从缓存中获取。

    案例:

    @Test
    public void test(){
        SqlSession sqlSession = ssf.openSession();
        IDeptMapper mapper = sqlSession.getMapper(IDeptMapper.class);
        //第一次查询id=10的数据
        Dept dept = mapper.selectById(10);
        System.out.println(dept);
        //第二次查询id=10的数据
        Dept dept2 = mapper.selectById(10); //不要求调用同一个方法,只要最终执行的SQL语句一致就会触发二级缓存
        System.out.println(dept2);
    }
    

    在这里插入图片描述

  • 注意:两次查询须在同一个sqlsession中完成,否则将不会走mybatis的一级缓存。在mybatis与spring进行整合开发时,事务控制在service中进行,重复调用两次servcie将不会走一级缓存,因为在第二次调用时session方法结束,SqlSession就关闭了。

8.2、二级缓存

  • 需要手动开启,mapper级别的缓存(同一个namespace公用这一个缓存),多个SQL会话(sqlSession)共享。
    • 多个sqlSession去操作同一个Mapper的sql语句,多个sqlSession可以共用二级缓存,二级缓存是跨sqlSession的。
  • 缓存机制与一级缓存一致。

工作流程:

  • 当一个sqlseesion执行了一次select后,并关闭此session的时候,就会将查询结果存储到二级缓存中。
  • 当另一个sqlsession执行相同select时,首先会查询二级缓存,二级缓存中无对应数据,再去查询一级缓存,一级缓存中也没有,最后去数据库查找,从而减少了数据库压力提高了性能。

开启步骤:

  • 通过application.yml配置二级缓存开启

    # mybatis相关配置
    mybatis:
      configuration:
          log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
          #开启MyBatis的二级缓存
          cache-enabled: true
    

    或通过MyBatis配置文件开启二级缓存

    <setting name="cacheEnabled" value="true"/>
    
  • 在 xxxMapper.xml 文件中添加

    //添加在外面,全局开启
    <cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"/>
    //将标签属性useCache="true",对当前语句开启
    
  • 在需要缓存的对象实现Serializable接口(注意,根据SQL语句查询字段所出现的对象都要继承接口),sqlSession关闭后才会缓存(便于一次性将所有的查询结果进行缓存)

    案例:

    @Test
    public void test2(){
        //开启SQL会话
        SqlSession sqlSession1 = ssf.openSession();
        IDeptMapper mapper1 = sqlSession1.getMapper(IDeptMapper.class);
        Dept dept = mapper1.selectById(10);
        System.out.println(dept);
        sqlSession1.close();
    
        //开启一个新的SQL会话
        SqlSession sqlSession2 = ssf.openSession();
        IDeptMapper mapper2 = sqlSession2.getMapper(IDeptMapper.class);
        Dept dept2 = mapper2.selectById(10);  //在同一个方法内的多个sqlSession共享数据,并且必须调用同一个方法是二级缓存才有效。
        System.out.println(dept2);
        sqlSession2.close();
    }
    

    在这里插入图片描述

8.3、注意事项

  • 如果sqlSession执行了DML操作(insert、update、delete),并commit了,就会清空sqlSession中的一级缓存,保证缓存中始终保存的是最新的信息,避免脏读。

  • mybatis的缓存是基于 [ namespace:sql语句:参数 ] 来进行缓存的(SqlSession的HashMap存储缓存数据时,是使用

    [ namespace:sql:参数 ] 作为key,查询返回的语句作为value保存的)。

工作流程:

  • 当一个sqlseesion执行了一次select后,并关闭此session的时候,就会将查询结果存储到二级缓存中。
  • 当另一个sqlsession执行相同select时,首先会查询二级缓存,二级缓存中无对应数据,再去查询一级缓存,一级缓存中也没有,最后去数据库查找,从而减少了数据库压力提高了性能。

开启步骤:

  • 通过application.yml配置二级缓存开启

    # mybatis相关配置
    mybatis:
      configuration:
          log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
          #开启MyBatis的二级缓存
          cache-enabled: true
    

    或通过MyBatis配置文件开启二级缓存

    <setting name="cacheEnabled" value="true"/>
    
  • 在 xxxMapper.xml 文件中添加

    //添加在外面,全局开启
    <cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"/>
    //将标签属性useCache="true",对当前语句开启
    
  • 在需要缓存的对象实现Serializable接口(注意,根据SQL语句查询字段所出现的对象都要继承接口),sqlSession关闭后才会缓存(便于一次性将所有的查询结果进行缓存)

    案例:

    @Test
    public void test2(){
        //开启SQL会话
        SqlSession sqlSession1 = ssf.openSession();
        IDeptMapper mapper1 = sqlSession1.getMapper(IDeptMapper.class);
        Dept dept = mapper1.selectById(10);
        System.out.println(dept);
        sqlSession1.close();
    
        //开启一个新的SQL会话
        SqlSession sqlSession2 = ssf.openSession();
        IDeptMapper mapper2 = sqlSession2.getMapper(IDeptMapper.class);
        Dept dept2 = mapper2.selectById(10);  //在同一个方法内的多个sqlSession共享数据,并且必须调用同一个方法是二级缓存才有效。
        System.out.println(dept2);
        sqlSession2.close();
    }
    
  • 41
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值