8.16、8.17 JavaWeb(MyBatis P116-P134)
MyBatis是一款优秀的持久层框架,用于简化JDBC的开发
MyBatis入门
快速入门
需求:使用MyBatis查询所有用户的数据
1.准备工作(创建springboot工程,数据库表user,实体类User)
2.引入MyBatis的相关依赖,配置MyBatis(数据库连接信息)
3.编写SQL语句(注解/XML)
创建springboot工程,注意勾选MyBatis相关依赖
配置数据库连接信息
新建User类,对象要和数据库表中的各列一致
package com.itheima.pojo;public class User {private Integer id;private String name;private Short age;private Short gender;private String phone;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Short getAge() {return age;}public void setAge(Short age) {this.age = age;}public Short getGender() {return gender;}public void setGender(Short gender) {this.gender = gender;}public String getPhone() {return phone;}public void setPhone(String phone) {this.phone = phone;}public User(Integer id, String name, Short age, Short gender, String phone) {this.id = id;this.name = name;this.age = age;this.gender = gender;this.phone = phone;}public User() {}@Overridepublic String toString() {return "User{" +"id=" + id +", name='" + name + '\'' +", age=" + age +", gender=" + gender +", phone='" + phone + '\'' +'}';}
}
编写SQL语句
package com.itheima.mapper;import com.itheima.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.springframework.beans.factory.annotation.Autowired;import java.util.List;@Mapper //在运行时,会自动生成该接口的实现类对象(代理对象),并且将该对象交给IOC容器管理
public interface UserMapper {//查询全部用户信息@Select("select * from user")public List<User> list();}
测试
package com.itheima;import com.itheima.mapper.UserMapper;
import com.itheima.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.util.List;@SpringBootTest //springboot整合单元测试的注解
class SpringbootMybatisQuickstartApplicationTests {@Autowiredprivate UserMapper userMapper;@Testpublic void testListUser() {List<User> userList = userMapper.list();userList.stream().forEach(user -> {System.out.println(user);});}}
JDBC介绍
Java DataBase Connectivity,是使用Java语言操作关系型数据库的一套API
sun公司提供一套操作所有关系型数据库的规范,即接口,各个数据库厂商去实现这些接口,提供数据库驱动jar包,我们就可以用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类
数据库连接池
数据库连接池是一个容器,负责分配、管理数据库连接
它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个
释放空闲时间超过最大空闲时间的连接,来避免因为没有释放连接而引起的数据库连接遗漏
Lombok
Lombok是一个实用的Java类库,能通过注解的形式自动生成构造器、getter/setter、equals、hashcode、toString等方法,并可以自动化生成日志变量,简化Java开发、提高效率
对于前面创建的User类可以进行简化
package com.itheima.pojo;import lombok.*;@Data
@NoArgsConstructor //无参构造器
@AllArgsConstructor //有参构造器
public class User {private Integer id;private String name;private Short age;private Short gender;private String phone;
}
MyBatis基础操作
环境准备
1.准备数据库表emp
2.创建一个新的springboot工程,选择引入对应的起步依赖(mybatis、mysql驱动、lombok)
3.appliction.properties中引入数据库连接信息
4.创建对应的实体类Emp
5.准备Mapper接口EmpMapper
快速入门中已经介绍,不再赘述
删除
在EmpMapper接口中写自己需要完成的需求,这里是删除某条记录
package com.itheima.mapper;import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface EmpMapper {//根据id删除数据@Delete("delete from emp where id = #{id}")public void delete(Integer id);//public int delete(Integer id);
}
然后进行测试,在测试启动类中调用delete方法
package com.itheima;import com.itheima.mapper.EmpMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
class SpringbootMybatisCrudApplicationTests {@Autowiredprivate EmpMapper empMapper;@Testpublic void testDelete() {//int delete = empMapper.delete(16);//System.out.println(delete);empMapper.delete(16);}}
这里如果delete的返回值是int,则表示这次操作影响的记录数
如果mapper接口方法形参只有一个普通类型的参数,#{...}里面的属性名可以随便写,如:#{id}、 #{value}
SQL预编译
?是一个占位符
SQL注入:通过操作输入的数据来修改事先定义好的SQL语句,以达到执行代码对服务器进行攻击的方法
例如,登陆的用户名为‘zhangwuji’,密码为‘123456’,输入正确的密码时执行查询操作
select count(*) from emp where username = 'zhangwuji' and password = '123456';
查询结果大于0说明查询到了,可以成功登录。输入错误的密码如
select count(*) from emp where username = 'zhangwuji' and password = '111';
查询结果为0说明数据库查询不到,登陆失败。SQL注入就是可以输入密码 'or '1' = '1
select count(*) from emp where username = 'zhangwuji' and password = '' or '1' = '1';
执行这条SQL语句时,where中的条件永远成立,因此可以登陆成功
使用预编译的SQL语句则不会出现此问题
参数占位符
1. #{...} :执行SQL时会将#{...}替换为?,生成预编译SQL,会自动设置参数值(推荐)
2. ${...} :拼接SQL,直接将参数拼接在SQL语句中,存在SQL注入问题
新增
在EmpMapper接口中新定义一个抽象方法,将Emp对象作为一个参数传进去
//新增员工@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);
在测试启动类使用该方法
@Testpublic 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);}
主键返回
可以在新增操作时,返回新增记录的主键值,首先在EmpMapper的insert方法上在添加一个注解
需要返回的主键为‘id’
将id值返回输出
更新
在EmpMapper接口中新定义一个抽象方法,将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);
在测试启动类使用该方法
@Testpublic void testUpdate() {Emp emp = new Emp();emp.setUsername("Tom2");emp.setName("汤姆2");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);emp.setId(19);empMapper.update(emp);}
查询
根据id查询
在EmpMapper接口中新定义一个抽象方法,将id作为一个参数传进去
//根据ID查询员工@Select(" select * from emp where id = #{id} ")public Emp getById(Integer id);
在测试启动类使用该方法
@Testpublic void testGetById() {Emp emp = empMapper.getById(19);System.out.println(emp);}
条件查询
在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);
在测试启动类使用该方法
@Testpublic void testList() {List<Emp> empList = empMapper.list("张", (short)1, LocalDate.of(2010,1,1), LocalDate.of(2020,1,1));System.out.println(empList);}
注意这里进行模糊查找时,使用了${...}而非#{...},这是因为在SQL预编译时#{...}会自动替换为占位符?,而占位符不能出现在'...'中,但是这种方法存在SQL注入问题(前文提到),性能低,不安全,可以使用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);
XML映射文件
前面是通过定义Mapper接口,添加注解完成MyBatis的增删改查,这里通过XML映射文件来实现
规范
1.XML映射文件的名称与Mapper接口名称一致,并且将XML映射文件和Mapper接口放置在相同包下(同包同名)
2.XML映射文件的namespace属性为Mapper接口全限定名一致
3.XML映射文件中SQL语句的id与Mapper接口中的方法名一致,并保持返回类型一致
实现
首先注释掉Mapper接口方法上的注解,只保留接口中的抽象方法
public List<Emp> list(String name, Short gender, LocalDate begin, LocalDate end);
在XML映射文件中配置
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//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 concat('%', #{name}, '%') and gender = #{gender} andentrydate between #{begin} and #{end} order by update_time desc</select></mapper>
一定要注意需要遵循规范
使用MyBatis的注解主要是来完成一些简单的增删改查功能。如果需要实现复杂的SQL功能,建议使用XML来配置映射语句
动态SQL
随着用户的输入或外部条件的变化而变化的SQL语句,我们称为动态SQL
如果查询条件并不都需要,例如前面的条件查询我只需要查询姓名中带‘张’的员工信息,不需要性别、入职日期等条件,如果依然采用上述SQL语句则无法查询成功,此时就需要动态SQL
<if>
<if>由于判断条件是否成立,使用test属性进行条件判断,如果条件为true,则拼接SQL
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//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><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></mapper>
@Testpublic void testList() {
// List<Emp> empList = empMapper.list("张", (short)1, LocalDate.of(2010,1,1), LocalDate.of(2020,1,1));
// List<Emp> empList = empMapper.list("张",null, null, null);List<Emp> empList = empMapper.list(null,(short)1, null, null);System.out.println(empList);}
<where>:where元素只会在子元素有内容的情况下才插入where子句,并且会自动去除子句开头的and或or
<set>:动态地在行首插入SET关键字,并会删除额外的逗号(用在update语句中)
<foreach>
在XML映射文件中新增一个delete标签
<!--批量删除员工(18,19)信息--><!--collection:遍历的集合item:遍历出来的元素separator:分隔符open:遍历开始前拼接的SQL片段close:遍历结束后拼接的SQL片段--><delete id="deleteByIds">deletefrom empwhere id in <foreach collection="ids" item="id" separator="," open="(" close=")">#{id}</foreach></delete>
//批量删除员工public void deleteByIds(List<Integer> ids);
@Testpublic void testDeleteByIds() {List<Integer> ids = Arrays.asList(18,19);empMapper.deleteByIds(ids);}
<sql><include>
<sql>:定义可重用的SQL片段
<include>:通过属性refid,指定包含的sql片段
<sql id="commonSelect">select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_timefrom emp
</sql><!--resultType:单条记录所封装的类型-->
<select id="list" resultType="com.itheima.pojo.Emp"><include refid="commonSelect"/><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>
从而提高代码的复用性