Mybatis学习整理

本文详细介绍了MyBatis的持久化框架特性,包括执行流程、第一个Mybatis程序的创建、CRUD操作、Map参数使用、配置解析、日志配置、分页、注解开发和动态SQL等内容。通过实例展示了MyBatis如何简化SQL操作,提高代码的可维护性。此外,还探讨了MyBatis的一级和二级缓存机制及其应用场景。
摘要由CSDN通过智能技术生成

本文根据狂神说一步步整理得到

Mybatis

中文官方文档

CSDN文档

概述

MyBatis 是一个持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录

  • 简单易学
  • 灵活
  • sql和代码的分离,提高了可维护性。
  • 提供映射标签,支持对象与数据库的orm字段关系映射
  • 提供对象关系映射标签,支持对象关系组建维护
  • 提供xml标签,支持编写动态sql。

持久化

数据持久化

  • 内存:断电即失

  • 持久化就是将程序的数据在持久状态和瞬时状态转化的过程

  • 常见方式:数据库(jdbc)、IO文件持久化

持久层

Dao层、Service层、Controller层……

  • 完成持久化的代码块

Mybatis 执行流程

P16

第一个Mybatis程序

  1. 导依赖
  2. resources下创建 mybatis-config.xml

注意 XML 头部的声明,它用来验证 XML 文档的正确性。environment 元素体中包含了事务管理和连接池的配置。mappers 元素则包含了一组映射器(mapper),这些映射器的 XML 映射文件包含了 SQL 代码和映射定义信息。

<?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 default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
          <property name="driver" value="com.mysql.jdbc.Driver"/>
          <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
          <property name="username" value="root"/>
          <property name="password" value="123456"/>
      </dataSource>
    </environment>
  </environments>
    <!--每个mapper.xml文件都需要在配置文件中注册 用 resource 时,文件路径用斜杠隔开-->
  <mappers>
    <mapper resource="***********.xml"/>
  </mappers>
