Mybatis

Mybatis

01、Mybatis简介

什么是Mybatis

  • MyBatis 是一款优秀的持久层框架

  • MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集的过程

  • MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 实体类 【Plain Old Java Objects,普通的 Java对象】映射成数据库中的记录。

  • MyBatis 本是apache的一个开源项目ibatis, 2010年这个项目由apache 迁移到了google code,并且改名为MyBatis 。

Mybatis官方文档 : http://www.mybatis.org/mybatis-3/zh/index.html

GitHub : https://github.com/mybatis/mybatis-3

持久化

持久化是将程序数据在持久状态和瞬时状态间转换的机制。

  • 即把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)。持久化的主要应用是将内存中的对象存储在数据库中,或者存储在磁盘文件中、XML数据文件中等等。
  • JDBC就是一种持久化机制。文件IO也是一种持久化机制。

简单来说,就是把内存里的写到硬盘里

为什么需要持久化服务呢?那是由于内存本身的缺陷引起的

  • 内存断电后数据会丢失,但有一些对象是无论如何都不能丢失的,比如银行账号等,所以需要写到硬件设备中

  • 内存过于昂贵,与硬盘、光盘等外存相比,内存的价格要高2~3个数量级,而且维持成本也高。所以即使对象不需要永久保存,也会因为内存的容量限制不能一直呆在内存中,需要持久化来缓存到外存。

持久层

  • 完成持久化工作的代码块 , ----> dao层 (Data Access Object数据访问对象)

  • 持久化的实现过程则大多通过各种关系数据库来完成(当然也有非关系型数据库)。

  • 不过这里有一个字需要特别强调,也就是所谓的“层”。对于应用系统而言,数据持久功能大多是必不可少的组成部分。之所以要独立出一个“持久层”的概念,而不是“持久模块”,“持久单元”,也就意味着,我们的系统架构中,应该有一个相对独立的逻辑层面,专注于数据持久化逻辑的实现。

与系统其他部分相对而言,这个层面应该具有一个较为清晰和严格的逻辑边界。(说白了就是用来操作数据库存在的!)

为什么需要Mybatis

  • Mybatis就是帮助程序猿将数据存入数据库中 , 和从数据库中取数据 。

  • 传统的jdbc操作 , 有很多重复代码块 ,比如 : 数据取出时的封装 , 数据库的建立连接等等,通过框架可以减少重复代码,提高开发效率

  • MyBatis 是一个半自动化的ORM框架 (Object Relationship Mapping) -->对象关系映射

  • 所有的事情,不用Mybatis依旧可以做到,只是用了它,所有实现会更加简单,减少了自己写jdbc的复杂操作

  • 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。

  • 提供xml标签,支持编写动态sql。

02、Mybatis的基本使用

预先准备工作

  • 导入相关jar包
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.25</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.7</version>
</dependency>
<!--偷懒必备-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.22</version>
</dependency>
  • 资源过滤配置
<build>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
        </resource>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*.*</include>
            </includes>
        </resource>
    </resources>
</build>

配置mybatis-config

  • mybatis-config.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>

    <!--添加配置文件,将连接数据库的一些信息比如url,username,password,driver写在配置文件中-->
    <properties resource="db.properties"/>

    <typeAliases>
        <!--

        给实体类起个别名
        <typeAlias type="com.sinan.pojo.User" alias="User"/>

        也可以在实体类上使用注解    @Alias("别名")  来起别名
        -->

        <!--给包下的所有实体类起别名,默认别名就是类名(懒人用法)-->
        <package name="com.sinan.pojo"/>
    </typeAliases>

    <!--可以有多套环境,但是每次只能选择一个-->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <!--配置数据源信息-->
            <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="other">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.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="123.com"/>
            </dataSource>
        </environment>
    </environments>


    <mappers>
        <!--
        通过xml路径来关联接口和xml配置
        <mapper resource="com/sinan/mapper/UserMapper.xml"/>

        通过mapper路径来关联接口和xml配置,需要保证接口名和xml配置名相同
        <mapper class="com.sinan.mapper.UserMapper"/>
        -->

        <!--直接扫描包下的所有接口和xml配置文件,需要保证接口名和xml配置名相同(懒人用法)-->
        <package name="com.sinan.mapper"/>
    </mappers>
