Springboot - mybatis

入门

1.准备工作(创建springboot工程,数据库表user,实体类user)
2.引入Mybatis 的相关依赖,配置Mybatis
3.编写SQL语句(注解/xml)

创建工程时直接引入mybatis依赖:

dao层编写mapper:

package com.chuangzhou.dao;

import com.chuangzhou.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;

@Mapper  //在运行时,会自动生成该接口的实现类对象(代理对象),并且将对象交给IOC容器管理
public interface UserMapper {
    //查询全部用户信息
    @Select("select * from user")
    public List<User> list();
}

application.properties 配置数据库连接信息:

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.184.152:3306/mybatis
spring.datasource.username=root
spring.datasource.password=123456

运行springboot的测试类:

package com.chuangzhou;

import com.chuangzhou.dao.UserMapper;
import com.chuangzhou.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
class SpringbootMybatisQuickstartApplicationTests {

	@Autowired
	private UserMapper userDao;

	@Test
	public void listUser(){
		List<User> list = userDao.list();
		System.out.println(list);
	}

}

配置SQL提示:

配置完毕后在@Select 注解中写SQL 时会进行语法的推荐:

Mybatis vs JDBC

jdbc:

  • 硬编码
  • 封装繁琐
  • 频繁的创建并释放资源,性能降低

Springboot + Mybatis:

  • 连接信息可配置化
  • 连接池
  • 自动将查询结果封装到JavaBean

数据库连接池

  • 是一个容器,负责分配、管理数据库连接
  • 它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个
  • 释放空闲时间超过最大空闲时间的连接,来避免因为没有释放连接而引起的数据库连接遗漏

优势:

  • 资源重用
  • 提升系统响应速度
  • 避免数据库连接遗漏

常见产品:

  • Hikari(springboot 默认)
  • Druid(阿里巴巴开源)

Lombok

是一个实用的JAVA类库,能通过注解的形式自动生成构造器,如getter/setter、equals、hashcode、toString等方法,并可以自动化生成日志变量,简化JAVA开发,提高效率

使用步骤:

  1. 引入依赖
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

2.@data 等价于
@Getter
@Setter
@ToString
@EqualsAndHashCode

package com.chuangzhou.pojo;

import lombok.*;
import org.springframework.beans.factory.annotation.Autowired;

//@Getter
//@Setter
//@ToString
//@EqualsAndHashCode

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Integer id;
    private String name;
    private Short age;
    private Short gender;
    private String phone;

Mybatis 基础操作

删除

根据ID删除员工:

package com.chuangzhou.mapper;

import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface EmpMapper {

    @Delete("delete from emp where id = #{id}")
    public int deleteEmp(Integer id);
}

开启mybatis执行日志到控制台:

# 开启mybatis 执行日志到控制台
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

执行后查看日志发现被转换为预编译SQL:

预编译SQL优势:

  • 性能更高
  • 更安全(防止SQL注入)

为什么性能更高?

需要指出的是在mysql中如果使用 $ 占位符,那么将不再使用预编译的SQL,而是字符串拼接到SQL语句中,如:

@Mapper
public interface EmpMapper {

    @Delete("delete from emp where id = ${id}")
    public int deleteEmp(Integer id);
}

新增

新增一条员工数据:

public interface EmpMapper {
    //新增一条数据
    @Insert("INSERT INTO emp (username, name, gender, image, job, entrydate,dept_id, create_time, update_time) " +
            "VALUES (#{username},#{name},#{gender},#{image},#{job},#{entrydate},#{deptId},#{createTime},#{updateTime})")
    public void insert(Emp emp);
}

新增数据时返回主键ID:

业务场景:多对多关系插入数据时,比如套餐和菜品是多对多的关系,新增一个套餐时,要把套餐的ID和菜品的ID插入到中间表,此时就需要使用新增数据时返回主键ID

