黑马程序员2024JavaWeb Mybatis基础笔记

介绍

MyBatis是一款优秀的持久层框架,用于简化JDBC连接数据库的开发。

通俗讲就是服务器访问数据库的工具

我们以一个emp员工表的增删改操作案例来了解Mybatis的使用。

准备工作

  1. 准备数据库表emp
    CREATE TABLE `emp` (
    `id` int NOT NULL AUTO_INCREMENT COMMENT '员工编号',
    `username` varchar(50) UNIQUE DEFAULT NULL COMMENT '用户名',
    `password` varchar(50) DEFAULT '123456' COMMENT '密码',
    `name` varchar(5) DEFAULT NULL COMMENT '姓名',
    `gender` int DEFAULT NULL COMMENT '性别',
    `job` int DEFAULT NULL COMMENT '职位',
    `entry_date` date DEFAULT NULL COMMENT '入职日期',
    `dept_id` int DEFAULT NULL COMMENT '部门编号',
    `create_time` datetime DEFAULT (now()) COMMENT '创建时间',
    `update_time` datetime DEFAULT (now()) COMMENT '最后操作时间',
    PRIMARY KEY (`id`)
    ) COMMENT='员工表'
  2. 创建一个新的springboot工程,选择引入对应的起步依赖(MyBatis Framework、MySQL Driver、Lombok)
  3. main\resources\application.properties中引入数据连接信息
  4. #驱动类名称,统一模板
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    #数据库连接的url,指定你要连接的某个数据库的url,//localhost:3306/数据库名
    spring.datasource.url=jdbc:mysql://localhost:3306/mybatis
    #连接数据库的用户名,写自己的
    spring.datasource.username=root
    #连接数据库的密码,写自己的
    spring.datasource.password=wl18375873492
    
  5. 创建对应的实体类Emp用于封装员工信息(实体类属性采用驼峰命名)驼峰命名:createTime,字段命名:create_time
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import java.time.LocalDate;
    import java.time.LocalDateTime;
    
    // 在springboot项目创建时添加了Lombok依赖
    @AllArgsConstructor
    @NoArgsConstructor
    @Data
    public class Emp {
        private Integer id;
        private String username;
        private String password;
        private String name;
        private Short gender;
        private Short job;
        private LocalDate entryDate;
        private Short deptId;
        private LocalDateTime createTime;
        private LocalDateTime updateTime;
    }
    
  6. 准备Mapper接口EmpMapper用于写sql语句,后面演示

注意:

  1. 若jdk为11,springboot的起步依赖版本号最好为2.7.5,运行报错时,可以将MyBatis的起步依赖版本改为2.2.2,可能是因为MyBatis版本冲突。
  2. 实体类中的数据类型最好用引用类型Integer,因为引用类型才能赋值为空,默认也为空,后面要用。字段类型要与实体类型对应,其中varchar对应String,date对应LocalDate,datetime对应LocalDateTime。
  3. mapper层就是dao层,Mybatis框架中sql语句是写在Mapper类型接口中的,而@Mapper注解用于声明该接口为Mapper类型接口,同时也声明该接口实现类为Bean对象,启动时自动创建该接口的实现类并交给IOC容器管理。

配置SQL提示

1、选中SQL语句右键->Show Context Actions->Inject language or reference->MySQL。

作用:SQL语法检查

2、导入数据库。

作用:有SQL提示

2377d6cf0823461f9aa2148b457cfc4b.png

bff1a51f3b304eeaa1e0586c29d9868f.png

删除

// 根据员工id删除员工信息

EmpMapper接口中delete()方法的实现

@Mapper
public interface EmpMapper {

    // 根据传入的id删除员工
    @Delete("delete from emp where id = #{id}")
    void deleteById(Integer id);

}

测试类测试

@SpringBootTest
class Test02ApplicationTests {
    // 前面说了添加@Mapper注解后,启动时会自动创建该接口实现类并交给IOC容器管理
    // 以自动注入方式创建EmpMapper接口实现类对象
    @Autowired
    EmpMapper empMapper;

    @Test
    void testDelete(){
        // 调用该对象的删除方法,删除id为27的员工
        empMapper.deleteById(27);
    }
}

注意:

1、#{..}为参数占位符,里面的参数最好与方法的形参名相同,表示将方法形参值传入进去。

2、返回值类型若为Integer,则表示影响的记录数。

