1.MyBatis简介
什么是Mybatis?
- MyBatis是一款可以定义SQL、存储过程和高级映射的持久层框架,用于简化JDBC的开发
- MyBatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去加载驱动、创建连接、创建statement等繁杂的过程。程序员直接编写原生态sql,可以严格控制sql执行性能,灵活度高
- MyBatis可以使用XML或注解配置和映射原生信息,将POJO映射成数据库中的记录,避免了几乎所有的JDBC代码和手动设置参数以及获取结果集
持久层
- 负责将数据到保存到数据库的那一层代码
- JavaEE三层架构:表现层、业务层、持久层
框架
- 框架就是一个半成品软件,是一套可重用的、通用的、软件基础代码模型
- 在框架的基础之上构建软件编写更加高效、规范、通用、可扩展
2.搭建MyBatis
2.1创建Maven工程,添加依赖:
<dependencies>
<!--mybatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<!--MySQL驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.18</version>
</dependency>
<!--junit单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- 添加slf4j日志api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.20</version>
</dependency>
<!-- 添加logback-classic依赖 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- 添加logback-core依赖 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
使用logback时需要导入一个logback.xml文件到项目的resource文件夹下。
2.2MyBatis核心配置文件
习惯上命名为
mybatis-config.xml
<!-- XML头部的声明,它用来验证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>
<!--environment环境配置-->
<!--加载类路径下的属性文件-->
<properties resource="db.properties"/>
<environments default="development">
<environment id="development">
<!--事务管理器-->
<transactionManager type="JDBC"/>
<!--数据库连接相关配置,db.properties文件中的内容-->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<!--引入mybatis映射文件-->
<mappers>
<mapper resource="Mapper/BookMapper.xml"/>
<mapper class="Dao.BookMapper"/>
</mappers>
</configuration>
核心配置文件详解
configuration(配置)
下划线转驼峰
<settings> <!-- 下划线 自动映射 驼峰 --> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>
延迟加载
- 如果某个查询不想使用懒加载,则在association和collection标签当中设置fetchType=“eager”(立即加载)即可
- 节省内存
<settings> <!-- 延迟加载 LazyLoadingEnabled: true,开启懒加载,所有关联对象都会延迟加载 aggressiveLazyLoading: false, 开启时,任何方法的调用都会加载该对象的所有属性,否则,每个属性会按需加载--默认为false不开启,即按需加载 --> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/> </settings>
类型别名,在Mapper的resultType属性中可以使用简单类型别名
<configuration> <!-- 别名 --> <typeAliases> <typeAlias type="com.atguigu.mybatis.pojo.User" alias="user"></typeAlias> <!-- 也可以指定一个包下面的别名, 且不区分大小写, 跟上方 typeLias 不能同时使用 --> <package name="com.atguigu.mybatis.pojo"></package> </typeAliases> </configuration>
在Mapper.xml文件中使用
<select id="getAllUser" resultType="user"> SELECT * FROM t_user; </select>
environments(环境配置)
environment(环境变量)
可以配置多个环境,比如测试环境和开发环境;使用id区分,不能重复
transactionManager(事务管理器)
<transactionManager type="JDBC"></transactionManager> <!--type: JDBC:表示使用JDBC原生事务管理方式,即可以手动的开启关闭事务,手动的提交和回滚。 MANAGED:被管理的,例如交给Spring管理。-->
dataSource(数据库连接池的配置)
- type: POOLED:使用数据库连接池 UNPOOLED:不使用数据库连接池,链接直接重新创建 JNDI:表示使用上下文当中的数据源(了解下)
2.2.1数据库配置db.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&\
characterEncoding=utf8&useUnicode=true&useSSL=false
username=root
password=xxxxxxx
2.2.2创建mapper接口
mybatis中的mapper接口相当于以前的Dao,但是区别在于,mapper仅仅是接口,我们不需要提供实现类
public interface BookMapper{
/**
*添加信息
*/
int insertBook();
}
2.2.3创建MyBatis的映射文件
相关概念:ORM(Object Relationship Mapping)对象关系映射。
- 对象;Java的实体类对象
- 关系:关系型数据库
- 映射:二者之间的对应关系
Java概念 | 数据库概念 |
---|---|
类 | 表 |
属性 | 字段/列 |
对象 | 记录/行 |
1、映射文件的命名规则:
表所对应的实体类的类名+Mapper.xml
一个映射文件对应一个实体类,对应一张表的操作
MyBatis映射文件用于编写SQL,访问以及操作表中的数据
MyBatis映射文件存放的位置是src/main/resources/mappers目录下
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--映射文件:主要用于配置SQL语句和Java对象之间的映射,使SQL语句查询出来的数据能够被封装成Java对象-->
<!-- mapper为映射的根节点-->
<!-- mapper为映射的根节点,namespace指定Mapper接口的完整类名
mybatis会依据这个接口动态创建一个实现类去实现这个接口,
而这个实现类是一个Mapper对象-->
<mapper namespace="Dao.BookMapper">
<!--id ="接口中的方法名
parameterType="传入的参数类型"
resultType = "返回实体类对象,使用包,类名"-->
<!--查询操作-->
<select id="findById" parameterType="int"
resultType="POJO.Book">
select * from book where id = #{id}
</select>
<!--添加操作-->
<insert id="insertBook">
insert into book VALUES (NULL, 'admin', '123456', 23, '男', '12345@qq.com');
</insert>
</mapper>
2.2.4测试功能
从XML中构建SqlSessionFactory
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory
的实例为核心的。SqlSessionFactory
的实例可以通过 SqlSessionFactoryBuilder
获得。而 SqlSessionFactoryBuilder
则可以从 XML 配置文件或一个预先配置的 Configuration
实例来构建出 SqlSessionFactory
实例。
MyBatis 包含一个名叫 Resources
的工具类,它包含一些实用方法,使得从类路径或其它位置加载资源文件更加容易。
@Test
public void testSession() throws IOException{
//创建字符输入流,读取mybatis-config.xml文件内容到reader对象中
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
// InputStream inputStream = Resources.getResourceAsStream(resource);也可以使用字节输入流
//初始化Mybatis数据库,创建SqlSessionFactory类的实例,build()使用Reader字符流封装了XML文件形式的配置信息
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
}
从SqlSessionFactory中获取SqlSession
既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。
openSession()
获得 SqlSession
默认是不自动提交事务,因此需要自己手动提交。
//获取sql的会话对象sqlSession,是MyBatis提供的操作数据库的对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//设置自动提交
SqlSession sqlSession = sqlSessionFactory.openSession(true);
在实际应用中使用工具类获取SqlSession对象会更加方便,将以上代码封装进工具类中。
//通过工具类获取SqlSession对象
SqlSession session = MybatisUtils.getSession();
通过 SqlSession 实例来直接执行已映射的 SQL 语句
//1.创建SqlSession实例,执行持久化操作
SqlSession sqlSession = sqlSessionFactory.openSession();
//2.1通过sql的唯一标识id
Book book = sqlSession.selectOne("findById",1);//Book是实体类,selectOne()是SqlSession提供的查询方法
通过获取Mapper的代理实现类对象,执行接口方法
//2.2获取BookMapper的代理实现类对象
BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);
//调用mapper接口中的方法,实现添加信息的功能
int result = bookMapper.insertBook();
//提交事务
sqlSession.commit();
//关闭事务
sqlSeesion.close();
3.MyBatis的增删改查
3.1查询
3.1.1 根据id查询数据
BookDao.java
接口
//查询一个实体类对象
public interface BookDao {
public Book getBookById(int id);
}
BookDao.xml
映射文件:
<mapper namespace="Dao.BookDao">
<!--查询操作-->
<select id="getBookById" parameterType="int"
resultType="pojo.Book">
select * from book where id = #{id}
</select>
</mapper>
BookTest.java
测试方法:
@Test
public void testGetBookById() throws IOException{
//接收参数,该id以后由前端传递过来
int id = 1;
//1.使用工具类获取会话工厂SqlSessionFactory
//2.创建SqlSession实例,执行持久化操作
SqlSession sqlSession = MybatisUtils.getSession();
//3.BookDao是一个接口,获取Mapper
BookDao bookDao=sqlSession.getMapper(BookDao.class);
//4.执行方法
Book book = bookDao.getBookById(id);
//5.释放资源
sqlSession.close();
}
注意:现在我们感觉测试这部分代码写起来特别麻烦,我们可以先忍忍。以后我们只会写上面的第3步的代码,其他的都不需要我们来完成。
3.1.1.1 参数占位符
-
mybatis提供了两种参数占位符:
#{}
:是预编译处理。执行SQL时,会将#{}占位符替换成?,将来自动设置参数值。从上述例子可以看出使用#{}底层使用的是PreparedStatement
的set方法来赋值。${}
:是字符串替换。就是把${}替换成变量的值底层使用的是Statement
,会存在SQL注入问题。
3.1.1.2 parameterType的使用
对于有参数的mapper接口方法,我们在映射配置文件中应该配置parameterType
来指定参数类型。只不过该属性都可以省略。
3.1.1.3 SQL语句中特殊字段处理
以后肯定会在SQL语句中写一下特殊字符,比如某一个字段大于某个值,如下图:
可以看出报错了,因为映射配置文件是xml类型的问题,而 > < 等这些字符在xml中有特殊含义,所以此时我们需要将这些符号进行转义,可以使用以下两种方式进行转义。
-
转义字符
<select id="selectById" resultMap="mapp"> select * from book where id < #{id} </select>
如上面代码所示中的
<
就是<
的转义字符。 -
<![CDATA[内容]]>
<select id="selectById" resultMap="mapp"> select * from book where id <![CDATA[<]]> #{id} </select>
3.1.2使用resultMap查询所有数据
3.1.2.1 字段名和属性名不一致的情况
- 方式1:起别名的方法
我们可以在编写sql语句的时候给这个字段用as
起别名,将别名定义成和属性名一致即可。
这里假设我数据库中的字段名publication_date
,在实体类中属性名是publicationDate
<select id="getBookById" parameterType="int"
resultType="pojo.Book">
select
id,title,author,publication_date as publicationDate,price
from book
</select>
而上面的SQL语句中的字段列表书写麻烦,如果表中还有更多的字段,同时其他的功能也需要查询这些字段时就显得我们的代码不够精炼。Mybatis提供了sql
片段可以提高sql的复用性。
-
SQL片段
- 将需要复用的SQL片段抽取到
SQL
标签中
<sql id="book_column"> id,title,author,publication_date as publicationDate,price </sql>
id属性是唯一标识,引用时也是通过该值进行引用。
- 在原sql语句中进行引用
使用
include
标签引用上述的SQL片段,而refid
指定上述SQL片段的id值。<select id="getBookById" parameterType="int" resultType="pojo.Book"> select <include refid="book_column" /> from book </select>
- 将需要复用的SQL片段抽取到
-
方式2:使用resultMap
//编写接口方法 List<Book> findAll();
<!--编写SQL语句--> <mapper namespace="Dao.BookDao"> <!-- id:完成主键字段的映射 column:表的列名 property:实体类的属性名 result:完成一般字段的映射 column:表的列名 property:实体类的属性名 --> <resultMap id="BookMap" type="POJO.Book"> <id property="id" column="id"/> <result property="title" column="title"/> <result property="author" column="author"/> <result property="publication_date" column="publicationDate"/> <result property="price" column="price"/> </resultMap> <!--查询结果集--> <select id="findAll" resultMap="BookMap"> select * from book </select> </mapper>
//编写测试方法 @Test public void testFindAll(){ //SqlSession执行映射文件中定义的SQL,并返回映射结果 List<Book> books = bookDao.findAll(); for(Book book:books){ System.out.println(book); } }
这里建议使用
resultMap
的方式,起别名的方法虽然也能解决这个问题,但是复用性不高。如果还有功能只需要查询部分字段,而不是查询所有字段,那么我们就需要再定义一个 SQL 片段,这就显得不是那么灵活。
-
方式3:开启下划线→小驼峰的配置,在
mybatis-config.cml
文件当中添加如下配置<settings> <!-- 下划线 自动映射 驼峰 --> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>
3.1.2.2 多对一关系
POJO:多个
Emp
对应一个Dept
,多个员工对应一个部门对象
public class Emp {
private Integer empId;
private String empName;
private Integer age;
private String gender;
private Dept dept;
// getter/setter/toString
}
- 方式一:级联方式处理,即一次连接出所有字段
//接口方法
Emp getEmpAndDeptByEmpId(@Param("empId") Integer empId);
<resultMap id="getEmpAndDeptByEmpId" type="Emp">
<id column="emp_id" property="empId"></id>
<result column="emp_name" property="empName"></result>
<!-- 级联方式:多对一的映射关系 -->
<result column="dept_id" property="dept.deptId"></result>
<result column="dept_name" property="dept.deptName"></result>
</resultMap>
<select id="getEmpAndDeptByEmpId" resultMap="getEmpAndDeptByEmpId">
SELECT *
FROM t_emp
LEFT JOIN t_dept
ON t_emp.dept_id = t_dept.dept_id
WHERE emp_id = #{empId};
</select>
-
方式二:association标签:
association:处理多对一的映射关系(处理实体类类型的属性)
property:设置需要处理映射关系的属性的属性名
javaType:设置处理的属性的类型
<resultMap id="getEmpAndDeptByEmpId" type="Emp">
<id column="emp_id" property="empId"></id>
<result column="emp_name" property="empName"></result>
<!-- association :专门处理多对一、一对一的映射关系 -->
<association property="dept" javaType="Dept">
<id column="dept_id" property="deptId"></id>
<result column="dept_name" property="deptName"></result>
</association>
</resultMap>
<select id="getEmpAndDeptByEmpId" resultMap="getEmpAndDeptByEmpId">
SELECT *
FROM t_emp
LEFT JOIN t_dept
ON t_emp.dept_id = t_dept.dept_id
WHERE emp_id = #{empId};
</select>
-
方式三:分布查询,先查询出
Emp
,再根据Emp
中的deptId
查询Dept
-
👍优点:可以延迟加载,但必须在核心配置文件设置全局配置(看核心配置详解)
-
EmpMapper.java
/** * 分步查询的第一步 * @param empId * @return */ Emp getEmpAndDeptByStepOne(@Param("empId") Integer empId);
- DeptMapper.java
/** * 分步查询的第二步 * @param deptId * @return */ Dept getEmpAndDeptByStepTwo(@Param("deptId") Integer deptId);
- EmpMapper.xml
<resultMap id="getEmpAndDeptByStepResultMap" type="Emp"> <id column="emp_id" property="empId"></id> <result column="emp_name" property="empName"></result> <!-- property: 关联的类型 fetchType: eager 表示全局配置了懒加载,但是这里我还是想立即加载 select: 第二步查询的唯一标识 column: 第一步查询出来的外键 --> <association property="dept" fetchType="eager" select="com.atguigu.mybatis.mapper.DeptMapper.getEmpAndDeptByStepTwo" column="dept_id" ></association> </resultMap> <!-- Emp getEmpAndDeptByStepOne(@Param("empId") Integer empId); --> <select id="getEmpAndDeptByStepOne" resultMap="getEmpAndDeptByStepResultMap"> SELECT * FROM t_emp WHERE emp_id = #{empId}; </select>
- DeptMapper.xml
<!-- Dept getEmpAndDeptByStepTwo(@Param("deptId") Integer deptId); 这里没有使用 ResultMap, 因为开启了 下划线 转 驼峰的配置 --> <select id="getEmpAndDeptByStepTwo" resultType="dept"> SELECT * FROM t_dept WHERE dept_id = #{dept_id}; </select>
-
3.1.2.3 一对多关系
POJO:一个 Dept 对应多个 Emp
public class Dept {
private Integer deptId;
private String deptName;
private List<Emp> emps;
// getter/setter/toString
}
方式一:collection标签
Dept getDeptAndEmpsByDeptId(@Param("deptId") Integer deptId);
<resultMap id="getDeptAndEmpsByDeptIdResultMap" type="Dept">
<id column="dept_id" property="deptId"></id>
<result column="dept_name" property="deptName"></result>
<!--
property: 关联的属性.
ofType: 集合内部元素的类型.
-->
<collection property="emps" ofType="Emp">
<id column="emp_id" property="empId"></id>
<result column="emp_name" property="empName"></result>
</collection>
</resultMap>
<!-- Dept getDeptAndEmpsByDeptId(@Param("deptId") Integer deptId); -->
<select id="getDeptAndEmpsByDeptId" resultMap="getDeptAndEmpsByDeptIdResultMap">
SELECT *
FROM t_dept
LEFT JOIN t_emp
ON t_dept.dept_id = t_emp.dept_id
WHERE t_dept.dept_id = #{deptId};
</select>
方式二:分布查询,先查询部门,再根据部门的id查询部门下的员工
- DeptMapper.java
/**
* 分步查询 查询部门以及部门中的员工的信息
* @param deptId
* @return
*/
Dept getDeptAndEmpdsStepOne(@Param("deptId") Integer deptId);
- EmpMapper.java
/**
* 分布查询的第二步:查询出一个部门下的员工
* @param empId
* @return
*/
Emp getDeptAndEmpdsStepTwo(@Param("empId") Integer empId);
- DeptMapper.xml
<resultMap id="getDeptAndEmpdsStepOneResultMap" type="dept">
<id column="dept_id" property="deptId"></id>
<result column="dept_name" property="deptName"></result>
<!-- fetchType="eager" 关闭懒加载 -->
<collection property="emps" fetchType="eager" ofType="emp"
select="com.atguigu.mybatis.mapper.EmpMapper.getDeptAndEmpdsStepTwo"
column="dept_id">
</collection>
</resultMap>
<!-- Dept getDeptAndEmpdsStepOne(@Param("deptId") Integer deptId); -->
<select id="getDeptAndEmpdsStepOne" resultMap="getDeptAndEmpdsStepOneResultMap">
SELECT *
FROM t_dept
WHERE dept_id = #{deptId};
</select>
- EmpMapper.xml
<!-- Emp getDeptAndEmpdsStepTwo(@Param("empId") Integer empId);
这里没有使用resultMap, 因为已经开启了下划线转驼峰的配置
-->
<select id="getDeptAndEmpdsStepTwo" resultType="emp">
SELECT *
FROM t_emp
WHERE emp_id = #{empId};
</select>
3.1.3 多条件查询
在 BookDao
接口中定义多条件查询的方法。
3.1.3.1 MyBatis获取多参数:
而该功能有三个参数,我们就需要考虑定义接口时,参数应该如何定义。Mybatis针对多参数有多种实现
-
使用
@Param("参数名称")
标记每一个参数,在映射配置文件中就需要使用#{参数名称}
进行占位List<Book> selectByCondition(@Param("title") int status, @Param("author") String companyName,@Param("price") String brandName);
-
将多个参数封装成一个 实体对象 ,将该实体对象作为接口的方法参数。该方式要求在映射配置文件的SQL中使用
#{内容}
时,里面的内容必须和实体类属性名保持一致。List<Book> selectByCondition(Book book);
-
将多个参数封装到map集合中,将map集合作为接口的方法参数。该方式要求在映射配置文件的SQL中使用
#{内容}
时,里面的内容必须和map集合中键的名称一致。List<Book> selectByCondition(Map map);
3.1.3.2 查询到Map集合
查询的结果没有对应的实体类的时候,就可以使用Map集合。
resultType设置成map即可
❗❗查询为null的字段是不会放到Map集合里面的。
存放一条记录
Map<String, Object> getUserById(@Param("id") String id);
<select id="getUserById" resultType="map">
SELECT * FROM t_user WHERE id = ${id}
</select>
存放多条记录
-
方式一:使用@MapKey()注解
@MapKey("id") Map<String, Object> getAllUserToMap();
<select id="getAllUserToMap" resultType="map"> SELECT * FROM t_user; </select>
// Map<id, User>运行结果 { 3={password=123, gender=男, id=3, age=23, email=12345@qq.com, username=root}, 4={password=123456, gender=男, id=4, age=23, email=12345@qq.com, username=admin}, 5={password=123456, gender=男, id=5, age=23, email=12345@qq.com, username=admin}, 6={password=123456, gender=男, id=6, age=20, email=geek_zh@163.com, username=zs} }
-
方式二:使用List<Map<>>,泛型为Map的List
List<Map<String, Object>> getAllUser();
<select id="getUserById" resultType="map"> SELECT * FROM t_user; </select>
3.1.3.3 模糊查询
在BookDao.xml
映射配置文件中编写statement
,使用resultMap
而不是resultType
<select id="selectByCondition" resultMap="bookRetMap">
select *
from book
where title = #{title}
and author like #{author}
and price < #{price}
</select>
模糊查询书的作者的名字,例如查询作者姓王的、书名为Java程序设计的、价格小于80的所有书籍信息。
测试方法
//定义测试方法
@Test
public void testSelectByCondition() throws IOException{
//接收参数
String title = "Java程序设计";
String author = "王";
int price = 80;
//处理参数
author = "%"+author+"%";
//1.通过工具类获取SqlSession
SqlSession session = MyBatisUtils.getSession();
//2.获取Mapper接口的代理对象
BookDao bookdao = seesion.getMapper(Book.class);
//3.执行方法
//方式一:接口方法参数使用@Param方式的方法
//List<Book> books = bookdao.selectByCondition(title,author,price);
//方式二:接口方法参数使用实体类对象方式调用
//封装对象
/*Book book = new Book();
book.setTitle(title);
book.setAuthor(author);
book.setPrice(price);*/
//执行方法
//List<Book> books = bookdao.selectByCondition(book);
//方式三:接口方法参数是map集合对象 方式调用的方法
Map map = new HashMap();
map.put("title",title);
map.put("author",author);
map.put("price",price);
List<Book> books = bookdao.selectByCondition(map);
System.out.println(books);
//4.释放资源
session.close();
}
3.1.3.4 动态SQL-if、where、trim
上述例子的功能实现存在很大的问题。它会强行要求用户查询时必须输入三个条件才允许查询,但现实有可能出现用户只想输入一个条件的情况
例如,用户只想查询书名为Java程序设计的所有书籍亦或者想要查询作者姓王的价格小于80的书籍……
这样子的SQL语句就会需要发生改变,再用上面例子的SQL语句就会无法查询出来。
针对上述的需要,MyBatis对动态SQL有很大的支撑:
- if
- choose(when,otherwise)
- trim(where,set)
- foreach
我们先来看if
标签和where
标签
-
if标签:条件判断
-
test属性:逻辑表达式
<select id="selectByCondition" resultMap="brandResultMap"> select * from book where <if test="title != null"> and title = #{title} </if> <if test="author != null and author != '' "> and author like #{author} </if> <if test="price != null and price != '' "> and price < #{price} </if> </select>
如上这种SQL语句就会根据传递的参数值进行动态的拼接。当传递的参数为null或者为空字符就不执行那条语句。
但是这样也会有问题,若此时给的参数值是:
Map map = new HashMap(); //map.put("title",title); map.put("author",author); map.put("price",price);
拼接的SQL语句就变成了:
select * from book where and author like ? and price < ?
where关键字后直接跟了and关键字,这就是一条错误的SQL语句。
这个就需要用到where标签
-
-
where标签
-
作用:
- 替换where关键字
- 会动态的去掉第一个条件前的and
- 如果所有的参数没有值则不加where关键字
<select id="selectByCondition" resultMap="brandResultMap"> select * from book <where> <if test="title != null"> and title = #{title} </if> <if test="author != null and author != '' "> and author like #{author} </if> <if test="price != null and price != '' "> and price < #{price} </if> </where> </select>
注意:需要给每个条件前都加上and关键字
-
-
trim标签
Mybatis的trim标签一般用于去除sql语句中多余的and关键字、逗号,或者给sql语句前拼接
where、set、values
等前缀或者后缀,可用于选择性插入、更新、删除和条件查询等操作。✈prefix:给sql语句拼接的前缀
✈suffix:给sql语句拼接的后缀
✈prefixOverrides:去除sql语句前面的关键字或者字符,该关键字或者字符由prefixOverrides属性指定,假设该属性指定为"AND",当sql语句的开头为"AND",trim标签将会去除该"AND"–>等同于
where标签
✈suffixOverrides:去除sql语句后面的关键字或者字符,该关键字或者字符由suffixOverrides属性指定
<select id="selectByCondition" resultMap="Map"> select * from book <trim prefix="where" prefixOverrides="and"> <if test="title != null"> and title = #{title} </if> <if test="author != null and author != '' "> and author like #{author} </if> <if test="price != null and price != '' "> and price < #{price} </if> </trim> </select>
3.1.4 单个条件
3.1.4.1 动态SQL-choose(when,otherwise)
看一下这个例子,当出现这种情况的时候,就需要使用到choose(when,otherwise)
标签来实现,choose
标签相当于Java中的switch
语句。
<select id="selectByConditionSingle" resultMap="brandResultMap">
select *
from tb_brand
<where>
<choose><!--相当于switch-->
<when test="status != null"><!--相当于case-->
status = #{status}
</when>
<when test="companyName != null and companyName != '' "><!--相当于case-->
company_name like #{companyName}
</when>
<when test="brandName != null and brandName != ''"><!--相当于case-->
brand_name like #{brandName}
</when>
</choose>
</where>
</select>
3.2新增
3.2.1 添加数据
//接口方法
public void insertBook(Book book);
<!--插入操作-->
<insert id="insertBook" parameterType="POJO.Book">
insert into book(id,title,author,publication_date,price)
values (#{id},#{title},#{author},#{publication_date},#{price})
</insert>
//测试方法
@Test
public void testInsertBook(){
Book book = new Book();
book.setId("5");
book.setTitle("Python'");
book.setAuthor("张三");
book.setPublication_date("2011-10-20 16:41:21");
BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);
bookMapper.insertBook(book);
sqlSession.commit();
sqlSession.close();
}
3.2.1.1 获取自增主键
<insert id="add" useGeneratedKeys="true" keyProperty="id">
insert into book(title,author,publication_date,prie) values (#{title},#{author},#{publication_date},#{price})
</insert>
在insert标签中添加如下属性:
- userGeneratedKeys:能够获取自动增长的主键值。true表示获取
- keyProperty:指定将获取到的主键值封装到哪个属性里
3.3修改
3.3.1 修改数据
public void updateBook(Book book);
public void upDate(Book book);
<!--方式1:更新操作(改变所有数据)-->
<update id="updateBook" parameterType="pojo.Book">
update book set title=#{title},author=#{author},publication_date=#{publication_date},price=#{price} where id=#{id}
</update>
<!--方式2:使用动态SQL改变部分数据-->
<update id="upDate" parameterType="pojo.Book">
update book
<set>
<if test="title != null and title != ''">
title = #{title}
</if>
<if test="author != null and author != ''">
author = #{author}
</if>
<if test="publication_date != null and publication_date != ''">
publication_date = #{publication_date}
</if>
<if test="price != null and price != ''">
price = #{price}
</if>
</set>
where id = #{id}
</update>
set标签可以用于动态包含需要更新的列,忽略其他不需要更新的列
@Before
public void init(){
String resources = "mybatis-config.xml";
try{
Reader reader = Resources.getResourcesAsReader(resources);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession sqlSession = sqlSessionFactory.openSession();
}catch(IOException e){
e.printStackTrace();
}
}
@Test
public void testUpdate() throws IOException{
//获取mapper
BookDao bookDao = sqlSession.getMapper(BookDao.class);
//方式1:适用于更改全部数据
Book book1 = bookDao.getBookById(5);
book1.setTitle("java");
//···
bookDao.updateBook(book1);
//方式2:可单独修改部分数据
Book book2 = new Book();
book2.setTitle("python");
book2.setId(5);
bookDao.update(book2);
sqlSession.commit();
sqlSession.close();
}
3.4删除
3.4.1 删除一行数据
public void deleteBook(int BookId);
<!--删除操作-->
<delete id="deleteBook" parameterType="pojo.Book">
delete from book where id=#{id}
</delete>
@Test
public void testDeleteBook() throws IOException {
//接收参数
int id = 6;
//3. 获取Mapper接口的代理对象
BookDao bookMapper = sqlSession.getMapper(BookDao.class);
//4. 执行方法
bookMapper.deleteBook(id);
//提交事务
sqlSession.commit();
//5. 释放资源
sqlSession.close();
}
3.4.2批量删除处理
在 BrandMapper
接口中定义删除多行数据的方法。
/**
* 批量删除
*/
void deleteByIds(int[] ids);
参数是一个数组,数组中存储的是多条数据的id
如果不使用foreach标签的话
delete from tb_brand where id in(${ids}) --只能使用${},#{}会默认给数据加上单引号,而在in中如果存在单引号的话,它只会删除第一条数据
3.4.2.1 动态SQL-foreach
用来迭代任何可迭代的对象(如数组,集合)。
- collection 属性:
- mybatis会将数组参数,封装为一个Map集合。
- 默认:array = 数组
- 使用@Param注解改变map集合的默认key的名称
- mybatis会将数组参数,封装为一个Map集合。
- item 属性:本次迭代获取到的元素。
- separator 属性:集合项迭代之间的分隔符。
foreach
标签不会错误地添加多余的分隔符。也就是最后一次迭代不会加分隔符。 - open 属性:该属性值是在拼接SQL语句之前拼接的语句,只会拼接一次
- close 属性:该属性值是在拼接SQL语句拼接后拼接的语句,只会拼接一次
<delete id="deleteByIds">
delete from tb_brand where id
in
<foreach collection="array" item="id" seperatoe="," open="(" close=")">
#{id}
</foreach>
</delete>
加入数组中的id数据是{1,2,3},那么拼接后的sql语句就是:
delete from tb_brand where id in(1,2,3);
4.MyBatis的缓存
4.1 MyBatis的一级缓存
- MyBatis的一级缓存是SqlSession级别的,即通过同一个sqlSession查询的数据会被缓存,再次使用同一个sqlSession查询同一条数据,会从缓存中获取。
- 在MyBatis中一级缓存是默认开启的。
- 使一级缓存失效的四种情况:
- 不同的SqlSession对应不同的一级缓存
- 同一个SqlSession但是查询条件不同
- 同一个SqlSession再次查询期间执行了任何一次增删改操作
- 同一个SqlSession再次查询期间手动清空了缓存
sqlSession.clearCache()
4.2 MyBatis的二级缓存
MyBatis的二级缓存是SqlSessionFactory级别的,即通过一个SqlSessionFactory所获取的sqlSession对象查询的数据会被缓存,在通过同一个SqlSessionFactory所获取的SqlSession查询相同的数据会从缓存中获取
二级缓存的开启条件
在核心配置文件中,设置全局属性配置 cacheEnabled=“true”, 默认为 true,不需要设置
<settings> <setting cacheEnabled="true"></setting> </settings>
核心配置文件,设置全局属性配置
<mapper namespace="com.atguigu.mybatis.mapper.DynamicMapperSQLMapper"> <cache/> <!-- 余下代码 --> </mapper>
❗ ❗二级缓存必须是在 SQLSession 关闭或提交之后有效。(即SQLSession关闭或提交后,一级缓存当中的数据才会保存到一级缓存中)
❗❗二级缓存必须是在 SQLSession 关闭或提交
使二级缓存失效的情况
❗❗两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效
4.3 二级缓存相关配置(简单了解即可)
4.4 MyBatis缓存查询的顺序
- 先查询二级缓存,因为二级缓存当中可能会有其他线程已经查询出来的数据。
- 二级缓存没有命中,则再查询一级缓存。
- 一级缓存也没有命中,则执行查询数据库。
- SQLSession关闭之后,一级缓存当中的数据会写入到二级缓存。
5.逆向工程
正向工程:先创建Java实体类,由框架负责根据实体类生成数据库表。Hibernate 是支持正向工程的。 逆向工程:先创建数据库表,由框架负责根据数据库表反向生成如下资源:
- Java实体类。
- Mapper接口。
- Mapper映射文件
用到的时候再看吧,工作中也没啥用。
6.分页插件
6.1分页插件的使用步骤
①添加依赖
<!-- 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
②配置分页插件
在MyBatis的核心配置文件中配置插件
<configuration>
<plugins>
<!-- 设置分页插件 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
</configuration>
6.2分页插件的使用
@Test
public void testPage(){
SqlSession sqlSession = MyBatisUtils.getSession();
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
//查询功能之前开启分页
PageHelper.startPage(1,4);
List<Emp> list = mapper.slectByExample(null);//没有数据就是查询所有数据
//查询功能之后可以获取分页相关的所有数据
PageInfo<Emp> pageInfo = new PageInfo<>(list,5);
}
7.注解方式编写CRUD
除了在xml文件中编写SQL语句之外,还可以使用注解的方法在接口中直接编写,例如BookTest.java
:
public interface BookTest{
//映射查询语句
@Select("select * from book where id = #{id}")
Book selectBook(int id);
//映射插入语句
@Insert("insert into book(id,title,author,publication_date,price)"
+"value (#{id},#{title},#{author},#{publication_date},#{price})")
int insertBook(Book book);
//映射更新语句
@Update("update book set title=#{title},author=#{author},publication_date=#{publication_date},price=#{price} "
+"where id=#{id}")
int updateBook(Book book);
//映射删除语句
@Delete(" delete from book where id=#{id}")
int deleteBook(int id);
}
注意:
- 注解是用来替换映射配置文件方式配置的,所以使用了注解,就不需要再映射配置文件中书写对应的
statement
Mybatis 针对 CURD 操作都提供了对应的注解,已经做到见名知意。如下:
- 查询 :@Select
- 添加 :@Insert
- 修改 :@Update
- 删除 :@Delete
所以,注解完成简单功能,配置文件完成复杂功能。
logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--
CONSOLE :表示当前的日志信息是可以输出到控制台的。
-->
<appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>[%level] %blue(%d{HH:mm:ss.SSS}) %cyan([%thread]) %boldGreen(%logger{15}) - %msg %n</pattern>
</encoder>
</appender>
<logger name="com.itheima" level="DEBUG" additivity="false">
<appender-ref ref="Console"/>
</logger>
<!--
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF
, 默认debug
<root>可以包含零个或多个<appender-ref>元素,标识这个输出位置将会被本日志级别控制。
-->
<root level="DEBUG">
<appender-ref ref="Console"/>
</root>
</configuration>
MyBatisUtils.java
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory=null;
//初始化SqlsessionFactory对象
static {
try {
//使用MyBatis提供的Resource类加载MyBatis的配置文件
Reader reader= Resources.getResourceAsReader("mybatis-config.xml");
//构建SqlSessionFactory
sqlSessionFactory=new SqlSessionFactoryBuilder().build(reader);
}catch (Exception e){
e.printStackTrace();
}
}
//获取SqlSession对象的静态方法
public static SqlSession getSession(){
return sqlSessionFactory.openSession();
}
}
=注解完成简单功能,配置文件完成复杂功能。==
logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--
CONSOLE :表示当前的日志信息是可以输出到控制台的。
-->
<appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>[%level] %blue(%d{HH:mm:ss.SSS}) %cyan([%thread]) %boldGreen(%logger{15}) - %msg %n</pattern>
</encoder>
</appender>
<logger name="com.itheima" level="DEBUG" additivity="false">
<appender-ref ref="Console"/>
</logger>
<!--
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF
, 默认debug
<root>可以包含零个或多个<appender-ref>元素,标识这个输出位置将会被本日志级别控制。
-->
<root level="DEBUG">
<appender-ref ref="Console"/>
</root>
</configuration>
MyBatisUtils.java
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory=null;
//初始化SqlsessionFactory对象
static {
try {
//使用MyBatis提供的Resource类加载MyBatis的配置文件
Reader reader= Resources.getResourceAsReader("mybatis-config.xml");
//构建SqlSessionFactory
sqlSessionFactory=new SqlSessionFactoryBuilder().build(reader);
}catch (Exception e){
e.printStackTrace();
}
}
//获取SqlSession对象的静态方法
public static SqlSession getSession(){
return sqlSessionFactory.openSession();
}
}