</configuration>
  1. 创建工具类MybatisUtils.java通过sqlSessionFactory为核心,创建SqlSession对象,包含了面向数据库执行 SQL 命令所需的所有方法(读取配置文件,创建工厂
  • 用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,一旦创建 SqlSessionFactory,就不再需要它
  • SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,使用单例模式
  • SqlSession 线程不安全,每次收到的 HTTP 请求,就可以打开一个 SqlSession,返回一个响应,就关闭它,关闭需要放在finally内确保执行
  • sqlSessionFactory.openSession(true);为 true 时,自动提交事务
private static SqlSessionFactory sqlSessionFactory;
static {
    try {
        // 获取sqlSessionFactory 对象
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    } catch (IOException e) {
        e.printStackTrace();
    }
}
// SqlSession 包含了面向数据库执行 SQL 命令所需的所有方法
public static SqlSession getSqlSession(){
	// 自动提交事务
    // sqlSessionFactory.openSession(true);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    return sqlSession;
}
  1. 创建实体类pojo、接口dao、映射文件mapper.xml

接口dao

public interface UserDao {
    List<User> selectUserList();
}

映射文件mapper.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">
<!--绑定对应的Dao/Mapper接口文件-->
<mapper namespace="com.kayden.dao.UserDao">
    <select id="selectUserList" resultType="com.kayden.pojo.User">
        select * from user
    </select>
</mapper>
  1. 测试
@Test
public void test(){
    // 1、获取sqlSession 对象
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    // 2、getMapper
    UserDao userDao = sqlSession.getMapper(UserDao.class);
    List<User> userList = userDao.selectUserList();
    for (User user:userList) {
        System.out.println(user);
    }
    //3、关闭SQLSession
    sqlSession.close();
}

CRUD

namespace命名空间,用来绑定对应的Dao/Mapper接口文件

  • select的使用的时候有固定的三步模板
  • 增删改的时候需要加事务处理sqlSession.commit();(可以将commit设置为true,保证自动处理事务)
// 1、获取sqlSession 对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
// 2、getMapper
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 增删改的事务处理 sqlSession.commit();
//3、关闭SQLSession
sqlSession.close();

select

parameterType传入参数类型

resultType返回值类型

<select id="selectUserId" parameterType="int" resultType="com.kayden.pojo.User">
    select * from mybatis.user where id = #{id};
</select>

insert

<insert id="addUser" parameterType="com.kayden.pojo.User">
    insert into mybatis.user (id, name, pwd) values (#{id},#{name},#{pwd});
</insert>

update

<update id="updateUser" parameterType="int">
    update mybatis.user set
    name = #{name,jdbcType=VARCHAR},
    pwd = #{pwd,jdbcType=VARCHAR},
    where id = #{id};
</update>

delete

<delete id="deleteUser" parameterType="int">
    delete from mybatis.user where id = #{id};
</delete>

Map

  • Map传递参数,直接在sql中取出key即可(对象传递参数,直接在sql中取对象的属性即可)
  • 对象属性太多时,用map可以避免重载很多构造方法的情况
  • 可以用来修改要传的参数名(映射文件的 nameAdfsdf/pwdAdfsdf 对应 map 中的 key 值)

分页例子

List<User> getUserByLimit(Map<String,Integer> map);
<select id="getUserByLimit" parameterType="map" resultType="com.kayden.pojo.User">
    select * from mybatis.user limit #{startIndex},#{pageSize};
</select>
@Test
public void getUserByLimit(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    HashMap<String, Integer> map = new HashMap<String, Integer>();
    map.put("startIndex",0);
    map.put("pageSize",2);
    List<User> userList = mapper.getUserByLimit(map);
    for (User user : userList) {
        System.out.println(user);
    }
    sqlSession.close();
}

模糊查询

  1. 在代码执行时,传通配符% %
select * from mybatis.user where name like #{name}
List<User> userList = userMapper.getUserLike("%李%");
  1. 使用sql拼接CONCAT
select * from mybatis.user where name like concat('%',#{name},'%')
List<User> userList = userMapper.getUserLike("李");

配置解析

MyBatis官方文档-XML 配置

mybatis-config.xml

注意:在核心配置文件中的标签有着各自固定的先后顺序,改变顺序会报错
在这里插入图片描述

configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
	environment(环境变量)
	transactionManager(事务管理器)
	dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)

properties 属性

通过properties属性来实现引用配置文件

这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置

  • 可以直接引入外部文件
  • 可以在其中增加一些属性配置
  • 优先使用内部的字段,如果两个文件有同一个字段,外部会替换内部的
  1. 编写一个配置文件db.properties
driver=com.mysql.jdbc.Driver
# 其中的 & 符号与在核心配置文件中不同,不需要额外的转义
url= jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8
username=root
password=123456
  1. 修改核心配置文件,引入db.properties
<properties resource="db.properties">
    <!--可以在其中添加db.properties中没有的字段,先读取内部在读取外部文件,重复会被覆盖-->
    <!--<property name="username" value="root"/>-->
</properties>

<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>

settings 设置(未完)

<settings>
  <setting name="cacheEnabled" value="true"/>
    ...
</settings>
设置名描述有效值默认值
cacheEnabled缓存开关true | falsetrue
lazyLoadingEnabled懒加载 fetchTypetrue | falsefalse
logImpl日志,未指定时将自动查找SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING未设置
mapUnderscoreToCamelCase开启驼峰命名自动映射,将数据库中的_改为驼峰命名SESSION | STATEMENTSESSION

environments 环境配置

  • 尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境
  • Mybatis默认的事务管理器就是JDBC,连接池:POOLED

environments & environment

尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境

每个数据库对应一个 SqlSessionFactory 实例

<!-- 有多个环境时,通过 default = 确认当前的环境 -->
<environments default="development11111">
  <environment id="development11111">
    <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>
    
  <environment id="development22222">
  ....
  </environment>
</environments>

事务管理器(transactionManager)

在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]")

  • JDBC- 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域
  • managed - 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭(了解就好)

数据源(dataSource)

用来连接数据库

有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]")

默认就是POOLED,有池连接,能使并发 Web 应用快速响应请求。

typeAliases 类型别名

  • 类型别名可为 Java 类型设置一个缩写名字
  • 存在的意义:降低冗余的全限定类名书写

1、给指定实体类取别名

<typeAliases>
	<typeAlias alias="Author" type="domain.blog.Author"/>
<typeAliases/>

2、扫描实体类的包

在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名

<typeAliases>
    <package name="com.kayden.pojo"/>
</typeAliases>

扫描了包后,在返回的结果可以直接用小写的实体类名

