1. 什么是MyBatis
MyBatis是一款持久层框架,用于简化JDBC的开发(持久层指的就是持久化操作的层,通常指数据访问层(dao),即用于操作数据库),简单来说MyBatis 是更简单完成程序和数据库交互的框架。下面我们通过一个例子来简单了解MyBatis
1.1 创建项目
创建Spring Boot 项目,并导入mybatis依赖和mysql驱动包:
1.2 数据准备
创建用户表和对应的实体类,并在配置文件中配置数据的的信息(url,用户名,密码等)
创建用户表:
-- 创建数据库
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' );
创建对应的实体类 UserInfo:
package com.example.mybatis.model;
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;
}
写配置信息:
spring:
application:
name: J20240402-MyBatis
#数据库连接配置
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
driver-class-name用于指定数据库驱动程序的类名
1.3 使用示例
创建一个接口:UserInfoMapper, 用于执行各种SQL语句:
package com.example.mybatis.mapper;
import com.example.mybatis.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface UserInfoMapper {
@Select("select * from userinfo")
List<UserInfo> getUserInfoAll();
}
注意:
- 数据库相关操作一般放在mapper包下。
- @Mapper注解的作用是将一个接口标记为MyBatis的Mapper接口,告诉MyBatis框架需要为这个接口生成对应的实现类,用于执行数据库操作。
- @Select注解代表这个SQL语句是用于查询操作。
接下来我们通过单元测试来测试我们的代码能否正常运行:
我们生成项目时会自动生成一个测试版本:
我们在这里写测试代码:
package com.example.mybatis;
import com.example.mybatis.mapper.UserInfoMapper;
import com.example.mybatis.model.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;
@SpringBootTest
class J20240402MyBatisApplicationTests {
@Autowired()
private UserInfoMapper userInfoMapper;
@Test
void contextLoads() {
List<UserInfo> userInfos = userInfoMapper.getUserInfoAll();
System.out.println(userInfos);
}
注意:被@Mapper注解注解的接口会在项目启动时由MyBatis框架生成对应的实现类,并将该实现类作为Bean交给Spring容器管理。这样,在Spring容器中就可以使用@Autowired注解来注入Mapper接口的实例,从而在代码中直接调用Mapper接口中定义的方法来执行数据库操作。
接下来我们执行代码:
点击方法左边的绿色三角形然后点击Run即可运行:
我们可以看到成功打印出了信息。
接下来我们在正式项目中编写代码:
我们同样使用三层架构的模式:
package com.example.mybatis.controller;
import com.example.mybatis.model.UserInfo;
import com.example.mybatis.servise.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/getUserInfoAll")
public List<UserInfo> getUserInfoAll() {
return userService.getUserInfoAll();
}
}
package com.example.mybatis.servise;
import com.example.mybatis.mapper.UserInfoMapper;
import com.example.mybatis.model.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
public List<UserInfo> getUserInfoAll() {
return userInfoMapper.getUserInfoAll();
}
}
运行代码,在浏览器访问:
可以看到返回的json格式的数据 ,我们发现返回的数据中后三个字段的值都为null,原因我们下面再讲,接下来先说一下单元测试。
1.4 单元测试
上面我们已经使用过单元测试,但是如果我们要测试的方法很多,如果全部写在一个类里的话看起来会非常的杂乱,所以我们可以对应我们正式项目中的代码“照搬”进Test项目中,我们可以使用Idea提供的快捷方式来快速生成这一代码:
在要测试的类点击鼠标右键,点击generate
然后点击Test:
把下面我们要测试的方法打上勾点击OK :
我们发现下面就已经生成了一个对应的Test代码:
package com.example.mybatis.mapper;
import org.junit.jupiter.api.Test;
class UserInfoMapperTest {
@Test
void getUserInfoAll() {
}
}
我们在其中注入对应的依赖即可测试:
package com.example.mybatis.mapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void getUserInfoAll() {
System.out.println(userInfoMapper.getUserInfoAll());
}
}
注意要加上@SpringBootTest注解,不然无法通过@Autowried注入,同样点击绿色三角形运行指定方法:
运行成功。
2. MyBatis 基础操作
2.1 打印日志
在MyBatis中我们可以借助日志查看sql语句的执行,传递的参数以及执行结果,只需在配置文件中进行配置即可:
mybatis:
configuration: # 配置打印 MyBatis⽇志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
我再运行单元测试:
可以看到多出了MySql返回的结果
2.2 参数传递
很多时候我们的SQL语句是不能写死的,于是就需要把某些可能会改变的部分SQL作为参数传入:
@Mapper
public interface UserInfoMapper {
@Select("select * from userinfo where id = #{id}")
List<UserInfo> getUserInfoId(Integer id);
}
我们同样生成单元测试代码来测试
@SpringBootTest
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void getUserInfoId() {
System.out.println(userInfoMapper.getUserInfoId(2));
}
}
我们查询id为2的数据:
可以看到MyBatis帮我们转为了对应的JDBC语句,注意编写代码时,#{ }中的字段名要和方法中的参数名对应,否则有多个参数时会出错。
传递多个参数:
@Mapper
public interface UserInfoMapper {
@Select("select * from userinfo where id = #{id} and age = #{age}")
List<UserInfo> getUserInfoIdAndAge(Integer id, Integer age);
}
传递多个参数时也可以根据参数位置来传:
@Mapper
public interface UserInfoMapper {
@Select("select * from userinfo where id = #{param1} and age = #{param2}")
List<UserInfo> getUserInfoIdAndAge(Integer id, Integer age);
}
也可以使用@Param 注解指定参数对应的位置:
@Mapper
public interface UserInfoMapper {
@Select("select * from userinfo where username = #{userName} and id = {id}")
List<UserInfo> getUserInfoNameAndId(@Param("userName")String name, @Param("id") Integer id);
}
注意:@Param注解中 对应的是 #{ } 中的字段名,使用时推荐既要保证#{}中字段名和参数名一致,同时也要加上@Param注解。
2.3 Insert操作
增加操作需要使用@Insert注解
@Mapper
public interface UserInfoMapper {
@Insert("insert into userinfo(id, username, password, age) values (#{id}, #{userName}, #{password}, #{age})")
Integer insert(UserInfo userInfo);
}
这是我们借助UserInfo对象来传递参数,userinfo(id, username, password, age) 中的字段对应的时userinfo表中的字段,#{ }中对应的是UserInfo对象中的属性名。
单元测试:
@SpringBootTest
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void insert() {
UserInfo userInfo = new UserInfo();
userInfo.setUserName("liHua");
userInfo.setPassword("liHua");
userInfo.setAge(20);
System.out.println(userInfoMapper.insert(userInfo));
}
}
返回了1表示影响了一行, 我们在数据库中查询:
插入成功。
注意:这里我们没有给id赋值,因为我们建表时标识了让id自增,所以id会自动自增,很多场景我们需要根据id的值做一些业务,所以我们需要获取到id。
@Mapper
public interface UserInfoMapper {
@Options(useGeneratedKeys = true, keyProperty = "id")
@Insert("insert into userinfo(id, username, password, age) values (#{id}, #{userName}, #{password}, #{age})")
Integer insert(UserInfo userInfo);
}
@Options(useGeneratedKeys = true, keyProperty = "id"): 这是 MyBatis 的一个注解,用于指示生成主键并将其赋值给指定的属性。useGeneratedKeys = true 表示要生成主键,keyProperty = "id" 表示将生成的主键值赋给 UserInfo 对象中的 id 属性。于是我们就可以从UserInfo的对象中获取到id:
@SpringBootTest
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void insert() {
UserInfo userInfo = new UserInfo();
userInfo.setUserName("liHua");
userInfo.setPassword("liHua");
userInfo.setAge(20);
System.out.println(userInfoMapper.insert(userInfo));
System.out.println("id = " + userInfo.getId());
}
}
注意:
调用insert方法之后UserInfo对象中的id才会被赋值。
如果这里给参数加上 @Param注解 会把 userInfo对象整体当作一个参数,需要写成如下形式:
@Options(useGeneratedKeys = true, keyProperty = "id")
@Insert("insert into userinfo(id, username, password, age) values (#{userInfo.id}, #{userInfo.userName}, #{userInfo.password}, #{userInfo.age})")
Integer insert(@Param("userInfo") UserInfo userInfo);
2.4 Delete 操作
与前面相似,我们需要使用@Delete注解:
@Mapper
public interface UserInfoMapper {
@Delete("delete from userinfo where id = #{id}")
Integer delete(Integer id);
}
@SpringBootTest
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void delete() {
System.out.println(userInfoMapper.delete(5));
}
}
删除id为5的数据:
2.5 Update 操作
@Mapper
public interface UserInfoMapper {
@Update("update userinfo set password = #{password}, age = #{age}, gender = #{gender} where id = #{id}")
Integer update(UserInfo userInfo);
}
@SpringBootTest
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void update() {
UserInfo userInfo = new UserInfo();
userInfo.setId(1);
userInfo.setPassword("666");
userInfo.setAge(19);
userInfo.setGender(2);
System.out.println(userInfoMapper.update(userInfo));
}
}
2.6 Select 操作
Select操作前面我们已经演示过,但在途中遇到了一些问题:
查询到的后三个字段的值都为null,这是因为我们是使用UserInfo对象来接收的数据,当字段名相同时,会自动赋值,我们对比我们的数据库字段和UserInfo类:
我们发现我们没有接收到的三个字段,都是字段名对应不上的字段。
解决方法:
1. 起别名
我们知道了赋值失败的原因是字段名对不上,那么我们就可以通过SQL语句中的取别名的方式来,使字段名能对应得上:
@Mapper
public interface UserInfoMapper {
@Select("select id, username, password, age, dender, phone, " +
"delete_flag as deleteFlag, create_time as createTime, " +
"update_time as updateTime from userinfo")
List<UserInfo> getUserInfoAll();
}
2. 结果映射
使用@Results 和 @Result注解对数据库字段和对应类的属性进行结果映射:
@Mapper
public interface UserInfoMapper {
@Results({
@Result(column = "delete_flag", property = "deleteFlag"),
@Result(column = "create_time", property = "creatTime"),
@Result(column = "update_time", property = "updateTime")
})
@Select("select * from userinfo")
List<UserInfo> getUserInfoAll();
}
在此基础上,我们可以给@Results 的id属性赋值,当有其他地方需要用到这个映射关系时,可以直接使用@ResultMap注解使用这个映射关系,而不用再重新写一遍映射关系:
@Mapper
public interface UserInfoMapper {
@Results(id = "resultMap", value = {
@Result(column = "delete_flag", property = "deleteFlag"),
@Result(column = "create_time", property = "createTime"),
@Result(column = "update_time", property = "updateTime")
})
@Select("select * from userinfo")
List<UserInfo> getUserInfoAll();
@ResultMap("resultMap")
@Select("select * from userinfo")
List<UserInfo> getUserInfoAll2();
}
3. 驼峰自动转换
数据库字段和Java类字段对应不上的原因是因为命名规范不同导致的,Java是驼峰法命名,而SQL是以蛇形命名,所以我们可以在配置文件中添加驼峰自动转换配置,把接收到的字段自动转为驼峰形式:
mybatis:
configuration: # 配置打印MyBatis⽇志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true #配置驼峰⾃动转换
@Mapper
public interface UserInfoMapper {
@Select("select * from userinfo")
List<UserInfo> getUserInfoAll();
}
现在我们直接使用最开始的代码也能正常获取到值: