目录
一,概念
MyBatis是⼀款优秀的持久层框架,⽤于简化JDBC的开发.简单来说MyBatis是更简单完成程序和数据库交互的框架,也就是更简单的操作和读取数据库⼯具
二,准备工作及基本流程
1,引入依赖
可以在创建项目的时候就引入依赖
也可以在已有的代码上,在pom文件中手动引入依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
2,配置数据库连接的字符串
#数据库连接配置
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
一般情况下,直接复制上述代码就可以了,但是要注意更改:你的mysql的端口号,还有你有引入的库名,登录名,和密码(如果密码为纯数字,需要将密码加上引号,避免后续出错)
注意:如果使⽤MySQL是5.x之前的使⽤的是"com.mysql.jdbc.Driver",如果是⼤于5.x使⽤的 是“com.mysql.cj.jdbc.Driver”
3,写持久层代码
创建一个mapper文件,并创建一个持久层的接口UserInfoMapper,
package com.ABdolphin.mybatis.demo.mapper;
import com.ABdolphin.mybatis.demo.model.UserInfo;
import org.apache.ibatis.annotations.*;
import java.util.List;
@Mapper
public interface UserInfoMapper {
@Select("select * from user_info")
List<UserInfo> selectAll();
}
@Mapper注解:表示Mybatis中的Mapper接口,程序运⾏时,框架会⾃动⽣成接⼝的实现类对象(代理对象),并给交Spring的IoC容器管理
4,单元测试
我们在上述代码中右击鼠标,点击generate,然后选择Test,然后选择你要测试的方法,将其进行勾选,然后点击确定,就会在src下test路径下自动生成测试类
@SpringBootTest
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void selectAll() {
userInfoMapper.selectAll().forEach(x-> System.out.println(x));
}
}
需要注意的是,如果需要注入之前创建类对象,就需要使用注解@SpringBootTest,如果只是单纯的写个Main方法,也没有类对象的调用,就可以不加该注解.
三,基本操作
1,打印日志
在Mybatis当中我们可以借助⽇志,查看到sql语句的执⾏、执⾏传递的参数以及执⾏结果,直接复制下述代码即可:
# 配置打印 MyBatis⽇志
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
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' );
我们根据字段,在Java中创建一个类
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;
}
(1)传递参数
@Select注解:代表的就是select查询,也就是注解对应⽅法的具体实现内容,所以如果我们想要根据id查询来进行查询的时候,就可以直接在注解中写对应的SQL语句
@Select("select * from user_info where id=2")
List<UserInfo> selectAllById();
但是这是一个死代码,只可以查询id=2的数据,所以我们就需要进行传参,从而达到想要id为几,就可以传相应的id,@Select里面接收参数的方法为,数据库的字段名=#{参数名称}
@Select("select * from user_info where id=#{id}")
UserInfo selectAllById2(Integer id);
当参数有一个的时候,<数据库的字段名=#{参数名称}>中的参数名称可以跟真正的参数名称不一致,但是不推荐这样写
@Select("select * from user_info where id=#{aa}")
//单个参数赋值可以随便写
UserInfo selectAllById2(Integer id);
当有多个参数的时候:
//顺序可以颠倒
@Select("select * from user_info where age=#{age} and gender=#{gender}; ")
List<UserInfo> selectAllByAgeAndGender( Integer gender, Integer age);
//可以正确执行,但是不推荐
@Select("select * from user_info where age=#{param2} and gender=#{param1}; ")
List<UserInfo> selectAllByAgeAndGender(Integer gender, Integer age);
也可以对参数进行重命名,使用注解@param:但是建议第二种写法
//参数重命名
@Select("select * from user_info where age=#{age1} and gender=#{gender1}; ")
List<UserInfo> selectAllByAgeAndGender(@Param("gender1") Integer gender, @Param("age1") Integer age);
//建议参数和注解保持一致,即:
@Select("select * from user_info where age=#{age} and gender=#{gender}; ")
List<UserInfo> selectAllByAgeAndGender(@Param("gender") Integer gender, @Param("age") Integer age);
(2)insert增
使用@Insert标签
有以下两种方法:
//法一:
@Insert("insert into user_info (username,`password`,age,gender)"+
"values(#{username},#{password},#{age},#{gender})")
Integer insertUser(UserInfo userInfo);
//法二:
@Insert("insert into user_info (username,password,age,gender)"+
"values(#{userInfo1.username},#{userInfo1.password},#{userInfo1.age},#
{userInfo1.gender})")
Integer insertUserByParam(@Param("userInfo1") UserInfo userInfo);
如果对对象进行重命名的话,就需要用:重命名的名字.属性的方式去获得参数
(3)主键的获取
如果想要拿到⾃增id,需要在Mapper接⼝的⽅法上添加⼀个Options的注解,useGeneratedKeys:这会令MyBatis使⽤JDBC的getGeneratedKeys⽅法来取出由数据库内 部⽣成的主键,默认值:false.
keyProperty:指定能够唯⼀识别对象的属性,MyBatis会使⽤getGeneratedKeys的返回值或 insert 语句的selectKey⼦元素设置它的值,默认值:未设置(unset)
@Options(useGeneratedKeys = true,keyProperty = "id")
@Insert("insert into user_info (username,password,age,gender)"+
"values(#{userInfo.username},#{userInfo.password},#{userInfo.age},#
{userInfo.gender})")
Integer insertUserByParam(@Param("userInfo") UserInfo userInfo);
(4)删delete
@Delete("delete from user_info where id=#{id}")
Integer delete(Integer id);
(5)改update
@Update("update user_info set password=#{password} where id=#{id}")
Integer update(Integer id,String password);
(6)查select
我们之前已经见过,select的查询结果,可以发现其中有些值是空的,但是数据库中是存在数据的
这是因为数据库中的字段和Java类中定义的属性是不一致的,这就导致无法实现两者的映射,所以无法调用出deleteFlag,createTime,updateTime的值(这是由于数据库和Java字段命名的方法不同所导致的,数据库单词之间是使用_来连接的,但是Java是用小驼峰的形式)
我们有三种解决办法:
法一:运算SQL语句中的重命名的写法:
@Select("select id, username, password, age, gender, phone,"+
" delete_flag as deleteFlag, create_time as createTime, update_time as updateTime from user_info")
List<UserInfo> selectAll();
法二:结果映射
@Results(id = "BaseMap",value = {
@Result(column = "delete_flag",property = "deleteFlag"),
@Result(column = "create_time",property = "createTime"),
@Result(column = "update_time",property = "updateTime")
})
@Select("select id, username, password, age, gender, phone,"+
" delete_flag , create_time , update_time from user_info")
List<UserInfo> selectAll3();
@ResultMap("BaseMap")
@Select("select id, username, password, age, gender, phone,"+
" delete_flag , create_time , update_time from user_info where id=2")
List<UserInfo> selectAllById3();
我们也可以对@result设置id中,从而用@ResultMap注解实现复用对应的@resul
法三:通过配置完成
前面陈述了字段不同是由于,数据库和Java不同命名规范引起的,因此两者转换也是有一定规范的,所以我们就可以通过配置来进行自动映射
mybatis:
configuration:
map-underscore-to-camel-case: true #配置驼峰⾃动转换
3,Mybatis XML配置文件 实现
(1)引入依赖(同上)
(2)配置连接字符串和MyBatis(同上)
(3)写持久层代码(同上)
(4)添加UserInfoXMLMapper.xml
# 配置mybatis xml的⽂件路径,在resources/mapper创建所有表的xml⽂件
mybatis:
mapper-locations: classpath:mapper/**Mapper.xml
数据持久成的实现,MyBatis的固定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.example.demo.mapper.UserInfoMapper">
</mapper>
注意: mapper namespace 写的是实现接口的全限定名称
(5)<insert>标签
Integer insertUser(UserInfo userInfo);
Integer insertUser2(@Param("userInfo") UserInfo userInfo);
//法一:
<insert id="insertUser" >
insert into user_info(username,`password`,age,gender)
values (#{username},#{password},#{age},#{gender})
</insert>
//法二:
<insert id="insertUser2">
insert into user_info(username,`password`,age,gender)
values (#{userInfo.username},#{userInfo.password},#{userInfo.age},#
{userInfo.gender})
</insert>
(6)主键获取
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
<include refid="inserCol"></include>
values (#{username},#{password},#{age},#{gender})
</insert>
(6)<update>标签
Integer updateUser2(UserInfo userInfo);
Integer updateUser(Integer id,String password);
<update id="updateUser">
update user_info set password=#{password} where id=#{id}
</update>
(7)<delete>标签
<delete id="deleteUser">
delete from user_info where id=#{id}
</delete>
(8)<select>标签
注意:<select>标签,需要写返回值类型(类的全限定名称),
这时也会遇到同样的问题,解决方法也同样是上述三种,这里就不过多赘述了
//法一:
<select id="selectAll2" resultType="com.ABdolphin.mybatis.demo.model.UserInfo">
select id, username, password, age, gender, phone,
delete_flag as deleteFlag,
create_time as createTime,
update_time as updateTime
from user_info
</select>
//法二:
<resultMap id="BaseMap" type="com.ABdolphin.mybatis.demo.model.UserInfo">
<id property="id" column="id"></id>
<result property="deleteFlag" column="delete_flag"></result>
<result property="createTime" column="create_time"></result>
<result property="updateTime" column="update_time"></result>
</resultMap>
<select id="selectAll2" resultMap="BaseMap">
select id, username, password, age, gender, phone, delete_flag ,
create_time , update_time from user_info
</select>
//法三:配置
4,多表查询
我们创建一个,文章类的表
-- 创建⽂章表
DROP TABLE IF EXISTS articleinfo;
CREATE TABLE articleinfo (
id INT PRIMARY KEY auto_increment,
title VARCHAR ( 100 ) NOT NULL,
content TEXT NOT NULL,
uid INT NOT NULL,
delete_flag TINYINT ( 4 ) DEFAULT 0 COMMENT '0-正常, 1-删除',
create_time DATETIME DEFAULT now(),
update_time DATETIME DEFAULT now()
) DEFAULT charset 'utf8mb4';-- 插⼊测试数据
INSERT INTO articleinfo ( title, content, uid ) VALUES ( 'Java', 'Java正⽂', 1 );
package com.ABdolphin.mybatis.demo.model;
import lombok.Data;
import org.apache.ibatis.annotations.Mapper;
@Data
public class ArticleInfo {
private Integer id;
private String title;
private String content;
private Integer uid;
private String deleteFlag;
private String createTime;
private String updateTime;
}
@Mapper
public interface ArticleInfoMapper {
@Select("select ta.id,tb.username,tb.`password`,tb.phone,"+
" ta.content,ta.title,ta.uid, ta.create_time,ta.delete_flag,ta.update_time " +
"from article_info ta " +
"left join user_info tb on ta.id=tb.id " +
"where ta.id =#{id}")
ArticleInfo selectArticleInfoAndUserInfo(Integer id);
}
多表查询大多慢SQL,在企业开发中要避免使用多表查询,慢SQL会影响集体性能,进而影响其他项目的使用!!
5,#{}和${}的区别
(1)基本概念
#{}的值是使⽤ MySQL ? 进⾏占位. 这种SQL我们称之为"预编译SQL",${}参数是直接拼接在SQL语句中了,我们称之为"即时SQL"
@Select("select * from user_info where username=#{userName}")
UserInfo selectAllByName(String userName);
@Select("select * from user_info where username=${userName}")
UserInfo selectAllByName(String userName);
参数依然是直接拼接在SQL语句中了,但是字符串作为参数时,需要添加引号,⽤ ${} 不会拼接引号 '' ,导致程序报错,以下为正确写法(手动加上单引号):
@Select("select * from user_info where username='${userName}'")
UserInfo selectAllByName(String userName);
(2)预编译SQL优点
1. 性能更⾼ 绝⼤多数情况下,某⼀条SQL语句可能会被反复调⽤执⾏,或者每次执⾏的时候只有个别的值不同.如果每次都需要 经过上⾯的语法解析,SQL优化、SQL编译等,则效率就明显不⾏了.预编译SQL,编译⼀次之后会将编译后的SQL语句缓存起来,后⾯再次执⾏这条语句时,不会再次编译 (只是输⼊的参数不同),省去了解析优化等过程,以此来提⾼效率
2. 更安全(防⽌SQL注⼊) SQL注⼊:是通过操作输⼊的数据来修改事先定义好的SQL语句,以达到执⾏代码对服务器进⾏攻击的⽅法。
例如:
一个小小的栗子(登录页面)
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/selectUserList")
public List<UserInfo> selectUserList(){
return userService.selectUserList();
}
@RequestMapping("/login")
public boolean login(String userName,String password){
UserInfo userInfo = userService.selectUserNameAndPassword(userName, password);
if (userInfo==null){
return false;
}else {
return true;
}
}
}
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
public List<UserInfo> selectUserList() {
return userInfoMapper.selectAll();
}
public UserInfo selectUserNameAndPassword(String userName, String password) {
List<UserInfo> userInfos = userInfoMapper.selectAllByNameAndPassword(userName, password);
if (userInfos.size()>0){
System.out.println("========"+userInfos.get(0)+"===========");
return userInfos.get(0);
}else {
return null;
}
}
}
@Mapper
public interface UserInfoMapper {
@Select("select * from user_info where username='${userName}' and password='${password}'")
List<UserInfo> selectAllByNameAndPassword(String userName,String password);
}
sql 注⼊代码: ' or 1='1.这种情况无论你的密码是否正确都可以登录
(3)排序中的${}
//排序
@Select("select * from user_info order by id #{order}")
List<UserInfo> selectUserByOrder(String order);
//测试代码
@Test
void selectUserByOrder() {
userInfoMapper.selectUserByOrder("desc").forEach(x-> System.out.println(x));
}
如果参数是字符串,用井号来写,会将参数自动加上单引号放到SQL语句中,如下图所示:
所以这里我们就需要使用$符号来实现该功能:
@Select("select * from user_info order by id ${order}")
List<UserInfo> selectUserByOrder(String order);
@Test
void selectUserByOrder() {
userInfoMapper.selectUserByOrder("desc").forEach(x-> System.out.println(x));
}
实现排序只能使用$符号,但是使用$符号,就要考虑到SQL注入的问题,可以是使用枚举(desc/asc),参数校验
(4)模糊查询中的${}
@Select("select* from user_info where username like '%#{name}%'")
List<UserInfo> selectUserByLike(String name);
//测试代码
@Test
void selectUserByLike() {
userInfoMapper.selectUserByLike("ABDol");
}
可以使用MySQL的内置函数进行拼接字符的方式:
运用上述函数的写法,这样就可以使用#符合的写法,他会自动加上单引号,代码如下:
@Select("select* from user_info where username like CONCAT('%',#{name},'%')")
List<UserInfo> selectUserByLike(String name);
//测试代码
@Test
void selectUserByLike() {
userInfoMapper.selectUserByLike("ABDol");
}
(5),#和$的使用原则:
1.,能使用#,使用#
2,不能使用#,使用$时,一定要规避SQL注入的问题
6,数据库连接池:
程序启动时,会在数据库连接池中创建⼀定数量的Connection对象,当客⼾ 请求数据库连接池,会从数据库连接池中获取Connection对象,然后执⾏SQL,SQL语句执⾏完,再把Connection归还给连接池
优点:减少了网络开销,资源重用,提升了系统性能
常见数据库连接池:C3P0,DBCP,Druid,Hikari
(1)Hikari是Spring-Boot默认的数据库连接池
(2)Druid
如果想要把连接池更改成Druid,只需要添加相关的依赖就可以了.
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
<version>1.2.21</version>
</dependency>
7,进阶标签
(1),<if>标签:
//SQL
insert into user_info (username,`password`,age,gender)
values ('test','1111',12,1);
insert into user_info (username,`password`,age)
values ('test','1111',12);
insert into user_info (username,`password`,age,gender)
values ('test','1111',12,NULL);
想要实现,有的参数可填可不填的情况,就可以使用if标签,这样就可以对SQL语句进行拼接
<insert id="insertUserByCondition">
insert into user_info(username,`password`,age
<if test="gender!=null">
, gender
</if>
)
values (#{username},#{password},#{age}
<if test="gender!=null">
, #{gender}
</if>
)
</insert>
(2),<trim>标签
prefix::表⽰整个语句块,以prefix的值作为前缀
suffix::表⽰整个语句块,以suffix的值作为后缀
suffixOverrides:表⽰整个语句块要去除掉的前缀
prefixOverrides:表⽰整个语句块要去除掉的后缀
Integer insertUserByCondition(UserInfo userInfo);
<insert id="insertUserByCondition">
insert into user_info
<trim prefix="(" suffix=")" suffixOverrides="," >
<if test="username!=null">
username,
</if>
<if test="password!=null">
`password`,
</if>
<if test="age!=null">
age,
</if>
<if test="gender!=null">
gender
</if>
</trim>
values
<trim suffixOverrides="," prefix="(" suffix=")">
<if test="username!=null">
#{username},
</if>
<if test="password!=null">
#{password} ,
</if>
<if test="age!=null">
#{age},
</if>
<if test="gender!=null">
#{gender}
</if>
</trim>
</insert>
(3),<where>标签
当where后面没有其他语句的时候,,<where>标签就可以将where也自动去掉
List<UserInfo> selectUserByCondition(UserInfo userInfo);
//法一:
<select id="selectUserByCondition" resultType="com.ABdolphin.mybatis.demo.model.UserInfo">
select * from user_info where
1=1
<trim suffixOverrides="and">
<if test="age!=null">
and age=#{age}
</if>
<if test="deleteFlag!=null">
and delete_flag=#{deleteFlag}
</if>
</trim>
</select>
//法二:
<select id="selectUserByCondition" resultType="com.ABdolphin.mybatis.demo.model.UserInfo">
select * from user_info
<where>
<trim suffixOverrides="and">
<if test="age!=null">
age=#{age} and
</if>
<if test="deleteFlag!=null">
delete_flag=#{deleteFlag}
</if>
</trim>
</where>
</select>
(4),<set>标签
添加set关键字,去除末尾的逗号
Integer updateUserByCondition(UserInfo userInfo);
<update id="updateUserByCondition">
update user_info
<set>
<if test="username!=null">
username=#{username},
</if>
<if test="password!=null">
`password`=#{password},
</if>
<if test="gender!=null">
gender=#{gender}
</if>
</set>
<where>
id=#{id}
</where>
</update>
(5),<foreach>标签
可用于批量删除
Integer deleteUserByCondition(List<Integer> ids);
<delete id="deleteUserByCondition">
delete from user_info where id in
<foreach collection="ids" separator="," item="id" open="(" close=")">
#{id}
</foreach>
</delete>
(6),<include>标签
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
insert into user_info(username,`password`,age,gender)
values (#{username},#{password},#{age},#{gender})
</insert>
当我们有很多共性代码的时候,就可以将这样的代码打包,之后复用的时候用include标签进行复用,上述代码可以写成以下代码
<sql id="inserCol">
insert into user_info(username,`password`,age,gender)
</sql>
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
<include refid="inserCol"></include>
values (#{username},#{password},#{age},#{gender})
</insert>