预编译SQL

Mybatis日志

配置mybatis的日志,指定输出到控制台,配置到resources\application.properties文件中

mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

普通编译SQL

参数占位符${…},每次运行都要先编译,编译时,${…}参数占位符用传入的参数代替并拼接到SQL语句中,再执行SQL。

如:select*from emp where id = ${id}

编译时,用传入参数直接代替${id}拼接到SQL语句中,若id为 1 and name = ‘张三丰’,拼接后就为select*from emp where id = 1 and name = ‘张三丰’ 违背了SQL根据id查询。

SQL注入

SQL注入是通过操作输入的数据来修改事先定义好的SQL语句,以达到执行代码对服务器的攻击的方法。

如:账号登录

select count(*) from emp where username = ${username} and password = ${password}

账号随便写,密码传入 abc or 1,编译拼接为

select count(*) from emp where username = aaa and password = abc or 1

这样就会导致无论怎样都会登录成功!

普通编译SQL使用${…}就可能会造成SQL注入。

预编译SQL

参数占位符#{…},预编译SQL会进行先编译,#{…}参数占位符用?,再传入参数代替?来执行,只需编译一次。

如:@Delete("delete from emp where id = #{id}")

        int delete(Integer id);

先预编译为delete from emp where id = ?,只编译一次

再传入参数id代替?来执行SQL

特点:

1、提升性能(只编译一次)。

 2、防止SQL注入,预编译时#{…}用?代替,?只被认定为一个参数,无论怎么写系统都会把认定为一个参数,而不是认定为SQL片段,所以不会造成SQL注入。​​​​​​​

参数占位符

#{…}

执行SQL时,会将#{…}替换为?,生成预编译SQL,会自动设置参数值

使用时机:参数传递,都使用#{…},几乎都使用#{…}占位符

${…}

拼接SQL,直接将参数拼接在SQL语句中,存在SQL注入问题

使用时机:如果对表名、列表进行动态设置时使用

新增

EmpMapper接口实现

    // 将主键id返回并封装到实体类Emp对象id属性中,看自己需要
    @Options(keyProperty = "id",useGeneratedKeys = true) 
    @Insert("insert into emp(id, username, password, name, gender, job, entry_date, dept_id, create_time, update_time) VALUES (" +
            "null,#{username},#{password},#{name},#{gender},#{job},#{entryDate},#{deptId},#{createTime},#{updateTime})")
    void insert(Emp emp);

测试类测试

    @Autowired
    EmpMapper empMapper;// 获取EmpMapper接口实现类对象

    @Test
    void testInsert(){
        String username = "zhansanfeng";
        String password = "123456";
        String name = "张三丰";
        Short gender = 1;
        Short job = 1;
        LocalDate entryDate = LocalDate.of(2015,9,15);
        Short deptId = 2;
        LocalDateTime createTime = LocalDateTime.now();
        LocalDateTime updateTime = LocalDateTime.now();
        // 将用户信息封装到实体类对象中
        Emp emp = new Emp(null,username,password,name,gender,job,entryDate,deptId,createTime,updateTime);
        // 将该实体对象插入
        empMapper.insert(emp);
    }

注意:

1、方法形参最好传递Emp对象,SQL中#{…}可以自动获取形参对象的属性。

2、对象的属性是驼峰命名,如:deptId,所以获取对象属性时别写错了。

3、表字段与对象属性类型要匹配,其中:date对应LocalDate,datetime对应LocalDateTime。

4、若方法的返回值类型为整数,则返回值为插入的记录数。

5、由于id为主键且自增,所以直接赋值为null就行,不用管它。

新增(主键返回)

描述:在数据添加成功后,需要获取插入数据库数据的主键。

如:添加套餐数据时,还需要维护套餐菜品关系表数据。因为关系表的维护需要主表的主键。

在SQL语句上添加@Options(keyProperty = "id",useGeneratedKeys = true)       

其中keyProperty属性值代表将主键封装到实体类中哪个属性,useGeneratedKeys属性值为true代表需要主键返回。

更新

思路:将更新的用户信息封装到实体类对象,再将实体类对象传入进行用户更新。

Mapper接口实现

// 根据主键修改员工信息
@Update("update emp set username = #{username},name = #{name},gender = #{gender}," +
  "job = #{job},entry_date = #{entryDate},dept_id = #{deptId},update_time " +
  "= #{updateTime} where id = #{id};")
