自制笔记 | JavaWeb——MyBatis(持续更新...)


MyBatis是一款优秀的 持久层(dao)框架,用于简化JDBC的开发

官网:https://mybatis.org/mybatis-3/zh/index.html

入门

入门程序:

MyBatis入门程序

① 准备工作(创建springboot工程、数据库表user,实体类User)

User:

public class User { //这里的实体类中成员的名字最好与数据库表中的名字相同,且类型要尽量一致,且最好用包装类
    private Integer id;
    private String name;
    private Short age;
    private Short gender;
    private String phone;
}

② 引入MyBatis的相关依赖,配置MyBatis(数据库连接信息)

application.properties:

#配置数据库的连接信息
#驱动类名称
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#数据库连接的url(localhost前面为协议,后面为端口号,/后面跟的是数据库的名字)
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis
#连接数据库的用户名
spring.datasource.username=root
#连接数据库的密码
spring.datasource.password=123456

③ 编写SQL语句(注解 / XML)

UserMapper:

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

测试类:

@SpringBootTest //springboot整合单元测试的注解
class SpringbootMybatisQuickstartApplicationTests {
    @Autowired //依赖注入
    private UserMapper userMapper;
    
    @Test
    public void testListUser() {
        List<User> userList = userMapper.list();
        userList.stream().forEach(user -> {
            System.out.println(user);
        });
    }
}

配置SQL提示:

image-20240310195324070

注意:需要在IDEA中配置MySQL数据库连接

配置数据库连接

JDBC:

JDBC(Java DataBase Connectivity):就是使用Java语言操作关系型数据库的一套API

image-20240310195341841

本质:

① sun公司官方定义的一套操作所有关系型数据库的规范,即接口
② 各个数据库厂商去实现这套接口,提供数据库驱动jar包
③ 我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类

数据库连接池:

数据库连接池

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

优势:

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

标准接口:DataSource

官方(sun)提供的数据库连接池接口,由第三方组织实现此接口

功能:获取连接

Connection getConnection() throws SQLException;

常见产品:C3P0、DBCP、Druid、Hikari(springboot默认)

Druid(德鲁伊):是阿里巴巴开源的数据连接池项目,功能强大,性能优秀,是Java语言最好的数据库连接池之一

切换Druid数据库连接池:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.8</version>
</dependency>

lombok:

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

注解作用
@Getter / @Setter为所有的属性提供get / set方法
@ToString会给类自动生成易阅读的toString方法
@EqualsAndHashCode根据类所拥有的非静态字段自动重写equals方法和hashcode方法
@Data提供了更综合的生成代码功能(@Getter + @Setter + @ToString + @EqualsAndHashCode)
@NoArgsConstructor为实体类生成无参的构造器方法
@AllArgsConstructor为实体类生成除了static修饰的字段之外带有各参数的构造器方法

依赖:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

注意:Lombok会在编译时,自动生成对应的java代码。我们使用lombok时,还需要安装一个lombok的插件(idea自带)

基础操作

准备工作:

① 准备数据库表emp

② 创建一个新的springboot工程,选择引入对应的起步依赖(mybatis、mysql驱动、lombok)

准备工作2

③ application.properties中引入数据库连接信息

#配置数据库的连接信息
#驱动类名称
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#数据库连接的url(localhost前面为协议,后面为端口号,/后面跟的是数据库的名字)
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis
#连接数据库的用户名
spring.datasource.username=root
#连接数据库的密码
spring.datasource.password=123456

④ 创建对应的实体类Emp(实体类属性采用驼峰命名)

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Emp {
    private Integer id;
    private String username;
    private String password;
    private String name;
    private Short gender;
    private String image;
    private Short job;
    private LocalDate entryDate;
    private Integer deptId;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}

⑤ 准备Mapper接口EmpMapper

@Mapper
public interface EmpMapper {}

1.删除

EmpMapper:

@Mapper
public interface EmpMapper {
    //根据ID删除数据
    @Delete("delete from emp where id = #{id}") //#{}为占位符
    public void delete(Integer id);
    //public int delete(Integer id); delete语句是有返回值的,返回删除的记录数(int),但一般不使用
}

测试类:

@SpringBootTest
class SpringbootMybatisCrudApplicationTests {
    @Autowired
    private EmpMapper empMapper;

    @Test
    public void testDelete() {
        empMapper.delete(16);
        //int delete = empMapper.delete(16);
        //System.out.println(delete);
    }
}

