Mybatis
MyBatis是一款优秀的持久层框架, 用于简化JDBC的开发
MyBatis本是Apache的一个开源项目iBatis, 2010年这个项目由apache迁移到了google code, 并且改名为MyBatis, 2013年11月迁移到Github
官网: https://mybatis.org/mybatis-3/zh/index.html
1. Mybatis入门
1.1 快速入门
要求: 使用Mybatis查询表内所有数据
步骤:
- 准备工作(创建springboot工程, 数据库user, 实体类User)
- 引入Mybatis的相关依赖, 配置Mybatis(数据库连接信息)
- 编写SQL语句(注解/XML)
根据黑马程序员的这个视频p117
记录一下自己的问题: 运行时报错: ava.sql.SQLException: Access denied for user '11529'@'localhost' (using password: YES)
, 然后修改了对应封装的实体类的类型, 依然报错, 然后看到了一个问题, 在Application.properties中spring.datasource.url
没有加上数据库名, 修改后依然报错, 最后才发现原来是spring.datasource.username=root
写成了spring.datasource.name=root
, 少了一个user, 服了呀
最后将入门程序运行起来了
1.2 配置SQL提示
默认在mybatis中编写SQL语句是不识别的, 可以做如下配置
选择sql语句, 右键选择Show Context Actions, 选中Inject Language or reference, 然后选择MySQL
然后会出现表报红, 因为IDEA没有和数据库建立连接, 不识别表信息
解决方式: 在IDEA中配置MySQL数据库连接(要连接相应的数据库, 要不然就需要选择数据库再选择表)
1.3 JDBC
Java DataBase Connectivity , 就是使用Java操作关系型数据库的一套API
本质:
- sun公司官方定义的一套操作所有关系型数据库的规范, 即接口
- 各个数据库厂商去实现这套接口, 提供数据库驱动jar包
- 我们可以使用这套接口(JDBC)编程, 真正的执行代码是驱动jar包中的实现类
1.4 数据库连接池
数据库连接池是个容器, 负责分配管理数据库连接
它允许应用程序重复使用一个现有的数据库连接, 而不是再重新建立一个
释放空闲时间超过最大空闲时间的连接, 来避免因为没有释放连接而引起的数据库连接遗漏
优势:
- 资源重用
- 提升系统响应速度
- 避免数据库连接遗漏
标准接口: DataSource
- 官方(sun)提供的数据库连接池接口, 由第三方组织实现此接口
- 功能: 获取连接
Connection getConnection() throws SQLException;
常见产品: C3P0 DBCP Druid Hikari
Druid(德鲁伊)
- Druid连接池是阿里巴巴开源的数据库连接池项目
- 功能强大, 性能优秀, 是Java语言最好的数据库连接池之一
切换Druid数据库连接池
官方地址: https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter
步骤:
- 导入起步依赖
- 设置application.properties中的配置
1.5 lombok
Lombook是一个实用的Java类库, 能通过注解的形式自动生成构造器, getter/setter, equals, hashcode, toString等方法, 并可以自动化生成日志变量, 简化java开发, 提高效率
注解 | 作用 |
---|---|
@Getter/@Setter | 为所有的属性提供get/set方法 |
@ToString | 会给类自动生成易阅读的toString方法 |
@EqualsAndHashCode | 根据类所拥有的非静态字段自动重写equals方法和hashCode方法 |
@Data | 提供了更综合的代码生成功能(@Getter + @Setter + @ToString + @EqualsAndHashCode) |
@AllArgsConstructor | 为实体类生成除了static修饰的字段之外带有各个参数的构造器方法 |
@NoArgsConstructor | 为实体类生成无参的构造器方法 |
注意:
- Lombok会在编译时, 自动生成对应的java代码, 我们使用lombok时, 还需要安装一个lombok的插件(idea自带)
2. Mybatis基础操作
2.1 环境准备
和之前差不多, 创建springboot模块, 然后添加依赖, 然后配置数据库连接, 中间出了一点小问题, 就是数据库中有下划线的字段封装的时候不能转为驼峰命名的属性, 需要在applictaion.properties中加入以下代码:
#下划线转驼峰
mybatis.configuration.map-underscore-to-camel-case=true
这样就能正常封装了
2.2 删
SQL语句
delete from emp where id = 20;
接口方法:
@Delete("delete from emp where id = #{id}")//#{id}传递参数
//预编译为 delete from emp where id = ?
public void delete(Integer id);
注意: 如果mapper接口方法形参只有一个普通类型的参数, #{...}里面的属性名可以随便写, 如#{id}, #{value}
日志输出:
#指定mybatis的日志, 指定输出到控制台
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
预编译
性能更高
防止SQL注入
SQL注入是通过操作输入来修改事先定义好的SQL语句, 以达到执行代码对服务器进行攻击的方法
参数占位符
{...}:
- 执行SQL时, 会将
#{...}
替换为?
, 生成预编译SQL, 会自动设置参数值 - 使用时机: 参数传递, 都使用
#{...}
${...}:
- 拼接SQL, 直接将参数拼接在SQL语句中, 存在SQL注入问题
- 使用时机: 如果对表名, 列表进行动态设置时使用
2.3 增
接口方法
@Insert("insert into emp(username, name, gender, job, entrydate, dept_id, create_time, update_time) VALUES (#{userName}, #{name}, #{gender}, #{job}, #{entryDate}, #{deptId}, #{createTime}, #{updateTime})")
public void insert(Emp emp);
主键返回
在数据添加成功后, 需要获取插入数据库数据的主键
如: 添加套餐数据时, 还需要维护套餐菜品关系表数据
@Options(keyProperty = "id", useGeneratedKeys = true)
//自动将生成的主键值, 赋值给emp对象的id属性
@Insert("insert into emp(username, name, gender, job, entrydate, dept_id, create_time, update_time) VALUES (#{userName}, #{name}, #{gender}, #{job}, #{entryDate}, #{deptId}, #{createTime}, #{updateTime})")
public void insert(Emp emp);
2.4 改
根据主键修改数据
@Update("update emp set username = #{userName}, name = #{name}, gender = #{gender}, job = #{job}, entrydate = #{entryDate}, dept_id = #{deptId}, update_time = #{updateTime} where id = #{id}")
public void update(Emp emp);
2.5 查
2.5.1 根据ID查询
@Select("select * from emp where id = #{id}")
public Emp getById(Integer id);
数据封装(解释了我在上面的问题)
- 实体属性名和数据库表查询返回的字段名一致, mybatis会自动封装(大小写不同是可以封装的)
- 如果实体类属性名和数据库表查询返回的字段名不一致, 不能自动封装
解决方案1: 给字段起别名
解决方案2: 通过@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);
解决方案3: 开启mybatis的驼峰命名的自动映射开关
mybatis.configuration.map-underscore-to-camel-case=true
2.5.2 根据条件查询
//条件查询//#{...}不能放在双引号内
@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> backList(String name, Short gender, LocalDate begin ,LocalDate end );
使用concat()函数解决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> backList(String name, Short gender, LocalDate begin ,LocalDate end );
我的版本多参数会报错, 需要加上@Param注解
//条件查询
@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> backList(@Param("name") String name,@Param("gender") Short gender,@Param("begin") LocalDate begin ,@Param("end") LocalDate end );
}
2.6 XML配置文件
规范:
- XML映射文件的名称与Mapper接口名称一致, 并且将XML映射文件和Mapper接口放置在相同包下(同包同名)
- XML映射文件的namespace属性为Mapper接口全限定名一致
- XML映射文件中sql语句的id与Mapper接口中的方法一致, 并保持返回类型一致
查询示例:
<?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.tyrant.mapper.EmpMapper">
<select id="backList" resultType="com.tyrant.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>
resultType是返回单个的类型, 原Mapper类中方法的@Param注解要保留
安装插件mybatisx, 方便使用
2.7 动态SQL
随着用户的输入或外部条件的变化而变化的SQL语句, 我们称为动态SQL
2.7.1 < if >, < where >
< if >: 用于判断条件是否成立, 使用test属性进行条件判断, 如果条件为true, 则拼接SQL
< where >: where元素只会在子元素有内容的情况下才插入where子句, 而且会自动去除子句的开头的AND 或者 OR
<mapper namespace="com.tyrant.mapper.EmpMapper">
<select id="backList" resultType="com.tyrant.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
</where>
</select>
</mapper>
2.7.2 < set >
< set >: 动态地在行首插入SET关键字, 并会删掉额外的逗号(用在update语句中)
<update id="update2">
<set>
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="job != null">job = #{job},</if>
<if test="entrydate != null">entrydate = #{entryDate},</if>
<if test="dept_id != null">dept_id = #{deptId},</if>
<if test="update_time != null">update_time = #{updateTime}</if>
where id = #{id}
</set>
</update>
2.7.3 < foreach >
<!--动态删除员工
collection : 遍历的集合
item : 遍历出来的元素
separator : 用什么分隔符
open : 开始前拼接的SQL片段
close: 结束后拼接的SQL片段-->
<delete id="deleteByIds">
delete where id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
2.7.4 < sql > < include >
< sql >: 定义可重用的SQL片段
< include >: 通过属性refid, 指定包含的sql片段
<!--抽取-->
<sql id="commentSelect">
select id, username, name, gender, job, entrydate, password, dept_id, create_time, update_time
from emp
</sql>
<!--查询员工信息-->
<select id="backList" resultType="com.tyrant.pojo.Emp">
<include refid="commentSelect"/>
<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
</where>
</select>
3. 案例
3.1 开发规范
Restful
REST(Representational State Transfer), 表述性状态转换, 它是一种软件架构风格
注意: REST是风格, 是约定方式, 约定不是规定, 可以打破
描述模块的功能通常使用复数, 也就是加s的格式来描述, 表示此类资源, 而非单个资源, 如: users, emps, books ...
统一响应结果