【长文】Spring学习笔记(七):Mybatis映射器+动态SQL

1 概述

本文主要讲述了如何使用MyBatis中的映射器以及动态SQL的配置。

2 MyBatis配置文件概览

MyBatis配置文件主要属性如下:

  • :相关设置,键值对形式
  • :类型别名
  • :类型处理器
  • :对象工厂
  • :插件,包含若干个
  • :环境配置,包含若干个,在中可以指定事务管理器以及数据源
  • :数据库厂商标识
  • :映射器,包含若干个

注意顺序不能颠倒,否则启动时会发生异常。

3 准备步骤

由于本文大部分的代码都只给出了关键的语句而没有完整的工程,因此如果想要实现一遍请clone此处的代码(Kotlinclone此处),并:

  • 利用resources/sql下的脚本文件创建数据库以及数据表,并插入相应数据
  • 修改MyBatisSpringdhcp2等依赖为最新版本并修改MySQL驱动为对应版本
  • 修改applicationContext.xml文件中的数据库用户名,密码以及数据库URL,可能需要修改驱动
  • 开启数据库服务并进行测试,运行MainTest中的测试方法即可,正常来说会出现如下结果:

【长文】Spring学习笔记(七):Mybatis映射器+动态SQL_第1张图片

4 映射器概述

