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开发,提高效率
使用步骤:
- 引入依赖
<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接口中的方法名一致,并保持返回类型一致。
- 创建包
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);
}
解决 => 借助and
和 or
关键字去除掉
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>
本文来自博客园,作者:chuangzhou,转载请注明原文链接:https://www.cnblogs.com/czzz/p/17610156.html