public interface EmpMapper {
    //新增一条数据,并返回主键封装到ID 属性
    @Options(useGeneratedKeys = true, keyProperty = "id")
    @Insert("INSERT INTO emp (username, name, gender, image, job, entrydate,dept_id, create_time, update_time) " +
            "VALUES (#{username},#{name},#{gender},#{image},#{job},#{entrydate},#{deptId},#{createTime},#{updateTime})")
    public void insert(Emp emp);
}

测试类:

package com.chuangzhou;

import com.chuangzhou.mapper.EmpMapper;
import com.chuangzhou.pojo.Emp;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.time.LocalDate;
import java.time.LocalDateTime;

@SpringBootTest
class SpringbootMybatisCrudApplicationTests {


    @Autowired
    private EmpMapper empMapper;

    @Test
    void testInsertEmp() {
        Emp e = new Emp();
        e.setUsername("Tom1");
        e.setName("汤姆1");
        e.setGender((short) 1);
        e.setImage("1.jpg");
        e.setJob((short)2);
        e.setEntrydate(LocalDate.of(2023,8,9));
        e.setDeptId(1);
        e.setCreateTime(LocalDateTime.now());
        e.setUpdateTime(LocalDateTime.now());
        empMapper.insert(e);
        System.out.println(e.getId());
    }
}

useGeneratedKeys = true: 是否返回主键
keyProperty = "id": 将返回的主键封装到JavaBean的该属性

更新

mapper:

@Mapper
public interface EmpMapper {
    //更新一条数据
    @Update("update emp set username = #{username}, name = #{name},gender = #{gender},image= #{image}," +
            "job = #{job},entrydate = #{entrydate},dept_id= #{deptId},create_time = #{createTime},update_time=#{updateTime} " +
            "where id = #{id}")
    public void update(Emp emp);
}

测试类:

package com.chuangzhou;

import com.chuangzhou.mapper.EmpMapper;
import com.chuangzhou.pojo.Emp;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.time.LocalDate;
import java.time.LocalDateTime;

@SpringBootTest
class SpringbootMybatisCrudApplicationTests {


    @Autowired
    private EmpMapper empMapper;

    @Test
    void testUpdateEmp() {
        Emp e = new Emp();
        e.setId(18);
        e.setUsername("Tom2");
        e.setName("汤姆2");
        e.setGender((short) 1);
        e.setImage("1.jpg");
        e.setJob((short)2);
        e.setEntrydate(LocalDate.of(2023,8,9));
        e.setDeptId(1);
        e.setCreateTime(LocalDateTime.now());
        e.setUpdateTime(LocalDateTime.now());
        empMapper.update(e);
    }

}

需要注意的是SQL中的占位符需要与javabean的属性名一样,sql中的字段要与数据库表的字段名一样

根据ID查询

package com.chuangzhou.mapper;

import com.chuangzhou.pojo.Emp;
import org.apache.ibatis.annotations.*;

@Mapper
public interface EmpMapper {


    //根据ID查询一条数据
    @Select("select * from emp where id = #{id}")
    public Emp getById(Integer id);
}

发现最后三个字段没有封装上:

那是因为:

解决方式:

    //方法一:起别名
    @Select("select id, username, password, name, gender, image, job, entrydate, " +
            "dept_id deptId, create_time createTime, update_time updateTime from emp where id = #{id}")
    public Emp getById(Integer id);

    //方法二:不常用了解即可,比较繁琐
    @Results({
            @Result(column = "dept_id", property = "deptId"),
            @Result(column = "create_time", property = "createTime"),
            @Result(column = "update_time", property = "updateTime")
    })
    @Select("select * from emp where id = #{id}")
    public Emp getById(Integer id);

   //方法三:application.properties 中开启配置 => 
   //开启mybatis 驼峰命名自动映射,最常用
   //mybatis.configuration.map-underscore-to-camel-case=true

条件查询

前置只是: 预编译SQL 占位符?不能出现在字符串中

mapper:

    @Select("select * from emp where name like '%${name}%' and gender =#{gender} and entrydate between #{startData} " +
            "and  #{endData} order by update_time desc")
    public List<Emp> getEmpByCondition(String name, Short gender, LocalDate startData, LocalDate endData);

