什么是MyBatis
MyBatis是一款优秀的持久层框架,用于简化JDBC的开发。
JDBC流程(√的部分是MyBatis已经集成的):
MyBatis本来是Apache的一个开源项目iBatis,2010年这个项目于由apache迁移到了google code,并且改名为MyBatis。2013年11月迁移到Github。
官网:MyBatis中文网
简单来说MyBatis是更简单完成程序和数据库交互的框架,也就是简单的操作和读取数据库工具。接下来,我们就通过一个入门程序,感受一下如何通过Mybatis来操作数据库。
MyBatis入门
准备
项目创建
需要在我们之前spring web和lombok依赖的基础上,引入mybatis framework和mysql的驱动包。(这里springBoot版本选择较低的且不带snapshop的版本,否则可能无法引入mybatis framework依赖)。
数据准备
-- 创建数据库
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' );
企业建表规范:
1、字段名统一小写,单词之间使用下划线分割
2、建表字段:自增字段,更新时间、创建时间
删除标志:
常见方案:物理删除(delete)/逻辑删除(update)
创建对应实体类userinfo:
@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;
}
配置数据库连接字符串
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
注意:password处需要改为当前数据库的密码。端口号3306/后面跟的是数据库的名称。如果使用MySql是5.x之前需要将driver-class-name改为com.mysql.jdbc.Driver。
写持久层代码
在项目中,创建持久层接口UserMapper:
@Mapper
public interface UserMapper {
//查询所有用户
@Select("select * from userinfo")
List<Userinfo> select();
}
Mybatis的持久层接口规范一般都叫XxxMapper
@Mapper:表示的是Mybatis中的Mapper接口
程序运行时,框架会自动生成接口的实现类对象(代理对象),并交给Spring的IoC容器管理
@Select:代表的是select查询,也就是注解对应方法具体内容的实现。
单元测试
按住Alt+Insert,按图示进行点击,Idea会自动生成测试类。
测试代码:
@SpringBootTest
class UserMapperTest {
@Autowired
private UserMapper mapper;
@Test
void select() {
System.out.println(mapper.select());
}
}
注意:这里一定要加上@SpringBootTest注解,如果不加,无法拿到mapper对象,会报空指针异常。(加上了注解,该测试类在运行时,就会自动加载Spring运行环境)。
测试结果:
常见问题
问题1:sql错误
如图,使查询语句错误:
问题2:陪置信息错误(检查账号密码)
这里我们故意输入一个错误的密码:
问题3:未配置数据库
这里我们将配置数据库的代码注掉:
问题4 :数据库不存在
如图,将数据库改为一个不存在的数据库
问题5:表不存在
将表名改成一个不存在的表:
问题6:方法名重复
MyBatis基本操作
MyBatis打印日志
在配置文件中进行如下配置:
mybatis:
configuration: # 配置打印 MyBatis⽇志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
重新运行程序:
参数传递
可以在sql语句中使用” #{参数名} “来获取方法中的参数。
//传递一个参数
@Select("select * from userinfo where username = #{username}")
List<Userinfo> select2(String username);
测试用例:
@Test
void testSelect() {
System.out.println(mapper.select2("zhangsan"));
}
测试结果:
更改sql语句#{ }中的方法参数名:
//传递一个参数
@Select("select * from userinfo where username = #{name}")
List<Userinfo> select2(String username);
可以看到可以正常获取:但是当我们传递的参数是多个时(此时sql语句中的#{ }中的参数名仍然与方法中的不同):
//传递多个参数
@Select("select * from userinfo where delete_flag = #{deleteFlag} and age = #{age}")
List<Userinfo> select3(Integer flag,Integer age);
}
测试用例:
@Test
void select3() {
System.out.println(mapper.select3(0, 18));
}
测试结果: 可以看到报错结果显示没有找到deleteFlag这个参数。
想要解决这个问题一共有三种方法:
方法1:保持sql语句中的#{ }的参数名和方法参数名一致。
//传递多个参数
@Select("select * from userinfo where delete_flag = #{deleteFlag} and age = #{age}")
List<Userinfo> select3(Integer deleteFlag,Integer age);
测试结果:
方法2:在sql中使用param+数字来标记参数
//传递多个参数
@Select("select * from userinfo where delete_flag = #{param1} and age = #{param2}")
List<Userinfo> select3(Integer deleteFlag,Integer age);
测试结果: 方法3:在方法中使用@Param设置别名
//传递多个参数
@Select("select * from userinfo where delete_flag = #{deleteFlag} and age = #{age}")
List<Userinfo> select3(@Param("deleteFlag") Integer flag, Integer age);
测试结果:
阿里巴巴开发手册建议的写法(如果是使用阿里云创建项目则一定要加上@Param注解):
//阿里巴巴开发手册建议的写法
@Select("select * from userinfo where delete_flag = #{deleteFlag} and age = #{age}")
List<Userinfo> select4(@Param("deleteFlag")Integer deleteFlag,@Param("age")Integer age);
如果结果集只有一个对象时可以使用对象进行接收:
@Select("select * from userinfo where username = #{name}")
Userinfo select5(String username);
测试代码
@Test
void select5() {
System.out.println(mapper.select5("lisi"));
}
测试结果:
如果返回对象有多个,但是仍然使用对象接收:
@Select("select * from userinfo where delete_flag = #{deleteFlag} and age = #{age}")
Userinfo select4(@Param("deleteFlag")Integer deleteFlag,@Param("age")Integer age);
测试代码 :
@Test
void select4() {
System.out.println(mapper.select4(0,18));
}
可以看到,在对象有多个时并不能采用对象进行接收。因此,在实际开发中查询操作建议都使用List进行接收。
增(Insert)
//增删改返回的都是Integer
@Insert("insert into userinfo (username,password,age,gender) values (#{username},#{password},#{age},#{gender}) ")
//因为传入的属性较多,所以使用对象传递
Integer insert1(Userinfo userinfo);
测试代码:
@Test
void insert1() {
Userinfo userinfo = new Userinfo();
userinfo.setAge(30);
userinfo.setUsername("tianqi");
userinfo.setPassword("tianqi");
userinfo.setGender(2);
mapper.insert1(userinfo);
}
测试结果:
如果使用了@Param修改参数,则在sql中需要使用对象.参数进行获取。
@Insert("insert into userinfo (username,password,age,gender) values (#{userinfo.username},#{userinfo.password},#{userinfo.age},#{userinfo.gender})")
Integer insert2(@Param("userinfo")Userinfo userinfo);
测试代码:
@Test
void insert2() {
Userinfo userinfo = new Userinfo();
userinfo.setAge(30);
userinfo.setUsername("zhaoliu");
userinfo.setPassword("zhaoliu");
userinfo.setGender(2);
Integer i = mapper.insert2(userinfo);
System.out.println("返回"+i+"条数据");
}
测试结果:
返回主键
Insert语句默认返回的是受影响的行数,但是有些情况下,数据插入之后,还需要有后续的关联操作,需要获取到新插入的数据id。
如果要拿到自增id,需要在Mapper接口的方法上添加一个@Options的注解
@Options(useGeneratedKeys = true,keyProperty = "id")
//增删改返回的都是Integer
@Insert("insert into userinfo (username,password,age,gender) values (#{username},#{password},#{age},#{gender}) ")
//因为传入的属性较多,所以使用对象传递
Integer insert1(Userinfo userinfo);
useGeneratedKeys:这会令MyBatis使用JDBC的getGenerateKeys方法来取出由数据库内部生成的主键(比如:Mysql的自增主键,默认值:false)
keyProperty:指定能够唯一识别对象的属性,MyBatis会使用getGenerateKeys的返回值或insert语句的selectKey子元素设置它的值,默认值:未设置。
测试代码:
@Test
void insert1() {
Userinfo userinfo = new Userinfo();
userinfo.setAge(30);
userinfo.setUsername("tianqi");
userinfo.setPassword("tianqi");
userinfo.setGender(2);
Integer i = mapper.insert1(userinfo);
System.out.println("返回"+i+"条数据,id:"+userinfo.getId());
}
测试结果:
删(delete)
接口代码:
//删除
@Delete("delete from userinfo where id = #{id}")
Integer delete(Integer id);
测试代码:
@Test
void delete() {
Integer delete = mapper.delete(10);
System.out.println("删除"+delete+"条数据");
}
测试结果:
改(update)
mapper代码:
//改
@Update("update userinfo set username = #{userinfo.username},password = #{userinfo.password},gender = #{userinfo.gender},age = #{userinfo.age} where id = #{userinfo.id}")
Integer update(@Param("userinfo")Userinfo userinfo);
测试代码 :
@Test
void update() {
Userinfo userinfo = new Userinfo();
userinfo.setUsername("zhangsan333");
userinfo.setAge(10);
userinfo.setGender(1);
userinfo.setPassword("zhangsan333");
userinfo.setId(11);
Integer update = mapper.update(userinfo);
System.out.println("更新"+update+"条数据");
}
测试结果: