在应?分层学习时, 我们了解到web应?程序?般分为三层,即:Controller、Service、Dao .
之前的案例中,请求流程如下: 浏览器发起请求, 先请求Controller, Controller接收到请求之后, 调?Service进?业务逻辑处理, Service再调?Dao, 但是Dao层的数据是Mock的, 真实的数据应该从数据库
中读取.
我们学习MySQL数据库时,已经学习了JDBC来操作数据库, 但是JDBC操作太复杂了.
问题提出:
对于 JDBC 来说,整个操作?常的繁琐,我们不但要拼接每?个参
数,?且还要按照模板代码的?式,?步步的操作数据库,并且在每次操作完,还要?动关闭连接等,?所有的这些操作步骤都需要在每个?法中重复书写. 那有没有?种?法,可以更简单、更?便的操作数据库呢?
答案是肯定的,这就是我们要学习 MyBatis 的真正原因,它可以帮助我们更?便、更快速的操作数据库.
MyBatis是?款优秀的 持久层 框架,?于简化JDBC的开发。
持久层:指的就是持久化操作的层, 通常指数据访问层(dao), 是?来操作数据库的.
简单来说 MyBatis 是更简单完成程序和数据库交互的框架,也就是更简单的操作和读取数据库?具
Mybatis操作数据库的步骤:
创建springboot?程,并导? mybatis的起步依赖、mysql的驱动包
项??程创建完成后,?动在pom.xml?件中,导?Mybatis依赖和MySQL驱动依赖
<!--Mybatis 依赖包-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.1</version>
</dependency>
<!--mysql驱动包-->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
创建??表, 并创建对应的实体类User
-- 创建数据库
DROP DATABASE IF EXISTS mybatis_test;
CREATE DATABASE mybatis_test DEFAULT CHARACTER SET utf8mb4;
-- 使?数据数据
USE mybatis_test;
-- 创建表[??表]
DROP TABLE IF EXISTS userinfo;
CREATE TABLE `userinfo` (
`id` INT ( 11 ) NOT NULL AUTO_INCREMENT,
`username` VARCHAR ( 127 ) NOT NULL,
`password` VARCHAR ( 127 ) NOT NULL,
`age` TINYINT ( 4 ) NOT NULL,
`gender` TINYINT ( 4 ) DEFAULT '0' COMMENT '1-男 2-? 0-默认',
`phone` VARCHAR ( 15 ) DEFAULT NULL,
`delete_flag` TINYINT ( 4 ) DEFAULT 0 COMMENT '0-正常, 1-删除',
`create_time` DATETIME DEFAULT now(),
`update_time` DATETIME DEFAULT now(),
PRIMARY KEY ( `id` )
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;
-- 添加??信息
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'admin', 'admin', 18, 1, '18612340001' );
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'zhangsan', 'zhangsan', 18, 1, '18612340002' );
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'lisi', 'lisi', 18, 1, '18612340003' );
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'wangwu', 'wangwu', 18, 1, '18612340004' );
实体类的属性名与表中的字段名??对应
import lombok.Data;
import java.util.Date;
@Data
public class UserInfo {
private Integer id;
private String username;
private String password;
private Integer age;
private Integer gender;
private String phone;
private Integer deleteFlag;
private Date createTime;
private Date updateTime;
}
Mybatis中要连接数据库,需要数据库相关参数配置
如果是application.yml?件, 配置内容如下:
#数据库连接配置
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
在项?中, 创建持久层接?UserInfoMapper
import com.example.demo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface UserInfoMapper {
//查询所有??
@Select("select username, `password`, age, gender, phone from userinfo")
public List<UserInfo> queryAllUser();
}
Mybatis的持久层接?规范?般都叫 XxxMapper
@Mapper注解:表?是MyBatis中的Mapper接?
- 程序运?时, 框架会?动?成接?的实现类对象(代理对象),并给交Spring的IOC容器管理
- @Select注解:代表的就是select查询,也就是注解对应?法的具体实现内容.
在创建出来的SpringBoot?程中,在src下的test?录下,已经?动帮我们创建好了测试类 ,我们可以直接使?这个测试类来进?测试.
测试类上添加了注解 @SpringBootTest,该测试类在运?时,就会?动加载Spring的运?环境.我们通过@Autowired这个注解, 注?我们要测试的类, 就可以开始进?测试了
运?结果如下:
返回结果中, 可以看到, 只有SQL语句中查询的列对应的属性才有赋值
在Mybatis当中我们可以借助?志, 查看到sql语句的执?、执?传递的参数以及执?结果
在配置?件中进?配置即可
配置MyBatis打印日志
mybatis:
configuration: # 配置打印 MyBatis?志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
重新运?程序, 可以看到SQL执?内容, 以及传递参数和执?结果
需求: 查找id=4的??,对应的SQL就是: select * from userinfo where id=4
解决?案:在queryById?法中添加?个参数(id),将?法中的参数,传给SQL语句
使? #{} 的?式获取?法中的参数
如果mapper接??法形参只有?个普通类型的参数,#{…} ??的属性名可以随便写,如:#{id}、#{value}。建议和参数名保持?致
返回主键
Insert 语句默认返回的是 受影响的?数
但有些情况下, 数据插?之后, 还需要有后续的关联操作, 需要获取到新插?数据的id
?如订单系统
当我们下完订单之后, 需要通知物流系统, 库存系统, 结算系统等, 这时候就需要拿到订单ID
如果想要拿到?增id, 需要在Mapper接?的?法上添加?个Options
的注解
// 插入一条信息
@Options(useGeneratedKeys = true,keyProperty = "id") // 获取自增主键 ID
@Insert("insert into userinfo (username,password,age,gender,phone ) values(#{username},#{password},#{age},#{gender},#{phone})")
public Integer insert(UserInfo userInfo);
测试代码
运?结果:
我们在上?查询时发现, 有?个字段是没有赋值的, 只有Java对象属性和数据库字段?模?样时, 才会进?赋值
接下来我们多查询?些数据
@Select("select id, username, `password`, age, gender, phone, delete_flag,
create_time, update_time from userinfo")
List<UserInfo> queryAllUser();
查询结果:
从运?结果上可以看到, 我们SQL语句中, 查询了delete_flag, create_time, update_time, 但是这?个属性却没有赋值.
MyBatis 会根据?法的返回结果进?赋值
原因分析:
当?动映射查询结果时,MyBatis 会获取结果中返回的列名并在 Java 类中查找相同名字的属性(忽略??写)。 这意味着如果发现了 ID 列和 id 属性,MyBatis 会将列 ID 的值赋给 id 属性
解决办法:
在SQL语句中,给列名起别名,保持别名和实体类属性名?样
如果其他SQL, 也希望可以复?这个映射关系, 可以给这个Results定义?个名称
使? id 属性给该 Results 定义别名, 使? @ResultMap 注解来复?其他定义的 ResultMap
mybatis:
configuration:
map-underscore-to-camel-case: true #配置驼峰?动转换
驼峰命名规则: abc_xyz => abcXyz
Java 代码不做任何处理
// 查询所有用户
@Select("select * from userinfo")
public List<UserInfo> queryAllUser();
Mybatis的开发有两种?式:
上?学习了注解的?式, 接下来我们学习XML的?式
使?Mybatis的注解?式,主要是来完成?些简单的增删改查功能. 如果需要实现复杂的SQL功能,建议使?XML来配置映射语句,也就是将SQL语句写在XML配置?件中.
MyBatis XML的?式需要以下两步:
此步骤需要进?两项设置,数据库连接字符串设置和 MyBatis 的 XML ?件配置。
如果是application.yml?件, 配置内容如下:
#数据库连接配置
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
#配置 mybatis xml 的?件路径,在 resources/mapper 创建所有表的 xml ?件
mybatis:
mapper-locations: classpath:mapper/**Mapper.xml
持久层代码分两部分
数据持久层的接?定义:
import com.example.demo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface UserInfoXMlMapper {
List<UserInfo> queryAllUser();
}
数据持久成的实现,MyBatis 的固定 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.demo.mapper.UserInfoMapper">
</mapper>
创建UserInfoXMLMapper.xml, 路径参考yml中的配置
查询所有??的具体实现 :
<?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.demo.mapper.UserInfoXMlMapper">
<select id="queryAllUser" resultType="com.example.demo.model.UserInfo">
select username,`password`, age, gender, phone from userinfo
</select>
</mapper>
以下是对以上标签的说明:
<mapper>
标签:需要指定 namespace 属性,表?命名空间,值为 mapper 接?的全限定名,包括全包名.类名。<select>
查询标签:是?来执?数据库的查询操作的:
@SpringBootTest
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void queryAllUser() {
List<UserInfo> userInfoList = userInfoMapper.queryAllUser();
System.out.println(userInfoList);
}
}
运?结果如下:
接下来,我们来实现?下??的增加、删除和修改的操作.
UserInfoMapper接?:
Integer insertUser(UserInfo userInfo);
UserInfoMapper.xml实现
<insert id="insertUser">
insert into userinfo (username, `password`, age, gender, phone)
values (#{username}, #{password}, #{age}, #{gender}, #{phone})
</insert>
返回?增 id
接?定义不变, Mapper.xml 实现 设置useGeneratedKeys 和keyProperty属性
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
insert into userinfo (username, `password`, age, gender, phone)
values (#{username}, #{password}, #{age}, #{gender}, #{phone})
</insert>
UserInfoMapper接?:
UserInfoMapper.xml实现:
同样的, 使?XML 的?式进?查询, 也存在数据封装的问题
解决办法和注解类似:
接下来看下xml如果来写结果映射
多表查询和单表查询类似, 只是SQL不同?已
上?建了?张??表, 我们再来建?张?章表, 进?多表关联查询.
?章表的uid, 对应??表的id.
数据准备
-- 创建?章表
DROP TABLE
IF EXISTS articleinfo;
CREATE TABLE articleinfo (
id INT PRIMARY KEY auto_increment,
title VARCHAR (100) NOT NULL,
content TEXT NOT NULL,
uid INT NOT NULL,
delete_flag TINYINT (4) DEFAULT 0 COMMENT '0-正常, 1-删除',
create_time DATETIME DEFAULT now(),
update_time DATETIME DEFAULT now()
) DEFAULT charset 'utf8mb4';
-- 插?测试数据
INSERT INTO articleinfo (title, content, uid)
VALUES
('Java', 'Java正文', 1)
对应Model:
@Data
public class ArticleInfo {
private Integer id;
private String title;
private String content;
private Integer uid;
private Integer deleteFlag;
private Date createTime;
private Date updateTime;
}
需求: 根据uid查询作者的名称等相关信息
SQL:
SELECT
ta.id, ta.title, ta.content,ta.uid,tb.username,tb.age,tb.gender
FROM
articleinfo AS ta
LEFT JOIN userinfo AS tb ON ta.uid = tb.id;
补充实体类:
MyBatis 参数赋值有两种?式, 咱们前?使?了 #{} 进?赋值, 接下来我们看下?者的区别
#{} 和 ${} 的区别就是预编译SQL和即时SQL 的区别.
性能更?
绝?多数情况下, 某?条 SQL 语句可能会被反复调?执?, 或者每次执?的时候只有个别的值不同(?如 select 的 where ?句值不同, update 的 set ?句值不同, insert 的 values 值不同). 如果每次都需要经过上?的语法解析, SQL优化、SQL编译等,则效率就明显不?了.
预编译SQL,编译?次之后会将编译后的SQL语句缓存起来,后?再次执?这条语句时,不会再次编译(只是输?的参数不同), 省去了解析优化等过程, 以此来提?效率
更安全(防?SQL注?)
SQL注?:是通过操作输?的数据来修改事先定义好的SQL语句,以达到执?代码对服务器进?攻击的?法。
由于没有对??输?进?充分检查,?SQL?是拼接?成,在??输?参数时,在参数中添加?些SQL关键字,达到改变SQL运?结果的?的,也可以完成恶意攻击。
sql 注?代码: ' or 1='1
可以看出来, 查询的数据并不是??想要的数据. 所以?于查询的字段,尽量使? #{} 预查询的?式
排序的 desc 和 asc, 使用${} 直接拼接即可