void update(Emp emp);

测试类测试

    @Autowired
    EmpMapper empMapper;// 获取EmpMapper接口实现类对象

    @Test
    void testUpdate(){
        // 一定要封装主键id,因为是根据id更新的
        Integer id = 29;
        String username = "xvshiyou";
        String password = "12345";
        String name = "许世友";
        Short gender = 1;
        Short job = 2;
        LocalDate entryDate = LocalDate.of(2012,5,6);
        Short deptId = 3;
        LocalDateTime createTime = LocalDateTime.now();
        LocalDateTime updateTime = LocalDateTime.now();
        // 封装用户的更新信息
        Emp emp = new Emp(29,username,password,name,gender,job,entryDate,deptId,createTime,updateTime);
        // 更新
        empMapper.update(emp);
    }

注意:

1、形参为修改后的员工emp,emp的id一定要正确赋值,因为是根据id修改员工信息

2、缺点:不能修改单个信息,每一次都是更新用户所有信息,如当只修改name属性时,其余属性就默认为null,则更新时,这些属性就会被修改为null,不符合业务逻辑。

3、这里只是简了Mybatis的update语句和更新逻辑,之后再进行优化。

查询

EmpMapper接口实现

    // 查询所有员工信息
    @Select("select * from emp")
    List<Emp> list();

    // 根据主键id查询员工
    @Select("select * from emp where id = #{id}")
    Emp listById(Integer id);

测试类测试与上面一样,只需调用该方法并接收其返回值即可。

注意:

1、Select语句会自动将查询的信息封装到返回值里面。

2、查询所有员工,肯定有多条员工信息,所以用Emp的集合进行封装并返回。

3、根据主键id查询,id是唯一的,若id存在,查询的员工记录只有一条,所以只需Emp进行封装并返回即可。

数据封装

前面讲了Select语句会将查询的信息自动封装到返回值Emp实体类对象中

表的字段:id、username、password、name、dept_id、entry_date ...

实体类属性:id、username、password、name、deptId、entryDate ...

我们理想中Select语句查询的字段id值封装到属性id中,字段username值封装到属性username中,字段dept_id值封装到属性deptId中。

Mybatis如何实现呢?

  • 实体类属性名和数据库查询返回的字段名一致,不区分大小写,mybatis会自动封装。即表字段id自动封装到实体类对象属性id中。
  • 如果实体类属性名和数据库返回的字段名不一致,不能自动封装则属性值为默认值。即表字段dept_id与实体类属性deptId不一致,无法自动封装,则deptId就为默认值null。
  • 属性名为驼峰命名deptId,字段名为下划线dept_id,不一致,在封装时要注意。

解决:

方案一:给字段取别名,让别名与实体属性保持一致。   

    // 查询所有员工信息
    @Select("select id, username, password, name, gender, job, entry_date entryDate, " +
            "dept_id deptId, create_time createTime, update_time updateTime from emp")
    List<Emp> list();

方案二:通过@Results,@Result注解手动映射封装。

    @Results({
            @Result(column = "dept_id",property = "deptId"),
            @Result(column = "entry_date",property = "entryDate"),
            @Result(column = "create_time",property = "createTime"),
            @Result(column = "update_time",property = "updateTime")
    })
    // 查询所有员工信息
    @Select("select id, username, password, name, gender, job, entry_date, " +
            "dept_id, create_time, update_time from emp")
    List<Emp> list();

其思路就是将不能自动封装的字段如dept_id,通过@Result注解手动映射封装。

其中column属性值为字段名,property属性值为要封装到的属性名。

方案三:开启mybatis的驼峰命名自动映射开关 --- a_cloumn ----> aCloumn

开启mybatis的驼峰命名自动映射开关,配置到resources\application.properties文件中

mybatis.configuration.map-underscore-to-camel-case=true

配置后除了字段名与属性名会自动封装,同时字段名下划线命名dept_id也会自动封装到属性驼峰命名deptId中。

前提保证属性名为驼峰命名,字段名为下划线命名,才能自动映射封装。

XML映射文件

两种配置SQL语句方式:注解和XML映射文件​​​​​​​

