Spring-mybatis

Mybaits


[问题]:

对多查询没有映射成List!


注意事项:

  • 标签内部的语句一定要避免在语句后面使用标注

官方文档(中文)

准备工作

依赖导入

pom.xml

<!--myBatis驱动-->
<dependency>
   <groupId>org.mybatis</groupId>
   <artifactId>mybatis</artifactId>
   <version>3.5.16</version>
</dependency>

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

<!--        实体类(对象映射)——非必需(主要是不用再创建数据库)-->
<dependency>
  	<groupId>org.springframework.boot</groupId>
  	<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

或者

<dependency>
   <groupId>org.mybatis.spring.boot</groupId>
   <artifactId>mybatis-spring-boot-starter</artifactId>
   <version>3.0.3</version>
</dependency>
<dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <version>8.0.33</version>
</dependency>

常用目录结构

 ------------------------
|---com.example\
|   |---pojo\
|   |---mapper\
|   |---service\
|---resources\
|   |---mapper\
|   |   |---xxmapper.xml
|		|---mybatis-config.xml
 ----------------------

mapper实现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为接口的全限定符-->
<mapper namespace="">
        
<!--        声明sql语句-->
<!--        每个标签为一个接口的实现-->
<!--        resultType是返回的实体对象-->
        <select id="methodName" resultType="returnType">
#                 sql语句
        </select>

</mapper>

[!WARNING]

Mapper接口不能重载,仅仅只是根据接口方法名进行标识

Mybatis配置文件

配置的基本结构:

数据库连接信息,性能配置,mapper.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>
        <!--    配置环境-->
        <environments default="mysql">
                <!--        配置mysql环境
                           id要与default一致
                -->
                <environment id="mysql">
                        <!--            配置事务管理的类型-->
                        <transactionManager type="JDBC"></transactionManager>
                        <!--            配置连接池-->
                        <dataSource type="POOLED">
                                <!--                配置连接数据库的基本信息-->
                                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                                <property name="url" value="jdbc:mysql://localhost:3306/mybatislearning"/>
                                <property name="username" value="root"/>
                                <property name="password" value="55646478aas"/>
                        </dataSource>
                </environment>
        </environments>
        <!--    指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件-->
        <mappers>
                <mapper resource="mapper/studentMapper.xml"/>
        </mappers>
</configuration>

[!note]

  1. 环境配置<environments>

    • 事务管理配置transactionManager:type=[JDBC|MANAGED]
    • 数据源配置dataSource:type=[UNPOOLED|POOLED|JNDI]
  2. 运行时的功能设置<settings>

  3. 映射器配置**<mappers>**

执行的基本流程

加载配置文件->根据配置文件创建会话工厂->工厂开启会话->会话穿件代理对象->代理对象执行方法->会话提交->会话关闭

[!note]

  • 只有增、删、改才需要提交

  • 只有查才会返回实体对象

//配置文件路径
String xmlPath = “”;   //resoures为根
//加载配置文件
InputStream resourceAsStream = Resources.getResourceAsStream(studentXML);

//根据配置文件穿件绘画工厂
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

//工厂开启绘画
SqlSession sqlSession = sessionFactory.openSession();

//获取mapper接口的代理对象(动态代理)
StudentsMapper mapper = sqlSession.getMapper(StudentsMapper.class);

//执行方法
mapper.method();

//会话提交内容
sqlSession.commit();

//关闭会话
sqlSession.close();


[!note]

ibatis

  • 不用创建mapper接口
  • 在调用的时候不用创建Mapper代理对象,直接用sqlSeesion执行标签的sql语句(namespace.id,object)(需要整合参数,返回全是Object)
  • 提供的CRUD方法实际上是通过Id找到对应的标签,获取sql语句然后执行,将结果返回

创建实体类(自动生成表)

准备Mapper接口和MapperXML文件

在xml文件中专门写sql语句

[!NOTE]

Mybatis中的Mapper接口相当于Dao,区别在与Mapper仅仅值是规定了接口,不需要提供实现类,具体SQL语句写在Mapper文件中

  • 这个过程使用了接口的代理

测试

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 org.springframework.context.annotation.Bean;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.junit.jupiter.api.Test;
import org.zhuhaihong.entity.Student;
import org.zhuhaihong.mapper.StudentsMapper;

import java.io.IOException;
import java.io.InputStream;

//@SpringJUnitConfig
public class TestDemo {


    @Test
    public void  testDemo1() throws IOException {
        //配置文件
        String studentXML ="mybatis-conf.xml";

        //加载配置文件
        InputStream resourceAsStream = Resources.getResourceAsStream(studentXML);

        //根据配置文件穿件绘画工厂
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

        //工厂开启绘画
        SqlSession sqlSession = sessionFactory.openSession();

        //获取mapper接口的代理对象(动态代理)
        StudentsMapper mapper = sqlSession.getMapper(StudentsMapper.class);

        //代理对象执行sql语句(xml/接口中的方法)
        Student student = mapper.selectById(1L);

        System.out.println(student);
        
        //会话提交增删改
        sqlSession.commit();
        
        //关闭会话
        sqlSession.close();
    }
}