注意:如果mapper接口方法形参只有一个普通类型的参数,#{…}里面的属性名可以随便写,如:#{id}、#{value}

日志输出:

可以在application.properties中,打开mybatis的日志,并指定输出到控制台

#指定mybatis输出日志的位置,输出控制台
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

使用#{}的占位符时,最终这个占位符会被?替代,生成一个预编译SQL,最终这个?会被参数替代

预编译SQL

预编译SQL优势:

① 性能更高

image-20240310195411315

Java发送给MySQL服务器一个sql语句时,会先检查缓存中是否有相同的sql语句,若没有,MySQL会先进行3步操作(缓存),然后再执行SQL;若有,则直接执行SQL。因此若没有预编译SQL,上面的语句会缓存3次,而有了预编译SQL后,由于执行的SQL语句含占位符,所以三条语句都是一致的,因此只需缓存1次,这提高了性能

② 更安全(防止SQL注入)

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

若不采用预编译SQL的方式,用户名为theshy,密码为' or '1' = '1,检验用户登录的sql语句就变为:

select count(*) from emp where username = 'theshy' and password = '' or '1' = '1';

此时条件是永真的,因此会直接判断登录成功

若采用预编译SQL的方式,由于sql语句是带?的占位符,因此即使密码为上面那种情况,也会将这一个密码视作字符串,而不会修改原先的sql语句

参数占位符:

① #{…}

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

使用时机:参数传递,都使用#{…}

② ${…}

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

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

2.新增

因为id是自增的,password具有默认值,且不需要作为参数输入,因此这里的插入字段没有id和password

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);

注意:如果需要的参数比较多,可以进行包装,将类作为参数。占位符对应的名字是类的属性名(驼峰命名)

测试类:

@Test
public void testInsert() {
    //构造员工对象
    Emp emp = new Emp();
    emp.setUsername("Tom");
    emp.setName("汤姆");
    emp.setImage("1.jpg");
    emp.setGender((short)1);
    emp.setJob((short)1);
    emp.setEntryDate(LocalDate.of(2000, 1, 1));
    emp.setCreateTime(LocalDateTime.now());
    emp.setUpdateTime(LocalDateTime.now());
    emp.setDeptId(1);

    //执行新增员工信息操作
    empMapper.insert(emp);
}

主键返回:

在插入数据时,因为主键ID是生成的,所以获取不了,而在有些业务场景是需要获取当前插入数据的主键ID值的

实现:

//新增员工
@Options(useGeneratedKeys = true, keyProperty = "id") //会自动将生成的主键值,赋值给emp对象的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);

3.更新

EmpMapper:

//更新员工
@Update("update emp set username = #{username}, name = #{name}, gender = #{gender}, image = #{image}," + "job = #{job}, entrydate = #{entryDate}, dept_id = #{deptId}, update_time = #{updateTime} where id = #{id}")
public void update(Emp emp);

测试类:

@Test
public void testUpdate() {
    //构造员工对象
    Emp emp = new Emp();
    emp.setId(19);
    emp.setUsername("Tom1");
    emp.setName("汤姆1");
    emp.setImage("1.jpg");
    emp.setGender((short)1);
    emp.setJob((short)1);
    emp.setEntryDate(LocalDate.of(2000, 1, 1));
    emp.setUpdateTime(LocalDateTime.now());
    emp.setDeptId(1);

    //执行更新员工信息操作
    empMapper.update(emp);
}

4.查询

根据ID查询:

EmpMapper:

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

测试类:

@Test
public void testGetById() {
    Emp emp = empMapper.getById(20);
    System.out.println(emp);
}

问题:

MyBatis问题

原因在于,在数据封装中:

① 实体类属性名和数据库查询返回的字段名一致,mybatis会自动封装
② 如果实体类属性名和数据库表查询返回的字段名不一致,不能自动封装

数据封装

解决方案:

① 起别名:在SQL语句,对不一样的列名起别名,别名和实体类属性名一样

@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进行手动结果映射

@Select("select * from emp where id = #{id}")
@Results({
        @Result(column = "dept_id", property = "deptId"),
        @Result(column = "create_time", property = "createTime"),
        @Result(column = "update_time", property = "updateTime")
})
public Emp getById(Integer id);

开启驼峰命名(推荐):如果字段名与属性名符合驼峰命名规则,mybatis会自动通过驼峰命名规则映射

application.properties:

#开启驼峰命名自动映射,即从数据库字段名a_column映射到Java属性名aColumn
mybatis.configuration.map-underscore-to-camel-case=true