</configuration>
  • db.properties
# url有时候需要加上时区(mysql版本8以上)
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf-8
username=root
password=123.com

编写接口和相应xml

  • UserMapper
package com.sinan.mapper;

import com.sinan.pojo.User;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;

public interface UserMapper {
    //查询所有用户
    List<User> getAllUser();

    //查询指定用户
    //@Param("id"),在xml的sql语句中使用#{id},不能使用#{userId}
    User getUserById(@Param("id") int userId);

    //插入用户
    int addUser(User user);

    //插入用户map用法
    int addUser2(Map<String, Object> map);

    //修改用户信息
    int updateUser(User user);

    //删除用户
    int deleteUserById(int id);
}
  • UserMapper.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">

<!--这里需要绑定对应的接口-->
<mapper namespace="com.sinan.mapper.UserMapper">

    <!--表里的字段是name,但是实体类里的属性是username,需要映射一下,在sql语句的resultType写上userMap
    就可以映射到这里了
    -->
    <resultMap id="userMap" type="User">
        <!-- id为主键 -->
        <id column="id" property="id"/>
        <!-- column是数据库表的列名 , property是对应实体类的属性名 -->
        <result column="name" property="username"/>
        <result column="pwd" property="password"/>
    </resultMap>

    <!--
    id  需要和接口里的方法名相同
    resultType表示返回值类型,对象或对象数组(也就是List<对象>)都写返回对象
    -->
    <select id="getAllUser" resultType="User">
        select * from user
    </select>

    <!--parameterType表示参数类型,int会自动装箱为Integer
    当只有一个参数的时候,可以不写parameterType
    -->
    <select id="getUserById" resultType="User" parameterType="int">
        select * from user where id = #{id}
    </select>

    <insert id="addUser" parameterType="User" >
        insert into user (id, name, sex, age) values (#{id}, #{name}, #{sex}, #{age})
    </insert>

    <!--使用map作为参数,直接根据map中的键(key)取值
    对于一些需要修改较少信息的sql,使用map就可以了,不需要重新new一个实体类,然后再来update
    返回值为基本类型,可以不写
    -->
    <insert id="addUser2" parameterType="map">
        insert into user (id, name, sex, age) values (#{id}, #{name}, #{sex}, #{age})
    </insert>

    <update id="updateUser" parameterType="User">
        update user set name = #{name}, sex = #{sex}, age = #{age} where id = #{id}
    </update>

    <delete id="deleteUserById" parameterType="int" >
        delete from user where id = #{id}
    </delete>
</mapper>

测试

  • 测试类
import com.sinan.mapper.UserMapper;
import com.sinan.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;

public class MyTest {
    public static void main(String[] args) throws IOException {

        //下面这一段可以打包为工具类,是固定代码
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        //参数为true表示自动提交事务,否则需要加上        sqlSession.commit()来进行事务提交(不包括查数据)

        
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

//        //查询所有用户
//        List<User> users = userMapper.getAllUser();
//        for (User user : users) {
//            System.out.println(user);
//        }
//
//        //根据id查询指定用户
//        User user = userMapper.getUserById(1);
//        System.out.println(user);
//
//        //插入新用户
//        int i = userMapper.addUser(new User(5, "菠萝吹雪", 10, "水果"));//返回结果非0表示插入成功
//
//        //map插入新用户
//        HashMap<String, Object> map = new HashMap<>();
//        map.put("id",6);
//        map.put("name","成刘翔");
//        map.put("age",6);
//        map.put("sex","橙子");
//        userMapper.addUser2(map);
//
//        //更新用户信息
//        userMapper.updateUser(new User(5, "菠萝吹牛", 10, "水果"));
//
//        //根据id删除用户
//        int i = userMapper.deleteUserById(8);//返回结果非0表示删除成功


        //用完不要忘了关闭
        sqlSession.close();

    }
}

总结:

  • 所有的增删改操作都需要提交事务!

  • 接口所有的普通参数,尽量都写上@Param参数,尤其是多个参数时,必须写上!

  • 有时候根据业务的需求,可以考虑使用map传递参数!

  • 为了规范操作,在SQL的配置文件中,我们尽量将Parameter参数和resultType都写上!

配置文件(mybatis-config)

mybatis-config中可以配置的内容如下:

configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
        environment(环境变量)
            transactionManager(事务管理器)
            dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)
<!-- 注意元素节点的顺序!顺序不对会报错 -->

作用域(Scope)和生命周期

下面是Mybatis的执行流程图:其实就是在测试类的那段固定代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y7uvOPGt-1647759431719)(image-20220319144321740.png)]

  • SqlSessionFactoryBuilder 的作用在于创建 SqlSessionFactory,创建成功后,SqlSessionFactoryBuilder 就失去了作用,所以它只能存在于创建 SqlSessionFactory 的方法中,而不要让其长期存在。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。

  • SqlSessionFactory 可以被认为是一个数据库连接池,它的作用是创建 SqlSession 接口对象。因为 MyBatis 的本质就是 Java 对数据库的操作,所以 SqlSessionFactory 的生命周期存在于整个 MyBatis 的应用之中,所以一旦创建了 SqlSessionFactory,就要长期保存它,直至不再使用 MyBatis 应用,所以可以认为 SqlSessionFactory 的生命周期就等同于 MyBatis 的应用周期。

  • 由于 SqlSessionFactory 是一个对数据库的连接池,所以它占据着数据库的连接资源。如果创建多个 SqlSessionFactory,那么就存在多个数据库连接池,这样不利于对数据库资源的控制,也会导致数据库连接资源被消耗光,出现系统宕机等情况,所以尽量避免发生这样的情况。

  • **因此在一般的应用中我们往往希望 SqlSessionFactory 作为一个单例,让它在应用中被共享。**所以说 SqlSessionFactory 的最佳作用域是应用作用域。

  • 如果说 SqlSessionFactory 相当于数据库连接池,那么 SqlSession 就相当于一个数据库连接(Connection 对象),你可以在一个事务里面执行多条 SQL,然后通过它的 commit、rollback 等方法,提交或者回滚事务。所以它应该存活在一个业务请求中,处理完整个请求后,应该关闭这条连接,让它归还给 SqlSessionFactory,否则数据库资源就很快被耗费精光,系统就会瘫痪,所以用 try…catch…finally… 语句来保证其正确关闭。

