Java Web实战(四)Web后端之MyBatis-基础用法详解

  • MyBatis是一款优秀的 持久层 框架,用于简化JDBC的开发
  • MyBatis本是 Apache的一个开源项目iBatis,2010年这个项目由apache移到了google code,并且改名为MyBatis。2013年11月迁移到Github。
  • 官网: https://mybatis.org/mybatis-3/zh/index.html

1. 使用MyBatis

创建 Springboot 项目
勾选 MyBatis framework 和 Mysql connector 依赖
准备数据库和数据库表

create table user(
    id int unsigned primary key auto_increment comment 'ID',
    name varchar(100) comment '姓名',
    age tinyint unsigned comment '年龄',
    gender tinyint unsigned comment '性别, 1:男, 2:女',
    phone varchar(11) comment '手机号'
) comment '用户表';

insert into user(id, name, age, gender, phone) VALUES (null,'白眉鹰王',55,'1','18800000000');
insert into user(id, name, age, gender, phone) VALUES (null,'金毛狮王',45,'1','18800000001');
insert into user(id, name, age, gender, phone) VALUES (null,'青翼蝠王',38,'1','18800000002');
insert into user(id, name, age, gender, phone) VALUES (null,'紫衫龙王',42,'2','18800000003');
insert into user(id, name, age, gender, phone) VALUES (null,'光明左使',37,'1','18800000004');
insert into user(id, name, age, gender, phone) VALUES (null,'光明右使',48,'1','18800000005');

书写数据接收实体

public class User {
    private Integer id;
    private String name;
    private short age;
    private short gender;
    private String phone;
    // getter setter constructor toString
}

配置连接信息

# 数据库连接信息 - 四要素
spring.datasource.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver
spring.datasource.url=jdbc:mysql://localhost:3306/db_mybatis
spring.datasource.username=root
spring.datasource.password=root226

编写 Mapper 类, 定义 Select 函数

@Mapper
public interface UserMapper {
    // 在运行时,会自动生成资接口的实现类对象(代理对象),并且将该对象交给TOC容器营理
    @Select("select * from user")
    public List<User> listAll();
}

编写Test类, 测试 MyBatis 是否正常工作

1-1. JDBC介绍

Java DataBase Connectivity, 是使用Java语言操作关系型数据库的一套API. 它只提供一些接口, 也即sun公司官方定义的一套操作所有关系型数据库的规范, 各个数据库厂商(例如MySQL, Oracle, SqlServer)去实现这套接口, 提供数据库驱动jar包。我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。

JDBC的所有操作均在Java代码中完成, 第一是注册驱动, 例如MySQL, 二是配置连接属性, 包括url/name/password等, 三是连接数据库, 四是单独配置属性和SQL语句并完成数据操作, 最后释放资源. 如图所示, 有三大缺点:
在这里插入图片描述

一是数据库连接属性与数据处理相互独立, 然而它们均放在了代码中, 在数据库连接属性改变时, 还需要修改代码, 难以维护.
二是数据处理过程过于繁琐.
三是每次要与数据库交互都需要连接和释放, 影响性能.

在 MyBatis 框架下, 有如下处理:
一是数据库连接的四要素被放在配置文件, 是全局属性, 当数据库更换时只需要更改配置, 同时, 数据库连接(con, cursor)交由数据库连接池管理, 可重用, 效率高;
二是定义了数据实体, 通过数据实体的各个字段接收查询返回的数据;
三是实现了 Mapper 注解, 在Mapper接口中定义函数并与SQL操作进行绑定, 另外Mapper接口遵循依赖注入机制, 在IOC容器中会存在一个Mapper接口实现类的对象, 因此在其它地方的Java代码中定义完成后即可使用其中的函数。

1-2. 数据库连接池

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

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

