1. 什么是MyBatis框架
MyBatis框架是一个优秀的持久层框架,为了简化JDBC开发。传统的JDBC编程编写起来很麻烦。
MyBatis框架使用了数据库连接池技术,避免了频繁的创建和销毁操作。
初始情况下,数据库连接池会默认创建一定数量的connection对象,当有sql操作的时候就给这个sql操作分配一个connection对象,sql执行完毕后会把connection对象归还给数据库连接池
数据库连接池的优点:降低网络资源开销,资源重用,提高性能。
持久层:指的是持久化操作的层,负责进行数据库相关操作
下面创建Spring Boot项目,并加入MyBatis和MySQL,
Mybatis框架配置方式有两种,一种是注解,一种是xml
2. 注解
2.1 查询操作
数据库相关操作:
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 = utf8mb4INSERT INTO user_info( username, `password`, age, gender, phone )
VALUES ( 'admin', 'admin', 18, 1, '18612340001' );
INSERT INTO user_info( username, `password`, age, gender, phone )
VALUES ( 'zhangsan', 'zhangsan', 18, 1, '18612340002' );
INSERT INTO user_info( username, `password`, age, gender, phone )
VALUES ( 'lisi', 'lisi', 18, 1, '18612340003' );
INSERT INTO user_info( username, `password`, age, gender, phone )
VALUES ( 'wangwu', 'wangwu', 18, 1, '18612340004' )
//创建UserInfo对象负责接收/修改数据库中的数据
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;
}
在properties中配置
#驱动类名称
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#数据库连接的url
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false
#连接数据库的⽤⼾名
spring.datasource.username=root
#连接数据库的密码
spring.datasource.password=root
#指定mybatis输出⽇志的位置, 输出控制台
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
简单进行查找操作,查找所有数据
package com.example.demo.mapper;
import com.example.demo.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;@Mapper
public interfance UserInfoMapper{@Select("select username, password, age, gender, phone from user_info")public List<UserInfo> getAll();
}
使用idea自带的测试生成功能,在UserInfoMapper接口下右键-Generate-test,选择要生成的测试代码编写测试代码,注意要在类加上@SpringBootTest注解,运行@Test注解旁边的启动按钮即可启动测试
@SpringBootTest
class UserInfoTest{@Autowiredprivate UserInfoMapper userInfoMapper;@Testvoid getAll() {List<UserInfo> list = userInfoMapper.getAll();System.out.println(list);}
}
2.2. 传递参数
在刚才的UserInfoMapper接口里加入新的代码,使用#{}获取方法中的参数,建议把#{}内部的参数跟形参名保持一致
还可以使用@Param进行重命名,如果重命名,那么value必须和#{}中的值保持一致!!
2.3 新增
直接构造一个对象然后插入
从上面可以看到insert返回值是数据库中受影响的行数,有时要求返回数据库中的某个值,比如一个购物网站,用户下单之后需要给用户返回一个订单号,这个订单号一般是自动生成的,此时就需要使用新的注解。
userGeneratedKeys:默认值是false。设置为true,这会令JDBC执行getGenerateKey,获取到数据库中的主键(一般是MySQL数据库中的自增字段)
keyProperty:指定唯一能够识别对象的属性,默认的值是useGeneratedKeys的返回值或者insert语句的selectKey子元素,默认值未设置(unset)
上面演示所示,可以看到count拿到的还是受到返回的行数,返回值类型还是数字。自增id会设置在keyProperty指定的属性当中。
2.4 删和改
删和改一起展示
可以看到id=9的行已经删除了,id=10的username已经修改为zhaoliu。
2.5 查
可以看到,红框中的三个字段的值都为null。为什么没有获取到?
可以看到我们自己定义的对象中的属性使用的是驼峰式,但数据库中的字段是以下划线来进行连接的,所以自然没办法获取到。有三种方法可以解决这个问题:1. 配置application文件 2. 结果映射 3. 起别名
1. 配置文件开启驼峰命名(推荐方法)
#properties:
mybatis.configuration.map-underscore-to-camel-case=true#yml:
mybatis:configuration:map-underscore-to-camel-case: true
2. 结果映射
@Select("select id, username, `password`, age, gender, phone, delete_flag,
create_time, update_time from user_info")
@Results(id = "resultMap",value = {@Result(column = "delete_flag",property = "deleteFlag"),@Result(column = "create_time",property = "createTime"),@Result(column = "update_time",property = "updateTime")
})
List<UserInfo> queryAllUser();
@Select("select id, username, `password`, age, gender, phone, delete_flag,
create_time, update_time " +"from user_info where id= #{userid} ")
@ResultMap(value = "resultMap")
UserInfo queryById(@Param("userid") Integer id);
3. 起别名
@Select("select id, username, password, age, gender, phone, delete_flag as
deleteFlag, " +"create_time as createTime, update_time as updateTime from user_info")
public List<UserInfo> queryAllUser()
已经可以获取到了。
3. xml
上面的注解方式只适合于简单的sql操作,一些复杂的sql操作最好使用xml的方式。
3.1 持久层代码
刚才介绍的注解方法,持久层代码只创建了一个接口,然后用idea自带的测试功能进行测试。
xml的持久层代码需要创建两个:
1. 方法定义:interface
2. 方法实现:xml文件
先来配置文件
# 数据库连接配置 yml配置方式
spring:datasource:url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=falseusername: rootpassword: 1234driver-class-name: com.mysql.cj.jdbc.Driver
# 配置 mybatis xml 的⽂件路径,在 resources/mapper 创建所有表的 xml ⽂件
mybatis:mapper-locations: classpath:mapper/**Mapper.xmlconfiguration:map-underscore-to-camel-case: true
#驱动类名称 properties配置方式
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#数据库连接的url
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false
#连接数据库的⽤⼾名
spring.datasource.username=root
#连接数据库的密码
spring.datasource.password=1234
# 配置 mybatis xml 的⽂件路径,在 resources/mapper 创建所有表的 xml ⽂件
mybatis.mapper-locations=classpath:mapper/**Mapper.xml
#下划线转驼峰
mybatis.configuration.map-underscore-to-camel-case=true
#指定mybatis输出⽇志的位置, 输出控制台
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
3.2 查
创建接口跟刚才一样,下面来进行xml文件的编写,xml文件的配置是固定的,就是下面这个格式。
import com.example.demo.UserInfo;
import org.apache.ibatis.annotations.Mapper;import java.util.List;@Mapper
public interface UserInfoXmlMapper {List<UserInfo> queryAllUser();
}
<?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=""></mapper>
创建xml文件的路径要跟yml中配置的路径相同,并且必须以Mapper.xml结尾,笔者命名的是UserMapper.xml,前面的User可以自定义,但是后面必须跟yml中的保持一致
在xml中编写select语句
mapper标签中的namespace中写的是接口的全限定名称;
select标签中的id中写的是方法名,resultMap写的是返回的数据类型,只有select标签需要加resultMap
3.3 增
#返回自增id
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">insert into user_info (username, `password`, age, gender, phone) values(#{userInfo.username},#{userInfo.password},#{userInfo.age},#
{userInfo.gender},#{userInfo.phone})
</insert>
3.4 删
3.5 改
4. 多表查询
再创建一个表以完成多表查询
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
);
import lombok.Data;
import java.util.Date;
@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;private Integer gender;
}
import com.example.demo.ArticleInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;@Mapper
public interface ArticleInfoMapper {@Select("SELECT ta.id,ta.title,ta.content,ta.uid,tb.username,tb.age,tb.gender " +"FROM articleinfo ta LEFT JOIN user_info tb ON ta.uid = tb.id " +"WHERE ta.id = #{id}")ArticleInfo queryUserByUid(Integer id);
}
5. #{}和${}的区别
前面代码中展示的都是#{},还有一种写法是${},那么他们的区别是什么呢?
之前使用#{}时运行时可以看到使用?来作为占位符,这种称为“预编译SQL”
换成${}
发现id的位置直接拼接成了数字,而不是占位符"?"
接下来把where条件变成查询username,发现报错了,可以看到直接把admin拼接到了后面,没有加引号,这就是报错的原因。而#{}会根据数据类型自动拼接引号,如果不想报错需要修改为:'${username}',此处不做展示了
总结一下:
#{}使用的预编译SQL,会先进行编译,用?作为占位符,然后再把参数填充进去。
${}使用的是即时sql,需要自行填充引号。
6. 预编译SQL和即时SQL的区别是什么?
1. 预编译SQL的性能更高:比如select语句,上面刚才举例区别是一个where条件用的是id字段,另一个用的是username字段,所以他们之间的区别很小,预编译SQL当编译第一次的时候会将编译后的sql语句缓存起来,下次再使用的时候直接使用,不用再次编译,提高了效率。
2. 预编译SQL可以防止SQL注入,SQL注入是:通过操作修改的数据来修改事先定义好的sql功能,达到攻击数据库的目的,比如有的登录场景,一些攻击者会在密码框内加入sql关键字,就可以直接登录成功。
虽然${}有上面这两个缺点,但是也有它的优点,一个是当有排序的场景的时候必须使用${},使用 #{}会报错,原因是#{}自动拼接了引号,导致sql语句出错
@Select("select id, username, age, gender, phone, delete_flag, create_time,
update_time " +"from user_info order by id ${sort} ")
List<UserInfo> queryAllUserBySort(String sort);
另一个是使用like操作,#{}一样会报错,但是${}会导致sql注入,所以加入了concat()函数
@Select("select id, username, age, gender, phone, delete_flag, create_time,
update_time " +"from user_info where username like concat('%',${key},'%')")
List<UserInfo> queryAllUserByLike(String key);