03、属性名和字段名不一致

<select id="getUserById" resultType="User" parameterType="int">
    select * from user where id = #{id}
</select>

# User的属性为id、username、password
#表字段为id、name、pwd

自动映射

<select id="getUserById" resultType="User" parameterType="int">
    select id, pwd, name from user where id = #{id}
</select>

这种方式很拉,不建议使用

ResultMap

在上面的编写接口与相应xml部分中已经介绍了这类的解决方案

<!--表里的字段是name,但是实体类里的属性是username,需要映射一下,在sql语句的resultType写上userMap
就可以映射到这里了
-->
<resultMap id="userMap" type="User">
    <!-- id为主键 -->
    <id column="id" property="id"/>
    <!-- column是数据库表的列名 , property是对应实体类的属性名 -->
    <result column="name" property="username"/>
    <result column="pwd" property="password"/>
</resultMap>


<!--这里的resultType引用上面的userMap-->
<select id="getUserById" resultType="userMap" parameterType="int">
    select * from user where id = #{id}
</select>

as起别名

<select id="getUserById" resultType="User" parameterType="int">
    select id, pwd as password, name as username from user where id = #{id}
</select>

直接在sql里给他起个别名

开启驼峰命名转换

<settings>
    <!--开启驼峰自动转换-->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

# User的属性为id、userName、userEmail
#表字段为id、user_name、user_email

开启驼峰自动转换,即可自动将表字段中的名称转化为驼峰命名

04、日志工厂

我们在测试SQL的时候,如果能够在控制台输出 SQL 的话,就能够有更快的排错效率

  • 如果一个数据库相关的操作出现了问题,我们可以根据输出的SQL语句快速排查问题。

  • 对于以往的开发过程,我们会经常使用到debug模式来调节,跟踪我们的代码执行过程。

  • 但是现在使用Mybatis是基于接口,看不到配置文件的源代码执行过程。因此,我们必须选择日志工具来作为我们开发,调节程序的工具。

