什么是MyBatis?
MyBatis是一个优秀的持久层框架,⽤于简化JDBC的开发。
MyBatis本是Apache的⼀个开源项⽬iBatis,2010年这个项目由apache迁移到了googlecode,并且改名为MyBatis。
简单来说MyBatis是更加简单完成数据和数据库交互的框架
什么是持久层呢?
web应⽤程序⼀般分为三层,有Controller、Service、
Dao流程如下浏览器发起请求,先请求Controller,Controller接收到请求之后Service进⾏业务逻辑处理Service再调⽤Dao。而持久层就是持久操作的层,就是数据访问层dao,是用来操作数据库
MyBatis入门
(1)创建spingboot工程、并导入mybatis起步依赖、mysql的驱动包
Mybatis 是⼀个持久层框架,具体的数据存储和数据操作还是在MySQL中操作的,所以需要添加MySQL驱动
我们可以看到创建完成之后pom.xml文件中导入的依赖
(2)
创建对应的UserInfo
package com.example.mybatisdemo.demos.web;
import javax.xml.crypto.Data;
public class UserInfo {
private Integer id;
private String usernane;
private String password;
private Integer age;
private Integer gender;
private String phone;
private Integer deleteFlag;
private Data createTime;
private Data updateTime;
}
数据准备创建用户表 并创建对应的实体类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' );
配置数据库连接字符串
配置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”
写入持久层代码
在项目中建立持久层接口UserIonfoMapper
package com.example.mybatisdemo.demos.web.mapper;
import com.example.mybatisdemo.demos.web.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();
}
@Mapper注解:表示是MyBatis中的接口
程序运⾏时,框架会⾃动⽣成接⼝的实现类对象(代理对象),并给交Spring的IOC容器管理
@Select注解:代表的就是select查询,也就是注解对应⽅法的具体实现内容
单元测试
创建出来的SpringBoot⼯程中,在src下的test⽬录下,已经⾃动帮我们创建好了测试类,我们可以直接使⽤这个测试类来进⾏测试
package com.example.mybatisdemo;
import com.example.mybatisdemo.demos.web.UserInfo;
import com.example.mybatisdemo.demos.web.mapper.UserInfoMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
class MyBatisdemoApplicationTests {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void contextLoads() {
List<UserInfo> userInfoList = userInfoMapper.queryAllUser();
System.out.println(userInfoList);
}
}
测试类有注解@SpringBootTest在运⾏时⾃动加载Spring的运⾏环境通过@Autowired这个注解,注⼊我们要测试的类,就可以开始进⾏测试了
然后结果中只有SQL语句中查询的列对应的属性才有赋值
使用idea自动生成测试类
除此之外,也可以使⽤Idea⾃动⽣成测试类
在使用Mapper接口时
右键->Generate->Test
编写代码
package com.example.mybatisdemo.demos.web.mapper;
import com.example.mybatisdemo.demos.web.UserInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void queryAllUser() {
List<UserInfo> userInfoList = userInfoMapper.queryAllUser();
System.out.println(userInfoList);
}
}
MyBatis的基础操作
打印MyBatis日志我们借助日志查看sql语句执行 执行传递的参数和执行结果
配置文件
#数据库连接配置
mybatis:
configuration: #配置打印MyBatis日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
重新运行查看结果
其中
表示:查询语句
Preparing: select username, 'password',age, gender, phone from userinfo
表示:查询类型
Parameters:
表示: 查询SQL语句结果
<== Columns: username, password, age, gender, phone
<== Row: admin, password, 18, 1, 18612340001
<== Row: zhangsan, password, 18, 1, 18612340002
<== Row: lisi, password, 18, 1, 18612340003
<== Row: wangwu, password, 18, 1, 18612340004
<== Total: 4
参数传递
需求:查找id为4的用户
@Mapper
public interface UserInfoMapper {
//查询所有用户
@Select("select username, 'password',age, gender, phone from userinfo")
List<UserInfo> queryAllUser();
@Select("select username, 'password', age, gender, phone from userinfo where id = 4")
UserInfo queryById();
}
生成测试用例
package com.example.mybatisdemo.demos.web.mapper;
import com.example.mybatisdemo.demos.web.UserInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
class UserInfoMapperTestquerById {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void queryById() {
UserInfo userInfo = userInfoMapper.queryById();
System.out.println(userInfo);
}
}
结果
@Select(“select username, ‘password’, age, gender, phone from userinfo where id = #{id}”)
UserInfo queryById(Integer id);
但是这样的话, 只能查找id=4的数据,所以SQL语句中的id值不能写成固定数值,需要变为动态的数值解决⽅案:在queryById⽅法中添加⼀个参数(id),将⽅法中的参数,传给SQL语句
例如:
@Select("select username, 'password', age, gender, phone from userinfo where id = #{id}")
UserInfo queryById(Integer id);
@Test
void queryById() {
UserInfo userInfo = userInfoMapper.queryById(4);
System.out.println(userInfo);
}
结果
也可以通过注解@Param设置参数别名#{…}里面的属性名要和@Param里面的一样
@Select("select username, `password`, age, gender, phone from userinfo where
id= #{userid} ")
UserInfo queryById(@Param("userid") Integer id);
MyBatis的增删查改
增
SQL语句
insert into userinfo (username, `password`, age, gender, phone) values
("zhaoliu","zhaoliu",19,1,"18700001234")
我们把sql中的常量换成动态的参数
例如:
@Insert("insert into userinfo(username,'password',age,gender,phone) values (#{username},#{password},#{age},#{gender},#{phone})")
Integer insert(UserInfo userInfo);
生成测试案例
package com.example.mybatisdemo.demos.web.mapper;
import com.example.mybatisdemo.demos.web.UserInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class UserInfoMapperTestInsert {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void insert() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("zhaoliu");
userInfo.setPassword("zhaoliu");
userInfo.setGender(2);
userInfo.setAge(21);
userInfo.setPhone("18612340005");
userInfoMapper.insert(userInfo);
}
}
也可以设置使用@Param属性 使用#{…} 需要参数.属性获取
@Insert("insert into userinfo (username, password, age, gender, phone) values (#{userinfo.username},#{userinfo.password},#{userinfo.age},#{userinfo.gender},#{userinfo.phone})")
Integer insert(@Param("userinfo")UserInfo userinfo);
返回主键
Insert语句默认返回的是受影响行数
有些情况数据插入之后还需要用于后续的关联操作需要新插入数据的id
如果我们想拿到自增id就要在mapper接口上添加一个
@Options注解
例如:
@Options(useGeneratedKeys = true, keyProperty = "id")
@Insert("insert into userinfo (username, password, age, gender, phone) values (#{userinfo.username},#{userinfo.password},#{userinfo.age},#{userinfo.gender},#{userinfo.phone})")
Integer insert(@Param("userinfo")UserInfo userinfo);
useGeneratedKeys:
这会令 MyBatis 使⽤ JDBC 的 getGeneratedKeys ⽅法来取出由数据库内的部⽣成的主键(⽐如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的⾃动递增字
段),默认值:false.
keyProperty:指定能够唯⼀识别对象的属性,MyBatis 会使⽤ getGeneratedKeys 的返回值或insert 语句的 selectKey ⼦元素设置它的值,默认值:未设置(unset)
测试:
@Test
void insert() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("zhaoliu");
userInfo.setPassword("zhaoliu");
userInfo.setGender(2);
userInfo.setAge(21);
userInfo.setPhone("18612340005");
Integer count =userInfoMapper.insert(userInfo);
System.out.println(userInfo);
System.out.println("添加数据条数" + count +",数据ID:" + userInfo.getId());
}
结果:
删除:
对应sql语句
delete from userinfo where id=6
@Delete("delete from userinfo where id = #{id}")
void delete(Integer id);
生成测试案例
@Test
void delete() {
userInfoMapper.delete(9);
}
运行结果
改:
对应sql语句
update userinfo set username="xiaozhao" where id=8
接口
@Update("update userinfo set username=#{username} where id=#{id}")
void update(UserInfo userInfo);
测试代码
@Test
void update() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("lisi");
userInfo.setId(8);
Integer result = userInfoMapper.update(userInfo);
if (result>0){
System.out.println("修改成功");
}
}
结果:
查:
我们对表上的数据进行查询
@Select("select id, username,password,age,gender,phone,delete_flag,create_time,update_time from userinfo")
List<UserInfo> queryALLUser2();
生成的测试类:
@Test
void queryALLUser2() {
List<UserInfo> userInfoList = userInfoMapper.queryALLUser2();
System.out.println(userInfoList);
结果:
我们可以看到表中的delete_flag,create_time,update_time查询到了却没有附上属性的值
MyBatis 会根据⽅法的返回结果进⾏赋值 ⽅法⽤对象 UserInfo接收返回结果, MySQL 查询出来数据为⼀条,
就会⾃动赋值给对象 ⽅法⽤List接收返回结果, MySQL 查询出来数据为⼀条或多条时, 也会⾃动赋值给List
但如果MySQL 查询返回多条, 但是⽅法使⽤UserInfo接收, MyBatis执⾏就会报错.
这是因为当自动映射查询结果时,MyBatis会获取结果中返回的列名并在 Java 类中查找相同名字的属性(忽略
⼤⼩写)这意味着如果发现了 ID 列和 id 属性,MyBatis 会将列 ID 的值赋给 id 属性 如果不一致就不会赋值
可以看到后面三个的名字不一致
这时我们怎么解决呢?
- 起别名
在SQL语句给列名起别名保持别名和实体类属性名一样
例如:
@Select("select id, username, password,age, gender, phone,delete_flag as deleteFlag, " +
"create_time as createTime, update_time as updateTime" +
" from userinfo")
List<UserInfo> queryAllUser();
测试案例:
@Test
void queryAllUser() {
List<UserInfo> userInfoList = userInfoMapper.queryAllUser();
log.info(userInfoList.toString());
}
运行结果:
这里就可以看到之前本为空的
delete_flag 和 create_time 被起别名成createTime
updateTime(和userInfo的属性名对上)之后就能赋值上了
错误笔记:
之前这里的的updateTime和createTime就算改了别名之后赋值还是null找了很久都没有发现错误,最后发现是属性的类型对不上
这里的类型的包应该是这个包的
但是我没注意用了这个包的Date从而导致代码看起来是对的但是结果不对
所以当我们发现mybatis查询值为null时可以从以下几个方面排除
- 数据库对应的纪录的字段确实为空;
- 映射配置问题:确保在数据库映射配置(比如 MyBatis 的配置)中,对于这两个时间字段的映射处理是正确的。
- 查询语句问题: 检查查询语句中对于这两个字段的获取和别名设置是否准确无误
- 数据类型不匹配 例如: 代码中定义的 Data 类型与数据库中实际的时间字段类型不匹配,导致无法正确映射。
(2) 结果映射
@Results(id = "BaseMap",value = {
@Result(column = "delete_flag",property = "deleteFlag"),
@Result(column = "create_time",property = "createTime"),
@Result(column = "update_time",property = "updateTime")
})
@Select("select * from userinfo")
测试类
@Test
void testQueryALLUser2() {
List<UserInfo> userInfoList = userInfoMapper.queryAllUser2();
log.info(userInfoList.toString());
}
结果:
如果我们也希望其他的SQL语句使用这个映射关系
可以使用注解@ResultMap注解来复用其他定义的ResultMap
例如:
运行结果:
@Select("select id, username, password,age,gender,phone,delete_flag,create_time,update_time " +
"from userinfo where id = #{userid}")
@ResultMap(value = "BaseMap")
UserInfo queryByid(@Param("userid") Integer id);
测试类
@Test
void queryByid() {
UserInfo userInfo = userInfoMapper.queryByid(3);
log.info(userInfo.toString());
}
开启驼峰命名
通常数据库列使⽤蛇形命名法(下划线分割各个单词)
Java 属性⼀般遵循驼峰命名法约定为了在这两种命名⽅式之间启⽤⾃动映射
需要将 mapUnderscoreToCamelCase 设置为 true
例如:
mybatis:
configuration: #配置打印MyBatis日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
驼峰命名规则: abc_xyz => abcXyz
表中字段名:abc_xyz
类中属性名:abcXyz
这时我们java代码不做任何处理就可以通过驼峰命名自动转换
例如:
@Select("select id, username, password,age,gender,phone,delete_flag,create_time,update_time " +
"from userinfo")
List<UserInfo> selectAllUser();
@Test
void selectAllUser() {
List<UserInfo> userInfoList = userInfoMapper.selectAllUser();
log.info(userInfoList.toString());
}
结果