MyBatis的映射器由一个接口加上XML映射文件组成,是最复杂的组件,映射文件常用元素如下:

  • 4.1 select * from user where id = #{id}

    其中id是唯一标识符,接受一个Integer,返回com.pojo.User对象,结果集自动映射到com.pojo.User中。

    常用属性如下:

    • id如下:

      参数名name以及ageMap的键。

      接着在UserDao下添加:

      User selectByMap(Map map);

      然后在主类中使用Map添加键值对:

      @Test
      public void selectByMap()
      {
          Map map = new HashMap<>();
          map.put("name","111");
          map.put("age","33");
          System.out.println(dao.selectByMap(map));
      }

      这样就能传递多个参数进行查询了。

      4.1.2 使用JavaBean

      传递多个参数的另一种方法是利用JavaBean传递,创建一个POJO类:

      @Getter
      @Setter
      @Builder
      @ToString
      public class UserPOJO {
          private String name;
          private Integer age;
      }

      修改UserDao接口方法:

      public User selectByPOJO(UserPOJO user)

      接着修改映射文件,实际上修改parameterType即可:

      注意访问传递的参数时直接使用POJO类的属性名即可,无须加上类似UserPOJO.的前缀。

      最后进行测试:

      @Test
      public void selectByPOJO()
      {
          UserPOJO pojo = UserPOJO.builder().age(33).name("111").build();
          System.out.println(dao.selectByPOJO(pojo));
      }

      4.2

      用于插入,大部分属性与类似,简单示例如下:

      
          update user set name=#{name}, age=#{age} where id = #{id}
      
      
      
          delete from user where id = #{id}
      

      同理update/delete返回一个整数,表示操作影响的行数,因此设置UserDao接口如下:

      int updateUser(User user);
      int deleteUser(Integer id);

      测试:

      @Test
      public void updateUser()
      {
          User user = User.builder().id(3).name("3333333").age((short)11).build();
          selectAll();
          System.out.println(dao.updateUser(user));
          selectAll();
      }
      
      @Test
      public void deleteUser()
      {
          selectAll();
          System.out.println(dao.deleteUser(3));
          selectAll();
      }

      4.4

      用于定义SQL的一部分,以方便后面的SQL语句引用,比如:

      
          id,name,age
      
      

      UserDao接口:

      List selectBySqlColumn();

      测试:

      @Test
      public void selectBySqlColumn()
      {
          System.out.println(dao.selectBySqlColumn());
      }

      5

      上面提高过,要强大,但是需要手动定义映射关系,一个常见的如下:

      
                                 
                                     
                                       
          
                                        
                                    
                     
                      
                   
                               
          
      

      5.1 使用Map

      查询SQL的结果可以使用Map/POJO存储,使用Map存储不需要手动编写,默认表属性名是键值对的键:

      可用List来接收返回结果,一条记录映射到一个Map对象,Map中的keyselect的字段名。

      示例的UserDao方法如下:

      List> selectReturnMap();

      其中Map类型为Map,测试方法如下:

       @Test
      public void selectReturnMap()
      {
          dao.selectReturnMap().forEach(System.out::println);
      }

      5.2 使用POJO

      如果使用POJO存储返回的对象时,需要先定义一个POJO类,可以在上面的UserPOJO基础上加上一个id属性:

      @Getter
      @Setter
      @Builder
      @ToString
      public class UserPOJO {
          private Integer id;
          private String name;
          private Integer age;
      }

      接着编写映射文件:

      
          
          
          
      

      其中property指定POJO的属性,column是表字段名,最后配合 select * from user

      返回结果可以用List接收:

      List selectReturnPOJO();

      测试方法:

      @Test
      public void selectReturnPOJO()
      {
          dao.selectReturnPOJO().forEach(System.out::println);
      }

      6 级联查询

      级联查询就是利用主键与外键的关系进行组合查询,比如表A的一个外键引用了表B的一个主键,查询A时,通过A的外键将B的相关记录返回,这就是级联查询。常见的级联查询有三种:

      • 一对一
      • 一对多
      • 多对多

      MyBatis支持一对一以及一对多级联,没有对多对多级联提供支持,但是可以用多个一对多级联实现多对多级联。下面分别来看一下。

      6.1 一对一

      一对一级联查询是最常见的级联查询,可以通过中的进行配置,通常使用的属性如下:

      • property:映射到实体类的对象属性
      • column:指定表中对应的字段
      • javaType:指定映射到实体对象属性的类型
      • select:指定引入嵌套查询的子SQL语句,用于关联映射中的嵌套查询

      下面通过一个例子进行说明,例子分五步:

      • 创建数据表
      • 创建实体类
      • 编写映射文件
      • 修改持久层接口
      • 添加测试方法

      6.1.1 数据表

      为了方便新增表以及数据都写在一起:

      use test;
      
      drop table if exists idcard;
      drop table if exists person;
      
      create table idcard(
          id int(10) primary key auto_increment,
          code char(18) collate utf8mb4_unicode_ci default null
      );
      
      create table person(
          id int(10) primary key,
          name varchar(20) collate utf8mb4_unicode_ci default null,
          age smallint default null,
          idcard_id int(10) default null,
          key idcard_id(idcard_id),
          constraint idcard_id foreign key (idcard_id) references idcard(id)
      );
      
      insert into idcard(`code`) values('123456789123456789');
      
      insert into person(`id`,`name`,`age`,`idcard_id`) values (1,'111',22,1);

      6.1.2 实体类

      @Data
      public class IdCard {
          private Integer id;
          private String code;
      }
      
      @Data
      public class Person {
          private Integer id;
          private String name;
          private Integer age;
          private IdCard card;
      }

      另外还需要创建一个映射结果的POJO类:

      @Data
      public class PersonPOJO {
          private Integer id;
          private String name;
          private Short age;
          private String code;
      }

      6.1.3 映射文件

      映射文件分为两个:

      • IdCardMapper.xml
      • PersonMapper.xml

      首先是IdCardMapper.xml,加上一个 select * from idcard where id = #{id}

      其次是PersonMapper.xml

      
      
      
          
              
              
              
              
          
          
      
          
              
              
              
              
                  
                  
              
          
      
          
      
          
      

      首先第一个先指定id等属性,接着是

      • property是实体类属性,注意类型为IdCard
      • column是表字段名,类型为int(10)
      • javaType是通过后面的select返回的类型,可以理解成是property的类型,也就是IdCard
      • select指定嵌套查询使用的SQL,对应于IdCardDao.xml中的selectCodeById

      接着在一个是进行连接查询,无需额外的,实际执行情况如下:

      【长文】Spring学习笔记(七):Mybatis映射器+动态SQL_第4张图片

      6.1.4 Dao接口

      这个比较简单:

      public interface PersonDao {
          Person selectPersonById1(Integer id);
          Person selectPersonById2(Integer id);
          PersonPOJO selectPersonById3(Integer id);
      }

      6.1.5 测试

      @Test
      public void selectPersonById()
      {
          System.out.println(dao.selectPersonById1(1));
          System.out.println(dao.selectPersonById2(1));
          System.out.println(dao.selectPersonById3(1));
      }

      注意在测试之前,需要修改配置文件mybatis-config.xml

      
          
              
              
              
              
              
      
          
          
              
              
              
              
              
              
          
      

      前两个表示开启延迟加载以及按需加载,后面一个是设置调试开关,最后在下面的加上对应的xml的位置。

      要注意的一个是需要写在的前面。

      另外因为Dao接口没有加上@Mapper注解,因此需要在applicationContext.xml中手动加上Dao位置:

      
          
          
      

      测试结果:

      【长文】Spring学习笔记(七):Mybatis映射器+动态SQL_第5张图片

      6.2 一对多

      一对多的级联查询与一对一处理有相似之处,主要是映射文件中的配置,例子也是和上面一样分五步。

      6.2.1 数据表

      需要两张表:

      • user
      • order

      user可以沿用前面的user表,而order表如下:

      use test;
      drop table if exists orders;
      
      create table orders(
          id int(10) primary key auto_increment,
          ordersn varchar(10) collate utf8mb4_unicode_ci default null,
          user_id int(10) default null,
          key user_id(user_id),
          constraint user_id foreign key (user_id) references user(id)
      );
      
      insert into orders(`ordersn`,`user_id`) values ('testorder1',1),('testorder2',1),('testorder3',1);

      6.2.2 实体类

      添加实体类Orders

      @Data
      public class Orders {
          private Integer id;
          private String ordersn;
      }

      同时创建一个带OrdersUser

      @Data
      @Builder
      @NoArgsConstructor
      @AllArgsConstructor
      public class UserWithOrders {
          private Integer id;
          private String name;
          private Short age;
          private List ordersList;
      }

      6.2.3 映射文件

      两个:

      • OrdersMapper.xml
      • UserWithOrdersMapper.xml

      首先是OrdersMapper.xml,只有一个简单的 select * from orders where user_id=#{id}

      接着是UserWithOrdersMapper.xml

      
      
      
      
          
              
              
              
              
          
          
      
          
              
              
              
              
                  
                  
              
          
          
      
          
      

      相比起一对一的级联,重点改变的就是其中的,重要属性如下:

      • property:指定实体类的属性字段
      • ofType:指定集合中的类型
      • column:将哪些值传递给select中的方法
      • select:嵌套查询的语句

      第二个类似,将查询的结果直接映射到Orders的属性上面。最后一种是直接使用连接查询。

      6.2.4 Dao接口

      public interface OrdersDao {
          List selectOrdersById(Integer id);
      }
      
      public interface UserWithOrdersDao {
          UserWithOrders selectUserOrders1(Integer id);
          UserWithOrders selectUserOrders2(Integer id);
          List selectUserOrders3(Integer id);
      }

      6.2.5 测试

      @Test
      public void selectUserOrders()
      {
          System.out.println(dao.selectUserOrders1(1));
          System.out.println(dao.selectUserOrders2(1));
          System.out.println(dao.selectUserOrders3(1));
      }

      6.3 多对多

      MyBaits其实不支持多对多级联,但是可以通过多个一对多级联实现,比如一个订单对应多个商品,一个商品对应多个订单,这样两者就是多对多级联关系,这样使用一个中间表,就可以转换为两个一对多关系。

      下面同样通过五个步骤实现多对多级联。

      6.3.1 数据表

      需要订单表、商品表以及一个中间表,由于订单表Orders之前已创建,这里只需要创建两个表:

      use test;
      
      create table product(
          id int(10) primary key auto_increment,
          name varchar(10) collate utf8mb4_unicode_ci default null,
          price double default null
      );
      
      create table orders_detail(
          id int(10) primary key auto_increment,
          orders_id int(10) default null,
          product_id int(10) default null,
          key orders_id(orders_id),
          key product_id(product_id),
          constraint orders_id foreign key (orders_id) references orders(id),
          constraint product_id foreign key (product_id) references product(id)
      );
      
      insert into product(`name`,`price`) values('product1',1.1),('product2',2.2),('product3',3.3);
      insert into orders_detail(`orders_id`,`product_id`) values(1,1),(1,2),(1,3),(2,1),(2,3);

      6.3.2 实体类

      订单类可以沿用之前的,只需要两个实体类:

      @Data
      @AllArgsConstructor
      @NoArgsConstructor
      public class Product {
          private Integer id;
          private String name;
          private Double price;
          private List orders;
      }
      
      @Data
      @AllArgsConstructor
      @NoArgsConstructor
      public class OrdersWithProduct {
          private Integer id;
          private String ordersn;
          private List products;
      }

      6.3.3 映射文件

      
      
      
          
              
              
              
                  
                  
                  
              
          
          
      

      这里的多对多级联实质上是通过每次指定不同的OrdersId去查询对应的Product实现的,也就是分成了多次的一对多级联。

      6.3.4 Dao接口

      public interface OrdersWithProductDao {
          List selectOrdersAndProduct();
      }

      6.3.5 测试

      @Test
      public void test()
      {
          ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
          OrdersWithProductDao dao = context.getBean(OrdersWithProductDao.class);
          System.out.println(dao.selectOrdersAndProduct());
      }

      7 动态SQL

      最后来看一下动态SQL,动态SQL可以避免手动拼接SQL语句,比如在某些条件成立的情况下添加and xxx=xxxx之类的操作。先来看一下最常用的

      7.1

      类似Java中的if语句,最简单的例子如下:

      也就是说当test中的条件成立时,便添加and xxx语句。注意test这个属性是必须的,不能省略。

      (注这里用到了一个关键的1=1,仅作说明使用,实际开发请勿使用1=1进行拼接)

      Dao接口:

      List selectByIf(User user);

      测试:

      @Test
      public void testIf()
      {
          System.out.println(dao.selectByIf(User.builder().age((short) 33).name("111").build()));
      }

      7.2 ++

      类似Java中的switch语句:

      • 类似switch
      • 类似case
      • 类似default

      当其中一个成立时,语句便结束,类似于自动加上了"break"

      示例:

      Dao接口:

      List selectByChoose(User user);

      测试:

      @Test
      public void testChoose()
      {
          System.out.println(dao.selectByChoose(User.builder().age((short)33).build()));
      }

      7.3

      主要功能:

      • 加前缀
      • 加后缀
      • 替换某些首部/尾部内容

      这里是一个使用来实现的例子:

      Dao接口:

      List selectByTrim(User user);

      测试:

      @Test
      public void testTrim()
      {
          System.out.println(dao.selectByTrim(User.builder().build()));
          System.out.println(dao.selectByTrim(User.builder().name("test2").build()));
      }

      7.4

      最常用的就是拼接查询条件,比如有多个查询条件,仅仅使用多个的话会出现首个有一个多余的and的问题,而使用会进行智能处理,当然也对or适用,例子如下:

      Dao接口:

      List selectByWhere(User user);

      测试:

      @Test
      public void testWhere()
      {
          System.out.println(dao.selectByWhere(User.builder().build()));
          System.out.println(dao.selectByWhere(User.builder().name("111").build()));
          System.out.println(dao.selectByWhere(User.builder().age((short)-3).build()));
      }

      7.5

      一般配合update语句使用,比如:

      
          update user
          
              
                  name = #{name},
              
              
                  age = #{age}
              
          
          where id=#{id}
      

      Dao接口:

      int updateBySet(User user);

      测试:

      @Test
      public void testSet()
      {
          System.out.println(dao.updateBySet(User.builder().name("999999").age((short)39).id(1).build()));
          System.out.println(dao.selectByWhere(User.builder().build()));
      }

      7.6

      主要用于in中,可以认为是一个集合,典型的使用场景是select xxx from xxx where xxx in

      的主要属性有:

      • item:每个元素的别名
      • index:每个元素的下标
      • collection的类型,有listarraymap三种,当传入单个参数且该参数类型为List时,则为list,传入单个参数且该参数类型为数组时,则为array,否则应将其封装成Map,并设置属性值为map
      • open:语句开始标志
      • close:语句结束标志

      例子:

      Dao接口:

      List selectByForeach(List id);

      测试:

      @Test
      public void testForeach()
      {
          System.out.println(dao.selectByForeach(List.of(1,2,3)));
      }

      7.7

      可用于对字符串进行拼接,对于字符串拼接,MySQL使用的是concat,而Oracle使用的是||,而MyBatis提供了可以屏蔽这种DBMS之间的差异,无需修改xml即可进行移植,例子如下:

      Dao接口:

      List selectByBind(User user);

      测试:

      @Test
      public void testBind()
      {
          System.out.println(dao.selectByBind(User.builder().name("test1").build()));
      }

      8 源码

      此处给出了实现所有例子后的代码,仅供参考,但不建议直接clone,建议从初始化工程开始逐步实现。

      Java版:

      Kotlin版:

你可能感兴趣的:(spring,mybatis)