Mybatis内置的日志工厂提供日志功能,具体的日志实现有以下几种工具:

  • SLF4J
  • Apache Commons Logging
  • Log4j 2
  • Log4j
  • JDK logging

具体选择哪个日志实现工具由MyBatis的内置日志工厂确定。它会使用最先找到的(按上文列举的顺序查找)。如果一个都未找到,日志功能就会被禁用。

标准日志实现

最简单的,只需要设置一下即可

<settings>
    <!--标准的日志工厂的实现-->
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

这样在执行sql的时候就会显示sql执行的细节。

Log4j

简介:

  • Log4j是Apache的一个开源项目

  • 通过使用Log4j,我们可以控制日志信息输送的目的地:控制台,文本,GUI组件…

  • 我们也可以控制每一条日志的输出格式;

  • 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

使用:

  • 导入log4j的包
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
  • 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/sinan.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
  • setting设置
<settings>

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

05、分页

这一块很简单,就是一个map~~

为什么需要分页:

在学习mybatis等持久层框架的时候,会经常对数据进行增删改查操作,使用最多的是对数据库进行查询操作,如果查询大量数据的时候,我们往往使用分页进行查询,也就是每次处理小部分数据,这样对数据库压力就在可控范围内。

使用limit实现分页

#语法
SELECT * FROM table LIMIT stratIndex,pageSize
 
SELECT * FROM table LIMIT 5,10; // 从下标5开始,每一页10行数据 
 
#如果只给定一个参数,它表示返回最大的记录行数目:   
SELECT * FROM table LIMIT 5; //检索前 5 个记录行  

  • 修改Mapper文件
<select id="selectUser" parameterType="map" resultType="User">
    select * from user limit #{startIndex},#{pageSize}
</select>

没了。

06、使用注解开发

面向接口编程

  • 大家之前都学过面向对象编程,也学习过接口,但在真正的开发中,很多时候我们会选择面向接口编程

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

  • 在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;

  • 而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。

关于接口的理解

  • 接口从更深层次的理解,应是定义(规范,约束)与实现(名实分离的原则)的分离。

  • 接口的本身反映了系统设计人员对系统的抽象理解。

  • 接口应有两类:

    • 第一类是对一个个体的抽象,它可对应为一个抽象体(abstract class);
    • 第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface);
  • 一个体有可能有多个抽象面。抽象体与抽象面是有区别的。

三个面向区别

  • 面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性及方法 .
  • 面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现 .
  • 接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题.更多的体现就是对系统整体的架构

注解开发

主要注解是:

  • @select ()
  • @update ()
  • @Insert ()
  • @delete ()

有了注解,就不需要mapper对应的xml了,但是,但是啊,使用注解只能写一些简单的sql,对于稍微复杂一点的sql,还是需要xml。

具体实现:

public interface UserMapper {
    //使用注解开发的增删改查,注解开发只能做最简单的SQL,对于项目还是需要使用xml开发

    @Select("select * from user")
    List<User> getUserList();

    @Select("select * from user where id = #{uid}")
    User getUserById(@Param("uid") int id);//这里的参数注解相当于起的参数别名,在select注解中需要使用别名

    @Insert("insert into user (id,name,sex,age) values (#{id},#{name},#{sex},#{age})")
    int addUser(User user);

    @Update("update user set name = #{name},sex = #{sex},age = #{age} where id = #{id}")
    int updateUser(User user);

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

#与$的区别:

  • #{} 的作用主要是替换预编译语句(PrepareStatement)中的占位符? (推荐使用)

  • ${} 的作用是直接进行字符串替换(小心sql注入啊)

关于@Param:

  • @Param注解用于给方法参数起一个名字。

  • 在方法只接受一个参数的情况下,可以不使用@Param。

  • 在方法接受多个参数的情况下,建议一定要使用@Param注解给参数命名。

  • 如果参数是 JavaBean , 则不能使用@Param。

07、一对多和多对一处理

多对一的处理

多对一的理解:

  • 多个学生对应一个老师
  • 如果对于学生这边,就是一个多对一的现象,即从学生这边关联一个老师!

学生和老师的实体类:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private int id;
    private String name;
    private Teacher teacher;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
    private int id;
    private String name;
}

数据库表格式:

