介绍
MyBatis是一款优秀的持久层框架,用于简化JDBC连接数据库的开发。
通俗讲就是服务器访问数据库的工具
我们以一个emp员工表的增删改操作案例来了解Mybatis的使用。
准备工作
- 准备数据库表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='员工表'
- 创建一个新的springboot工程,选择引入对应的起步依赖(MyBatis Framework、MySQL Driver、Lombok)
- main\resources\application.properties中引入数据连接信息
-
#驱动类名称,统一模板 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
- 创建对应的实体类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; }
- 准备Mapper接口EmpMapper用于写sql语句,后面演示
注意:
- 若jdk为11,springboot的起步依赖版本号最好为2.7.5,运行报错时,可以将MyBatis的起步依赖版本改为2.2.2,可能是因为MyBatis版本冲突。
- 实体类中的数据类型最好用引用类型Integer,因为引用类型才能赋值为空,默认也为空,后面要用。字段类型要与实体类型对应,其中varchar对应String,date对应LocalDate,datetime对应LocalDateTime。
- mapper层就是dao层,Mybatis框架中sql语句是写在Mapper类型接口中的,而@Mapper注解用于声明该接口为Mapper类型接口,同时也声明该接口实现类为Bean对象,启动时自动创建该接口的实现类并交给IOC容器管理。
配置SQL提示
1、选中SQL语句右键->Show Context Actions->Inject language or reference->MySQL。
作用:SQL语法检查
2、导入数据库。
作用:有SQL提示
删除
// 根据员工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
- 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"/>