准备工作:
-
准备数据库表
-
创建一个新的springboot工程,选择引入对应的起步依赖(mybatis、mysql驱动、lombok)
-
application.properties中引入数据库连接信息
-
创建对应的实体类 Emp(实体类属性采用驼峰命名)
-
准备Mapper接口 EmpMapper
删除:
需求:
当我们点击前端页面中的"删除"按钮时,前端页面会给服务端传递一个参数,也就是该行数据的ID。 我们接收到ID后,根据ID删除数据即可。
实现:
- 使用SQL语句实现需求:
-- 删除id=17的数据
delete from emp where id = 17;
-
使用Mybatis框架实现需求
@Mapper
public interface EmpMapper {
//@Delete("delete from emp where id = 17")
//public void delete();
//以上delete操作的SQL语句中的id值写成固定的17,就表示只能删除id=17的用户数据
//SQL语句中的id值不能写成固定数值,需要变为动态的数值
//解决方案:在delete方法中添加一个参数(用户id),将方法中的参数,传给SQL语句
/**
* 根据id删除数据
* @param id 用户id
*/
@Delete("delete from emp where id = #{id}")//使用#{key}方式获取方法中的参数值
public void delete(Integer id);
}
@Delete注解:用于编写delete操作的SQL语句
如果mapper接口方法形参只有一个普通类型的参数,#{…} 里面的属性名可以随便写,如:#{id}、#{value}。但是建议保持名字一致。
日志输入:
在我们日常的开发过程中,为了方便开发中监控SQL语句的执行,通常会在Mybatis中借助日志,查看sql语句的执行、执行传递的参数以及执行结果。具体操作如下:
-
打开application.properties文件
-
开启mybatis的日志,并指定输出到控制台
#指定mybatis输出日志的位置, 输出控制台
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
开启日志之后,我们再次运行单元测试,可以看到在控制台中,输出了以下的SQL语句信息:
但是我们发现输出的SQL语句:delete from emp where id = ?,我们输入的参数16并没有在后面拼接,id的值是使用?进行占位。那这种SQL语句我们称为预编译SQL。
预编译SQL:
前面我们看到日志中输出的SQL语句带有?
符号,这种SQL语句称为预编译SQL,那么预编译SQL有什么用呢?下面介绍来一下预编译SQL。
预编译SQL有两个优势:
-
性能更高
性能更高:预编译SQL,编译一次之后会将编译后的SQL语句缓存起来,后面再次执行这条语句时,不会再次编译。(只是输入的参数不同) -
更安全(防止SQL注入)
更安全(防止SQL注入):将敏感字进行转义,保障SQL的安全性。
SQL注入:
刚刚我们提到了SQL注入,下面我们就来聊聊SQL注入是什么:
SQL注入:是通过操作输入的数据来修改事先定义好的SQL语句,以达到执行代码对服务器进行攻击的方法。
由于没有对用户输入进行充分检查,而SQL又是拼接而成,在用户输入参数时,在参数中添加一些SQL关键字,达到改变SQL运行结果的目的,也可以完成恶意攻击。
参数占位符:
在前面我们书写的代码中,使用到两种参数占位符:${...} 、#{...},那么这两种参数占位符有什么区别呢?
-
#{...}
-
执行SQL时,会将#{…}替换为?,生成预编译SQL,会自动设置参数值
-
使用时机:参数传递,都使用#{…}
-
-
${...}
-
拼接SQL。直接将参数拼接在SQL语句中,存在SQL注入问题
-
使用时机:如果对表名、列表进行动态设置时使用
-
注意事项:在项目开发中,建议使用#{...},生成预编译SQL,防止SQL注入安全
新增:
实现:
- 使用SQL语句实现需求:
insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time,
update_time) values ('songyuanqiao','宋远桥',1,'1.jpg',2,'2012-10-09',2,'2022-10-01
10:00:00','2022-10-01 10:00:00');
-
使用Mybatis框架实现需求
@Mapper
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);
}
说明:#{...} 里面写的名称是对象的属性名(需要保持一致否则程序运行时会报错)
emp(...)里面写的是数据库的字段名(需要保持一致否则程序运行时会报错)
日志输出:
主键返回:
我们开发中会存在一种需求,希望获取到新增数据的主键值,这种需求就需要用到主键返回技术
概念:
在数据添加成功后,需要获取插入数据库数据的主键。
业务场景:
添加套餐数据时,还需要维护套餐菜品关系表数据。
菜品与套餐模块的表结构,菜品与套餐是多对多的关系,一个套餐对应多个菜品。既然是多对多的关系,就需要有一张套餐菜品中间表来维护它们之间的关系。
在添加套餐的时候,我们需要在界面当中来录入套餐的基本信息,还需要来录入套餐与菜品的关联信息。这些信息录入完毕之后,我们一点保存,就需要将套餐的信息以及套餐与菜品的关联信息都需要保存到数据库当中。其实具体的过程包括两步,首先第一步先需要将套餐的基本信息保存了,接下来第二步再来保存套餐与菜品的关联信息。套餐与菜品的关联信息就是往中间表当中来插入数据,来维护它们之间的关系。而中间表当中有两个外键字段,一个是菜品的ID,就是当前菜品的ID,还有一个就是套餐的ID,而这个套餐的 ID 指的就是此次我所添加的套餐的ID,所以我们在第一步保存完套餐的基本信息之后,就需要将套餐的主键值返回来供第二步进行使用。这个时候就需要用到主键返回功能。
如何实现在插入数据之后返回所插入行的主键值呢:
默认情况下,执行插入操作时,是不会主键值返回的。如果我们想要拿到主键值,只需要在Mapper接口中的方法上添加一个Options注解,并在注解中指定属性useGeneratedKeys=true和keyProperty="实体类属性名"(只能实现主键ID的返回,不能返回其他的属性值)
主键返回代码实现:
@Mapper
public interface EmpMapper {
//会自动将生成的主键值,赋值给emp对象的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);
}
更新:
需求:
点击"编辑"按钮后,会查询所在行记录的员工信息,并把员工信息回显在修改员工的窗体上
在修改员工的窗体上,可以修改的员工数据:用户名、员工姓名、性别、图像、职位、入职日期、归属部门
在修改员工数据时,要以员工id做为条件
实现:
- 使用SQL语句实现需求:
update emp set username = 'linghushaoxia', name = '令狐少侠', gender = 1 , image = '1.jpg'
job = 2, entrydate = '2012-01-01', dept_id = 2, update_time = '2022-10-01 12:12:12'
where id = 18;
-
使用Mybatis框架实现需求
@Mapper
public interface EmpMapper {
/**
* 根据id修改员工信息
* @param 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);
注意:字段名=#{属性名}
字段名需要和数据库的字段名保持一致,属性名需要和对象的属性名保持一致否则代码会报错
查询:
根据ID查询
在员工管理的页面中,当我们进行更新数据时,会点击 “编辑” 按钮,然后此时会发送一个请求到服务端,会根据Id查询该员工信息,并将员工数据回显在页面上。
实现:
- 使用SQL语句实现需求:
select id, username, password, name, gender, image, job, entrydate, dept_id, create_time,
update_time from emp where id=1;
-
使用Mybatis框架实现需求
@Mapper
public interface EmpMapper {
@Select("select id, username, password, name, gender, image, job, entrydate, dept_id,
create_time, update_time from emp where id=#{id}")
public Emp getById(Integer id);
}
执行结果:
数据缺失:
在进行测试的过程中,我们发现有几个字段(deptId、createTime、updateTime)是没有数据值的
我们看到查询返回的结果中大部分字段是有值的,但是deptId,createTime,updateTime这几个字段是没有值的,而数据库中是有对应的字段值的,这是为什么呢?
原因如下:
-
实体类属性名和数据库表查询返回的字段名一致,mybatis会自动封装。
-
如果实体类属性名和数据库表查询返回的字段名不一致,不能自动封装。
解决方案:使字段名与属性名一致
-
起别名
-
结果映射
-
开启驼峰命名
起别名:(使用繁琐)
在SQL语句中,对不一样的列名起别名,别名和实体类属性名一样
@Select("select id, username, password, name, gender, image, job, entrydate, " +
"dept_id AS deptId, create_time AS createTime, update_time AS 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 id, username, password, name, gender, image, job, entrydate, dept_id,
create_time, update_time from emp where id=#{id}")
public Emp getById(Integer id);
@Results源代码:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Results {
String id() default "";
Result[] value() default {}; //Result类型的数组
}
@Result源代码:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Repeatable(Results.class)
public @interface Result {
boolean id() default false;//表示当前列是否为主键(true:是主键)
String column() default "";//指定表中字段名
String property() default "";//指定类中属性名
Class<?> javaType() default void.class;
JdbcType jdbcType() default JdbcType.UNDEFINED;
Class<? extends TypeHandler> typeHandler() default UnknownTypeHandler.class;
One one() default @One;
Many many() default @Many;
}
开启驼峰命名(推荐):(字段名与属性名需要符合驼峰命名规则)
如果字段名与属性名符合驼峰命名规则,mybatis会自动通过驼峰命名规则映射
驼峰命名规则: abc_xyz => abcXyz
-
表中字段名:abc_xyz
-
类中属性名:abcXyz
# 在application.properties中添加:
mybatis.configuration.map-underscore-to-camel-case=true
要使用驼峰命名前提是 实体类的属性 与 数据库表中的字段名严格遵守驼峰命名。
条件查询
在员工管理的列表页面中,我们需要根据条件查询员工信息,查询条件包括:姓名、性别、入职时间;
通过页面原型以及需求描述我们要实现的查询:
-
姓名:要求支持模糊匹配
-
性别:要求精确匹配
-
入职时间:要求进行范围查询
-
根据最后修改时间进行降序排序
实现:
- 使用SQL语句实现需求:
select id, username, password, name, gender, image, job, entrydate, dept_id, create_time,
update_time
from emp
where name like '%张%'
and gender = 1
and entrydate between '2010-01-01' and '2020-01-01 '
order by update_time desc;
-
使用Mybatis框架实现需求
-
方式一:
-
@Mapper
public interface 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);
}
以上方式注意事项:
-
方法中的形参名和SQL语句中的参数占位符名保持一致
-
模糊查询使用${...}进行字符串拼接,这种方式呢,由于是字符串拼接,并不是预编译的形式,所以效率不高、且存在sql注入风险。
-
使用Mybatis框架实现需求
-
方式二:(解决SQL注入风险)
- 使用MySQL提供的字符串拼接函数:concat('%' , '关键字' , '%')
-
@Mapper
public interface EmpMapper {
@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);
}
执行结果:生成的SQL都是预编译的SQL语句(性能高、安全)
当方法中的形参名和SQL语句中的占位符参数名不相同时,就会出现以下问题:
除了上述情况外,参数名在不同的SpringBoot版本中,处理方案还不同:
在springBoot的2.x版本(保证参数名一致)
springBoot的父工程对compiler编译插件进行了默认的参数parameters配置,使得在编译时,会在生成的字节码文件中保留原方法形参的名称,所以#{…}里面可以直接通过形参名获取对应的值
在springBoot的1.x版本/单独使用mybatis(使用@Param注解来指定SQL语句中的参数名)
在编译时,生成的字节码文件当中,不会保留Mapper接口中方法的形参名称,而是使用var1、var2、...这样的形参名字,此时要获取参数值时,就要通过@Param注解来指定SQL语句中的参数名