基本功能

自动提交

增加、删除、修改一定要提交数据,或者开启自动提交

//开启会话
SqlSession sqlSession = build.openSession(true);

日志输出

在配置文件中的settings标签设置

        <!--运行时功能设置-->
        <settings>
<!--                指定 MyBatis 所用日志的具体实现,未指定时将自动查找。-->
                <setting name="logImpl" value="STDOUT_LOGGING"/>
        </settings>

向SQL传参

  1. #{参数名}
  • 表示占位符+赋值

  • 可以防止注入攻击

  • 只能代替值,不能替代容器名(标签,列名,sql关键字)

    <select id="selectById1" resultType="org.zhuhaihong.entity.Student">
            select *
            from student
            where id = #{id}
        </select>
    
    ==>  Preparing: select * from student where id = ?  #占位
    ==> Parameters: 1(Long)  #传参
    <==    Columns: id, name, age
    <==        Row: 1, hellen, 19
    <==      Total: 1
    
  1. ${参数名}
  • 字符串拼接

  • 会发生注入攻击

  • 能够代替容器名

    <select id="selectById2" resultType="org.zhuhaihong.entity.Student">
            select student.id, student.name, student.age
            from student
            where id = ${id}
        </select>
    
    ==>  Preparing: select student.id, student.name, student.age from student where id = 2 #直接拼接
    ==> Parameters: 
    <==    Columns: id, name, age
    <==        Row: 2, liutao, 20
    <==      Total: 1
    

数据输入

  • 使用#{key}或者${key}
简单类型(单值)
  1. **单个参数:**传入的单个简单类型,key随意写

  2. 多个参数:传入多个根据key名称使用参数名进行对应(不可以)

    • 方式一:@param指定参数名称**[推荐]**

      Student selectByNameAndAge1(@Param("name") String name, @Param("age") int age);
      
      <select id="selectByNameAndAge1" resultType="org.zhuhaihong.entity.Student">
              select student.name, student.age
              from student
              where name = #{name}
                and age = #{age}
          </select>
      
      ==>  Preparing: select student.name, student.age from student where name = ? and age = ?
      ==> Parameters: liutao(String), 20(Integer)
      <==    Columns: name, age
      <==        Row: liutao, 20
      <==      Total: 1
      Student(Id=null, name=liutao, age=20)
      
    • 方式二:mybaits默认机制——参数顺序的参数名:arg[0],arg[1]…/param1,param2…

      Student selectByNameAndAge2(String name,int age);
      
      <select id="selectByNameAndAge2" resultType="org.zhuhaihong.entity.Student">
              select student.name, student.age
              from student
              where name = #{arg[0]}
                and age = #{arg[1]}
          </select>
      
      ==>  Preparing: select student.name, student.age from student where name = ? and age = ?
      ==> Parameters: hellen(String), 19(Integer)
      <==    Columns: name, age
      <==        Row: hellen, 19
      <==      Total: 1
      Student(Id=null, name=hellen, age=19)
      