<select id="getUserLike" resultType="user">
    select * from mybatis.user where name like concat('%', #{name},'%')
</select>

适用场景

  • 在实体类比较少的时候,使用第一种方式。

    • 第一种可以DIY别名
  • 如果实体类十分多,建议使用第二种。

    • 第二种则不行,如果非要改,需要在实体上增加注解

在方法 2 的基础上使用注解

@Alias("author")
public class Author {
    ...
}

默认的别名

  • 在基本数据类型前面加_——基本数据类型
  • 单纯的基本数据可惜——包装类

mappers 映射器

告诉 MyBatis 到哪里去找映射文件

  • 使用相对于类路径的资源引用**resource(推荐)**

  • 使用完全限定资源定位符(URL)url

  • 使用映射器接口实现类的完全限定类名class

    • 接口和他的Mapper映射文件必须同名
    • 接口和他的Mapper映射文件必须在同一个包下
  • 将包内的映射器接口实现全部注册为映射器name

    • 接口和他的Mapper映射文件必须同名
    • 接口和他的Mapper映射文件必须在同一个包下
<mappers>
	<!-- 使用相对于类路径的资源引用 -->
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
    <!-- 使用完全限定资源定位符(URL) -->
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
    <!-- 使用映射器接口实现类的完全限定类名 -->
  <mapper class="org.mybatis.builder.AuthorMapper"/>
    <!-- 将包内的映射器接口实现全部注册为映射器 -->
  <package name="org.mybatis.builder"/>
</mappers>

生命周期和作用域

生命周期,和作用域,是至关重要的,因为错误的使用会导致非常严重的并发问题

在这里插入图片描述

SqlSessionFactoryBuilder

  • 一旦创建了SqlSessionFactory,就不再需要它了
  • 局部变量

SqlSessionFactory

  • 可以想象成数据库连接池
  • SqlSessionFactory 一旦被创建就一直在应用的运行期间存在,没有任何理由丢弃它或重新创建另一个实例
  • 因此SqlSessionFactory的最佳作用域是应用作用域。
  • 最简单的就是使用单例模式或者静态单例模式。

SqlSession

  • 连接到连接池的一个请求
  • Sqlsession的实例线程不安全,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用用完之后,需要赶紧关闭,否则资源被占用
  • 这里面的每一个Mapper,就代表一个具体的业务

在这里插入图片描述

数据库和实体类的字段不一致

  • 当两个字段一致时,mybatis的类型处理器会自动匹配

  • 字段不一致会导致查询为 NULL

比如:实体类 password 对应数据库字段的 pwd

<select id="selectUserId" parameterType="int" resultType="com.kayden.pojo.User">
    select * from user where id = #{id};
    <!-- 实质是 select id,name,pwd from user where id = #{id}; 与数据库字段不匹配 -->
</select>

使用数据库的as起别名

select id,name,pwd as password from user where id = #{id};

resultMap 结果集映射

  1. 加入resultMap映射
    • column—— 数据库中的字段
    • property—— 实体类的属性名
    • 同名可以不用写
  2. resultType修改为resultMap
  3. 其中有两个标签,常用来应对复杂属性/查询
    1. 对象 —— association
    2. 集合 —— collection
<resultMap id="UserMap" type="User">
    <result column="id" property="id"/>
    <result column="name" property="name"/>
    <result column="pwd" property="password"/>
</resultMap>

<select id="selectUserId" parameterType="int" resultMap="UserMap">
    select * from user where id = #{id};
</select>

适用场景

  • 对于复杂一点的语句(嵌套查询),只需要描述它们的关系即可。

复杂的 Mappers 使用方法见下文的复杂查询

日志

日志工厂

SLF4J | LOG4J |STDOUT_LOGGING

需要在配置文件中配置(setting中的内容必须正确,改大小写,加空格都会出错)

STDOUT_LOGGING标准的日志输出

<settings>
  <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

LOG4J

Log4j 是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUl组件。可以控制每一条日志的输出格式;

  1. 导依赖
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
  1. 修改配置文件
<settings>
  <setting name="logImpl" value="LOG4J"/>
</settings>
  1. 创建常见的日志配置log4j.properties
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file

#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n

#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
# 输出日志文件
log4j.appender.file.File=./log/kuang.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n

#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
  1. 在要用的log4j的类中导入包import org.apache.log4j.Logger;
Logger logger = Logger.getLogger(UserMapperTest.class);
  1. 日志级别
logger.info("info-----");
logger.debug("debug-----");
logger.error("error-----");

分页

用来减少数据的处理量

Limit 分页

limit 起始值(下标从0开始),页长select * from table limit 0,2;

List<User> getUserByLimit(Map<String,Integer> map);
<select id="getUserByLimit" parameterType="map" resultType="com.kayden.pojo.User">
    select * from mybatis.user limit #{startIndex},#{pageSize};
</select>
@Test
public void getUserByLimit(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    HashMap<String, Integer> map = new HashMap<String, Integer>();
    map.put("startIndex",0);
    map.put("pageSize",2);
    List<User> userList = mapper.getUserByLimit(map);
    for (User user : userList) {
        System.out.println(user);
    }
    sqlSession.close();
}

RowBounds 分页(了解)

视频地址

PageHelper 分页

官网文档

注解开发

面向接口编程

根本原因解耦可拓展,提高复用,分层开发中,上层不用管具体的实现,大家都遵守共同的标准,使得开发变得容易,规范性更好

  • 在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;
  • 而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。

使用注解

使用反射实现

  1. 注解在接口上实现
@Select("select * from user")
List<User> selectUserList();
  1. 在核心配置文件下绑定接口
<mappers>
    <mapper class="mapper.UserMapper.xml"/>
</mappers>

使用注解的CRUD

工具类实现自动提交事务sqlSessionFactory.openSession(true)

public static SqlSession getSqlSession(){
    return sqlSessionFactory.openSession(true);
}

@Param()如果是多参数基本类型或String类型的时候必须加上

@Select("select * from user where id = #{ID}")
User getUserById(@Param("ID") int id);

@Insert("insert into user(id,name,pwd) values (#{id} ,#{name} ,#{pwd} )")
int addUser2(User user);

@Update("update user set name = #{name},pwd = #{pwd} where id = #{id} ")
int updateUser2(User user);

@Delete("delete from user where id = #{id} ")
int deleteUser2(@Param("id") int i);

复杂查询

使用resultMap映射,官网举例

多对一处理

多个学生 —— 一个老师,有学生表和教师表,学生表的tid是教师表的外键

public class Student {
    private int id;
    private String name;
    private Teacher teacher;
}

public class Teacher {
    private int id;
    private String name;
}

如果只是对 Student表 查询是无法将Teacher的数据读出

结果嵌套查询(推荐)

resultMap中嵌套result

(效率要比子查询要高)

  1. 先写出查询的sql语句,最好先起别名(在起别名之后,在column中都需要使用对应别名)
  2. resultMap的外层result中一一写出对应的属性和字段
  3. association中写入对应的属性和 javaBean
  4. 在内层的result中是另一张表所需要的属性和字段
<select id="selectStudentList02" resultMap="studentTeacher02">
    select s.id as sid,s.name as sname,t.name as tname
    from student s ,teacher t
    where s.tid = t.id;
</select>

<resultMap id="studentTeacher02" type="com.kayden.pojo.Student">
    <result property="id" column="sid" />
    <result property="name" column="sname"/>
    <association property="teacher" javaType="com.kayden.pojo.Teacher">
        <result property="name" column="tname"/>
    </association>
</resultMap>
子查询

这里需要使用和resultMap

  1. 先查询单个教师的信息
  2. 查询所有的学生信息,返回值resultMap="studentTeacher"
  3. 编写resultMap,有association表示对象,联合所查询的教师数据
    • property—— 类中的复杂属性
    • javaType—— 需要返回的类
    • column—— 在数据库中的字段(外键)
    • select—— 属性对象所查询出的数据(外键)
  4. 对象 —— association;集合 —— collection
<resultMap id="studentTeacher" type="Student">
    <result property="id" column="id"/>
    <result property="name" column="name"/>
    <!-- 复杂属性,对象 —— association ;集合 —— collection -->
    <association property="teacher" javaType="Teacher" select="selectTeacherById" column="tid"/>
</resultMap>

<select id="selectStudentList" resultMap="studentTeacher">
    select * from student
</select>

<select id="selectTeacherById" resultType="teacher">
    select * from teacher where id = #{id};
</select>

一对多处理

一个学生对应多个老师

public class Student {
    private int id;
    private String name;
    private int tid;
}

public class Teacher {
    private int id;
    private String name;
    private List<Student> students;
}

查询一对多的 sql

select t.id tid,t.name tname,s.id sid,s.name sname
from student s,teacher t
where s.tid = t.id and t.id = 1;
结果嵌套查询
  1. 一对多是以教师为主体,写出对应的映射
  2. resultMap中由于是List集合类,需要使用collection
    • 而且集合内属性是对象 —— 使用ofType代替javaType来表示返回的对象集合
List<Teacher> selectTeacherById(@Param("tid") int tid);
<select id="selectTeacherById" resultMap="teacherStudent" >
    select t.id tid,t.name tname,s.id sid,s.name sname
    from student s,teacher t
    where s.tid = t.id and t.id = #{tid};
</select>
<resultMap id="teacherStudent" type="com.kayden.pojo.Teacher">
    <result property="id" column="tid"/>
    <result property="name" column="tname"/>
    <collection property="students" ofType="com.kayden.pojo.Student">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
    </collection>
</resultMap>

返回值

Teacher(id=1, name=秦老师, students=[Student(id=1, name=小明, tid=0), Student(id=2, name=小红, tid=0), Student(id=3, name=小张, tid=0), Student(id=4, name=小李, tid=0), Student(id=5, name=小王, tid=0)])

一对多 子查询

总结

  • 关联 - association - 多对一
  • 集合 - collection - 一对多
  • javaType 和 ofType 都是用来指明对象类型
    • javatype 是在pojo属性类型
    • ofType 指定是当对象需要 Collection 进行list集合映射对象的时候使用,泛型中的约束类型(一对多)

动态sql(标签)

狂神说-Mybatis

动态SQL:指根据不同的条件生成不同的SQL语句

if

常用于条件搜索(有数据查数据,没数据查全部)

List<Blog> selectIf(Map map);
<select id="selectIf" parameterType="map" resultType="blog">
    select * from blog where 1=1
    <if test="title != null ">
      and title = #{title}
    </if>
    <if test="author != null ">
        and author = #{author}
    </if>
</select>
@Test
public void test02(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

    HashMap hashMap = new HashMap();
    hashMap.put("title","Mybatis");
    hashMap.put("author","狂神说");
    List<Blog> blogs = mapper.selectIf(hashMap);
    for (Blog blog : blogs) {
        System.out.println(blog);
    }
    sqlSession.close();
}

where 来修改原先的 if

  • 在映射文件中有个问题,where后面一般有具体的条件
  • 如果没有,在判断条件中一般不使用where 1=1
  • 但是在where中无法直接使用 if 标签,因为在标签中的and 、or符号会被拼接,导致sql语句出错
  • 所以有一个where标签,可以设置成动态条件,会将sql中的and、or舍弃
  • 如果条件都不匹配,就省略where标签
select * from blog where
<!--不删除 and,当查找title时,会被拼接成 SQL: select * from blog where and author = ? 会报错-->
<!--如果删除了 and 符号,当查找title时,能够正常输出-->
<!--但是当查找author时,还是会报错-->
<if test="title != null ">
   and title = #{title}
</if>
<if test="author != null ">
    and author = #{author}
</if>

优化

使用where标签代替where

当where内的if语句都不匹配时,就输出全部

<select id="selectIf" parameterType="map" resultType="blog">
    select * from blog
    <where>
        <if test="title != null ">
            and title = #{title}
        </if>
        <if test="author != null ">
            and author = #{author}
        </if>
    </where>
</select>

sql 片段

实现代码的重用

使用sql标签抽取公共部分,在需要的地方由include引用

  • 一般用于if判断
  • 最好基于单表操作
  • 在sql中不要使用where标签
<sql id="sqlUpdte" >
    <if test="title != null">
        title = #{title},
    </if>
    <if test="author != null">
        author = #{author},
    </if>
</sql>
<update id="updateBlogSetsql" parameterType="map">
    select * from blog
    <include refid="sqlUpdte"></include>
</update>

choose(when, otherwise)

相当于switch...case...otherwise只能优先输出符合的第一个case/otherwise

<select id="selectChoose" parameterType="map" resultType="com.kayden.pojo.Blog">
    select * from blog
    <where>	
        <choose>
            <when test="title != null">
                and title = #{title}
            </when>
            <when test="author != null">
                and author = #{author}
            </when>
            <otherwise>
                and views = #{views}
            </otherwise>
        </choose>
    </where>
</select>

set

动态更新语句,忽略其它不更新的列

注意

  • 在update的时候有,如果省略会拼接错误
  • 如果只修改了一个字段,但后面还有,在set标签中会自动将,舍弃
  • 所以为了统一最好在每个set标签结尾都加上,

真正的sql语句update blog SET title = ?, author = ? where id = ?

在这里插入图片描述

<update id="updateBlogSet" parameterType="map">
    update blog
    <set>
        <if test="title != null">
            title = #{title},
        </if>
        <if test="author != null">
            author = #{author},
        </if>
    </set>
    where id = #{id}
</update>
HashMap hashMap = new HashMap();
hashMap.put("author","狂神说Set2");
hashMap.put("id","0e28c4546c9a4df786d99bf66f2222eb");
int i = mapper.updateBlogSet(hashMap);

foreach

foreach使用场景:对集合进行遍历(尤其是在构建 IN 条件语句的时候)

使用in遍历

select * from mybatis.blog where id in (1,2);
<select id="selectForeach" resultType="com.kayden.pojo.Blog" parameterType="map">
    select * from mybatis.blog
    where id in
        <foreach collection="ids" item="id" open=" (" close=")" separator=",">
            id = #{id}
        </foreach>
</select>

使用and/or拼接

select * from mybatis.blog where 1=1 and (id = 1 or id = 2);
<select id="selectForeach" resultType="com.kayden.pojo.Blog" parameterType="map">
    select * from mybatis.blog
    <where>
        <foreach collection="ids" item="id" open="and (" separator="or" close=")">
            id = #{id}
        </foreach>
    </where>
</select>
HashMap hashMap = new HashMap();
ArrayList<String> ids = new ArrayList<String>();
ids.add("1");
hashMap.put("ids",ids);
List<Blog> blogs = mapper.selectForeach(hashMap);

在这里插入图片描述

缓存(了解)

Mybatis官网-缓存

概述

  • 存在内存中的临时数据。
  • 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。

目的

减少和数据库的交互次数,减少系统开销,提高系统效率

什么样的数据适合从缓存中读取

经常查询并且不经常改变的数据。

MyBatis缓存

MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存

  • 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)
  • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
  • 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存

