四、Mybatis
概念: Mybatis是一款持久层(Dao层)框架,用于简化JDBC(Sun操作数据库的规范,较繁琐)的开发
历史: Apache的一个开源项目iBatis,2010年由apache迁移到了google code,并改名MyBatis。2013年11月迁移到Github
1. 入门程序
1)创建Spring module;创建的时候勾选MyBatis Framework和MySql service(对应数据库的类型)
2)准备数据以及数据库环境
3)定义一个实体类,实体类的变量要与表中的数据类型以及名称对应,实体类变脸阿哥使用包装类,命名使用驼峰命名
4)创建Mybatis的环境,在模块的application.properties中声明
#驱动类名称
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#数据库连接的url
spring.datasource.url=jdbc:mysql://124.221.237.48(地址):3306/test(数据库名)
#连接数据库的用户名
spring.datasource.username=xy
#连接数据库的密码
spring.datasource.password=123456
5)定义mapper,就是使用Sql语句的类,要使用Mapper进行注解,这样运行时,会自动生成该接口的实现类对象(代理对象),并将该对象交给IOC容器管理。
在类中定义抽象方法,如果是查询就使用@Select()注解,括号中指定sql查询语句
注:要想有sql语法提示
选中Sql语句右键Show Context Actions——>inject language——>MySql(要想提示表名,要将数据库连接到IDEA——使用IDEA连接数据库)
6)在测试启动类中编写测试方法,因为要使用mapper接口中的方法,所以使用Autowired,从IOC中获取bean对象
2. 数据库池连接
使用配置文件进行配置之后,SpringBoot底层就会自动使用数据库连接池技术管理和分配连接
概念:
- 数据库连接池是个容器,负责分配、管理数据库连接
- 它允许应用程序重复使用一个现有的数据库连接,而不是再重建一个
- 释放空闲时间超过最大空闲事件的连接,来避免因为没有释放连接而一起的数据库连接遗漏
优势:
- 资源重用
- 提升系统响应速度
- 避免数据库连接遗漏
产品:
Druid
引入依赖(版本要对应调整)
# Spring2
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
# Spring3
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
<version>1.2.20</version>
</dependency>
配置数据库连接池类型(可以不)
Hikari(springboot默认)
3. lombok工具包
原有问题: 数据库中的数据要对应一个实体类,并为其生成构造器,getter/setter,toString等方法
lombok: 一个实用的java类库,能通过注解的形式自动生成构造器、getter/setter、equals、hashcode、toString等方法,并可以自动化生成日志变量,简化java开发,提高效率
准备工作: 添加依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
注: Lombok会在编译时,自动生成对应的java代码。我们使用lombok时,还需要安装一个lombok的插件(idea自带)
4. Mybatis基础操作
1)删除
EmpMapper.java
@Mapper
public interface EmpMapper {
// 根据ID删除数据
@Delete("delete from emp where id= #{id}")
public void delete(Integer id);
}
// #{}是占位符,生成的就是预编译的SQL语句
测试方法
@SpringBootTest
class SpringbootMybatisCrudApplicationTests {
@Autowired
private EmpMapper empMapper;
@Test
public void testDelete() {
empMapper.delete(17);
}
}
想要查看执行的日志信息可以配置==>预编译SQL
# 配置mybatis日志,指定输出到控制台
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
参数占位符
2)新增
接口方法
// 插入数据
@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);
}
主键返回
插入数据成功后,想要获取这个插入数据的主键
@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);
3)更新
根据主键id查询回显
然后修改对应数据
@Update(更新的SQL语句)
4)查询
// 根据ID查询数据
@Select("select * from emp where id = #{id}")
public Emp getById(Integer id);
@Test
public void testGetById() {
Emp emp = empMapper.getById(1);
System.out.println(emp);
}
数据封装
- 实体类属性名和数据库表查询返回的字段名一致,mybatis会自动封装
- 如果实体类属性名和数据库表查询返回的字段名不一致,不能自动封装(数据库属性命名是下划线分隔,实体类变量命名是驼峰)
解决数据封装问题
方案一:给数据库字段起别名
方案二:通过@Results,@Result注解手动映射封装
// 通过@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的驼峰命名自动映射开关(前提:数据库字段名字和Java中的属性名命名都是严格按照规范的)
# 开启mybatis的驼峰命名自动映射开关
mybatis.configuration.map-underscore-to-camel-case=true
条件查询
// 条件查询
// @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注入
@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);
@Test
public void testSelect() {
List<Emp> empList = empMapper.list("张", (short)1, LocalDate.of(2010, 1, 1), LocalDate.of(2020, 1, 1));
System.out.println(empList);
}
5. XML映射文件(配置SQL语句)
条件查询的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.itheima.mapper.EmpMapper">
<!--第一个参数是方法名,第二个参数是查询返回的单条语句的全类名-->
<select id="list" resultType="com.itheima.pojo.Emp">
select * from emp where name like concat('%', #{name}, '%') and gender=#{gender} and entrydate between #{begin} and #{end} order by update_time desc
</select>
</mapper>
MybatisX是一款基于IDEA的快速开发Mybatis的插件,为效率而生
使用注解来映射简单的语句会使代码更加简洁,但是稍微复杂一点,就混乱不堪。——>如果做一些复杂的操作,最好使用XML来映射语句
6. 动态SQL
概念: 随着用户的输入或者外部条件的变化而变化的SQL语句
6.1 <if>
<mapper namespace="com.itheima.mapper.EmpMapper">
<!--第一个参数是方法名,第二个参数是查询返回的单条语句的全类名-->
<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>
order by update_time desc
</select>
</mapper>
@Test
public void testSelect() {
// 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);
System.out.println(empList);
}
问题: 如果第一个为空,wher就会紧跟着and,不合规的SQL
解决: 使用<where></where> 代替where
- 动态生成where关键字
- 自动去除条件前的and和or
<mapper namespace="com.itheima.mapper.EmpMapper">
<!--第一个参数是方法名,第二个参数是查询返回的单条语句的全类名-->
<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>
<set></set>
- 去掉字段之后多余的逗号
6.2 <foreach>
<!--批量删除-->
<!--
collection:遍历的集合
item:遍历的元素
separator:分隔符
open:遍历开始前拼接的SQL片段
close:遍历结束后拼接的SQL片段
-->
<delete id="deleteByIds">
delete from emp where id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
// 批量删除
public void deleteByIds(List<Integer> ids);
@Test
public void testDeletByIds() {
List<Integer> ids = Arrays.asList(13, 14, 15);
empMapper.deleteByIds(ids);
}
6.3 <sql><include>
重复SQL片段——》代码复用性差