条件查询:

EmpMapper:

//条件查询员工
@Select("select * from emp where name like '%${name}%' and gender = #{gender} and " + "entrydate between #{begin} and #{end} order by update_time desc")
public List<Emp> list(String name, Short gender, LocalDate begin, LocalDate end);

这里的name属性使用了$而不是#,原因在于若直接使用#,产生的预编译sql中的?是出现在引号内的,而这是不被允许的,因此没用使用#,而是采用直接拼接的方式

解决方式:

使用mysql中的函数concat进行字符串的拼接

//条件查询员工
@Select("select * from emp where name like concat('%', #{name}, '%') and gender = #{gender} and " + "entrydate between #{begin} and #{end} order by update_time desc")
public List<Emp> list(String name, Short gender, LocalDate begin, LocalDate end);

测试类:

public void testList() {
    List<Emp> list = empMapper.list("张", (short)1, LocalDate.of(2010, 1, 1), LocalDate.of(2020, 1, 1));
    System.out.println(list);
}

参数名说明:

参数名说明

早期直接使用#{}与参数名匹配是匹配不上的,需要加上@Param注解,原因在于编译过程中参数会丢失。而现在由于有编译插件,参数名不会丢失,因此不需要这个注解

XML映射文件

规范:

① XML映射文件的名称与Mapper接口名称一致,并且将XML映射文件和Mapper接口放置在相同包下(同包同名

XML映射文件规范1

② XML映射文件的namespace属性为Mapper接口全限定名一致
③ XML映射文件中sql语句的id与Mapper接口中的方法名一致,并保持返回类型一致

Mapper接口:

@Mapper
public interface EmpMapper {
    public List<Emp> list(String name, Short gender, LocalDate begin, LocalDate end);
}

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.itheima.mapper.EmpMapper">
    <!-- resultType:单条记录所封装的类型   -->
    <select id="list" resultType="com.itheima.pojo.Emp">
        select * from emp where name like '%${name}%' and gender = #{gender} and entrydate between #{begin} and #{end} order by update_time desc
    </select>
</mapper>

二者的使用场景:

使用MyBatis的注解,主要是来完成一些简单的增删改查功能。如果需要实现复杂的SQL功能,建议使用XML来配置映射语句

动态SQL

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

例如,有三个条件,分别为姓名、性别和入职时间(开始-结束),而若我们只输入姓名的条件,正常情况下也应该有查询返回项的(如果有),而若使用之前的方式,若其他两个条件没有传值,会变为null,此时条件永假,因此会没有数据返回

1.<if>

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

示例:动态查询员工信息

<select id="list" resultType="com.itheima.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="begin != null and end != null">
            and entrydate between #{begin} and #{end}
        </if>
    order by update_time desc
</select>

问题:若name属性为空,gender属性不为空,此时where语句后会直接跟随and,导致语法错误

解决:加上<where>标签

<where>:where元素只会在子元素有内容的情况下才插入where语句。而且会自动去除子句的开头的AND或OR

<select id="list" resultType="com.itheima.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="begin != null and end != null">
            and entrydate between #{begin} and #{end}
        </if>
    </where>
    order by update_time desc
</select>

示例:动态更新员工信息

<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="job != null">job = #{job},</if>
        <if test="entryDate != null">entrydate = #{entryDate},</if>
        <if test="deptId != null">dept_id = #{deptId},</if>
        <if test="updateTime != null">update_time = #{updateTime}</if>
    where id = #{id}
</update>

问题:若updateTime属性为空,而前面属性不为空,则会出现最后多了一个逗号的情况,导致语法错误

解决:加上<set>标签

<set>:动态地在行首插入SET关键字,并会删除额外的逗号

<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="job != null">job = #{job},</if>
        <if test="entryDate != null">entrydate = #{entryDate},</if>
        <if test="deptId != null">dept_id = #{deptId},</if>
        <if test="updateTime != null">update_time = #{updateTime}</if>
    </set>
    where id = #{id}
</update>

2.<foreach>

示例:动态删除员工信息

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

属性:

collection:集合名称
item:集合遍历出来的元素 / 项
separator:每一次遍历使用的分隔符
open:遍历开始前拼接的片段
close:遍历结束后拼接的片段

3.<sql><include>

sql片段:

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

示例:

<sql id="commonSelect">
     select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time from emp
 </sql>

<include refid="commonSelect"/>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值