复杂类型(多值)
  1. **类对象:**传入类对象,key使用类的属性名

    int insertStudent(Student student);		
    
    <insert id="insertStudent">
            insert into student(name, age)
            values (#{name}, #{age})
        </insert>
    
    Student student1 = new Student();
    student1.setName("dearfriend");
    student1.setAge(11);
    mapper.insertStudent(student1);
    
  2. map类型:key为Map的Key

    int insertStudent1(Map<String,String> map);
    
    <insert id="insertStudent1">
        insert into student(name, age)values
                (#{name}, #{age})
    </insert>
    
    HashMap<String, String> stringStringHashMap = new HashMap<>();
    stringStringHashMap.put("name","hahah");
    stringStringHashMap.put("age","15");
    System.out.println(stringStringHashMap);
    mapper.insertStudent1(stringStringHashMap);
    
    {name=hahah, age=15}
    ==>  Preparing: insert into student(name, age)values (?, ?)
    ==> Parameters: hahah(String), 15(String)
    <==    Updates: 1
    
    1. List类型:动态SQL语句foreach拼接

      collection标签传入参数指定(collection属性):

      • 列表使用类似简单类型形参传入:@param、arg[x]
      • 使用list、conllection专用名词代表形参列表中的集合类型

      内部列表的使用——collection标签中item属性定义列表元素名称:

      • 简单类型:#{itemName}
      • 复杂对象类型: #{itemName.fildeName}

    [!CAUTION]

    此处虽然数据库中的age为int,但是在传入值的时候会自动转换;如果为不能自动转换的值就会报错

数据输出

  • 增删改都是int(影响行数)

  • 查询返回为实体对象名,mapperxml中只需要指定实体类的类型,不需要考虑返回对象的数量取List等

单值简单类型
  1. 单个值类型,直接使用数据类型
单值类类型
  1. 返回自定义类类型:保证列名和类属性名对应

    • 默认驼峰和蛇形自动映射:需要手动开启设置:mapUnderscoreToCamelCase

      <setting name="mapUnderscoreToCamelCase" value="true"/>
      
    • 查询的sql语句进行从命名与类属性对应

map类型
  1. 传出为map(不指定实体类的情况下)

     Map selectS(String name);
    
    <select id="selectS" resultType="map">
            select name,age
            from student
            where name = #{name}
        </select>
    
    
    Map liutao = mapper.selectS("liutao");
    System.out.println(liutao);
    
    ==>  Preparing: select name,age from student where name = ?
    ==> Parameters: liutao(String)
    <==    Columns: name, age
    <==        Row: liutao, 20
    <==      Total: 1
    {name=liutao, age=20}
    
多值
  1. 传出List:只需要指定元素的数据类型(查询结果单行数据类型——泛型)(底层都是使用selectList进行查询,不管是selectOne还是其他)

[!note]

  • resultType可以使用别名(mybatis提供,基本类型用_int,类用小写首字母),不实用全限定副

  • 自定义类可以在config.xml中使用****设置别名

    • 方式一:类别名

      <!--        定义类的别名(传出参数)-->
              <typeAliases>
                      <typeAlias type="org.zhuhaihong.entity.Student" alias="student"/>
              </typeAliases>
      
    • 方式二:包别名,最后包下面的类的首字母小写为别名

              <typeAliases>
                      <typeAlias type="org.zhuhaihong.entity"/>
              </typeAliases>
      
    • 方式三:指定了包别名之后,可以使用@Alisas(“别名”).必须和批量注解一起使用

  • 如果接口定义使用列表接受,resultType只需要指定接受元素的类类型

  • 接口直接定义的事单个对象,如果查询到多个,则会报错

  • 接受值的类属性名一定要和列名对应**(sql重命名或者开启驼峰映射)**

  • 理解:将查询到的结果,按照resultType创建对象(没有实体类就使用map),按照列名给对象赋值(每条数据代表一个对象,多个值要使用List,resultType为每条数据的接值类型),如果没有则不进行赋值(null)

List<StudentName> selectName(int age);
<select id="selectName" resultType="org.zhuhaihong.entity.StudentName">
        select name
        from student
        where age > #{age}
    </select>
List<StudentName> studentNames = mapper.selectName(10);
System.out.println(studentNames)
public class StudentName {

    private String Aname; //没对应
}

[null, null, null, null, null, null, null, null, null]
public class StudentName {

    private String name; //对应
}
[StudentName(name=hellen), StudentName(name=liutao), StudentName(name=Liming), StudentName(name=dearfriend), StudentName(name=hahah), StudentName(name=dearfriend), StudentName(name=hahah), StudentName(name=dearfriend), StudentName(name=hahah), StudentName(name=dearfriend), StudentName(name=hahah)]
主键回显
  • 主键回显(插入、删除、修改):默认只返回影响行数(int)

  • 主键回显是将插入后的主键值赋值到插入时指定的对象属性上,而不是直接返回主键值到返回值上,返回值永远是影响行数

  1. 自增长

    int insertStudentId(Student s);
    
    <insert id="insertStudentId" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
        insert into student(name, age)
        values (#{name}, #{age})
    </insert>
    
    //执行
    Student student = new Student();
    student.setAge(15);
    student.setName("主键回显");
    System.out.println("插入前studentId:"+student.getId());
    int i = mapper.insertStudentId(student);
    System.out.println("插入后后studentId:"+student.getId());  //回显到实体对象
    System.out.println(i);
    
    • useGeneratedKeys:是否要使用主键值
    • keyColumn:主键的列名
    • keyProperty接受主键实体类的属性名
  • 非自增长:主要的难点不在与获取主键值,而在与维护主键(保证不重复)

    • java调用UUID,得到主键值,传入sql

       int insert1(Teacher t);
      
      <insert id="insert1">
          insert into teacher(id, name)
          values (#{id}, #{name})
      </insert>
      
      //外部生成主键值
      Teacher teacher = new Teacher();
      String s = UUID.randomUUID().toString().replaceAll("-", "");
      teacher.setId(s);
      teacher.setName("外部主键");
      int i = mapper.insert1(teacher);
      System.out.println(teacher);
      
      ==>  Preparing: insert into teacher(id, name) values (?, ?)
      ==> Parameters: 5e0c2eea2e6042398af48d91223d819b(String), 外部主键(String)
      <==    Updates: 1
      Teacher(id=5e0c2eea2e6042398af48d91223d819b, name=外部主键)
      
    • mybatis维护:插入前先指定一段sql语句,生成主键值

      int insert(Teacher t);
      
          <insert id="insert">
      #         生成主键值
              <selectKey order="BEFORE" resultType="string" keyProperty="id">
                  select replace(UUID(),'-','')
              </selectKey>
                  
              insert into teacher(id, name)
              values (#{id}, #{name})
          </insert>
      
      //内部主键
      Teacher teacher1 = new Teacher();
      teacher1.setName("内部主键");
      //不设置主键值
      int insert = mapper.insert(teacher1);
      System.out.println(teacher1);
      
      ==>  Preparing: select replace(UUID(),'-','')
      ==> Parameters: 
      <==    Columns: replace(UUID(),'-','')
      <==        Row: fa4fe36c526311ef9b32a5809259071a
      <==      Total: 1
      ==>  Preparing: # 生成主键值 insert into teacher(id, name) values (?, ?)
      ==> Parameters: fa4fe36c526311ef9b32a5809259071a(String), 内部主键(String)
      <==    Updates: 1
      Teacher(id=fa4fe36c526311ef9b32a5809259071a, name=内部主键)
      
    • 使用标签在插入语句前生成主键

      • order:BEFORE|AFTER——在插入执行前还是后执行

      • resultType:语句返回类型

      • keyProperty:给哪个属性赋值(下面的语句参数名)

[!CAUTION]

  • 如果有这个语句,即使在外部指定了主键值,selectKey同样会将自己的值传入,并且将至返回给对象.

  • 主键回显是将插入后的主键值赋值到插入时指定的对象属性上,而不是直接返回主键值到返回值上,返回值永远是影响行数

自定义映射关系和resultMap

解决实体属性名和表列名不一致的方法总结

  1. sql语句查询时对列名取别名和类的属性名统一

  2. 开启驼峰映射[见单值类类型],只能映射一层结构,从第二层就不映射了,按照列名查找

  3. reultMap自定义映射 可以深层映射

        <!--    resultMap映射-->
        <!--    定义映射关系-->
        <resultMap id="retM" type="org.zhuhaihong.entity.Teacher">
            <id column="id" property="id"/>
            <result column="name" property="name"/>
        </resultMap>
        <!--使用resultMap接受值-->
        <select id="select" resultMap="retM">
            select id, name
            from teacher
            where name = #{name}
        </select>
    
    • :定义映射关系
      • id:resultMap的标识
      • type:对应的类的类型
      • :主键映射关系
        • column:表的列名
        • property:指定类的对应属性名
        • :普通映射关系
    • reusutlMap:引用的resultMap的标识

多表映射

  • 编写多表的连接查询
  • 设计存储数据的实体类,承接多表查询结果
  • 定义结果集映射(resultMap)
实体类设计

多表关系:

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

查询关系:

  • 对一
  • 对多
对一

类中类中包含对方对象类型即可

public class Customer {
    private Integer customerId;
    private String customerName;
  	private Integer orderId;
}

public class OrderVO {    
    private Integer orderId;
    private String orderName;
  	private Integer customerId;
  
    private Customer customer;  //对方
}

对多

类中类中包含对方对象类型的集合

public class Order {
    private Integer orderId;
    private String orderName;
  	private Integer customerId;
  
}

public class CustomerVO {
    private Integer customerId;
    private String customerName;
 		private Integer orderId;
  
    private List<Order> orders;   //对方

}

[!IMPORTANT]

  • 无论多少张表,实体类设计都是两两考虑
  • 查询映射的时候,只需要关注本次查询相关属性,其他的属性为null就行(防止死循环)
多表查询
方式一:手动配置映射resultMap

对一:

自定义实体类order.java

@Data
public class Order {
    private Integer orderId;
    private String orderName;
    private Integer customerId;


//    对一查询
    private Customer customer;
}
public interface OrderMapper {
     Order selectCustomer(Integer orderId);
}
<!--自定义映射关系-->
<resultMap id="orderCustomer" type="org.zhuhaihong.entity.Order">
                <id column="order_id" property="orderId"/>
                <result column="order_name" property="orderName"/>
                <result column="customer_id" property="customerId"/>
                <association property="customer" javaType="org.zhuhaihong.entity.Customer">
                        <id column="customer_id" property="customerId"/>
                        <result column="customer_name" property="customerName"/>
<!--                        这里面的订单好和订单列表就不需要了,就不建立映射了-->
                </association>
        </resultMap>

<!--使用映射-->
        <select id="selectCustomer" resultMap="orderCustomer">
            select * from orders join customer on orders.customer_id = customer.customer_id where orders.order_id  = #{id}
        </select>
//测试
@Test
public void testToOne() throws IOException {
    SqlSession sqlSession = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-conf.xml")).openSession(true);
    OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);

    Order order = mapper.selectCustomer(1);
    System.out.println(order);
    sqlSession.close();

}
==>  Preparing: select * from orders join customer on orders.customer_id = customer.customer_id where orders.order_id = ?
==> Parameters: 1(Integer)
<==    Columns: order_id, order_name, customer_id, customer_id, customer_name, order_id
<==        Row: 1, order1, 1, 1, cus1, 1
<==      Total: 1
Order(orderId=1, orderName=order1, customerId=1, customer=Customer(customerId=1, customerName=cus1, orderId=null, orders=null))

对多:

@Data
public class Customer {
    private Integer customerId;
    private String customerName;
    private Integer orderId;


//    对多查询
    private List<Order> orders;

}
public interface customerMapper {
    Customer listOrder(Integer id);
}
<resultMap id="toMany" type="org.zhuhaihong.entity.Customer">
    <id column="customer_id" property="customerId"/>
    <result column="customer_name" property="customerName"/>
    <collection property="orders" ofType="org.zhuhaihong.entity.Order">
        <id column="order_id" property="orderId"/>
        <result column="order_name" property="orderName"/>
    </collection>
</resultMap>

<select id="listOrder" resultMap="toMany">
    select * from customer join orders on customer.customer_id = orders.customer_id where orders.customer_id = #{id};
</select>
@Test
public void testToMany() throws IOException {
    SqlSession sqlSession = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-conf.xml")).openSession(true);
    customerMapper mapper = sqlSession.getMapper(customerMapper.class);
    Customer customer = mapper.listOrder(1);
    System.out.println(customer);
    System.out.println(customer.getCustomerId());
    System.out.println(customer.getCustomerName());
    System.out.println("订单:");
    for (Order order:customer.getOrders()){
        System.out.println(order.getOrderId());
        System.out.println(order.getOrderName());
    }
    sqlSession.close();
}
==>  Preparing: select * from customer join orders on customer.customer_id = orders.customer_id where orders.customer_id = ?;
==> Parameters: 1(Integer)
<==    Columns: customer_id, customer_name, order_id, order_id, order_name, customer_id
<==        Row: 1, cus1, 1, 1, order1, 1
<==        Row: 1, cus1, 1, 2, order2, 1
<==      Total: 2
Customer(customerId=1, customerName=cus1, orderId=null, orders=[Order(orderId=1, orderName=order1, customerId=null, customer=null)])
1
cus1
订单:
1
order1
自动映射功能设置
设置名描述有效值默认值
autoMappingBehavior指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。NONE, PARTIAL, FULLPARTIAL
  • 配置:

    <!--                开启深层自动映射-->
                    <setting name="autoMappingBehavior" value="FULL"/>
    
  • resultMap只需要指定id就行(不需要指定普通字段)

        <resultMap id="toMany" type="org.zhuhaihong.entity.Customer">
            <id column="customer_id" property="customerId"/>
            <collection property="orders" ofType="org.zhuhaihong.entity.Order">
                <id column="order_id" property="orderId"/>
            </collection>
        </resultMap>
    
            <resultMap id="orderCustomer" type="org.zhuhaihong.entity.Order">
                    <id column="order_id" property="orderId"/>
                    <association property="customer" javaType="org.zhuhaihong.entity.Customer">
                            <id column="customer_id" property="customerId"/>
                    </association>
            </resultMap>
    
总结
  • 一定要弄清楚查询返回的是什么数据类型,查询主体是谁.
  • 对一的类属
    • 属性名
    • 属性的类型
  • 对多的类属性(列表)
    • 属性名
    • 元素类型
  • 对多根据查询对象的主键值进行映射(对多元素的放在列表中)

动态SQL语句

[!important]

所有的标签使用都以拼接的思维去理解

所以只要满足标签的基本原理,可以随意拼接

  • if多条件使用and/or
  • trim在prefixOverrrides 、suffixOverrides中都可以指定多个值,用 | 来分隔

[场景]

在查询的时候动态设置查询条件(动态选择参与条件).

  • 如果传入属性,就判断条件,如果没有传入就不添加对应的条件
  • 原理:sql字符串拼接
if-where标签
  1. if标签:条件判断,满足条件进行拼接
<if test = "逻辑表达式">
 #sql语句
</if>

#逻辑表达式
> &gt
<&lt

满足test条件则将sql语句拼接.

  • 若都不满足,可能会出现sql多了where关键子
  • 解决,使用标签:
    • 自动添加where关键字
    • 自动去掉多余的and/or
    • 自动添加**,**
List<Student> queryCondition(Student student);
<select id="queryCondition" resultType="student">
    select * from student
    <where>
        <if test="id !=null">
            and id = #{id}
        </if>
        <if test="name!=null">
            and name = #{name}
        </if>
        <if test="age !=null">
            or age = #{age}
        </if>
    </where>
</select>
        Student student = new Student();
        List<Student> students2 = mapper.queryCondition(student);

//        ==>Preparing:
//        select * from student
//         ==>Parameters: 

        student.setName("liutao");
        List<Student> students = mapper.queryCondition(student);
        
//        ==>  Preparing: select * from student WHERE name = ?
//        ==> Parameters: liutao(String)
        
        student.setAge(19);
        List<Student> students1 = mapper.queryCondition(student);
//        ==>  Preparing: select * from student WHERE name = ? or age = ?
//        ==> Parameters: liutao(String), 19(Integer)

[!warning]

  1. 一定不要加**,** 条件之间不存在**,**
  2. 一定要写or/and,以此表明该语句条件是或还是与(最好都加上,where自动管理)
  3. if也是用key(参考传入参数)接受值,直接用key进行判断
set-if标签

[场景]

更新数据,字段满足条件再更新某些字段

  • 包围条件拼接语句:
    • 自动添加set关键字,
    • 自动去掉多余的**,**(一定要添加)
    • 一定不要加and/or,set语句没有
int update(@Param("age") Integer age, @Param("name") String name,@Param("ageC") Integer ageC, @Param("nameC") String nameC);
<!--    set-if-->
<update id="update">
    update student
    <set>
        <if test="age!=null">
            age = #{age},
        </if>
        <if test="name!=null">
            name = #{name},
        </if>
    </set>
    <where>
        <if test="nameC!=null">
            and name = #{nameC}
        </if>
        <if test="ageC!=null">
            and name = #{ageC}
        </if>
    </where>
</update>
        mapper.update(10,"update1",1,"a1");

//        ==>  Preparing: update student SET age = ?, name = ? WHERE name = ? and name = ?
//        ==> Parameters: 10(Integer), update1(String), a1(String), 1(Integer)

        mapper.update(11,null,null,"a2");

//        ==>  Preparing: update student SET age = ? WHERE name = ?
//        ==> Parameters: 11(Integer), a2(String)

[!warning]

  1. 一定要添加**,set只会自动去掉**多余的,不会自动添加(前后都可以)
  2. set一定不要添加and/or,set语句中没有
trim标签
  • 自定义——内部满足条件拼接sql语句前,前缀后缀的处理(增加或者删除)

  • 可以利用这个编写set和where

规则

  • prefix:表示在 trim 标签内的 sql 语句加上前缀
  • suffix:表示在 trim 标签内的 sql 语句加上后缀
  • prefixOverrides:表示去除第一个前缀
  • suffixOverrides:表示去除最后一个后缀
List<Student> queryConditionTrim(Student student);
<!--trim where-->
<select id="queryConditionTrim" resultType="student">
    select * from student
    <trim prefix="where" prefixOverrides="and|or" suffix=";">#在包围的sql语句加上前缀where,去掉前缀and,加上;后缀
        <if test="id!=null">
            and id = #{id}
        </if>
        <if test="name !=null ">
            and name = #{name}
        </if>
        <if test="age!=null">
            and age >#{age}
        </if>
    </trim>
</select>
        Student student = new Student();
        System.out.println(mapper.queryConditionTrim(student));

//        ==>  Preparing: select * from student
//        ==> Parameters:

        student.setId(1L);
        System.out.println(mapper.queryConditionTrim(student));

//        ==>  Preparing: select * from student where id = ? ;
//        ==> Parameters: 1(Long)

        student.setId(null);

        student.setAge(3);
        System.out.println(mapper.queryConditionTrim(student));

//        ==>  Preparing: select * from student where age >? ;
//        ==> Parameters: 3(Integer)


        student.setName("liutao");
        System.out.println(mapper.queryConditionTrim(student));

//			由此可看出trim包围的所有sql语句先进行拼接,内部sql拼接完成后先使用trim规则对整个sql语句前缀后缀处理后再凭借到外部				的sql语句
//        ==>  Preparing: select * from student where name = ? and age >? ;
//        ==> Parameters: liutao(String), 3(Integer)

[!important]

  • trim 标签是对包围的内容预拼接后的sql语句进行处理,然后再进行最终拼接
  • 如果内部没有sql语句,则不会增加前缀
  • prefix:表示在 trim 标签内的 sql 语句加上前缀
  • suffix:表示在 trim 标签内的 sql 语句加上后缀
  • prefixOverrides:表示去除第一个前缀
  • suffixOverrides:表示去除最后一个后缀
chooes-when-otherwise
  • 和if作用相同,此为多分支
  • 可以用来设置条件的优先级,有高的优先级的sql语句拼接,低优先级条件不生效
  • 根据不同的条件选择执行不同的 SQL 片段
  • 多分支语句:从上倒下依次判断条件,满足的话执行内部sql语句凭借,后续不在执行(参考switch-case-break-otherwise)
/*choose-when-otherwise*/
List<Student> queryConditionChoose(Student student);
<!--choose-when-otherwise-->
<!--有id根据id搜索,无ID根据那么搜索,无name根据age搜索,无age默认输出age>10-->
<select id="queryConditionChoose" resultType="student">
    select * from student
    <where>
        <choose>
            <when test="id!=null">
                id = #{id}
            </when>
            <when test="name!=null">
                and name = #{name}
            </when>
            <when test="age!=null">
                and age>#{age}
            </when>
            <otherwise>
                age>10;
            </otherwise>
        </choose>
    </where>
</select>
    Student student = new Student();

        //默认条件
        System.out.println(student);
        mapper1.queryConditionChoose(student);
        
//        Student(Id=null, name=null, age=null)
//        ==>  Preparing: select * from student WHERE age>10;
//        ==> Parameters:
//                <==    Columns: id, name, age
//                <==        Row: 1, hellen, 19
//                <==        Row: 2, liutao, 20
//                <==        Row: 3, Liming, 21
//                <==        Row: 4, dearfriend, 11
//                <==        Row: 8, hahah, 15
//                <==        Row: 24, a2, 11
//                <==        Row: 43, a2, 11
//                <==      Total: 7


        //只根据age搜索
        student.setAge(15);
        System.out.println(student);
        mapper1.queryConditionChoose(student);
        
//        Student(Id=null, name=null, age=15)
//        ==>  Preparing: select * from student WHERE age>?
//        ==> Parameters: 15(Integer)
//                <==    Columns: id, name, age
//                <==        Row: 1, hellen, 19
//                <==        Row: 2, liutao, 20
//                <==        Row: 3, Liming, 21
//                <==      Total: 3

        //只根据name搜索
        
        student.setName("liutao");
        System.out.println(student);
        mapper1.queryConditionChoose(student);
        
//        Student(Id=null, name=liutao, age=15)
//        ==>  Preparing: select * from student WHERE name = ?
//        ==> Parameters: liutao(String)
//                <==    Columns: id, name, age
//                <==        Row: 2, liutao, 20
//                <==      Total: 1

        //只根据id搜索
        student.setId(1L);
        System.out.println(student);
        mapper1.queryConditionChoose(student);
        
//        Student(Id=1, name=liutao, age=15)
//        ==>  Preparing: select * from student WHERE id = ?
//        ==> Parameters: 1(Long)
//                <==    Columns: id, name, age
//                <==        Row: 1, hellen, 19
//                <==      Total: 1
foreach标签

批量增删改查:找单条语句的重复项,找不出来可以直接遍历整条语句

  • :批量标签
    • collection:要便利的集合
    • item:集合便利过程中的每一个对象,在此属性设置对象名称,后面使用此名称引用对象
    • Separartor:指定批拼接sql字符串的时候,各条语句之间的分割符
    • open:循环拼接字符串后,在整体前面添加的字符串
    • close:循环拼接字符串后,在整体后面添加的字符串
    • index: 在list和array中,index指元素序号;在map中,index指元素key。从0开始自增(相当于数组下标)
/*foreach*/
int insertBatch(List<Student> students);
    <!--insert foreach-->
<!--    collection = "collection"/arg[x]/接口使用@param定义的名称-->
    <insert id="insertBatch" >
        insert into student(name, age)
        <foreach collection="list" item="itemName" open="Values" close=";"  separator=",">
            (name = #{itemName.name},age = #{itemName.age})
        </foreach>
    </insert>
Student student1 = new Student();
student1.setName("111");
student1.setAge(111);
Student student2 = new Student();
student2.setName("222");
student2.setAge(222);
Student student3 = new Student();
student3.setName("333");
student3.setAge(333);


ArrayList<Student> students = new ArrayList<>();
students.add(student1);
students.add(student2);
students.add(student3);

mapper.insertBatch(students);

//        ==>  Preparing: insert into student(name, age) Values (name = ?,age = ?) , (name = ?,age = ?) , 								(name = ?,age = ?) ;
//        ==> Parameters: 111(String), 111(Integer), 222(String), 222(Integer), 333(String), 333(Integer) 
//                <==    Updates: 3

[!IMPORTANT]

  • 传入List类型的时候,mybatis不支持形参名称索引,只能使用如下方式表示传入的列表——放到collection表示列表形参

    • @param
    • arg[0]…
    • list、collection(默认寻找形参列表中的集合)
  • 传入参数的引用使用collection指定的item名称引用,对象列表使用itemName.fildeName传值

[!IMPORTANT]

批量操作存在多条完整的sql语句(一条语句的某个部分重复不算),需要开启允许多条语句执行

url: jdbc:mysql:///database?allowMultiQueries = true
sql片段

提取重复的sql片段:可以很方便的进行批量修改

  • 定义sql片段:提取的sql语句

[!WARNING]

id不能与方法名重复

  • 使用sql片段:
<!--优化:提取公共sql片段-->
<sql id="select">
    select * from student
</sql>
    <select id="queryCondition" resultType="student">
#         select * from student
        <include refid="selectAll"/>

        <where>
            <if test="id !=null">
                and id = #{id}
            </if>
            <if test="name!=null">
                and name = #{name}
            </if>
            <if test="age !=null">
                or age = #{age}
            </if>
        </where>
    </select>
//==>  Preparing: # select * from student # select * from student select * from student where id = ? ;
  • sql片段会使用#来标注上,不会影响功能

mybatis扩展

批量配置mapper包批量扫描配置

[!NOTE]

resources 和java是同级目录,为类路径的根文件夹,类路径从该路径下开始

要求:

  1. MapperXML文件和mapper接口命名相同

  2. 最终打包(package)位置一致,都在mybaits配置的mapper包下:

    • 方式一:xml文件直接放入接口包内

    • 方式二:在resource下创建mapper接口包对应的文件夹结构

      [!WARNING]

      resources目录下不能使用 . 所谓文件夹分割符,只能使用/

  • 单个mapperXML文件扫描的打包目录结构

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

批量包扫描配置(接口所在包)

    <mappers>
<!--                <mapper resource="mapper/studentMapper.xml"/>-->
<!--                <mapper resource="mapper/teacherMapper.xml"/>-->
<!--                <mapper resource="mapper/orderMapper.xml"/>-->
<!--                <mapper resource="mapper/customerMapper.xml"/>-->
<!--                <mapper resource="mapper/dynamicSql.xml"/>-->
                <package name="org.zhuhaihong.mapper"/>
      
        </mappers>

打包目录结构方式一:

配置打包资源(默认不会打包其他文件)

<build>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.*</include>
            </includes>
        </resource>
    </resources>
</build>

image-20240810213855091image-20240810214020615

目录结构方式二:

image-20240810214228961image-20240810214259464

插件:对拼接好后的sql’语句进一步处理

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

分页插件的使用

  • 不添加limit
  • 不实用;结尾
  • 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

依赖:

<dependency>
   <groupId>com.github.pagehelper</groupId>
   <artifactId>pagehelper</artifactId>
   <version>6.1.0</version>
</dependency>

mybatis配置(注意配置顺序)

        <plugins>
<!--                插件名称-->
                <plugin interceptor="com.github.pagehelper.PageInterceptor">
<!--                        指定数据库类型-->
                        <property name="helperDialect" value="mysql"/>
                </plugin>
        </plugins>

使用

List<Student> queryAll();
    <!--分页插件使用测试-->
    <select id="queryAll" resultType="org.zhuhaihong.entity.Student">
#         一定不能写;
        select * from student
    </select>
@Test
public void  testPageHelper() throws IOException {
    SqlSession sqlSession = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-conf.xml")).openSession(true);
    StudentsMapper mapper = sqlSession.getMapper(StudentsMapper.class);

    //先设置分页数据
    PageHelper.startPage(1,5);
    //正常执行
    List<Student> students = mapper.queryAll();
    //将查询数据放入PageInfo实体类(相当于将查询结果交给pageInfo管理)(不是全部查出,而是将sql语句进行拦截处理)
    PageInfo<Student> studentPageInfo = new PageInfo<>(students);

    //相关操作
    System.out.println("总页数:"+studentPageInfo.getPages());
    System.out.println("总条数:"+studentPageInfo.getTotal());
    System.out.println("当前页:"+studentPageInfo.getPageNum());
    System.out.println("当前页数据:"+studentPageInfo.getList());
}
==>  Preparing: select count(0) from ( # 一定不能写; select * from student ) tmp_count
==> Parameters: 
<==    Columns: count(0)
<==        Row: 43
<==      Total: 1
==>  Preparing: # 一定不能写; select * from student LIMIT ?
==> Parameters: 5(Integer)
<==    Columns: id, name, age
<==        Row: 1, hellen, 19
<==        Row: 2, liutao, 20
<==        Row: 3, Liming, 21
<==        Row: 4, dearfriend, 11
<==        Row: 8, hahah, 15
<==      Total: 5
总页数:9
总条数:43
当前页:1
当前页数据:Page{count=true, pageNum=1, pageSize=5, startRow=0, endRow=5, total=43, pages=9, reasonable=false, pageSizeZero=false}
[
Student(Id=1, name=hellen, age=19), 
Student(Id=2, name=liutao, age=20), 
Student(Id=3, name=Liming, age=21), 
Student(Id=4, name=dearfriend, age=11), 
Student(Id=8, name=hahah, age=15)
]

相关操作

/**
 * 当前页
 */
private int pageNum;
/**
 * 每页的数量
 */
private              int      pageSize;
/**
 * 当前页的数量
 */
private              int      size;

/**
 * 由于startRow和endRow不常用,这里说个具体的用法
 * 可以在页面中"显示startRow到endRow 共size条数据"
 * 当前页面第一个元素在数据库中的行号
 */
private long startRow;
/**
 * 当前页面最后一个元素在数据库中的行号
 */
private long endRow;
/**
 * 总页数
 */
private int pages;
/**
 * 前一页
 */
private int prePage;
/**
 * 下一页
 */
private int nextPage;

/**
 * 是否为第一页
 */
private boolean isFirstPage = false;
/**
 * 是否为最后一页
 */
private boolean isLastPage = false;
/**
 * 是否有前一页
 */
private boolean hasPreviousPage = false;
/**
 * 是否有下一页
 */
private boolean hasNextPage = false;

/**
 * 导航页码数
 */
private int navigatePages;
/**
 * 所有导航页号
 */
private int[] navigatepageNums;
/**
 * 导航条上的第一页
 */
private int navigateFirstPage;
/**
 * 导航条上的最后一页
 */
private int navigateLastPage;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值