MyBatis 操作数据库(⼊⻔)
1.什么是MyBatis
MyBatis 是一款优秀的持久层框架,用于简化数据库操作。它通过 XML 或注解配置 SQL 语句,并将 Java 对象与数据库记录映射,避免了手动编写 JDBC 代码的繁琐。MyBatis 支持定制化 SQL、存储过程及高级映射,同时提供灵活的缓存机制。
2.MyBatis⼊⻔
数据准备
创建数据库:
-- 创建数据库
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' );
创建对应的实体类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;
}
配置数据库连接字符串
spring:datasource:url: jdbc:mysql://127.0.0.1:3306/mycnblog?characterEncoding=utf8&useSSL=falseusername: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driver
写持久层代码
@Mapper
public interface UserMapper {//查询所有用户@Select("select * from user_info")List<UserInfo> selectAll();
}
单元测试
@SpringBootTest
class UserMapperTest {@Autowiredprivate UserMapper userInfoMapper;@Testvoid selectAll() {List<UserInfo> userInfoList = userInfoMapper.selectAll();System.out.println(userInfoList);// userInfoMapper.selectAll().stream().forEach(x-> System.out.println(x));}
}
运⾏结果如下:
Service层
@Service
public class UserService {@Autowiredprivate UserMapper userInfoMapper;public List<UserInfo> getAllUser() {return userInfoMapper.selectAll();}
}
Contriller层
@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;@RequestMapping("/getAllUser")public List<UserInfo> getAllUser(){return userService.getAllUser();}
}
3. MyBatis的基础操作
打印⽇志
mybatis:configuration: # 配置打印 MyBatis 执行的 SQLlog-impl: org.apache.ibatis.logging.stdout.StdOutImpl
重新运⾏程序,可以看到SQL执⾏内容,以及传递参数和执⾏结果
测试前后置
测试前置
@BeforeEachvoid setUp() {System.out.println("before....");}
测试后置
@AfterEachvoid tearDown() {System.out.println("after....");}
参数传递(查)
查找单个参数的情况下:
Mapper接⼝
@Select("SELECT * FROM `user_info` where id = #{num}")UserInfo selectById(Integer num);
//注意这里再对象这有一个的情况下可以用对象接收,如果不止一个的话用集合
测试代码:
@Testvoid selectById() {UserInfo userInfo = userInfoMapper.selectById(4);System.out.println(userInfo);}
运行结果:
也可以通过 @Param ,设置参数的别名
@Select("SELECT * FROM `user_info` where id = #{userId}")UserInfo selectById(@Param("userId") Integer num);
查找多个参数的情况下:
Mapper接⼝
//查找对象为多个参数的情况下@Select("SELECT * FROM `user_info` where username = #{username} and age = #{age} ")List<UserInfo> selectByNameAndAge(String username , Integer age);
测试代码:
@Testvoid testSelectById() {List<UserInfo> userInfoList = userInfoMapper.selectByNameAndAge("wangwu",18);System.out.println(userInfoList);}
增(Insert)
Mapper接⼝
//插入数据@Insert("insert into user_info (username, `password`, age) VALUES (#{username}, #{password}, #{age})")Integer insertUser(UserInfo userInfo);
测试代码:
@Testvoid insertUser() {UserInfo userInfo = new UserInfo();userInfo.setUsername("username3");userInfo.setPassword("password3");userInfo.setAge(4);userInfoMapper.insertUser(userInfo);}
运行结果:
返回主键
Insert 语句默认返回的是受影响的⾏数
但有些情况下,数据插⼊之后,还需要有后续的关联操作,需要获取到新插⼊数据的id
如果想要拿到⾃增id,需要在Mapper接⼝的⽅法上添加⼀个Options的注解
Mapper接⼝
//插入数据@Options(useGeneratedKeys = true, keyProperty = "id")@Insert("insert into user_info (username, `password`, age) VALUES (#{username}, #{password}, #{age})")Integer insertUser(UserInfo userInfo);
//插入数据重命名的情况下@Options(useGeneratedKeys = true,keyProperty = "id")@Insert("insert into user_info (username, `password`, age)" +" VALUES (#{user.username}, #{user.password}, #{user.age})")Integer insertUser1(@Param("user") UserInfo userInfo);
测试代码:
@Testvoid insertUser() {UserInfo userInfo = new UserInfo();userInfo.setUsername("username3");userInfo.setPassword("password3");userInfo.setAge(4);Integer result = userInfoMapper.insertUser(userInfo);System.out.println("影响行数:"+ result + ", id:"+ userInfo.getId());}
运行结果:
删(Delete)
Mapper接⼝
//删除数据@Delete("DELETE from user_info WHERE id = #{id};")Integer deleteUserById(Integer id);
测试代码:
@Testvoid deleteUserById() {Integer result = userInfoMapper.deleteUserById(2);System.out.println(result);}
改(Update)
Mapper接⼝
//修改数据@Update("UPDATE user_info SET username = #{username} where id = #{id};")Integer updateById(UserInfo userInfo);
测试代码:
@Testvoid testUpdateById() {UserInfo userInfo = new UserInfo();userInfo.setUsername("wqwwq");userInfo.setId(5);userInfoMapper.updateById(userInfo);}
4. MyBatis XML配置⽂件
配置连接字符串和MyBatis
mybatis:mapper-locations: classpath:mapper/*Mapper.xml
写持久层代码
mapper接⼝
public interface UserMapperXml {List<UserInfo> selectAll();
}
添加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="cn.demo.demomybatis.mapper.UserMapperXml"><select id="selectAll" resultType="cn.demo.demomybatis.model.UserInfo">SELECT * FROM user_info</select></mapper>
id
:与接口中定义的方法名称一致,表示对接口方法的具体实现。resultType
:指定返回的数据类型,通常是实体类的全限定名。
测试代码:
@SpringBootTest
class UserMapperXmlTest {@Autowiredprivate UserMapperXml userMapperXml;@Testvoid selectAll() {userMapperXml.selectAll().stream().forEach(x-> System.out.println(x));}
}
响应结果:
增删改查操作
增(Insert)
Mapper接⼝
//增Integer insertUser(UserInfo userInfo);
测试代码:
@Testvoid insertUser() {UserInfo userInfo = new UserInfo();userInfo.setUsername("username3");userInfo.setPassword("password3");userInfo.setAge(4);Integer result = userMapperXml.insertUser(userInfo);System.out.println("影响行数:"+ result + ", id:"+ userInfo.getId());}
返回⾃增id
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">insert into user_info (username, `password`, age) VALUES (#{username}, #{password}, #{age})</insert>
删改查也是用类似的方法就不具体演示
5.其他查询操作
多表查询
实体类:
@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;//用户相关信息private String username;private Integer age;
}
接⼝定义:
@Mapper
public interface ArticleMapper {@Select("SELECT * FROM article_info left JOIN " +"user_info on user_info.id = article_info.uid WHERE user_info.id = 1;")ArticleInfo SelectArticle();}
测试代码:
@SpringBootTest
class ArticleMapperTest {@Autowiredprivate ArticleMapper articleMapper;@Testvoid selectArticle() {ArticleInfo articleInfo = articleMapper.SelectArticle();System.out.println(articleInfo);}
}
测试结果:
6.#{} 和${}
//查找对象只有一个的情况下@Select("SELECT * FROM `user_info` where id = #{userId}")UserInfo selectById(@Param("userId") Integer num);
运行结果:
我们发现日志中我们输出的sql语句为
select username, `password`, age, gender, phone from user_info where id= ?
我们输⼊的参数并没有在后⾯拼接,id的值是使⽤ ? 进⾏占位. 这种SQL我们称之为"预编译SQL"
我们把 #{} 改成 ${} 再观察打印的⽇志:
可以看到,这次的参数是直接拼接在SQL语句中了,这种就是”即时SQL“
注意:String类型的参数需要添加引号’ ‘
#{} 和${}区别
性能更⾼
绝⼤多数情况下,某⼀条SQL语句可能会被反复调⽤执⾏,或者每次执⾏的时候只有个别的值不同(⽐ 如select的where⼦句值不同,update的set⼦句值不同,insert的values值不同).如果每次都需要经过上⾯的语法解析,SQL优化、SQL编译等,则效率就明显不⾏了
预编译SQL,编译⼀次之后会将编译后的SQL语句缓存起来,后⾯再次执⾏这条语句时,不会再次编译 (只是输⼊的参数不同),省去了解析优化等过程,以此来提⾼效率
更安全(防⽌SQL注⼊)
SQL注⼊:是通过操作输⼊的数据来修改事先定义好的SQL语句,以达到执⾏代码对服务器进⾏攻击的⽅法。
正常访问情况:
Mapper接⼝
@Select("SELECT * FROM `user_info` where id = '${username}'")List<UserInfo> selectByName(String username);
测试代码
@Testvoid selectByName() {List<UserInfo> userInfoList = userInfoMapper.selectByName("lisi");System.out.println(userInfoList);}
运行结果
SQL注⼊场景:
@Testvoid selectByName() {List<UserInfo> userInfoList = userInfoMapper.selectByName("' or 1='1");System.out.println(userInfoList);}
运行结果
结果依然被正确查询出来了,其中参数or被当做了SQL语句的⼀部分,这也就发生sql注入的问题