Mybatis简介
Mybatis3简介
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
快速上手Mybatis3
准备工作
CREATE DATABASE `mybatis-example`;
USE `mybatis-example`;
CREATE TABLE `t_emp`(
emp_id INT AUTO_INCREMENT,
emp_name CHAR(100),
emp_salary DOUBLE(10,5),
PRIMARY KEY(emp_id)
);
INSERT INTO `t_emp`(emp_name,emp_salary) VALUES("tom",200.33);
INSERT INTO `t_emp`(emp_name,emp_salary) VALUES("jerry",666.66);
INSERT INTO `t_emp`(emp_name,emp_salary) VALUES("andy",777.77);
mysql
mysql-connector-java
8.0.25
org.junit.jupiter
junit-jupiter-api
5.3.1
test
org.mybatis
mybatis
3.5.11
Mybatis做了什么
我们需要了解一下mybatis做了什么,这样才能更清楚的知道为什么要准备其他的一些东西。
我们一共分三步完成持久层的各种操作。
我们首先需要定义一个接口,在其中指定我们需要查询的信息,比如在上面的表中,我想要查询员工的姓名。那么我就需要在接口定义一个selectEmpName()
的方法。
好的,那么我们现在想要去查询这个信息就显然需要设计一个sql语句需要去查询,在mvc框架中加入mybatis,他会将sql语句从Java代码中抽离出来,放置在专门的xml文件中,我们称它为mapper。
现在假设我们查询出来了想要的结果,一个员工姓名,我们显然可以用一个String去接收它。但设想一下,如果我们要查询的是员工的姓名信息和工资信息呢?又或者还有员工的id呢?我们就不能单纯的用一个String去接收了。mybatis提供了一种新的方式,用一个与emp表中列所对应的对象去接收查询到的信息。
比如下面这个类,我们称他为pojo
public class Employee {
private Integer empId;
private String empName;
private Double empSalary;
//此处省略 getter | setter | toString
还有上面所说的一个接口以及它的方法
public interface EmployeeMapper {
Employee selectEmployee(Integer empId);
}
还有就是存放sql的xml文件。不过这个xml文件要做的有点多,他需要将接口中的方法与pojo相关联起来。简单来讲就是当接口中的方法被调用,他需要将sql执行的结果映射到pojo中。要把它写在classpath下面
,因为src只识别java文件,我建议你建个文件夹存放它,因为可以预见,它将来肯定不止一个。
select emp_name empName,emp_salary empSalary from t_emp where emp_id=#{id}
让我们看一下其中的标签以及属性。
配置mybatis
别忘了配置数据库,当然mybatis也需要一些简单的配置我们在类路径下写一个配置文件。
测试
InputStream ism = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(ism);
SqlSession sqlSession = build.openSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
Employee employee = mapper.selectEmployee(1);
System.out.println(employee);
sqlSession。close();
这个测试可以说非常繁琐,
mybatis原理的讲解
mybatis是在ibatis的基础上封装优化而来的。增加了使用mapper接口调用sql。
对参数的传入做了优化。但底层还是调用了ibatis来执行sql
我们使用#{}来拼接参数,不要使用#{}拼接,防止sql漏洞。
我们将更详细的内容打印在控制台,监测sql的拼接与执行(将mybatis的日志实现设置为打印控制台)。
mybatis传参
1. 简单数据类型
我们在接口中添加更多方法来演示
Integer deleteEmployeeById(Integer empId);
//返回值为复杂数据类型
List getEmployeesBySalary(Double salary);
//传入对象时
Integer addOneEmployee(Employee employee);
//传入多个简单参数
Integer updateEmployeeSalaryById(Integer id,Double salary);
处理方法如下
//当返回值是固定的时,可以省略resultType
delete * from t_emp where emp_id=#{id}
//当返回类型是多个对象时,无需任何操作,resultType传pojo即可
select emp_id empId,emp_name empName,emp_salary empSalary from t_emp where emp_salary>#{salary}
//当传入的是对象时,则参数名必须为pojo的参数名(get方法的名字)
insert into t_emp(emp_name,emp_salary) values(#{empName},#{empSalary});
传入多个简单数据类型的处理方式有点复杂
//传入参数为多个简单数据类型时,可以使用arg0 arg1...
// 也可以使用 param1 param2 ...
//但建议使用@param() 直接指定参数!!!
update t_emp set emp_salary=#{arg1} where emp_id=#{arg0}
@Param的用法
Integer updateEmployeeSalaryById(@Param("id") Integer id, @Param("salary") Double salary);
Map传参
一般来讲不建议这么用,但是它可以代替pojo直接传参查询
设置key为字段,value为值。
//传入map做为多个简单数据类型时,设为map中的key即可
update t_emp set emp_salary=#{map中的key} where emp_id=#{map中的key}
设置返回值类型
如果返回的是一个基本数据类型,比如String,Double,则全部用首字母小写,string,double,map,也可以写全类名。
select emp_name from t_emp where emp_id=#{empId};
@Alias注解
如果返回对象,而又不想写全限定名可以用下面这个注解
开启驼峰命名映射
如果我们的表的字段全部为XXX_XXX,而我们的pojo全部为驼峰命名。我们就可以配置一下,不写别名。
select emp_id empId,
emp_name empName,
emp_salary empSalary from t_emp where emp_salary>#{salary}
每一个都要写别名去对应pojo
我们在配置文件中写入
map接收任意参数
select emp_name 最高工资员工姓名,emp_salary 最高员工工资,(select AVG(emp_salary) from t_emp) 平均工资
from t_emp where
emp_salary=(select MAX(emp_salary) from t_emp)
当然也可以使用list接受上面的代码
如何开启事务自动提交?
InputStream ism = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(ism);
//传入true表示开启自动提交
SqlSession sqlSession = sqlSessionFactory.openSession(true);
如何获取插入数据的主键?
insert into t_emp(emp_name,emp_salary) values(#{empName},#{empSalary});
useGeneratedKeys="true" ------ 表示我们需要表自动生成的主键
keyColumn="emp_id" ----- 表示指定主键是谁
keyProperty="empId" 表示让mybatis直接将结果返回到我们用于插入数据的对象中。
mybatis维护UUID主键
select replace(UUID(),'-','');
insert into t_emp(emp_name,emp_salary) values(#{empName},#{empSalary});
多表查询
我们如何处理复杂的多表查询。如何设计与其相对应的pojo?多层嵌套关系的对象类型如何精准映射?
多表查询的设计原则
数据库表之间的关系无非分为3类
一对一的数据表关系
多对一,一对多的数据表关系
多对多的数据表关系
我们始终遵从以一张数据表为主的原则来设计。
比如订单表与用户表的关系,从订单角度来看,多张订单对应一个客户,所以是多对1,而如果从客户角度看,则是一个客户对应多张表,是1对多的关系。
当我们需要查询订单的id,订单的内容与订单对应的客户时,客户其实可以设计为订单表的单个对象属性几个。
CREATE TABLE t_order(
order_id INT(11) UNSIGNED,
order_name VARCHAR(32),
customer_id INT(11)
) CHARSET=utf8 ENGINE=INNODB;
CREATE TABLE t_customer(
customer_id INT(11) UNSIGNED,
customer_name VARCHAR(32)) CHARSET=utf8 ENGINE=INNODB;
INSERT INTO t_order(order_id,order_name) VALUES(1,'豆橛子订单')
INSERT INTO t_customer(customer_id,customer_name) VALUES(1001,'靓仔')
创建pojo
public class Order {
private Integer orderId;
private String orderName;
private Integer customerId;
private Customer customer;
/getter | setter | toString
}
@Data
public class Customer {
private Integer customerId;
private String customerName;
private List orderList;
/getter | setter | toString
}
Mapper相关的配置。
SELECT o.order_name, o.customer_id, c.customer_name
FROM t_order o
JOIN t_customer c
ON o.customer_id = c.customer_id
WHERE o.order_id = #{orderId}
代码解析:
一对多查询的xml写法
select ctm.customer_id,ctm.customer_name,order.order_id,order.order_name
from t_customer AS ctm JOIN t_order AS `order`
ON ctm.customer_id=order.customer_id
where ctm.customer_id=#{id}
他们之间的区别在于2个标签
不同于单个对象的写法,我们使用collection来接收多个对象(List)类型
。不同于单个对象的写法,我们使用ofType指定对象的pojo
多表映射的优化
在上面的代码中我们发现,我们无法对多层级的属性进行自动映射。比如List集合中的属性。我们需要手写映射关系。其实mybatis可以根据驼峰原则,进行自动映射(浅层和深层都能实现自动完成)。我们需要额外开启相关设置。
开启后,我们就不需要写一部分的映射了
但是注意,关于主键我们仍需要定义
!
自定义多条件查询
在实际开发中,我们常常遇到需要自定义的多条件查询。比如电商平台的购物筛选功能,用户可能添加附加条件查询,就像下面这种情况。如果我们给每条查询都设置对应的sql语句,这显然不现实。
那么如何解决这种问题呢?
mybatis为我们提供了新的标签:标签与
标签。
比如下面这个根据姓名和工资查找员工的请求。
List getEmpByNameAndSalary(@Param("name") String name,@Param("salary") Double salary);
如果上面的name和salar可以可能传也可能不传,那我们的sql语句就应该按照下面这样来写。
select * from t_emp
emp_name=#{name}
and emp_salary=#{salary}
来讲讲where标签做了什么?他做了2件事
它代替了传统手写的where
当条件不成立时,删掉中间的连接词(在上面的代码中是and,也可以是or)
if标签做了什么?
当test中的条件不成立,则if中的语句不存在
自定义多条件设置值
mybatis还为我们提供了set标签
,原理与用法与上面的相同,保证传入的参数出现问题时sql正常执行。set标签需要特别注意的地方是,如果条件均不成立,语法本身就无法成立
。而在上面的查询中,如果条件均不满足,其实会变成无条件查询。
trim标签
trim标签可以用来替代任何上面的标签,他的作用就是在sql语句上面进行直接的裁剪
他提供4个选项,中间可以用|
隔开
他的原则就是if条件不满足则开始判定是否有对应的单词,有就删掉;或者条件满足时判定是否有,没有就加上。(不会添加多个where)
设置如switch标签的多选一
使用mybatis提供的标签,我们可以实现多选一,就像java中的switch
mybatis批处理
mybatis并没有提供专业的批处理方法,但提供了一个forEach标签
。该标签的作用主要是遍历传入的list集合,我们通过遍历循环来拼接出想要的语句
。
List getOneEmpByIds(@Param("ids") List ids);
select * from t_emp where
id in
#{id}
代码解析,forEach标签没什么好说的,就是遍历传入的list,主要在于其中的属性。
open 表示要在遍历之前要拼接的东西
close 自然就是结束后要拼接的东西
至于separator则是每个元素遍历出来后之间的分割符
item无需多说,就是item项
sql片段的抽取与复用
抽取:
select emp_id,emp_name,emp_age,emp_salary,emp_gender from t_emp
引用:
xml文件的批量映射
我们会有一个很烦人的问题,我们所有的xml文件都必须在配置文件中注册。
我们如何批量的去映射他们,或者说直接指定一个包,让他自动扫描包里面的xml文件呢?
mybatis提供了一个package标签
但是这个标签很坑的一点是,
他要求mapper.xml文件必须跟mapper接口的名字保持完全一致!
他要求必须把他们放在同一个包里面。
要知道maven在打包时是默认不会扫描src下其他文件的,只会扫描java文件!,下面演示如何设置maven的打包规则(仅测试用)。
src/main/java
**/*.*
这样做还是很不方便,会导致很多问题。
我们在resources文件夹下创建与java包下同样的包结构即可,他会自动打包在一起!
,特别注意,创建包时要一层一层创建!!!
分页插件的使用
mybatis并没有提供分页功能,我们可以使用分页插件。这在开发中非常常用。
分页插件的原理
他的原理是在sql执行之间添加了一个拦截器,在拦截器中继续封装sql语句,所以他在使用时代码看起来会有点奇怪。
第一步我们导入依赖
com.github.pagehelper
pagehelper
5.1.11
第二步我们在配置文件中引入插件,
mybatis配置文件的各项配置有严格的顺序,我们可以点击configuration标签查看
configuration (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers?)>
我们将插件引入到mybatis中的指定位置,代码中的mysql表示需要拦截的竖起了类型。
插件使用的注意事项:
我们在写竖起了时正常写就行,但是需要删除掉最后竖起了语句结尾的分号---->;
否则语句执行会出问题!!!!
select * from t_emp
插件的使用看起来非常奇怪,它会提前设置当且页数与每页最大数据量。
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
//配置分页信息
PageHelper.startPage(1,2);
//查询代码执行
List employees = mapper.PageQueryAll();
//把查询结果放入PageInfo中,可以让我们获得更多有用信息
PageInfo pageInfo = new PageInfo(employees);
//获取数据的list合集
List list = pageInfo.getList();
System.out.println("pageInfo = " + pageInfo);
long total = pageInfo.getTotal(); // 获取总记录数
System.out.println("total = " + total);
int pages = pageInfo.getPages(); // 获取总页数
System.out.println("pages = " + pages);
int pageNum = pageInfo.getPageNum(); // 获取当前页码
System.out.println("pageNum = " + pageNum);
int pageSize = pageInfo.getPageSize(); // 获取每页显示记录数
System.out.println("pageSize = " + pageSize);
sqlSession.close();
如果我们出现了在同一套代码中需要2次分页查询,则需要重新写一套startPage与PageInfo()来嵌套查询语句