create table student(
    id int primary key auto_increment,
    name varchar(20),
    tid int
);
create table teacher(
    id int primary key auto_increment,
    name varchar(20)
);

studentMapper接口:

package com.sinan.mapper;

import com.sinan.pojo.Student;
import java.util.List;

public interface StudentMapper {

    //查询所有学生以及其对应老师信息(子查询)
    public List<Student> getStudent1();

    //联表查询
    public List<Student> getStudent2();
}
子查询方式
<!--结果映射至StudentTeacher1-->
<select id="getStudent1" resultMap="StudentTeacher1">
    select * from student
</select>
<resultMap id="StudentTeacher1" type="Student">
    <result column="id" property="id"/>
    <result column="name" property="name"/>
    <!--将查询结果tid关联属性teacher,进行再查询-->
    <association property="teacher" column="tid" select="getTeacher" />
</resultMap>
<select id="getTeacher" resultType="Teacher">
    select * from teacher where id = #{tid};
</select>
联表查询
<!--进行联表查询-->
<select id="getStudent2" resultMap="StudentTeacher2">
    select s.id as sid, s.name as sname, t.id as tid, t.name as tname from student as s, teacher as t where s.tid = t.id;
</select>
<resultMap id="StudentTeacher2" type="Student">
    <result column="sid" property="id"/>
    <result column="sname" property="name"/>
    <!--关联teacher表,直接将联表查询的列赋予teacher的属性-->
    <association property="teacher" javaType="Teacher">
        <result column="tid" property="id"/>
        <result column="tname" property="name"/>
    </association>
</resultMap>

一对多的处理

一对多的理解:

  • 一个老师拥有多个学生
  • 如果对于老师这边,就是一个一对多的现象,即从一个老师下面拥有一群学生(集合)!

Teacher类更改:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher2 {
    private int id;
    private String name;
    private ArrayList<Student> students;
}

TeacherMapper接口更改:

public interface TeacherMapper2 {
    //嵌套查询
    Teacher2 getTeacherById1(@Param("tid") int id);

    //子查询
    Teacher2 getTeacherById2(@Param("tid") int id);
}
结果嵌套处理
<!--结果嵌套处理-->
<select id="getTeacherById1" resultMap="TeacherStudent1" parameterType="int">
    select t.id tid,t.name tname,s.id sid,s.name sname
    from teacher t,student s
    where t.id = #{tid} and t.id = s.tid;
</select>
<resultMap id="TeacherStudent1" type="Teacher2">
    <result property="id" column="tid"/>
    <result property="name" column="tname"/>
    <!--通过collection对多行数据进行合并-->
    <collection property="students" javaType="ArrayList" ofType="Student2">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
        <result property="tid" column="tid"/>
    </collection>
</resultMap>
查询嵌套处理
<!--查询嵌套处理-->
<select id="getTeacherById2" parameterType="int" resultMap="TeacherStudent2">
    select * from teacher where id = #{tid}
</select>
<resultMap id="TeacherStudent2" type="Teacher2">
    <result property="id" column="id"/>
    <collection property="students" column="id" ofType="Student" select="getStudent"/>
</resultMap>
<select id="getStudent" resultType="Student2">
    select * from student where tid = #{id}
</select>

总结:

  • 关联-association

  • 集合-collection

  • association是用于一对一和多对一,而collection是用于一对多的关系

  • JavaType和ofType都是用来指定对象类型的

    • JavaType是用来指定pojo中属性的类型,属性是老师,JavaType就是Teacher,属性是数组,JavaType就是ArrayList
    • ofType指定的是映射到list集合属性中pojo的类型。

08、动态SQL

创建Blog实体类:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Blog {
    private String id;
    private String title;
    private String author;
    private Date createTime;
    private int views;
}

BlogMapper接口:

package com.sinan.mapper;

import com.sinan.pojo.Blog;
import java.util.List;
import java.util.Map;

public interface BlogMapper {

    //查询博客,要求,参数有title就查title,有author就查author,都有就都查
    List<Blog> queryBlogByIf(Map map);

    //类似于switch
    List<Blog> queryBlogByChoose(Map map);

    //使用set标签更新数据
    int updateBlog(Map map);

    //使用foreach查询
    List<Blog> queryBlogByForeach(Map map);
}

