Mybatis3全篇学习笔记

2023年 10月 5日 72.7k 0

Mybatis简介

Mybatis3简介

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

快速上手Mybatis3

准备工作

  • 创建数据库
  • CREATE DATABASE `mybatis-example`;
    
    USE `mybatis-example`;
    
    CREATE TABLE `t_emp`(
      emp_id INT AUTO_INCREMENT,
      emp_name CHAR(100),
      emp_salary DOUBLE(10,5),
      PRIMARY KEY(emp_id)
    );
    
    INSERT INTO `t_emp`(emp_name,emp_salary) VALUES("tom",200.33);
    INSERT INTO `t_emp`(emp_name,emp_salary) VALUES("jerry",666.66);
    INSERT INTO `t_emp`(emp_name,emp_salary) VALUES("andy",777.77);
    
  • 导入依赖
  • 
        
            mysql
            mysql-connector-java
            8.0.25
        
        
            org.junit.jupiter
            junit-jupiter-api
            5.3.1
            test
        
        
            org.mybatis
            mybatis
            3.5.11
        
    
    

    Mybatis做了什么

    我们需要了解一下mybatis做了什么,这样才能更清楚的知道为什么要准备其他的一些东西。

    我们一共分三步完成持久层的各种操作。

    我们首先需要定义一个接口,在其中指定我们需要查询的信息,比如在上面的表中,我想要查询员工的姓名。那么我就需要在接口定义一个selectEmpName()的方法。

    好的,那么我们现在想要去查询这个信息就显然需要设计一个sql语句需要去查询,在mvc框架中加入mybatis,他会将sql语句从Java代码中抽离出来,放置在专门的xml文件中,我们称它为mapper。

    现在假设我们查询出来了想要的结果,一个员工姓名,我们显然可以用一个String去接收它。但设想一下,如果我们要查询的是员工的姓名信息和工资信息呢?又或者还有员工的id呢?我们就不能单纯的用一个String去接收了。mybatis提供了一种新的方式,用一个与emp表中列所对应的对象去接收查询到的信息。
    比如下面这个类,我们称他为pojo

    public class Employee {
    
        private Integer empId;
    
        private String empName;
    
        private Double empSalary;
    
        //此处省略 getter | setter | toString
    
    

    还有上面所说的一个接口以及它的方法

    public interface EmployeeMapper {
        Employee selectEmployee(Integer empId);
    }
    

    还有就是存放sql的xml文件。不过这个xml文件要做的有点多,他需要将接口中的方法与pojo相关联起来。简单来讲就是当接口中的方法被调用,他需要将sql执行的结果映射到pojo中。要把它写在classpath下面,因为src只识别java文件,我建议你建个文件夹存放它,因为可以预见,它将来肯定不止一个。

    
    
    
        
            select emp_name empName,emp_salary empSalary from t_emp where emp_id=#{id}
        
    
    

    让我们看一下其中的标签以及属性。

  • mapper标签,表示一个完整的接口实现类。
  • namespace属性精确定位到它需要实现的接口上
  • select标签声明了这个sql的类型。
  • id则指明了接口中的方法,表明这个查询sql是对该方法的重写
  • 而resultType则指明了返回值得类型(他指向了我们的pojo)
  • 配置mybatis

    别忘了配置数据库,当然mybatis也需要一些简单的配置我们在类路径下写一个配置文件。

    
    
    
    
      
      
        
        
          
          
          
          
            
            
            
            
            
          
        
      
    
      
        
        
        
        
        
      
    
    
    

    测试

    InputStream ism = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory build = new SqlSessionFactoryBuilder().build(ism);
    SqlSession sqlSession = build.openSession();
    EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
    Employee employee = mapper.selectEmployee(1);
    System.out.println(employee);
    sqlSession。close();
    

    这个测试可以说非常繁琐,

  • 首先要使用一个工具类把配置文件变成流,
  • 然后使用 SqlSessionFactoryBuilder()构建一个会话工厂,
  • 再从工厂中取出会话,
  • 使用会话工具放入接口,获得接口实现类
  • 调用方法传入参数执行sql。
  • 关闭流。
  • mybatis原理的讲解

    mybatis是在ibatis的基础上封装优化而来的。增加了使用mapper接口调用sql。
    对参数的传入做了优化。但底层还是调用了ibatis来执行sql

    我们使用#{}来拼接参数,不要使用#{}拼接,防止sql漏洞。

    我们将更详细的内容打印在控制台,监测sql的拼接与执行(将mybatis的日志实现设置为打印控制台)。

    mybatis-log.png

    
      
      
    
    

    mybatis传参

    1. 简单数据类型

    我们在接口中添加更多方法来演示

    Integer deleteEmployeeById(Integer empId);
    //返回值为复杂数据类型
    List getEmployeesBySalary(Double salary);
    //传入对象时
    Integer addOneEmployee(Employee employee);
    //传入多个简单参数
    Integer updateEmployeeSalaryById(Integer id,Double salary);
    

    处理方法如下

        //当返回值是固定的时,可以省略resultType
    
        delete * from t_emp where emp_id=#{id}
    
    
    
    //当返回类型是多个对象时,无需任何操作,resultType传pojo即可
    
        select emp_id empId,emp_name empName,emp_salary empSalary from t_emp where emp_salary>#{salary}
    
    
    //当传入的是对象时,则参数名必须为pojo的参数名(get方法的名字)
    
        insert into t_emp(emp_name,emp_salary) values(#{empName},#{empSalary});
    
    
    

    传入多个简单数据类型的处理方式有点复杂

    //传入参数为多个简单数据类型时,可以使用arg0 arg1...
    // 也可以使用 param1 param2 ...
    //但建议使用@param() 直接指定参数!!!
    
        update t_emp set emp_salary=#{arg1} where emp_id=#{arg0}
    
    
    @Param的用法
    Integer updateEmployeeSalaryById(@Param("id") Integer id, @Param("salary") Double salary);
    

    Map传参

    一般来讲不建议这么用,但是它可以代替pojo直接传参查询
    设置key为字段,value为值。

    //传入map做为多个简单数据类型时,设为map中的key即可
    
        update t_emp set emp_salary=#{map中的key} where emp_id=#{map中的key}
    
    
    

    设置返回值类型

    如果返回的是一个基本数据类型,比如String,Double,则全部用首字母小写,string,double,map,也可以写全类名。

    
        select emp_name from t_emp where emp_id=#{empId};
    
    

    @Alias注解

    如果返回对象,而又不想写全限定名可以用下面这个注解

    Alias.png

    开启驼峰命名映射

    如果我们的表的字段全部为XXX_XXX,而我们的pojo全部为驼峰命名。我们就可以配置一下,不写别名。

    
        select emp_id empId,
        emp_name empName,
        emp_salary empSalary from t_emp where emp_salary>#{salary}
    
    

    每一个都要写别名去对应pojo
    我们在配置文件中写入

    
    

    setting.png

    map接收任意参数

    
        select emp_name 最高工资员工姓名,emp_salary 最高员工工资,(select AVG(emp_salary) from t_emp) 平均工资
        from t_emp where
        emp_salary=(select MAX(emp_salary) from t_emp)
    
    

    当然也可以使用list接受上面的代码

    如何开启事务自动提交?

    InputStream ism = Resources.getResourceAsStream("mybatis-config.xml");
    
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(ism);
    
    //传入true表示开启自动提交
    SqlSession sqlSession = sqlSessionFactory.openSession(true);
    

    如何获取插入数据的主键?

    
        insert into t_emp(emp_name,emp_salary) values(#{empName},#{empSalary});
    
    
  • useGeneratedKeys="true" ------ 表示我们需要表自动生成的主键

  • keyColumn="emp_id" ----- 表示指定主键是谁

  • keyProperty="empId" 表示让mybatis直接将结果返回到我们用于插入数据的对象中。

  • mybatis维护UUID主键

    
        
            select replace(UUID(),'-','');
        
        insert into t_emp(emp_name,emp_salary) values(#{empName},#{empSalary});
    
    

    多表查询

    我们如何处理复杂的多表查询。如何设计与其相对应的pojo?多层嵌套关系的对象类型如何精准映射?

    多表查询的设计原则

    数据库表之间的关系无非分为3类

  • 一对一的数据表关系

  • 多对一,一对多的数据表关系

  • 多对多的数据表关系

  • 我们始终遵从以一张数据表为主的原则来设计。

    比如订单表与用户表的关系,从订单角度来看,多张订单对应一个客户,所以是多对1,而如果从客户角度看,则是一个客户对应多张表,是1对多的关系。

    当我们需要查询订单的id,订单的内容与订单对应的客户时,客户其实可以设计为订单表的单个对象属性几个。

    CREATE TABLE t_order(
     order_id INT(11) UNSIGNED,
     order_name VARCHAR(32),
     customer_id INT(11)
     ) CHARSET=utf8 ENGINE=INNODB;
     
     
     CREATE TABLE t_customer(
     customer_id INT(11) UNSIGNED,
     customer_name VARCHAR(32)) CHARSET=utf8 ENGINE=INNODB;
     
     INSERT INTO t_order(order_id,order_name) VALUES(1,'豆橛子订单')
     
    INSERT INTO t_customer(customer_id,customer_name) VALUES(1001,'靓仔')
    
    

    创建pojo

    public class Order {
       private Integer orderId;
       private String orderName;
    
       private Integer customerId;
    
       private Customer customer;
       /getter |  setter  |  toString
    }
    
    @Data
    public class Customer {
        private Integer customerId;
        private String customerName;
        private List orderList;
         /getter |  setter  |  toString
    }
    

    Mapper相关的配置。

    
    
       
       
       
    
       
           
           
       
    
    
    
    
       SELECT o.order_name, o.customer_id, c.customer_name
       FROM t_order o
       JOIN t_customer c
       ON o.customer_id = c.customer_id
       WHERE o.order_id = #{orderId}
    
    

    代码解析:

  • 对于查询语句的映射,我们使用resultMap来解决多表映射。
  • resultMap中type我们依然选择主表Order表的Pojo。
  • 主键我们使用id来映射 column选择表中的列,property选择pojo字段。
  • 对于对象类型我们选择association,指定他的pojo为JavaType
  • 一对多查询的xml写法

    
    
    
    
        
    
            
    
            
    
            
                
                
            
    
        
    
        
            select  ctm.customer_id,ctm.customer_name,order.order_id,order.order_name
            from  t_customer AS ctm JOIN t_order AS `order`
            ON ctm.customer_id=order.customer_id
            where ctm.customer_id=#{id}
        
    
    

    他们之间的区别在于2个标签

  • 不同于单个对象的写法,我们使用collection来接收多个对象(List)类型
  • 不同于单个对象的写法,我们使用ofType指定对象的pojo
  • 多表映射的优化

    在上面的代码中我们发现,我们无法对多层级的属性进行自动映射。比如List集合中的属性。我们需要手写映射关系。其实mybatis可以根据驼峰原则,进行自动映射(浅层和深层都能实现自动完成)。我们需要额外开启相关设置。

    autoMapping.png

    
        
        
        
        
    
    

    开启后,我们就不需要写一部分的映射了

        
    
    
    
    
    
            
                
    
            
    

    但是注意,关于主键我们仍需要定义

    自定义多条件查询

    在实际开发中,我们常常遇到需要自定义的多条件查询。比如电商平台的购物筛选功能,用户可能添加附加条件查询,就像下面这种情况。如果我们给每条查询都设置对应的sql语句,这显然不现实。

    tb.png

    那么如何解决这种问题呢?

    mybatis为我们提供了新的标签:标签与标签。
    比如下面这个根据姓名和工资查找员工的请求。

    List getEmpByNameAndSalary(@Param("name") String name,@Param("salary") Double salary);
    

    如果上面的name和salar可以可能传也可能不传,那我们的sql语句就应该按照下面这样来写。

    
        select * from t_emp
        
                
                    emp_name=#{name}
                
                
                    and emp_salary=#{salary}
                
        
    
    

    来讲讲where标签做了什么?他做了2件事

  • 它代替了传统手写的where
  • 当条件不成立时,删掉中间的连接词(在上面的代码中是and,也可以是or)
  • if标签做了什么?

  • 当test中的条件不成立,则if中的语句不存在
  • 注意test中的name是我们在mapper中使用@param标签定义的,无法使用arg0代替
  • 自定义多条件设置值

    mybatis还为我们提供了set标签,原理与用法与上面的相同,保证传入的参数出现问题时sql正常执行。set标签需要特别注意的地方是,如果条件均不成立,语法本身就无法成立。而在上面的查询中,如果条件均不满足,其实会变成无条件查询。

    set.png

    trim标签

    trim标签可以用来替代任何上面的标签,他的作用就是在sql语句上面进行直接的裁剪
    他提供4个选项,中间可以用|隔开

  • prefix 表示在if标签的前面动态添加的语句。如where与set
  • suffix 表示在if标签的后面动态添加的语句。如and连接符
  • prefixOverrides 表示在if标签的前面动态删除的语句。
  • suffixOverrides 表示在if标签的后面动态删除的语句。
  • trim.png

    他的原则就是if条件不满足则开始判定是否有对应的单词,有就删掉;或者条件满足时判定是否有,没有就加上。(不会添加多个where)

    设置如switch标签的多选一

    使用mybatis提供的标签,我们可以实现多选一,就像java中的switch

    switch.png

    mybatis批处理

    mybatis并没有提供专业的批处理方法,但提供了一个forEach标签。该标签的作用主要是遍历传入的list集合,我们通过遍历循环来拼接出想要的语句

    List getOneEmpByIds(@Param("ids") List ids);
    
    
    
        select * from t_emp where
            id in
                    
                            #{id}
                    
    
    
    

    代码解析,forEach标签没什么好说的,就是遍历传入的list,主要在于其中的属性。

  • open 表示要在遍历之前要拼接的东西
  • close 自然就是结束后要拼接的东西
  • 至于separator则是每个元素遍历出来后之间的分割符
  • item无需多说,就是item项
  • sql片段的抽取与复用

    抽取:

    
    
        select emp_id,emp_name,emp_age,emp_salary,emp_gender from t_emp
    
    

    引用:

    
    
    

    xml文件的批量映射

    我们会有一个很烦人的问题,我们所有的xml文件都必须在配置文件中注册。
    我们如何批量的去映射他们,或者说直接指定一个包,让他自动扫描包里面的xml文件呢?

    mybatis提供了一个package标签

    
    

    但是这个标签很坑的一点是,

  • 他要求mapper.xml文件必须跟mapper接口的名字保持完全一致!
  • 他要求必须把他们放在同一个包里面。
  • 要知道maven在打包时是默认不会扫描src下其他文件的,只会扫描java文件!,下面演示如何设置maven的打包规则(仅测试用)。

    
        
            
                
                    src/main/java
                
                
                    
                        **/*.*
                    
                
            
        
    
    

    这样做还是很不方便,会导致很多问题。

    我们在resources文件夹下创建与java包下同样的包结构即可,他会自动打包在一起!,特别注意,创建包时要一层一层创建!!!

    build.png

    分页插件的使用

    mybatis并没有提供分页功能,我们可以使用分页插件。这在开发中非常常用。

    分页插件的原理

    他的原理是在sql执行之间添加了一个拦截器,在拦截器中继续封装sql语句,所以他在使用时代码看起来会有点奇怪。

    第一步我们导入依赖

    
        com.github.pagehelper
        pagehelper
        5.1.11
    
    

    第二步我们在配置文件中引入插件,

    mybatis配置文件的各项配置有严格的顺序,我们可以点击configuration标签查看

    configuration (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers?)>
    

    我们将插件引入到mybatis中的指定位置,代码中的mysql表示需要拦截的竖起了类型。

    
        
            
        
    
    

    插件使用的注意事项:

    我们在写竖起了时正常写就行,但是需要删除掉最后竖起了语句结尾的分号---->;否则语句执行会出问题!!!!

    
        
            select * from t_emp
        
    

    插件的使用看起来非常奇怪,它会提前设置当且页数与每页最大数据量。

    EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
    
    //配置分页信息
    PageHelper.startPage(1,2);
    //查询代码执行
    List employees = mapper.PageQueryAll();
    
    //把查询结果放入PageInfo中,可以让我们获得更多有用信息
    PageInfo pageInfo = new PageInfo(employees);
    
    //获取数据的list合集
    List list = pageInfo.getList();
    System.out.println("pageInfo = " + pageInfo);
    long total = pageInfo.getTotal(); // 获取总记录数
    System.out.println("total = " + total);
    int pages = pageInfo.getPages();  // 获取总页数
    System.out.println("pages = " + pages);
    int pageNum = pageInfo.getPageNum(); // 获取当前页码
    System.out.println("pageNum = " + pageNum);
    int pageSize = pageInfo.getPageSize(); // 获取每页显示记录数
    System.out.println("pageSize = " + pageSize);
    
    sqlSession.close();
    

    如果我们出现了在同一套代码中需要2次分页查询,则需要重新写一套startPage与PageInfo()来嵌套查询语句

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论