目录
一、环境准备
二、删除操作实现
1. 根据主键删除
2. 删除(预编译SQL)
2.1 SQL注入
2.2 参数占位符
三、新增操作实现
1. 新增代码实现
2. 新增(主键返回)
四、更新操作实现
五、查询操作实现
1. 根据ID查询
1.1 数据封装
2. 查询(条件查询)
2.1 参数名说明
通过实例案例进行学习MyBatis基本操作的增删查改。
需求说明:根据资料提供页面原型及需求,完成员工管理的需求开发
功能列表:
① 查询:根据主键ID查询、条件查询。
② 新增 ③ 更新
④ 删除:根据主键ID删除、根据主键ID批量删除。
一、环境准备
- 准备数据库表emp。
-- 部门管理
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());
- 创建一个新的springboot工程,选择引入对应的起步依赖(mybatis、mysql驱动、lombok)。
- application.properties中引入数据库连接信息。
- 创建对应的实体类Emp(实体类属性采用驼峰命名)。
- 准备Mapper接口EmpMapper。
二、删除操作实现
1. 根据主键删除
//接口方法
@Delete("delete from emp where id = #{id}")
public void delete(Integer id);
//实现类
@Test
public void testDelete(){
empMapper.delete(17);
}
注意事项:如果mapper接口方法形参只有一个普通类型的参数,#{ ... }里面的属性名可以随便写,如:#[id}、#value}。
2. 删除(预编译SQL)
① 在上述根据id主键删除元素,当我们执行单元测试时,我们可以看到数据库表结构发生变化,但是我们并不能直观了解到MyBatis框架底层到底执行了什么Sql语句以及执行结构。而我们可以借助MyBatis框架日志看到这些,日志默认关闭。
- 可以在application.properties中,打开mybatis日志,并指定输出到控制台。
#指定mybatis输出日志的位置,输出控制台 mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
在MyBatis的mapper接口中声明的SQL语句使用 #{ } 占位符,最终会被问号 ? 替代, 生成预编译SQL语句。问号为预编译SQL语句中的参数占位符,最终执行会将SQL语句以及下方参数一起发送给数据库,数据库执行SQL语句时又会将参数替换问号完成操作。
② 预编译对比与直接拼接参数的优势:
- 性能更高
- 更安全(防止SQL注入)
2.1 SQL注入
SQL注入 是通过操作输入的数据来修改事先定义好的SQL语句,以达到执行代码对服务器进行攻击的方法。
① 未防止SQL注入:直接拼接参数
正常情况下,数据库执行查询操作 -> count(*) = 1表示用户名和密码存在且正确,登录成功。
但是通过SQL注入攻击服务器:登录成功!(此时where 后条件一直成立)
select count(*) from emp where username = 'zhangwuji' and password = '123456';
select count(*) from emp where username = 'zhangwuji' and password = '111';
select count(*) from emp where username = 'wuieuwiueiwuiew' and password = '' or '1' = '1';
② 防止SQL注入: 通过 #{ } 占位符
2.2 参数占位符
① #{ ... } :
- 执行SQL时,会将 #{...} 替换为?,生成预编译SQL,会自动设置参数值。
- 使用时机:参数传递,都使用#{...}
② ${ ... } :
- 拼接SQL。直接将参数拼接在SQL语句中,存在SQL注入问题。
- 使用时机:如果对表名、列表进行动态设置时使用。
三、新增操作实现
1. 新增代码实现
//接口方法
@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);
}
MyBatis日志:
2. 新增(主键返回)
① 描述:在数据添加成功后,需要获取插入数据库数据的主键。如:添加套餐数据时,还需要维护套餐菜品关系表数据。
② 实现:@Options():会自动将生成的主键值,赋值给emp对象的id属性。
@Options(keyProperty = "id",useGeneratedKeys = true)
@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);
}
四、更新操作实现
//接口方法
@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(18);
emp.setUsername("Jery");
emp.setName("杰瑞");
emp.setImage("2.jpg");
emp.setGender((short)1);
emp.setJob((short)2);
emp.setEntrydate(LocalDate.of(2000, 1, 1));
emp.setCreateTime(LocalDateTime.now());
emp.setUpdateTime(LocalDateTime.now());
emp.setDeptId(1);
//执行更新员工信息操作
empMapper.update(emp);
}
MyBatis日志:
五、查询操作实现
1. 根据ID查询
//接口方法
@Select("select * from emp where id = #{id};")
public Emp getById(Integer id);
//实现类
@Test
public void testSeclet(){
Emp emp = empMapper.getById(10);
System.out.println(emp);
}
MyBatis日志:
在控制台中,我们可以看到username、password、name、gender等信息都查询出来,但是dept_id、createTime、updateTime均为null空值,并没有被封装进来,但是数据库表结构是有值的。这就涉及到MyBatis的数据封装:
1.1 数据封装
- 实体类属性名 和 数据库表查询返回的字段名一致,mybatis会自动封装。
- 如果 实体类属性名 和 数据库表查询返回的字段名不一致,不能自动封装。
解决方案:
① 给字段起别名,通过别名与实体类属性一致。
@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注解手动映射封装。
@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);
③ 开启 MyBatis的驼峰命名自动映射开关 。(推荐)
//application.properties
mybatis.configuration.map-underscore-to-camel-case=true
2. 查询(条件查询)
//接口方法
@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> getList(String name, short gender, LocalDate begin,LocalDate end);
//实现类
@Test
public void testList(){
List<Emp> list = empMapper.getList("张", (short)1, LocalDate.of(2010, 1, 1), LocalDate.of(2020, 1, 1));
System.out.println(list);
}
MyBatis日志:
此时我们注意到,当SQL语句中存在模糊匹配,在关键字前后加 % 百分号,此时这是一个字符串。在字符串中无法使用用 #{ } ,而我们使用的 ${ } 字符串拼接,其生成的不是一个预编译SQL,就有可能出现性能低、不安全、存在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> getList(String name, short gender, LocalDate begin,LocalDate end);
2.1 参数名说明
① 在springboot的 2.x 版本:#{ }中参数名与形参名直接对应。
② 在springboot的 1.x 版本 / 单独使用mybatis:需要在每一个形参加上@Param注解为其制定一个名字与#{ }中参数名再对应。