标准接口:DataSource
官方(sun)提供的数据库连接池接口, 第三方组织可以实现此接口以构造适合特性的数据库连接池, 常见的有C3P0, DBCP, Druid, Hikari(Springboot default). 后两个使用居多, 其中Druid连接池是阿里巴巴开源的数据库连接池项目, 功能强大, 性能优秀, 是Java语言最好的数据库连接池之一.
更换连接池只需要更改pom.xml

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

1-3. Lombook

在定义数据实体时, 除了自己定义属性字段, 其他的getter, setter, constructor, toString等等都还要自己去写, 虽然可以去生成, 但这些重复性的代码显得臃肿.

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

引入依赖:

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

2. mybatis 基础

准备工作:

  1. SQL数据
-- 部门管理
create table dept(
    id int unsigned primary key auto_increment comment '主键ID',
    name varchar(10) not null unique comment '部门名称',
    create_time datetime not null comment '创建时间',
    update_time datetime not null comment '修改时间'
) comment '部门表';

insert into dept (id, name, create_time, update_time) values(1,'学工部',now(),now()),(2,'教研部',now(),now()),(3,'咨询部',now(),now()), (4,'就业部',now(),now()),(5,'人事部',now(),now());



-- 员工管理
create table emp (
  id int unsigned primary key auto_increment comment 'ID',
  username varchar(20) not null unique comment '用户名',
  password varchar(32) default '123456' comment '密码',
  name varchar(10) not null comment '姓名',
  gender tinyint unsigned not null comment '性别, 说明: 1 男, 2 女',
  image varchar(300) comment '图像',
  job tinyint unsigned comment '职位, 说明: 1 班主任,2 讲师, 3 学工主管, 4 教研主管, 5 咨询师',
  entrydate date comment '入职时间',
  dept_id int unsigned comment '部门ID',
  create_time datetime not null comment '创建时间',
  update_time datetime not null comment '修改时间'
) comment '员工表';

INSERT INTO emp
	(id, username, password, name, gender, image, job, entrydate,dept_id, create_time, update_time) VALUES
	(1,'jinyong','123456','金庸',1,'1.jpg',4,'2000-01-01',2,now(),now()),
	(2,'zhangwuji','123456','张无忌',1,'2.jpg',2,'2015-01-01',2,now(),now()),
	(3,'yangxiao','123456','杨逍',1,'3.jpg',2,'2008-05-01',2,now(),now()),
	(4,'weiyixiao','123456','韦一笑',1,'4.jpg',2,'2007-01-01',2,now(),now()),
	(5,'changyuchun','123456','常遇春',1,'5.jpg',2,'2012-12-05',2,now(),now()),
	(6,'xiaozhao','123456','小昭',2,'6.jpg',3,'2013-09-05',1,now(),now()),
	(7,'jixiaofu','123456','纪晓芙',2,'7.jpg',1,'2005-08-01',1,now(),now()),
	(8,'zhouzhiruo','123456','周芷若',2,'8.jpg',1,'2014-11-09',1,now(),now()),
	(9,'dingminjun','123456','丁敏君',2,'9.jpg',1,'2011-03-11',1,now(),now()),
	(10,'zhaomin','123456','赵敏',2,'10.jpg',1,'2013-09-05',1,now(),now()),
	(11,'luzhangke','123456','鹿杖客',1,'11.jpg',5,'2007-02-01',3,now(),now()),
	(12,'hebiweng','123456','鹤笔翁',1,'12.jpg',5,'2008-08-18',3,now(),now()),
	(13,'fangdongbai','123456','方东白',1,'13.jpg',5,'2012-11-01',3,now(),now()),
	(14,'zhangsanfeng','123456','张三丰',1,'14.jpg',2,'2002-08-01',2,now(),now()),
	(15,'yulianzhou','123456','俞莲舟',1,'15.jpg',2,'2011-05-01',2,now(),now()),
	(16,'songyuanqiao','123456','宋远桥',1,'16.jpg',2,'2010-01-01',2,now(),now()),
	(17,'chenyouliang','123456','陈友谅',1,'17.jpg',NULL,'2015-03-21',NULL,now(),now());
  1. properties配置