配置XML映射文件规范

  • XML映射文件的名称与Mapper接口名称一致,并且将XML映射文件和Mapper接口放置在相同包下(同包同名)。注意resources下的多级包形式为com/example/mapper,java下的多级包才是com.example.mapper
  • 493d882d53db4f0c8e03b6607a2cb8c6.png​​​​​​​
  • XML映射文件中配置约束,在官网找。
  • XML映射文件的namespace属性为Mapper接口全限定名一致。绑定你的EmpMapper接口

  • XML映射文件sql语句的id与Mapper接口中的方法名一致即标识某个方法的sql语句,Select语句还要保持返回类型一致。指定返回类型的全类名,若为集合,只需指定元素的全类名。

  • 简单的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.example.mapper.EmpMapper">

<select id="list" resultType="com.example.pojo.Emp">
    select * from emp
</select>

</mapper>

1、最上面的是配置约束;

2、<mapper>为根标签,所有的sql语句写在该标签内,namespace属性绑定你的EmpMapper接口;

3、<select>标签为select语句,id属性为你要添加该sql语句的方法名,resultType属性为返回值类型,若为集合,只需写集合元素的全类名。<select>标签中写你的select语句,注意没有引号!

MybatisX

MybatisX是一款基于IDEA的快速开发Mybatis的插件,为效率而生。

安装:在Setting中Plugins里面搜索并安装。看个人需要!

功能:能够快速定位到XML中sql语句。

到底是用sql注解,还是xml映射文件?

总结:简单sql语句使用注解,复杂sql语句使用XML映射文件。

<if>

动态SQL

随着用户的输入或外部条件的变化而变化的SQL语句,我们称为动态SQL。

如:单条件查询,多条件查询,单属性修改,多属性修改。​​​​​​​

<if>

<if>:用于判断条件是否成立。使用test属性进行条件判断,如果条件为true,则拼接SQL。test属性中的name、gender ... 是传入的形参,不是表字段!

<where>:当name为null时,拼接的sql语句就为select * from emp where and ...,多一个and会导致编译错误。<where>可以用来代替SQL语句中的where关键字,只会在子元素中有内容时才插入where子句。而且会自动去除句子中多余的AND或OR。

<set>:在update语句中可以代替set关键字,能够自动去除句子中多余的逗号。

案例:根据姓名模糊匹配、性别、入职的区间日期进行查询,可只根据某个或某几个条件查询。

    <select id="listWhere" resultType="com.example.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="start != null and end != null">
                and entry_date between #{start} and #{end}
            </if>
        </where>
        order by update_time desc
    </select>

1、当用户只根据name条件查询时,name形参就不为null返回true拼接sql片段,其余形参默认为null返回false不拼接sql片段,此时sql语句为select * from emp where name like concat("%",#{name},"%"),实现了单个条件的查询!

2、当然用户可以输入多个条件就拼接多个sql片段,实现多条件查询!同理也可以通过<if>和<set>实现单属性修改和多属性修改。

<foreach>

<foreach>:用来遍历集合元素的,用于批量操作。

<foreach>:有四个属性:

  • collection是要遍历的集合。
  • item是遍历出来的元素,自己命名。
  • separator是每个元素的间隔符
  • open是遍历前拼接的符号。
  • close是遍历后拼接的符号。

案例:根据多个员工id进行批量删除员工

如:删除id为11,12,13,14的员工,sql语句:

delete from emp where id in (11,12,13,14)

<foreach>实现

思路:对这些id封装到ids集合中进行遍历,遍历前 ( 符号,遍历出的每个元素为id,每一个元素以 , 分割,遍历后 ) 符号 。

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

测试

    @Autowired
    EmpMapper empMapper;

    @Test
    void testDelete(){
        List<Integer> ids = new ArrayList<>();
        ids.add(11);
        ids.add(12);
        ids.add(13);
        ids.add(14);
        empMapper.deleteByIds(ids);
    }

预编译:delete from emp where id in (?,?,?,?)

再代入id:11,12,13,14

<sql><include>

两个标签类似于函数的定义和调用

  • <sql>:用于定义可重用的sql片段(函数),其中id属性值类似于函数名,通过id调用。
<sql id="commonSelect">
 select id, username, password, name, gender, job, entry_date, dept_id,
        create_time, update_time from emp
</sql>
  • <include>:用于调用<sql>标签中的sql语句。其中refid属性值为要调用的<sql>标签的id
<include refid="commonSelect"/>  

  • 44
    点赞
  • 54
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值