if语句和where标签

<!--
这个where标签会知道如果它包含的标签中有返回值的话,它就插入一个‘where’。
此外,如果标签返回的内容是以AND 或OR 开头的,则它会剔除掉。
-->
<select id="queryBlogByIf" resultType="Blog" parameterType="map">
    select * from blog
    <where>
        <if test="author != null">
            author = #{author}
        </if>
        <if test="create != null">
            and create = #{create}
        </if>
    </where>
</select>

set标签和sql代码块

<!--SQL片段,最好只对于一张表使用sql片段,而且,这样sql的维护性会变差-->
<sql id="updataBlog-sqlPart">
    <if test="title != null">
        title = #{title}
    </if>
    <if test="author != null">
        author = #{author}
    </if>
    <if test="views != null">
        views = #{views},
    </if>
</sql>
<!--这里的set和上面的where差不错-->
<update id="updateBlog" parameterType="map">
    update blog
    <set>
        <include refid="updataBlog-sqlPart"></include>
    </set>
    where id = #{id}
</update>

choose、when和otherwise

<!--有时候,我们不想用到所有的查询条件,只想选择其中的一个,查询条件有一个满足即可,
使用 choose 标签可以解决此类问题,类似于 Java 的 switch 语句-->
<select id="queryBlogByChoose" parameterType="map" resultType="Blog">
    select * from blog
    <where>
        <choose>
            <when test="title != null">
                title = #{title}
            </when>
            <when test="author != null">
                author = #{author}
            </when>
            <otherwise>
                views = #{views}
            </otherwise>
        </choose>
    </where>
</select>

foreach

<!--map<id, list<id>>,这里需要id对应的值是一个数组,然后通过foreach来进行访问
    效果为:select * from blog where (id = #{id1} or id = #{id2} or id = #{id3})
-->
<select id="queryBlogByForeach" parameterType="map" resultType="Blog">
    select * from blog
    <where>
        <foreach collection="ids" item="id" open="(" separator="or" close=")">
            id = #{id}
        </foreach>
    </where>
</select>

总结:

动态SQL就是一个拼接的活

09、缓存

缓存简介

  • 什么是缓存(Cache)

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

    • 减少和数据库的交互次数,减少系统开销,提高系统效率。
  • 什么样的数据能使用缓存?

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

Mybatis缓存

  • MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。

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

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

一级缓存

一级缓存也叫本地缓存:

  • 与数据库同一次会话期间查询到的数据会放在本地缓存中。
  • 以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库;
<select id="getUserById" resultType="User" parameterType="int">
	select * from user where id = #{id}
</select>

对于上述sql,两次查询的结果是相同的(hashcode是一样的)

一级缓存失效的四种方法

一级缓存是SqlSession级别的缓存,是一直开启的,我们关闭不了它;

  1. sqlSession不同,不同的sqlSession有自己的一级缓存区
  2. sqlSession相同,但通过UserMapper.class拿到了两个mapper实现类
  3. sqlSession相同,两次查询之间执行了增删改操作,执行增删改操作后对数据产生了影响,缓存消失
  4. sqlSession相同,手动清除一级缓存
sqlSession.clearCache();//手动清除一级缓存

二级缓存

  • 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存

  • 基于namespace级别的缓存,一个名称空间,对应一个二级缓存;

  • 工作机制

    • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;

    • 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;

    • 新的会话查询信息,就可以从二级缓存中获取内容;

    • 不同的mapper查出的数据会放在自己对应的缓存(map)中;

使用方法:

<!--开启二级缓存-->
<setting name="cacheEnabled" value="true"/>

在每个mapper.xml中配置:

<!--在config配置文件中,默认开启全局缓存-->
<!--如果不开启只读,那么实体类就需要序列化-->

<!--
 这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,
而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。   
-->

<cache
        eviction="FIFO"
        flushInterval="60000"
        size="512"
        readOnly="true"/>

总结:

  • 查出的数据都会被默认先放在一级缓存中
  • 只有会话提交或者关闭以后,一级缓存中的数据才会转到二级缓存中
  • 只要开启了二级缓存,我们在同一个Mapper中的查询,可以在二级缓存中拿到数据
  • 进行增删改操作同样会让二级缓存失效
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值