Mybatis简介
Mybatis3简介
MyBatis 是一款优秀的持久层框架 ,它支持自定义 SQL 、存储过程以及高级映射 。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作 。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
快速上手Mybatis3
准备工作
- 创建数据库
sql
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);
- 导入依赖
xml
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.3.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.11</version>
</dependency>
</dependencies>
Mybatis做了什么
我们需要了解一下mybatis做了什么,这样才能更清楚的知道为什么要准备其他的一些东西。
我们一共分三步完成持久层的各种操作。
我们首先需要定义一个接口,在其中指定我们需要查询的信息 ,比如在上面的表中,我想要查询员工的姓名。那么我就需要在接口定义一个selectEmpName()
的方法。
好的,那么我们现在想要去查询这个信息就显然需要设计一个sql语句需要去查询,在mvc框架中加入mybatis,他会将sql语句从Java代码中抽离出来,放置在专门的xml文件中,我们称它为mapper。
现在假设我们查询出来了想要的结果,一个员工姓名,我们显然可以用一个String去接收它。但设想一下,如果我们要查询的是员工的姓名信息和工资信息呢?又或者还有员工的id呢?我们就不能单纯的用一个String去接收了。mybatis提供了一种新的方式,用一个与emp表中列所对应的对象去接收查询到的信息 。 比如下面这个类,我们称他为pojo
kotlin
public class Employee {
private Integer empId;
private String empName;
private Double empSalary;
//此处省略 getter | setter | toString
还有上面所说的一个接口以及它的方法
csharp
public interface EmployeeMapper {
Employee selectEmployee(Integer empId);
}
还有就是存放sql的xml文件 。不过这个xml文件要做的有点多,他需要将接口中的方法与pojo相关联起来。简单来讲就是当接口中的方法被调用,他需要将sql执行的结果映射到pojo中 。要把它写在classpath下面
,因为src只识别java文件,我建议你建个文件夹存放它,因为可以预见,它将来肯定不止一个。
xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.Mapper.EmployeeMapper">
<select id="selectEmployee" resultType="com.atguigu.pojo.Employee">
select emp_name empName,emp_salary empSalary from t_emp where emp_id=#{id}
</select>
</mapper>
让我们看一下其中的标签以及属性。
- mapper标签,表示一个完整的接口实现类。
- namespace属性精确定位到它需要实现的接口上
- select标签声明了这个sql的类型。
- id则指明了接口中的方法,表明这个查询sql是对该方法的重写
- 而resultType则指明了返回值得类型(他指向了我们的pojo)
配置mybatis
别忘了配置数据库,当然mybatis也需要一些简单的配置我们在类路径下写一个配置文件。
xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- environments表示配置Mybatis的开发环境,可以配置多个环境,在众多具体环境中,使用default属性指定实际运行时使用的环境。default属性的取值是environment标签的id属性的值。 -->
<environments default="development">
<!-- environment表示配置Mybatis的一个具体的环境 -->
<environment id="development">
<!-- Mybatis的内置的事务管理器 -->
<transactionManager type="JDBC"/>
<!-- 配置数据源 -->
<dataSource type="POOLED">
<!-- 建立数据库连接的具体信息 -->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis-example"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- Mapper注册:指定Mybatis映射文件的具体位置 -->
<!-- mapper标签:配置一个具体的Mapper映射文件 -->
<!-- resource属性:指定Mapper映射文件的实际存储位置,这里需要使用一个以类路径根目录为基准的相对路径 -->
<!-- 对Maven工程的目录结构来说,resources目录下的内容会直接放入类路径,所以这里我们可以以resources目录为基准 -->
<mapper resource="mappers/EmployeeMapper.xml"/>
</mappers>
</configuration>
测试
ini
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();
这个测试可以说非常繁琐,
- 首先要使用一个工具类把配置文件变成流,
- 然后使用 SqlSessionFactoryBuilder()构建一个会话工厂,
- 再从工厂中取出会话,
- 使用会话工具放入接口,获得接口实现类
- 调用方法传入参数执行sql。
- 关闭流。
mybatis原理的讲解
mybatis是在ibatis的基础上封装优化而来的。增加了使用mapper接口调用sql。 对参数的传入做了优化。但底层还是调用了ibatis来执行sql
我们使用#{}来拼接参数,不要使用#{}拼接,防止sql漏洞。
我们将更详细的内容打印在控制台,监测sql的拼接与执行(将mybatis的日志实现设置为打印控制台)。
xml
<settings>
<!-- SLF4J 选择slf4j输出! -->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
mybatis传参
1. 简单数据类型
我们在接口中添加更多方法来演示
scss
Integer deleteEmployeeById(Integer empId);
//返回值为复杂数据类型
List<Employee> getEmployeesBySalary(Double salary);
//传入对象时
Integer addOneEmployee(Employee employee);
//传入多个简单参数
Integer updateEmployeeSalaryById(Integer id,Double salary);
处理方法如下
sql
//当返回值是固定的时,可以省略resultType
<delete id="deleteEmployeeById">
delete * from t_emp where emp_id=#{id}
</delete>
//当返回类型是多个对象时,无需任何操作,resultType传pojo即可
<select id="getEmployeesBySalary" resultType="com.atguigu.pojo.Employee">
select emp_id empId,emp_name empName,emp_salary empSalary from t_emp where emp_salary>#{salary}
</select>
//当传入的是对象时,则参数名必须为pojo的参数名(get方法的名字)
<insert id="addOneEmployee">
insert into t_emp(emp_name,emp_salary) values(#{empName},#{empSalary});
</insert>
传入多个简单数据类型的处理方式有点复杂
less
//传入参数为多个简单数据类型时,可以使用arg0 arg1...
// 也可以使用 param1 param2 ...
//但建议使用@param() 直接指定参数!!!
<update id="updateEmployeeSalaryById">
update t_emp set emp_salary=#{arg1} where emp_id=#{arg0}
</update>
@Param的用法
Integer updateEmployeeSalaryById(@Param("id") Integer id, @Param("salary") Double salary);
Map传参
一般来讲不建议这么用,但是它可以代替pojo直接传参查询 设置key为字段,value为值。
python
//传入map做为多个简单数据类型时,设为map中的key即可
<update id="updateEmployeeSalaryById">
update t_emp set emp_salary=#{map中的key} where emp_id=#{map中的key}
</update>
设置返回值类型
如果返回的是一个基本数据类型 ,比如String,Double,则全部用首字母小写,string,double,map,也可以写全类名。
csharp
<select id="selectEmployeeName" resultType="string">
select emp_name from t_emp where emp_id=#{empId};
</select>
@Alias注解
如果返回对象,而又不想写全限定名可以用下面这个注解
开启驼峰命名映射
如果我们的表的字段全部为XXX_XXX,而我们的pojo全部为驼峰命名。我们就可以配置一下,不写别名。
csharp
<select id="getEmployeesBySalary" resultType="com.atguigu.pojo.Employee">
select emp_id empId,
emp_name empName,
emp_salary empSalary from t_emp where emp_salary>#{salary}
</select>
每一个都要写别名去对应pojo 我们在配置文件中写入
ini
<setting name="mapUnderscoreToCamelCase" value="true"/>
map接收任意参数
sql
<select id="getMap" resultType="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)
</select>
当然也可以使用list接受上面的代码
如何开启事务自动提交?
ini
InputStream ism = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(ism);
//传入true表示开启自动提交
SqlSession sqlSession = sqlSessionFactory.openSession(true);
如何获取插入数据的主键?
sql
<insert id="addOneEmployee" useGeneratedKeys="true" keyColumn="emp_id" keyProperty="empId">
insert into t_emp(emp_name,emp_salary) values(#{empName},#{empSalary});
</insert>
-
useGeneratedKeys="true" ------ 表示我们需要表自动生成的主键
-
keyColumn="emp_id" ----- 表示指定主键是谁
-
keyProperty="empId" 表示让mybatis直接将结果返回到我们用于插入数据的对象中。
mybatis维护UUID主键
sql
<insert id="addOneEmployee" >
<selectKey keyColumn="emp_id" keyProperty="empId" resultType="string">
select replace(UUID(),'-','');
</selectKey>
insert into t_emp(emp_name,emp_salary) values(#{empName},#{empSalary});
</insert>
多表查询
我们如何处理复杂的多表查询。如何设计与其相对应的pojo?多层嵌套关系的对象类型如何精准映射?
多表查询的设计原则
数据库表之间的关系无非分为3类
-
一对一的数据表关系
-
多对一,一对多的数据表关系
-
多对多的数据表关系
我们始终遵从以一张数据表为主的原则来设计。
比如订单表与用户表的关系,从订单角度来看,多张订单对应一个客户,所以是多对1 ,而如果从客户角度看,则是一个客户对应多张表,是1对多的关系。
当我们需要查询订单的id,订单的内容与订单对应的客户时 ,客户其实可以设计为订单表的单个对象属性几个。
scss
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
vbnet
public class Order {
private Integer orderId;
private String orderName;
private Integer customerId;
private Customer customer;
/getter | setter | toString
}
vbnet
@Data
public class Customer {
private Integer customerId;
private String customerName;
private List<Order> orderList;
/getter | setter | toString
}
Mapper相关的配置。
ini
<resultMap id="orderAndMap" type="com.atguigu.pojo.Order">
<id column="order_id" property="orderId"/>
<result column="order_name" property="orderName"/>
<result column="customer_id" property="customerId"/>
<association property="customer" javaType="com.atguigu.pojo.Customer">
<id column="customer_id" property="customerId"/>
<result column="customer_name" property="customerName"/>
</association>
</resultMap>
<select id="selectOrderAndCustomer" resultMap="orderAndMap">
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}
</select>
代码解析:
- 对于查询语句的映射,我们使用resultMap来解决多表映射。
- resultMap中type我们依然选择主表Order表的Pojo。
- 主键我们使用id来映射 column选择表中的列,property选择pojo字段。
- 对于对象类型我们选择association,指定他的pojo为JavaType
一对多查询的xml写法
xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.Mapper.CustomerMapper">
<resultMap id="orderList" type="com.atguigu.pojo.Customer">
<id column="customer_id" property="customerId"/>
<result column="customer_name" property="customerName"/>
<collection property="orderList" ofType="com.atguigu.pojo.Order">
<id column="order_id" property="orderId"/>
<result column="order_name" property="orderName"/>
</collection>
</resultMap>
<select id="getOrderByCustomerId" resultMap="orderList">
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}
</select>
</mapper>
他们之间的区别在于2个标签
不同于单个对象的写法,我们使用collection来接收多个对象(List)类型
。不同于单个对象的写法,我们使用ofType指定对象的pojo
多表映射的优化
在上面的代码中我们发现,我们无法对多层级的属性进行自动映射 。比如List集合中的属性。我们需要手写映射关系。其实mybatis可以根据驼峰原则,进行自动映射(浅层和深层都能实现自动完成)。我们需要额外开启相关设置。
xml
<settings>
<!-- SLF4J 选择slf4j输出! -->
<setting name="logImpl" value="STDOUT_LOGGING"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="autoMappingBehavior" value="FULL"/>
</settings>
开启后,我们就不需要写一部分的映射了
xml
<resultMap id="orderList" type="com.atguigu.pojo.Customer">
<!-- <id column="customer_id" property="customerId"/>-->
<!-- <result column="customer_name" property="customerName"/>-->
<collection property="orderList" ofType="com.atguigu.pojo.Order">
<id column="order_id" property="orderId"/>
<!-- <result column="order_name" property="orderName"/>-->
</collection>
但是注意,关于主键我们仍需要定义
!
自定义多条件查询
在实际开发中,我们常常遇到需要自定义的多条件查询。比如电商平台的购物筛选功能,用户可能添加附加条件查询,就像下面这种情况。如果我们给每条查询都设置对应的sql语句,这显然不现实。
那么如何解决这种问题呢?
mybatis为我们提供了新的标签:<if>
标签与<where>
标签。 比如下面这个根据姓名和工资查找员工的请求。
less
List<Employee> getEmpByNameAndSalary(@Param("name") String name,@Param("salary") Double salary);
如果上面的name和salar可以可能传也可能不传,那我们的sql语句就应该按照下面这样来写。
bash
<select id="getEmpByNameAndSalary" resultType="com.atguigu.pojo.Employee">
select * from t_emp
<where>
<if test="name != null">
emp_name=#{name}
</if>
<if test="salary != null">
and emp_salary=#{salary}
</if>
</where>
</select>
来讲讲where标签做了什么?他做了2件事
它代替了传统手写的where
当条件不成立时,删掉中间的连接词(在上面的代码中是and,也可以是or)
if标签做了什么?
当test中的条件不成立,则if中的语句不存在
- 注意test中的name是我们在mapper中使用@param标签定义的,无法使用arg0代替
自定义多条件设置值
mybatis还为我们提供了set标签
,原理与用法与上面的相同,保证传入的参数出现问题时sql正常执行 。set标签需要特别注意的地方是,如果条件均不成立,语法本身就无法成立
。而在上面的查询中,如果条件均不满足,其实会变成无条件查询。
trim标签
trim标签可以用来替代任何上面的标签,他的作用就是在sql语句上面进行直接的裁剪 他提供4个选项,中间可以用|
隔开
- prefix 表示在if标签的前面动态添加的语句。如where与set
- suffix 表示在if标签的后面动态添加的语句。如and连接符
- prefixOverrides 表示在if标签的前面动态删除的语句。
- suffixOverrides 表示在if标签的后面动态删除的语句。
他的原则就是if条件不满足则开始判定是否有对应的单词,有就删掉;或者条件满足时判定是否有,没有就加上。(不会添加多个where)
设置如switch标签的多选一
使用mybatis提供的标签,我们可以实现多选一,就像java中的switch
mybatis批处理
mybatis并没有提供专业的批处理方法,但提供了一个forEach标签
。该标签的作用主要是遍历传入的list集合 ,我们通过遍历循环来拼接出想要的语句
。
csharp
List<Employee> getOneEmpByIds(@Param("ids") List<String> ids);
<select id="getOneEmpByIds" resultType="com.atguigu.pojo.Employee">
select * from t_emp where
id in
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
代码解析,forEach标签没什么好说的,就是遍历传入的list,主要在于其中的属性。
open 表示要在遍历之前要拼接的东西
close 自然就是结束后要拼接的东西
至于separator则是每个元素遍历出来后之间的分割符
item无需多说,就是item项
sql片段的抽取与复用
抽取:
xml
<!-- 使用sql标签抽取重复出现的SQL片段 -->
<sql id="mySelectSql">
select emp_id,emp_name,emp_age,emp_salary,emp_gender from t_emp
</sql>
引用:
xml
<!-- 使用include标签引用声明的SQL片段 -->
<include refid="mySelectSql"/>
xml文件的批量映射
我们会有一个很烦人的问题,我们所有的xml文件都必须在配置文件中注册。 我们如何批量的去映射他们,或者说直接指定一个包,让他自动扫描包里面的xml文件呢?
mybatis提供了一个package标签
ini
<package name="com.atguigu.Mapper"/>
但是这个标签很坑的一点是,
他要求mapper.xml文件必须跟mapper接口的名字保持完全一致!
他要求必须把他们放在同一个包里面。
要知道maven在打包时是默认不会扫描src下其他文件的,只会扫描java文件!,下面演示如何设置maven的打包规则(仅测试用)。
xml
<build>
<resources>
<resource>
<directory>
src/main/java
</directory>
<includes>
<include>
**/*.*
</include>
</includes>
</resource>
</resources>
</build>
这样做还是很不方便,会导致很多问题。
我们在resources文件夹下创建与java包下同样的包结构即可,他会自动打包在一起!
,特别注意,创建包时要一层一层创建!!!
分页插件的使用
mybatis并没有提供分页功能,我们可以使用分页插件。这在开发中非常常用。
分页插件的原理
他的原理是在sql执行之间添加了一个拦截器,在拦截器中继续封装sql语句,所以他在使用时代码看起来会有点奇怪。
第一步我们导入依赖
xml
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.11</version>
</dependency>
第二步我们在配置文件中引入插件,
mybatis配置文件的各项配置有严格的顺序 ,我们可以点击configuration标签查看
xml
<!ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers?)>
我们将插件引入到mybatis中的指定位置,代码中的mysql表示需要拦截的竖起了类型。
xml
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="helperDialect" value="mysql"/>
</plugin>
</plugins>
插件使用的注意事项:
我们在写竖起了时正常写就行,但是需要删除掉最后竖起了语句结尾的分号 ---->;
否则语句执行会出问题!!!!
xml
<!-- List<Employee> PageQueryAll();-->
<select id="PageQueryAll" resultType="com.atguigu.pojo.Employee">
select * from t_emp
</select>
插件的使用看起来非常奇怪,它会提前设置当且页数与每页最大数据量。
csharp
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
//配置分页信息
PageHelper.startPage(1,2);
//查询代码执行
List<Employee> employees = mapper.PageQueryAll();
//把查询结果放入PageInfo中,可以让我们获得更多有用信息
PageInfo<Employee> pageInfo = new PageInfo<>(employees);
//获取数据的list合集
List<Employee> 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()来嵌套查询语句