1、概述
1.1 MyBatis
MyBatis是持久层框架,用于简化JDBC的开发。
官网:https://mybatis.org/mybatis-3/zh/index.html
使用Mybatis操作数据库,就是在Mybatis中编写SQL查询代码,发送给数据库执行,数据库执行后返回结果。
1.2 预编译SQL
性能更高更安全,能防止sql注入
SQL注入是通过操作输入的数据来修改事先定义好的SQL语句,以达到执行代码对服务器进行攻击的目的。由于没有对用户输入进行充分检查,而SQL又是拼接而成,在用户输入参数时,在参数中添加一些SQL关键字,达到改变SQL运行结果的目的,也可以完成恶意攻击。
- 编译一次之后会将编译后的SQL语句缓存起来,后面再次执行这条语句时,不会再次编译。(只是输入的参数不同)
- 将敏感字进行转义
1.3 参数占位符
-
#{...}
:执行SQL时,会将#{…}
替换为?
,生成预编译SQL,会自动设置参数值使用时机:参数传递,都使用
#{…}
-
${...}
:拼接SQL。直接将参数拼接在SQL语句中,存在SQL注入问题使用时机:如果对表名、列表进行动态设置时使用
2、配置说明
2.1 application.properties
#驱动类名称
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#数据库连接的url
spring.datasource.url=jdbc:mysql://localhost:3306/数据库名
#连接数据库的用户名
spring.datasource.username=root
#连接数据库的密码
spring.datasource.password=1234
#指定mybatis输出日志的位置, 输出控制台
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# 开启驼峰命名;如果字段名与属性名符合驼峰命名规则,mybatis会自动通过驼峰命名规则映射
mybatis.configuration.map-underscore-to-camel-case=true
2.2 数据库连接池(Druid)
官方地址:https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter
资源重用、提升系统响应速度、避免数据库连接遗漏
客户端执行SQL语句:要先创建一个新的连接对象,然后执行SQL语句,SQL语句执行后又需要关闭连接对象从而释放资源,每次执行SQL时都需要创建连接、销毁链接,这种频繁的重复创建销毁的过程是比较耗费计算机的性能。
数据库连接池是个容器,负责分配、管理数据库连接(Connection)
程序在启动时,会在数据库连接池(容器)中,创建一定数量的Connection对象;允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;
客户端在执行SQL时,先从连接池中获取一个Connection对象,然后在执行SQL语句,SQL语句执行完之后,释放Connection时就会把Connection对象归还给连接池(Connection对象可以复用);
释放空闲时间超过最大空闲时间的连接,来避免因为没有释放连接而引起的数据库连接遗漏;客户端获取到Connection对象了,但是Connection对象并没有去访问数据库(处于空闲),数据库连接池发现Connection对象的空闲时间 > 连接池中预设的最大空闲时间,此时数据库连接池就会自动释放掉这个连接对象
官方(sun)提供了数据库连接池标准(javax.sql.DataSource接口);第三方组织必须按照DataSource接口实现:C3P0、DBCP、Druid、Hikari (springboot默认)
<dependency>
<!-- Druid连接池依赖 -->
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
spring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.druid.url=jdbc:mysql://localhost:3306/mybatis
spring.datasource.druid.username=root
spring.datasource.druid.password=1234
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis
spring.datasource.username=root
spring.datasource.password=1234
public Connection getConnection() throws SQLException; //获取连接
2.3 lombok
一个实用的Java类库
通过注解的形式自动生成构造器、getter/setter、equals、hashcode、toString等方法,并可以自动化生成日志变量,简化java开发、提高效率。
注解 | 作用 |
---|---|
@Getter/@Setter | 为所有的属性提供get/set方法 |
@ToString | 会给类自动生成易阅读的 toString 方法 |
@EqualsAndHashCode | 根据类所拥有的非静态字段自动重写 equals 方法和 hashCode 方法 |
@Data | 提供了更综合的生成代码功能(@Getter + @Setter + @ToString + @EqualsAndHashCode) |
@NoArgsConstructor | 为实体类生成无参的构造器方法 |
@AllArgsConstructor | 为实体类生成除了static修饰的字段之外带有各参数的构造器方法。 |
<!-- 在springboot的父工程中,已经集成了lombok并指定了版本号,故当前引入依赖时不需要指定version -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
Lombok会在编译时,会自动生成对应的java代码
在使用lombok时,还需要安装一个lombok的插件(新版本的IDEA中自带)
2.4 解决SQL警告与提示
- 在Idea中配置MySQL数据库连接时指定数据库可以识别表名(列名)
- 在@Select注解中编写SQL语句时自动提示功能:右键选择sql语句-->show context Actions-->inject language or reference---> MySQL(sql)
2.5 MybatisX
MybatisX是一款基于IDEA的快速开发Mybatis的插件,为效率而生。
2.6 数据封装
实体类属性名和数据库表查询返回的字段名一致,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);
开启驼峰命名(推荐):
- 如果字段名与属性名符合驼峰命名规则,mybatis会自动通过驼峰命名规则映射
- 实体类的属性 与 数据库表中的字段名严格遵守驼峰命名
mybatis.configuration.map-underscore-to-camel-case=true
2.7 参数名说明
条件查询中,需要保证接口中方法的形参名和SQL语句中的参数占位符名相同,否则会出现
not found
参数名在不同的SpringBoot版本中,处理方案不同:
在springBoot的1.x版本单独使用mybatis,编译生成的字节码文件中,不会保留Mapper接口中方法的形参名称,而是使用var1、var2...这样的形参名字,此时要获取参数值时,就要通过@Param注解来指定SQL语句中的参数名
public List<Emp> list(String var1,SHort var2,LocalDate var3,LocalDate var4);
@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(@Param("name")string name,@Param("gender")Short gender, @Param("begin")LocalDate begin,@Param("end")localDate end);
在springBoot的2.x版本中(保证参数名一致),springBoot的父工程对compiler编译插件进行了默认的参数parameters配置,使得在编译时,会在生成的字节码文件中保留原方法形参的名称,所以#{…}里面可以直接通过形参名获取对应的值
@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);
3、注解说明
@Mapper
- 表示当前接口是mybatis中的Mapper接口
- 程序运行时框架会自动生成接口的实现类对象(代理对象)并给交Spring的IOC容器管理
@Select
- select查询,后面书写select查询语句
@Delete
- 编写delete操作的SQL语句
- 如果mapper接口方法形参只有一个普通类型的参数,
#{…}
里面的属性名可以随便写,但是建议保持与形参一致。
@Options[主键返回]
在数据添加成功后,需要获取插入数据库数据的主键。
@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);
}
@Test
public void testInsert(){
Emp emp = new Emp();
emp.setUsername("jack");
emp.setDeptId(1);
//调用添加方法
empMapper.insert(emp);
// 返回主键
System.out.println(emp.getDeptId());
System.out.println(emp.getId());
}
@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);
@SpringBootTest
- 代表该测试类已经与SpringBoot整合,测试类在运行时会自动通过引导类加载Spring的环境(IOC容器)。
- 可以通过@Autowired注解注入需要测试的bean对象进行测试
@SpringBootTest
public class MybatisQuickstartApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
public void testList(){
List<User> userList = userMapper.list();
for (User user : userList) {
System.out.println(user);
}
}
}
@Param
- Spring1.x版本时,写在形参中,用来指定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(@Param("name")string name,@Param("gender")Short gender, @Param("begin")LocalDate begin,@Param("end")localDate end);
4、注解开发
创建实体类时LocalDate类型对应数据表中的date类型;LocalDateTime类型对应数据表中的datetime类型
@Mapper
public interface EmpMapper {
// 根据id删除数据
@Delete("delete from emp where id = #{id}")//使用#{key}方式获取方法中的参数值
public void delete(Integer 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修改员工信息
@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查询员工信息
@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);
// 模糊查询
@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);
}
5、XML开发【重点】
在Mybatis中使用XML映射文件方式开发,需要符合一定的规范:
- XML映射文件的名称与Mapper接口名称一致,并且将XML映射文件和Mapper接口放置在相同包下(同包同名)
- XML映射文件的namespace属性为Mapper接口全限定名一致
- XML映射文件中sql语句的id与Mapper接口中的方法名一致,并保持返回类型一致
resultType属性指查询返回的单条记录所封装的类型。
5.1 dtd约束+例子
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lmcode.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>
5.2 动态sql
条件不是写死的,应该传递参数后再组装这个查询条件;如果没有传递参数就不应该组装这个查询条件。
情景:
搜索姓名带有"张"的员工:
select * from emp where name like '%张%' order by update_time desc;
搜索姓名带有"张"的男性员工:
select * from emp where name like '%张%' and gender = 1 order by update_time desc;
<sql>
:定义可重用的SQL片段
<include>
:通过属性refid,指定包含的SQL片段
<if>
用于判断条件是否成立,如果条件为true,则拼接SQL
<where>
只会在子元素有内容的情况下才插入where子句,而且会自动去除子句的开头的AND或OR
<set>
动态地在行首插入 SET 关键字,并会删掉额外的逗号。(用在update语句中)
</foreach>
遍历方法中传递的集合
@Mapper
public interface EmpMapper {
public void update(Emp emp);
public void deleteByIds(List<Integer> ids);
}
<sql id="commonSelect">
id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time from emp
</sql>
<select id="list" resultType="com.itheima.pojo.Emp">
select <include refid="commonSelect"/>
from emp
<where>
<!-- if做为where标签的子元素 -->
<if test="name != null">
and 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>
<update id="update">
update emp
<set>
<if test="username != null">
username=#{username},
</if>
<if test="name != null">
name=#{name},
</if>
<if test="gender != null">
gender=#{gender},
</if>
<if test="image != null">
image=#{image},
</if>
<if test="job != null">
job=#{job},
</if>
<if test="entrydate != null">
entrydate=#{entrydate},
</if>
<if test="deptId != null">
dept_id=#{deptId},
</if>
<if test="updateTime != null">
update_time=#{updateTime}
</if>
</set>
where id=#{id}
</update>
<delete id="deleteByIds">
delete from emp where id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>