此写法存在的问题:
预编译SQL,?占位符不能出现在 引号中,因此可以使用 '%${name}%' ,写法相当于字符串拼接,但是存在SQL注入,性能低等问题

怎么解决?采用mysql的 concat函数

因此正确写法:

    @Select("select * from emp where name like concat('%',#{name},'%') and gender =#{gender} and entrydate between #{startData} " +
            "and  #{endData} order by update_time desc")
    public List<Emp> getEmpByCondition(String name, Short gender, LocalDate startData, LocalDate endData);

生成的SQL:

XML 映射文件

规范:

  • XML映射文件的名称与Mapper接口名称一致,并且将XML映射文件和Mappe接口放置在相同包下(同包同名)
  • XML映射文件的namespace属性为Mapper接口全限定名一致
  • XML映射文件中sql语句的id与Mapper接口中的方法名一致,并保持返回类型一致。
  1. 创建包

2.新建xml 文件并输入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">

<mapper namespace="com.chuangzhou.mapper.EmpMapper">
    <!--    resultType:单条记录封装的类型-->
    <select id="getEmpByCondition" resultType="com.chuangzhou.pojo.Emp">
        select *
        from emp
        where name like concat('%', #{name}, '%')
          and gender = #{gender}
          and entrydate between #{startData} and #{endData}
        order by update_time desc
    </select>
</mapper>

插件介绍:mybatisX

作用:

动态SQL

if

用于判断条件是否成立。使用test属性进行条件判断,如果条件为true,则拼接SQL

查询条件:

    @Test
    void testgetEmpByCondition() {
        List<Emp> l = empMapper.getEmpByCondition("张",  null, null, null);
        System.out.println(l);
    }

可以看到查询条件的 gender 和 entrydate 没有传值但是sql中还是会出现相关条件,就会导致查询到错误的结果:

此时可以借助mybatis 动态SQL,if 判断来解决

<mapper namespace="com.chuangzhou.mapper.EmpMapper">
    <select id="getEmpByCondition" resultType="com.chuangzhou.pojo.Emp">
        select *
        from emp
        where
            <if test="name != null">
                name like concat('%', #{name}, '%')
            </if>
            <if test="gender != null">
                and gender = #{gender}
            </if>
            <if test="startData != null and endData!= null">
                and entrydate between #{startData} and #{endData}
            </if>
        order by update_time desc
    </select>
</mapper>

result: 成功去除掉没有值的sql条件

此时还存在一个问题,当sql语句中的第一个条件没有传入值时, 会生成错误的SQL语句

查询条件:

    @Test
    void testgetEmpByCondition() {
        List<Emp> l = empMapper.getEmpByCondition(null, (short)1, null, null);
        System.out.println(l);
    }

解决 => 借助 标签可以将多余的 andor 关键字去除掉

mapper:

<mapper namespace="com.chuangzhou.mapper.EmpMapper">
    <select id="getEmpByCondition" resultType="com.chuangzhou.pojo.Emp">
        select *
        from emp
        <where>
            <if test="name != null">
                name like concat('%', #{name}, '%')
            </if>
            <if test="gender != null">
                and gender = #{gender}
            </if>
            <if test="startData != null and endData!= null">
                and entrydate between #{startData} and #{endData}
            </if>
        </where>
        order by update_time desc
    </select>
</mapper>

查询结果:

set

需求:修改id等于18的员工信息,不修改image、job、entrydate等信息

test:

     @Test
    void testUpdateEmp() {
        Emp e = new Emp();
        e.setId(18);
        e.setUsername("Tom3");
        e.setName("汤姆3");
        e.setGender((short)4);
        e.setCreateTime(LocalDateTime.now());
        e.setUpdateTime(LocalDateTime.now());
        empMapper.update(e);
    }

执行后发现:

image、job、entrydate 被修改为null ,这就是 注解方式存在的问题:

mybatis 生成的SQL:

使用动态SQL 来避免:

遇到的问题: if 标签的 test 属性用了 数据库的名字

    <update id="update2">
        update emp
        set
        <if test="username != null">username = #{username},</if>
        <if test="name != null">name = #{name},</if>
        <if test="gender != null">gender = #{gender},</if>
        <if test="image != null">image = #{image},</if>
        <if test="entrydate != null">entrydate = #{entrydate},</if>
        <if test="dept_id != null">dept_id = #{deptId},</if>
        <if test="create_time != null">create_time = #{createTime},</if>
        <if test="update_time != null">update_time = #{updateTime}</if>
        <where>
            <if test="id != null">id = #{id}</if>
        </where>
    </update>

报错:

Caused by: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'dept_id' in 'class com.chuangzhou.pojo.Emp'

因此需要注意的是当javabean属性和数据库属性不一致的时候,在使用动态标签if时test属性要使用javabean的属性名称
而不是数据库的字段名

正确写法:

    <update id="update2">
        update emp
        set
        <if test="username != null">username = #{username},</if>
        <if test="name != null">name = #{name},</if>
        <if test="gender != null">gender = #{gender},</if>
        <if test="image != null">image = #{image},</if>
        <if test="entrydate != null">entrydate = #{entrydate},</if>
        <if test="deptId != null">dept_id = #{deptId},</if>
        <if test="createTime != null">create_time = #{createTime},</if>
        <if test="updateTime != null">update_time = #{updateTime}</if>
        <where>
            <if test="id != null">id = #{id}</if>
        </where>
    </update>

此时还存在一个问题,当只更新一个条件时 不使用 <set> 标签,会有多余的 , 号错误:

    @Test
    void testUpdateEmp() {
        Emp e = new Emp();
        e.setId(17);
        e.setUsername("Tom5555");
        empMapper.update2(e);

修改mapper.xml

    <!--    动态更新-->
    <update id="update2">
        update emp
        <set>
            <if test="username != null">username = #{username},</if>
            <if test="name != null">name = #{name},</if>
            <if test="gender != null">gender = #{gender},</if>
            <if test="image != null">image = #{image},</if>
            <if test="entrydate != null">entrydate = #{entrydate},</if>
            <if test="deptId != null">dept_id = #{deptId},</if>
            <if test="createTime != null">create_time = #{createTime},</if>
            <if test="updateTime != null">update_time = #{updateTime}</if>
        </set>
        <where>
            <if test="id != null">id = #{id}</if>
        </where>
    </update>

更新成功:

forearch

需求:根据id 批量删除员工信息

mapper 接口:

package com.chuangzhou.mapper;

import com.chuangzhou.pojo.Emp;
import org.apache.ibatis.annotations.*;

import java.time.LocalDate;
import java.util.List;

@Mapper
public interface EmpMapper {
    public void deleteByIds(List<Integer> ids);
}

mapper.xml:

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

删除成功:

SQL片段

<sql>: 定义可重用的SQL片段
<include>: 通过属性refid,指定包含的sql片段

    <select id="getEmpByCondition" resultType="com.chuangzhou.pojo.Emp">
        select id,username,password,name,gender,image,job,entrydate,dept_id,create_time,update_time
        from emp
        <where>
            <if test="name != null">
                name like concat('%', #{name}, '%')
            </if>
            <if test="gender != null">
                and gender = #{gender}
            </if>
            <if test="startData != null and endData!= null">
                and entrydate between #{startData} and #{endData}
            </if>
        </where>
        order by update_time desc
    </select>

可以优化为:

    <sql id="selectCommend">
        select id,
               username,
               password,
               name,
               gender,
               image,
               job,
               entrydate,
               dept_id,
               create_time,
               update_time
        from emp
    </sql>
    <select id="getEmpByCondition" resultType="com.chuangzhou.pojo.Emp">
        <include refid="selectCommend"></include>
        <where>
            <if test="name != null">
                name like concat('%', #{name}, '%')
            </if>
            <if test="gender != null">
                and gender = #{gender}
            </if>
            <if test="startData != null and endData!= null">
                and entrydate between #{startData} and #{endData}
            </if>
        </where>
        order by update_time desc
    </select>

posted @ 2023-08-06 21:57  chuangzhou  阅读(23)  评论(0编辑  收藏  举报