一级缓存

默认开启,只在一次SQLSession中有效,也就是从拿到连接到关闭连接,默认的清除策略是 LRU(最近最少使用)

测试

  1. 开启日志
  2. 在一次会话里测试同一个语句

同一个查询在测试中只查询了一次

在这里插入图片描述
缓存失效的情况

  1. 查询不同的语句
  2. 在中间有增删改,会刷新缓存(数据变了)
  3. 手动清理SQLSession.clearCache()

二级缓存

二级缓存也叫全局缓存,是基于namespace级别的缓存,一个名称空间,对应一个二级缓存;

工作机制

  • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
  • 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭后,一级缓存中的数据被保存到二级缓存中;
  • 新的会话查询信息,就可以从二级缓存中获取内容;
  • 不同的 mapper 查出的数据会放在自己对应的缓存(map)中;

步骤

  1. 在核心配置文件中开启全局缓存
<!-- 默认是开启的,这个显式的开启缓存 -->
<setting name="cacheEnabled" value="true"/>
  1. 在需要的映射文件中开启二级缓存
<cache/>

<!--可以自定义参数-->
<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

测试代码

同一个mapper

SqlSession sqlSession = MybatisUtils.getSqlSession();
SqlSession sqlSession02 = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
BlogMapper mapper02 = sqlSession02.getMapper(BlogMapper.class);

Blog blog = mapper.selectBlogById(1);
System.out.println(blog);
sqlSession.close();

Blog blog2 = mapper02.selectBlogById(1);
System.out.println(blog == blog2);
sqlSession02.close();

可能存在的问题

  • 实体类需要序列化——java.io.NotSerializableException

总结

  • 只要开启了二级缓存,在同一个Mapper下就有效
  • 所有的数据都会先放在一级缓存中;
  • 只有当会话提交,或者关闭的时候,才会提交到二级缓冲中!

缓存的原理

执行顺序

  1. 先看二级缓存中有没有
  2. 在看一级缓存中有没有
  3. 查询数据库

在这里插入图片描述

自定义缓存 EhCache

视频地址

纯Java的进程内缓存框架

依赖

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-ehcache -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.0.0</version>
</dependency>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值