Mybatis---入门
1. 什么是MyBatis?
MyBatis是⼀款优秀的 持久层 框架,⽤于简化JDBC的开发。
MyBatis本是 Apache的⼀个开源项⽬iBatis,2010年这个项⽬由apache迁移到了google code,并且改名为MyBatis 。2013年11⽉迁移到Github.
官⽹:MyBatis中⽂⽹
在上⾯我们提到⼀个词:持久层
持久层:指的就是持久化操作的层, 通常指数据访问层(dao), 是⽤来操作数据库的
总而言之,Mybatis就是一个用于更加方便与数据库交互的框架
2.MyBatis入门
Mybatis操作数据库的步骤:
1. 准备⼯作(创建springboot⼯程、引⼊Mybatis的相关依赖、数据库表准备、实体类)
2. 配置Mybatis(数据库连接信息)
3. 编写SQL语句(注解/XML)
4. 测试
2.1准备工作
2.1.1创建springboot⼯程
在pom文件中右键
没有的话可以去下载一下插件
点击OK
添加这两个完成导⼊ mybatis的起步依赖、mysql的驱动包 ,刷新Maven即可
2.1.2 数据准备
创建数据库资源 和 实体类
DROP DATABASE IF EXISTS mybatis_test;
CREATE DATABASE mybatis_test DEFAULT CHARACTER SET utf8mb4;
-- 使⽤数据数据
USE mybatis_test;
-- 创建表
DROP TABLE IF EXISTS user_info;
CREATE TABLE `user_info` (
`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() ON UPDATE now(),
PRIMARY KEY ( `id` )
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;
-- 添加⽤⼾信息
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'admin', 'admin', 18, 1, '18612340001' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'zhangsan', 'zhangsan', 18, 1, '18612340002' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'lisi', 'lisi', 18, 1, '18612340003' );
INSERT INTO mybatis_test.user_info( 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;
}
(这里留了三个数据不一致,为后续展示做准备)
2.2 配置数据库连接字符串
Mybatis中要连接数据库,需要数据库相关参数配置
• MySQL驱动类
• 登录名
• 密码
• 数据库连接字符串
如果是application.yml⽂件, 配置内容如下:(以我的为例)
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false
username: root
password: '123456'
driver-class-name: com.mysql.cj.jdbc.Driver
数据库的密码记得更换!!
注意事项:
如果使⽤ MySQL 是 5.x 之前的使⽤的是"com.mysql.jdbc.Driver",如果是⼤于 5.x 使⽤的
是“com.mysql.cj.jdbc.Driver”
2.3创建持久层接口
以Select查询为例
@Mapper
public interface UserInfoMapper {
@Select("select `username`, `age`, `password` from user_info")
public List<UserInfo> getAllUserInfo();
}
@Mapper注解:用于标识是MyBatis中的Mapper接⼝,可以将类交给Spring容器管理
@Select注解:代表的就是select查询,也就是注解对应⽅法的具体实现内容
2.4单元测试
1.我们可以用自动创建的测试类直接进行测试
@SpringBootTest,该测试类在运⾏时,就会⾃动加载Spring的运⾏环境,在其他测试类中没有时一定要加!!!
点击运行,获得查询结果
2.也可以使⽤Idea ⾃动⽣成测试类
1. 在需要测试的Mapper接⼝中, 右键 -> Generate -> Test
2.选取要添加的测试方法
3.Mybatis基础操作
3.1 打印⽇志
在Mybatis当中我们可以借助⽇志, 查看到sql语句的执⾏、执⾏传递的参数以及执⾏结果
在yml配置⽂件中进⾏配置即可:
mybatis:
configuration: # 配置打印 MyBatis⽇志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
再次运行查看日志:
多出三部分信息
①: 查询语句
②: 传递参数及类型
③: SQL执⾏结果
3.2 参数传递
需求: 查找id=4的⽤⼾,对应的SQL就是: select * from user_info where id=4
@Select("select username, `password`, age, gender, phone from user_info where
id= 4 ")
UserInfo queryById();
但这样只能传递固定的参数,显然不能够满足我们的需求,我们可以使用#{}来获取参数
//参数传递
@Select("select * from user_info where id = #{id}")
public UserInfo getUserInfoById(Integer id);
内部的 “id” 与方法中的名称保持一致(如果只有一个参数可以不必保持一致)
或者使用@Param注解,如果使⽤ @Param 设置别名, #{...}⾥⾯的属性名必须和
@Param 设置的⼀样
@Select("select * from user_info where id = #{id666}")
public UserInfo getUserInfoById(@Param("id666") Integer id);
添加测试用例:
@Test
void getUserInfoById() {
UserInfo userInfo = userInfoMapper.getUserInfoById(1);
System.out.println(userInfo);
}
3.3@Insert
Mapper接⼝
@Insert("Insert into user_info (`id`, `username`, `password`, `age`) values (#{id}," +
" #{username}, #{password}, #{age})")
Integer insert(UserInfo userInfo);
直接使⽤UserInfo对象的属性名来获取参数
如果设置了 @Param 属性, #{...} 需要使⽤ 参数.属性 来获取
// @Insert("Insert into user_info (`id`, `username`, `password`, `age`) values (#{userInfo.id}, #{userInfo.username}, #{userInfo.password}, #{userInfo.age})")
// Integer insert(@Param("userInfo") UserInfo userInfo);
@Test
void insert() {
//new一个UserInfo传过去
UserInfo userInfo = new UserInfo();
userInfo.setId(8);
userInfo.setUsername("888");
userInfo.setPassword("123456");
userInfo.setAge(18);
//可以多传入参数,但接收不到,不能写入数据库中
//userInfo.setGender(1);
Integer count = userInfoMapper.insert(userInfo);
System.out.println("影响行数:" + count);
}
返回主键
Insert 语句默认返回的是 受影响的⾏数
但有些情况下, 数据插⼊之后, 还需要有后续的关联操作, 需要获取到新插⼊数据的id
⽐如订单系统
当我们下完订单之后, 需要通知物流系统, 库存系统, 结算系统等, 这时候就需要拿到订单ID
如果想要拿到⾃增id, 需要在Mapper接⼝的⽅法上添加⼀个Options的注解
@Options(useGeneratedKeys = true, keyProperty = "id")
@Insert("Insert into user_info (`id`, `username`, `password`, `age`) values (#{id}," +
" #{username}, #{password}, #{age})")
Integer insert(UserInfo userInfo);
@Test
void insert() {
//new一个UserInfo传过去
UserInfo userInfo = new UserInfo();
userInfo.setId(8);
userInfo.setUsername("888");
userInfo.setPassword("123456");
userInfo.setAge(18);
//可以多传入参数,但接收不到,不能写入数据库中
//userInfo.setGender(1);
Integer count = userInfoMapper.insert(userInfo);
System.out.println("影响行数:" + count + ", 数据ID:" + userInfo.getId());
}
useGeneratedKeys:这会令 MyBatis 使⽤ JDBC 的 getGeneratedKeys ⽅法来取出由数据库内
部⽣成的主键(⽐如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的⾃动递增字
段),默认值:false.
keyProperty:指定能够唯⼀识别对象的属性,MyBatis 会使⽤ getGeneratedKeys 的返回值或
insert 语句的 selectKey ⼦元素设置它的值,默认值:未设置
使用 userInfo.getId()获取id
3.4@Delete
@Delete("delete from user_info where id = #{id}")
public void delete(Integer id);
@Test
void delete() {
userInfoMapper.delete(8);
}
3.5@Update
@Update("update user_info set username = #{username} where id = #{id}")
public void update(UserInfo userInfo);
@Test
void update() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("zhaoliu666");
userInfo.setId(5);
userInfoMapper.update(userInfo);
}
3.6属性名称映射
我们在上⾯查询时发现, 有⼏个字段是没有赋值的, 只有Java对象属性和数据库字段⼀模⼀样时, 才会进⾏赋值
明明查询到了数据库中的信息但没有打印出来
原因分析:
当⾃动映射查询结果时,MyBatis 会获取结果中返回的列名并在 Java 类中查找相同名字的属性(忽略⼤⼩写)。 这意味着如果发现了 ID 列和 id 属性,MyBatis 会将列 ID 的值赋给 id 属性
我们知道数据库属性命名方式通常为 小写字母 加 下划线 分割
而Java属性命名规范是小驼峰
在Java中写数据库的属性就不太合适了
解决方法有三种:
1. 起别名
2. 结果映射
3. 开启驼峰命名
3.6.1 起别名
//1. as 起别名
@Select("select create_time as createTime from user_info where id = #{id}")
public UserInfo getUserInfoAsName(Integer id);
注意是将原数据库属性名as为Java属性名称
3.6.2结果映射
//2. Results
@Select("select * from user_info where id = #{id}")
@Results( value ={
@Result(property = "deleteFlag", column = "delete_flag"),
@Result(property = "createTime", column = "create_time"),
@Result(property = "updateTime", column = "update_time")
})
public UserInfo getUserInfoResults(Integer id);
如果只有括号内的内容value可以省略,加了id等就不可以省略了
如果其他SQL, 也希望可以复⽤这个映射关系, 可以给这个Results定义⼀个名称,加入id属性:
//2. Results
@Select("select * from user_info where id = #{id}")
@Results(id = "resultMap", value ={
@Result(property = "deleteFlag", column = "delete_flag"),
@Result(property = "createTime", column = "create_time"),
@Result(property = "updateTime", column = "update_time")
})
public UserInfo getUserInfoResults(Integer id);
//复用results
@Select("select * from user_info where id = #{id}")
@ResultMap("resultMap")
public UserInfo getUserInfoResultsMap(Integer id);
3.6.3开启驼峰命名(推荐)
这个方法可以直接将写的sql代码中像这种的“create_time”属性自动映射为小驼峰(不是转化)
在yml文件中配置:
mybatis:
configuration:
map-underscore-to-camel-case: true #配置驼峰⾃动转换
代码:
//3.配置驼峰⾃动转换 记得写的是原SQL语句
@Select("select create_time from user_info where id = #{id}")
public UserInfo getUserInfoCase(Integer id);
4. MyBatis XML配置⽂件
Mybatis的开发有两种⽅式:
1. 注解
2. XML
使⽤Mybatis的注解⽅式,主要是来完成⼀些简单的增删改查功能. 如果需要实现复杂的SQL功能,建议使⽤XML来配置映射语句,也就是将SQL语句写在XML配置⽂件中.
MyBatis XML的⽅式需要以下两步:
1. 配置数据库连接字符串和MyBatis
2. 写持久层代码
4.1 配置数据库连接字符串和MyBatis
在yml文件中配置:
# 数据库连接配置
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false
username: root
password: '123456'
driver-class-name: com.mysql.cj.jdbc.Driver
# 配置 mybatis xml 的⽂件路径,在 resources/mapper 创建所有表的 xml ⽂件
mybatis:
mapper-locations: classpath:mapper/**Mapper.xml
configuration: # 配置打印 MyBatis⽇志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true #3.配置驼峰⾃动转换
图标中的小鸟插件是:
可以帮助绑定代码
4.2 写持久层代码
持久层代码分两部分
1. ⽅法定义 Interface(可见无论如何也要建立接口)
2. ⽅法实现: XXX.xml
1.添加Mapper接口
2. 添加 UserInfoXMLMapper.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.cym.mybatis.mapper.UserInfoXmlMapper">
</mapper>
4.2.1Select
<?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.cym.mybatis.mapper.UserInfoXmlMapper">
<select id="getAllUserInfo" resultType="com.cym.mybatis.model.UserInfo">
select `username`, `age`, `password` from user_info
</select>
</mapper>
<mapper> 标签:需要指定 namespace 属性,表⽰命名空间,值为 mapper 接⼝的全限定
名,包括全包名.类名。
<select> 查询标签:是⽤来执⾏数据库的查询操作的
id :是和 Interface (接⼝)中定义的⽅法名称⼀样的,表⽰对接⼝的具体实现⽅法
resultType :是返回的数据类型,也就是我们定义的实体类
同样进行单元测试:
在XML文件中仍然会出现参数不匹配的现象
解决办法和注解类似:
1. 起别名
2. 结果映射
3. 开启驼峰命名
其中1,3的解决办法和注解⼀样,不再多说, 接下来看下xml如果来写结果映射
<resultMap id="BaseMap" type="com.cym.mybatis.model.UserInfo">
<id column="id" property="id"></id>
<result column="delete_flag" property="deleteFlag"></result>
<result column="create_time" property="createTime"></result>
<result column="update_time" property="updateTime"></result>
</resultMap>
4.2.2Insert
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
insert into user_info (`id`, `username`, `password`, `age`)values (#{userInfo.id},
#{userInfo.username}, #{userInfo.password}, #{userInfo.age})
</insert>
如果使⽤@Param设置参数名称的话, 使⽤⽅法和注解类似
同样也可以使用(useGeneratedKeys="true" keyProperty="id")获取自增id
4.2.3Delete
4.3.3Update
5.其他查询
5.1多表查询
其他(SQL, 映射关系和实体类)基本都一致,都是通过映射关系,把SQL运⾏结果和实体类关联起来,只是在写接口时写入多表查询的代码:
但我们通常不建议在开发环境中使用多表查询,为什么呢?
主要有两个原因:
1.生产环境中数据库资源非常宝贵
2.服务器扩容比MySQL扩容更加容易
生产环境中数据库资源非常宝贵:
首先环境分为:开发环境,测试环境,预发布环境(没上线),生产环境(上线)
预发布环境和生产环境使用的是同一个数据库,多表查询可能会导致越级访问等问题,不利于上线。
服务器扩容比MySQL扩容更加容易:
服务器扩容我们程序员就可以完成,也更加便宜,而数据库扩容就需要专业的人员了。
当我们要联合两张表进行查询时,假设使用多表查询消耗10ms,分别查询两张表后合并消耗7*2=14ms,看似多表查询快,但是查询两张表可以通过多线程完成,而多表查询只能单线程执行,会占用其他线程的时间,在真实生产中如果阻塞了其他线程服务,就会导致效率下降,我们要避免这种情况。