# 1. 数据库四要素
# ...
# 2. MySQL输出日志信息
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# 3. 自动驼峰命名转换
mybatis.configuration.map-underscore-to-camel-case=true
  1. 数据实体
package com.rainbow.pojo;
// some import statements here
// ...

@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;
}

2-2. CURD操作

2-2-1. delete 操作
```Java
package com.rainbow.Mapper;
// some import statements here
// ...

@Mapper
public interface EmpMapper {
    // #{id}: 预编译sql, 防止SQL注入
    @Delete("delete from tb_emp where id=#{id}")
    void delete(Integer id);    // 点击删除按钮时依据主键id删除一条记录

    // some other database operation
    // insert, update, select ...
}
```
2-2-2. 预编译sql

上述删除语句会预编译为delete from tb_emp where id=?, 运行时自动填入传入参数值

  • 性能更高
    • 执行一条包含多个步骤, 例如SQL语法解析检查, 优化SQL, 编译SQL, 执行SQL; 预编译可以完成前三个步骤并将结果缓存, 使得在执行多条语句时效率更高(例如批量的delete操作)
  • 更安全(防止SQL注入)
    • SQL注入是通过操作输入的数据来修改事先定义好的SQL语句
    • 例如用户登录页面, 基本逻辑是接收前台返回的account和password然后在数据库查询是否包含该记录并据此决定是否允许登入系统.其SQL语句为: select count(*) from tb_user where account='user_input_acc' and password='user_input_pwd', 通过构造user_input_acc和user_input_pwd可以绕过查询使得查询永远为真:
    • user_input_acc=xxxx
    • user_input_pwd=’ or ‘1’='1
    • 则: select count(*) from tb_user where account='xxxx' and password='' or '1'='1'永远为真
  • 预编译则不会直接进行字符串拼接, 防止了SQL注入.
2-2-3. 插入语句

新增一个员工:

package com.rainbow.Mapper;
// 新增一个员工
@Insert("insert into tb_emp(username, password, name, gender, " +
        "image, job, entrydate, dept_id, create_time, update_time) " +
        "VALUES (#{username}, #{password}, #{name}, #{gender}, #{image}, " +
        "#{job}, #{entrydate}, #{deptId}, #{createTime}, #{updateTime})")
void insert(Emp emp);

这里利用实体对象作为参数传入, 则#{}中的变量会与emp实体字段进行匹配, 需要把命名改为驼峰命名
测试:

@Test
void testInsert(){
    Emp emp = new Emp();
    emp.setUsername("Tom");
    emp.setName("汤姆");
    emp.setGender((short) 1);
    emp.setImage("1.jpg");
    emp.setJob((short) 2);
    emp.setEntrydate(LocalDate.of(2010, 9, 18));
    emp.setDeptId(2);
    emp.setCreateTime(LocalDateTime.now());
    emp.setUpdateTime(LocalDateTime.now());
    empMapper.insert(emp);
}
2-2-4. XML-SQL

当SQL语句很长很复杂时, 以这样的方式书写CURD操作看起来不舒服, 因此MyBatis使用XML语言将函数和SQL绑定:

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

官方文档:
https://mybatis.net.cn/getting-started.html
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="org.mybatis.example.BlogMapper">
  <select id="selectBlog" resultType="Blog">
    select * from Blog where id = #{id}
  </select>
</mapper>

将2-2-2的insert语句写入XML并测试:

<mapper namespace="com.rainbow.mapper.EmpMapper">
    <insert id="insert">
        insert into tb_emp(username, password, name, gender, image, job, entrydate,
                           dept_id, create_time, update_time)
        VALUES (#{username}, #{password}, #{name}, #{gender}, #{image}, #{job}, #{entrydate},
                #{deptId}, #{createTime}, #{updateTime})
    </insert>
</mapper>

原Java代码:

// 新增一个员工
void insert(Emp emp);

MyBatisX
是一个IDEA插件, 可以为Java代码和对应的MySQL代码两个位置相互关联, 鼠标单击即可跳转, 这样定位代码十分方便。

2-2-5. insert主键回显

考虑新增套餐的场景, 在插入套餐条目后, 还需要维护菜品与套餐的关系表, 因此需要在插入套餐后返回其ID;

@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
@Insert("insert into tb_emp(username, password, name, gender, image, job, entrydate,"+
                        " dept_id, create_time, update_time)"+
    "VALUES (#{username}, #{password}, #{name}, #{gender}, #{image}, #{job}, #{entrydate},"+
            "#{deptId}, #{createTime}, #{updateTime})")
void insert(Emp emp);

正如代码中所示, @Insert注解前添加一个@Options注解, 指明要返回的字段, 那么该字段可以在插入数据的调用者实体的id显示, 注意Options只和Insert搭配, 如果将Insert的SQL语句转到XML中, 则Options失效, 将不能返回主键值.

2-3. 查询语句

通过 id 查找

// EmpMapper
@Select("select id, username, password, name, gender, image, job, " +
            "entrydate, dept_id, create_time, update_time " +
            "from tb_emp where id=#{id}")
Emp getByID(Integer id);
// Test
public void testSelect(){
    System.out.println(empMapper.getByID(10));
}

可以看到, 结果查询到了, 并且 deptId, createTime 等字段也不为空, 这就说明 properties 文件的驼峰开启有效(仅在Select注解里).

条件查询, 姓名模糊查询/性别/入职时间, 这三个条件都是可选的, 因此使用动态SQL

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

有两个需要特别注意的点, 一是concat('%', #{name}, '%'), 利用了concat函数将字符串拼接起来, 但同时保留了预编译的特性, 其次注意between和and关键字的使用, 不要使用<>等符号(在xml中算特殊字符), 有一个解决方法是:
使用<![CDATA[]]>,因为CDATA 部分中的所有内容都会被解析器忽略,所以建议使用<![CDATA[]]>, 例如:
<![CDATA[ and create_time <= #{createTime} ]]>

3. 动态SQL

3-1. <if>

场景1: 在员工查询中, 我们有多个查询条件, 例如姓名、性别、入职时间, 通常会依据其中的零个、一个或者多个条件进行查询, 此时查询SQL语句就不是固定的了

解决方案: XML中使用<if><where>标签

场景2: 更新员工信息, 仅更新其中的几个关键字, 如果是静态的, 则其他未更新的字段被设置为null值, 这里需要使用ifset标签

  1. 原始的静态SQL语句:
<update id="update">
    update tb_emp
    set username=#{username}, password=#{password}, name=#{name},
        gender=#{gender}, image=#{image}, job=#{job}, entrydate=#{entrydate},
        dept_id=#{deptId}, update_time=#{updateTime}
    where id=#{id}
</update>

假如只想修改部分字段, 那么很自然的就只设置部分字段的值, 例如:

Emp emp = new Emp();
emp.setId(22);      // 更新 22 号数据
emp.setUsername("Amel0");
emp.setName("阿迈尔0");
emp.setGender((short) 2);
emp.setUpdateTime(LocalDateTime.now());
empMapper.update(emp);

主要问题在于, 第一是其他字段为null值, 这将会覆盖原有值, 这些"其他字段"可能是具有非空约束的, 这样的插入执行将导致异常
因此, 使用动态SQL语句如下:

<update id="update">
    update tb_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>

注意: 1. 不要写错字段名; 2.注意表字段和实体字段的命名对应关系; 3.不要忘了写逗号 4. 一定一定不要写错了字段名!!!

注意拼接时候可能出现SQL语法错误, 需要仔细检查.

  • 23
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值