Mybatis

发布时间 2023-04-01 01:12:09作者: pengrq

Mybais

1.简介

  • MyBatis是一款基于Java语言的持久层框架,它可以将数据库操作封装成简单的配置文件和SQL语句,并且提供了多种映射方式,使得开发者可以使用面向对象的方式来操作数据库。MyBatis不同于其他ORM框架(如Hibernate),它更加灵活,可以自由地编写SQL语句,不必在对象和表之间进行强制映射。此外,MyBatis还支持动态SQL、缓存机制、插件扩展等功能,为Java开发者提供了方便快捷的数据访问解决方案。

2.为什么要使用Mybatis

  1. 灵活性高:MyBatis不会对开发者的编程风格和对象模型做出强制限制,可以自由地编写SQL语句和配置文件,符合多种数据库的使用需求。
  2. 映射方式多样化:MyBatis支持多种映射方式(如基于注解、XML配置文件、API等),可以根据具体情况选择最适合的方式。
  3. 高效性能:MyBatis通过预处理和缓存机制等功能来提高数据库操作的性能,同时还支持延迟加载和批量操作等优化方式。
  4. 易于调试:MyBatis可以将运行时生成的SQL语句输出到日志中,方便开发者调试和优化代码。
  5. 易于整合:MyBatis可以与其他框架(如Spring、Spring Boot等)无缝整合,可以更方便地集成到现有系统中。

总之,MyBatis是一个灵活、高效、易于调试和整合的持久层框架,可以帮助开发者快速构建可靠的数据访问层。

3.Mybatis和jdbc的优缺点

3.1、Mybatis的优缺点

3.1.1、MyBatis的优点:

  • SQL语句与Java代码分离,易于维护和升级。
  • 大量使用XML配置和注解,简化了开发人员的编码难度。
  • 支持多种数据库操作,比如CRUD、存储过程、批量操作等。
  • 提供了一级缓存和二级缓存来提高性能。
  • 易于整合到Spring等框架中。

3.1.2、MyBatis的缺点:

  • 配置文件相对复杂,需要学习一定的MyBatis知识。
  • 对于简单的数据库操作,可能会显得繁琐,不如JDBC方便。

3.2、JDBC的优缺点

3.2.1、JDBC的优点:

  • 是Java标准API,无需额外导入jar包,并且跨平台能力强。
  • 直接操作底层数据库,可以更好地掌握SQL相关知识。
  • 对于简单的数据库操作,使用JDBC会更加方便快捷。

3.2.2、JDBC的缺点:

  • 代码冗长,需要大量重复代码进行连接、关闭、异常处理等操作。
  • 需要手写SQL语句,容易出现错误。
  • 不支持对象关系映射(ORM)。

总的来说,MyBatis相对于JDBC而言,更加灵活、易用、可维护性和扩展性更强,同时能有效提高数据库操作的性能。但是对于简单的数据库操作而言,使用JDBC可能会更加方便快捷。在实际应用中,开发者需要根据具体情况做出选择,或者二者结合起来使用。

4.怎么使用Mybatis

4.1、导入相关依赖

  • maven仓库
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.13</version>
</dependency>

4.2、快速入门

  • 创建mybatis的配置文件

    <!DOCTYPE configuration
      PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
      "https://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
      <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>
      </environments>
      <mappers>
        <mapper resource="org/mybatis/example/BlogMapper.xml"/>
      </mappers>
    </configuration>
    
  • 创建mapper配置文件

    <?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">
    <!--namespace=绑定一个对应的Dao/Mapper接口-->
    <mapper namespace="">
    
    </mapper>
    
  • 工具类

    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;
    
    //SqlSessionFactory-->sqlSession
    public class MybatisUtils {
        private static SqlSessionFactory sqlSessionFactory;
    
        static {
           try {
                //使用mybatis获取SqlSessionFactory对象
                String resource = "mybatis-config.xml";
               InputStream resourceAsStream = Resources.getResourceAsStream(resource);
               sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        //既然有了SqlSessionFactory顾名思义我们就可以从中获得SqlSession的实例了。
        //SqlSession完全包含了面向数据库执行SQL命令所需的所有方法
    
        public static SqlSession getSqlSession() {
            return sqlSessionFactory.openSession();
        }
    }
    
    

5.作用域和生命周期

5.1、MyBatis中有三个主要的作用域:SqlSessionFactory、SqlSession和Mapper。

  1. SqlSessionFactory作用域:表示整个MyBatis应用程序的作用域,由SqlSessionFactoryBuilder创建。它是线程安全的,一个应用通常只需要创建一个SqlSessionFactory实例即可。
  2. SqlSession作用域:表示一次数据库会话的作用域。它提供了对数据库的操作方法,包括插入、更新、删除和查询等。SqlSession使用完成后必须关闭,否则可能导致连接池泄漏。
  3. Mapper作用域:表示每个Mapper接口的作用域。它为进行数据库操作提供了不同的操作方法,并将配置文件中的SQL语句与Java方法进行映射。Mapper可以通过注解或XML文件来配置SQL语句。

5.2、MyBatis的生命周期指的是SqlSessionFactory、SqlSession和Mapper在整个应用程序运行过程中的状态变化:

  1. SqlSessionFactory生命周期:当应用程序启动时,SqlSessionFactory被创建并初始化,当应用程序关闭时,SqlSessionFactory被销毁。
  2. SqlSession生命周期:SqlSession在每次数据库访问时创建,并在操作结束后关闭。此外,SqlSession还可以被托管到Spring容器中,交由Spring容器管理其生命周期。
  3. Mapper生命周期:Mapper在应用程序中根据需要进行加载和卸载,当需要进行数据库操作时,Mapper被创建并执行相应的方法,然后再被卸载。

需要注意的是,SqlSession和Mapper在使用完之后必须进行关闭或销毁操作,以释放资源。否则,可能会导致连接泄漏、缓存溢出等问题。

5.3、Mybatis执行过程

img

5.4、SqlSession流程图

img

6.配置解析

6.1、核心配置文件

  • Mybatis-config.xml

  • MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。

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

6.2、环境配置(environment)

6.2.1、简介

Mybatis中 environments配置用于配置数据源和事务管理器。它可以支持不同的环境(如开发、测试、生产等),每个环境有独立的数据源和事务管理器

不过要记住:尽管可以配置多个环境,但每个SqlSessionFactory实例只能选择一种环境

6.2.2、environments配置包含以下几个元素:

  1. environment元素:表示一个环境,其id属性指定环境名,transactionManager属性指定事务管理器,dataSource属性指定数据源。
  2. transactionManager元素:表示事务管理器,其type属性指定事务管理器类型,通常使用JDBC或者Spring提供的事务管理器。
  3. dataSource元素:表示数据源,其type属性指定数据源类型,通常使用JDBC或者连接池数据源。

6.2.3、environments配置示例:

<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/test"/>
      <property name="username" value="root"/>
      <property name="password" value="test123"/>
    </dataSource>
  </environment>
</environments>

在上述示例中,定义了一个名为“development”的环境,使用JDBC事务管理器和连接池数据源,其中连接池数据源的相关参数通过property元素进行配置。默认的环境是“development”,可以通过default属性进行设置。

需要注意的是,environments配置只需要在MyBatis配置文件中声明一次即可。如果需要配置多个不同的环境,可以通过mybatis-config.xml中的profiles元素来实现。

6.2.3.1事务管理器(transactionManager)

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

  • JDBC – 这个配置直接使用了 JDBC 的提交和回滚功能,它依赖从数据源获得的连接来管理事务作用域。默认情况下,为了与某些驱动程序兼容,它在关闭连接时启用自动提交。然而,对于某些驱动程序来说,启用自动提交不仅是不必要的,而且是一个代价高昂的操作。因此,从 3.5.10 版本开始,你可以通过将 "skipSetAutoCommitOnClose" 属性设置为 "true" 来跳过这个步骤。例如:
<transactionManager type="JDBC">
  <property name="skipSetAutoCommitOnClose" value="true"/>
</transactionManager>
  • MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。例如:

    <transactionManager type="MANAGED">
      <property name="closeConnection" value="false"/>
    </transactionManager>
    

    提示: 如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置。

6.2.3.2数据源(dataSource)

dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。

  • 大多数 MyBatis 应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。

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

UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。 性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。

POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。

JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。

6.3属性(properties)

我们可以通过properties属性来实现引用配置文件

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

<properties resource="org/mybatis/example/config.properties">
  <property name="username" value="dev_user"/>
  <property name="password" value="F2Fa3!33TYyg"/>
</properties>

如果一个属性在不只一个地方进行了配置,那么,MyBatis 将按照下面的顺序来加载:

  • 首先读取在 properties 元素体内指定的属性。
  • 然后根据 properties 元素中的 resource 属性读取类路径下属性文件,或根据 url 属性指定的路径读取属性文件,并覆盖之前读取过的同名属性。
  • 最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。

因此,通过方法参数传递的属性具有最高优先级,resource/url 属性中指定的配置文件次之,最低优先级的则是 properties 元素中指定的属性。

6.4类型别名(typeAliases)

类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。例如:

<typeAliases>
  <package name="domain.blog"/>
</typeAliases>
  • 每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名

  • TypeAliases 是一种将 Java 类型映射到 MyBatis 中的别名的机制。它可以简化在映射文件中使用 Java 类型的繁琐过程,让映射文件更加简洁易读。

  • TypeAliases 可以在 MyBatis 的配置文件中定义,也可以在实体类上中定义。

  • 在配置文件中定义的 TypeAliases 是全局的,而在实体类中定义的 @Alias 只在该映射文件中生效。

  • 在定义 TypeAliases 时,可以指定别名和对应的 Java 类型,也可以只指定别名,此时 MyBatis 会自动根据别名推断对应的 Java 类型。

  • TypeAliases 可以让更方便地使用 Java 类型,减少代码量,提高代码可读性。

6.5映射器(mappers)

MapperRegistry:注册绑定我们的Mapper文件

方式一:

<!-- 每一个mapper.xml都需要在Mybatis核心配置文件中注册! -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>

方式二:使用class文件绑定注册

<!-- 每一个mapper.xml都需要在Mybatis核心配置文件中注册! -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>

注意点:

  • 接口和他的Mapper配置文件必须同名
  • 接口和他的Mapper配置文件必须在同一个包下

方式三:通过扫包注册

<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

Mapper文件

<?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.kuang.mapper.UserMapper">
   
</mapper>
  • namespace中文意思:命名空间,作用如下:

    • namespace的命名必须跟某个接口同名
    • 接口中的方法与映射文件中sql语句id应该一一对应
    1. namespace和子元素的id联合保证唯一 , 区别不同的mapper
    2. 绑定DAO接口
    3. namespace命名规则 : 包名+类名

MyBatis 的真正强大在于它的映射语句,这是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 为聚焦于 SQL 而构建,以尽可能地为你减少麻烦。

7.映射器配置和分页

  • 为了解决属性名和字段名不一致

    解决方案

方案一:为列名指定别名 , 别名和java实体类的属性名一致 .

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

方案二:使用结果集映射->ResultMap 【推荐】

<resultMap id="UserMap" type="User">
   <!-- id为主键 -->
   <id column="id" property="id"/>
   <!-- column是数据库表的列名 , property是对应实体类的属性名 -->
   <result column="name" property="name"/>
   <result column="pwd" property="password"/>
</resultMap>

<select id="selectUserById" resultMap="UserMap">
  select id , name , pwd from user where id = #{id}
</select>
  • ResultMap 的设计思想是,对于简单的语句根本不需要配置显式的结果映射,而对于复杂一点的语句只需要描述它们的关系就行了。

分页

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

7.2分页

使用Limit实现分页

#语法
SELECT * FROM table LIMIT stratIndex,pageSize

SELECT * FROM table LIMIT 5,10; // 检索记录行 6-15  

#为了检索从某一个偏移量到记录集的结束所有的记录行,可以指定第二个参数为 -1:   
SELECT * FROM table LIMIT 95,-1; // 检索记录行 96-last.  

#如果只给定一个参数,它表示返回最大的记录行数目:   
SELECT * FROM table LIMIT 5; //检索前 5 个记录行  

#换句话说,LIMIT n 等价于 LIMIT 0,n。 

RowBounds分页

RowBounds在Java代码层面实现分页,当然此种方式作为了解即可

1、mapper接口

//选择全部用户RowBounds实现分页
List<User> getUserByRowBounds();

2、mapper文件

<select id="getUserByRowBounds" resultType="user">
select * from user
</select>

3、测试类

在这里,我们需要使用RowBounds类

@Test
public void testUserByRowBounds() {
   SqlSession session = MybatisUtils.getSession();

   int currentPage = 2;  //第几页
   int pageSize = 2;  //每页显示几个
   RowBounds rowBounds = new RowBounds((currentPage-1)*pageSize,pageSize);

   //通过session.**方法进行传递rowBounds,[此种方式现在已经不推荐使用了]
   List<User> users = session.selectList("com.kuang.mapper.UserMapper.getUserByRowBounds", null, rowBounds);

   for (User user: users){
       System.out.println(user);
  }
   session.close();
}

8.注解开发

8.1面向接口编程

  • 大家之前都学过面向对象编程,也学习过接口,但在真正的开发中,很多时候我们会选择面向接口编程
  • 根本原因 : 解耦 , 可拓展 , 提高复用 , 分层开发中 , 上层不用管具体的实现 , 大家都遵守共同的标准 , 使得开发变得容易 , 规范性更好
  • 在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;
  • 而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。

关于接口的理解

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

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

  • 接口应有两类:

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

三个面向区别

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

8.2使用注解开发

  • mybatis最初配置信息是基于 XML ,映射语句(SQL)也是定义在 XML 中的。而到MyBatis 3提供了新的基于注解的配置。不幸的是,Java 注解的的表达力和灵活性十分有限。最强大的 MyBatis 映射并不能用注解来构建

  • sql 类型主要分成 :

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

注意:利用注解开发就不需要mapper.xml映射文件了 .

1、我们在我们的接口中添加注解

//查询全部用户
@Select("select id,name,pwd password from user")
public List<User> getAllUser();

2、在mybatis的核心配置文件中注入

<!--使用class绑定接口-->
<mappers>
   <mapper class="com.kuang.mapper.UserMapper"/>
</mappers>

本质上利用了jvm的动态代理机制

image-20230331231056611

Mybatis详细的执行流程

image-20230331231240671

8.4@Param注解

@Param注解用于给方法参数起一个名字。以下是总结的使用原则:

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

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

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

  • 不使用@Param注解时,参数只能有一个,并且是Javabean。

    #与$的区别

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

    INSERT INTO user (name) VALUES (#{name});
    INSERT INTO user (name) VALUES (?);
    
  • ${} 的作用是直接进行字符串替换

    INSERT INTO user (name) VALUES ('${name}');
    INSERT INTO user (name) VALUES ('kuangshen');
    

使用注解和配置文件协同开发,才是MyBatis的最佳实践!

9.一对多和多对一处理

Mybatis支持一对多和多对一的关联查询,其中一对多指的是一个实体类中包含多个子实体类的情况,多对一则相反。

一对多的处理可以通过在主实体类中添加一个List属性,然后在映射文件或注解中使用嵌套查询语句来实现。例如,在XML中,可以使用标签进行配置,指定子实体类对应的结果集。在Java代码中,Mybatis会自动将结果集转换为List对象,并设置到主实体类的对应属性中。

多对一的处理则需要在子实体类中添加一个主实体类类型的属性,并在映射文件或注解中使用外键关联语句进行处理。例如,在XML中,可以使用标签进行配置,指定主实体类对应的结果集。在Java代码中,Mybatis会自动将结果集转换为主实体类对象,并设置到子实体类的对应属性中。

需要注意的是,一对多和多对一的处理都涉及到数据库表之间的关联查询,因此在设计数据表时需要考虑好关联关系,并使用外键等机制进行维护。同时,在使用Mybatis进行关联查询时,也需要注意查询性能和结果集的正确性,避免出现慢查询或数据冗余等问题。

9.1多对一例如:

多对一的理解:

  • 多个学生对应一个老师
  • 如果对于学生这边,就是一个多对一的现象,即从学生这边关联一个老师!
@Data //GET,SET,ToString,有参,无参构造
public class Teacher {
   private int id;
   private String name;
}
@Data
public class Student {
   private int id;
   private String name;
   //多个学生可以是同一个老师,即多对一
   private Teacher teacher;
}

编写实体类对应的Mapper接口 【两个】

  • 无论有没有需求,都应该写上,以备后来之需!
public interface StudentMapper {
}
public interface TeacherMapper {
}

编写Mapper接口对应的 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">
<mapper namespace="com.kuang.mapper.StudentMapper">

</mapper>

<?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.kuang.mapper.TeacherMapper">

</mapper>

按查询嵌套处理

1、给StudentMapper接口增加方法

//获取所有学生及对应老师的信息
public List<Student> getStudents();

2、编写对应的Mapper文件

<?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.kuang.mapper.StudentMapper">

   <!--
   需求:获取所有学生及对应老师的信息
   思路:
       1. 获取所有学生的信息
       2. 根据获取的学生信息的老师ID->获取该老师的信息
       3. 思考问题,这样学生的结果集中应该包含老师,该如何处理呢,数据库中我们一般使用关联查询?
           1. 做一个结果集映射:StudentTeacher
           2. StudentTeacher结果集的类型为 Student
           3. 学生中老师的属性为teacher,对应数据库中为tid。
              多个 [1,...)学生关联一个老师=> 一对一,一对多
           4. 查看官网找到:association – 一个复杂类型的关联;使用它来处理关联查询
   -->
   <select id="getStudents" resultMap="StudentTeacher">
    select * from student
   </select>
   <resultMap id="StudentTeacher" type="Student">
       <!--association关联属性 property属性名 javaType属性类型 column在多的一方的表中的列名-->
       <association property="teacher"  column="tid" javaType="Teacher" select="getTeacher"/>
   </resultMap>
   <!--
   这里传递过来的id,只有一个属性的时候,下面可以写任何值
   association中column多参数配置:
       column="{key=value,key=value}"
       其实就是键值对的形式,key是传给下个sql的取值名称,value是片段一中sql查询的字段名。
   -->
   <select id="getTeacher" resultType="teacher">
      select * from teacher where id = #{id}
   </select>

</mapper>

3、编写完毕去Mybatis配置文件中,注册Mapper!

4、注意点说明:

<resultMap id="StudentTeacher" type="Student">
   <!--association关联属性 property属性名 javaType属性类型 column在多的一方的表中的列名-->
   <association property="teacher"  column="{id=tid,name=tid}" javaType="Teacher" select="getTeacher"/>
</resultMap>
<!--
这里传递过来的id,只有一个属性的时候,下面可以写任何值
association中column多参数配置:
   column="{key=value,key=value}"
   其实就是键值对的形式,key是传给下个sql的取值名称,value是片段一中sql查询的字段名。
-->
<select id="getTeacher" resultType="teacher">
  select * from teacher where id = #{id} and name = #{name}
</select>

5、测试

@Test
public void testGetStudents(){
   SqlSession session = MybatisUtils.getSession();
   StudentMapper mapper = session.getMapper(StudentMapper.class);

   List<Student> students = mapper.getStudents();

   for (Student student : students){
       System.out.println(
               "学生名:"+ student.getName()
                       +"\t老师:"+student.getTeacher().getName());
  }
}

按结果嵌套处理

除了上面这种方式,还有其他思路吗?

我们还可以按照结果进行嵌套处理;

1、接口方法编写

public List<Student> getStudents2();

2、编写对应的mapper文件

<!--
按查询结果嵌套处理
思路:
   1. 直接查询出结果,进行结果集的映射
-->
<select id="getStudents2" resultMap="StudentTeacher2" >
  select s.id sid, s.name sname , t.name tname
  from student s,teacher t
  where s.tid = t.id
</select>

<resultMap id="StudentTeacher2" type="Student">
   <id property="id" column="sid"/>
   <result property="name" column="sname"/>
   <!--关联对象property 关联对象在Student实体类中的属性-->
   <association property="teacher" javaType="Teacher">
       <result property="name" column="tname"/>
   </association>
</resultMap>

3、去mybatis-config文件中注入【此处应该处理过了】

4、测试

@Test
public void testGetStudents2(){
   SqlSession session = MybatisUtils.getSession();
   StudentMapper mapper = session.getMapper(StudentMapper.class);

   List<Student> students = mapper.getStudents2();

   for (Student student : students){
       System.out.println(
               "学生名:"+ student.getName()
                       +"\t老师:"+student.getTeacher().getName());
  }
}

小结

按照查询进行嵌套处理就像SQL中的子查询

按照结果进行嵌套处理就像SQL中的联表查询

9.2一对多的处理:

对多的理解:

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

实体类编写

@Data
public class Student {
   private int id;
   private String name;
   private int tid;
}
@Data
public class Teacher {
   private int id;
   private String name;
   //一个老师多个学生
   private List<Student> students;
}

按结果嵌套处理

1、TeacherMapper接口编写方法

//获取指定老师,及老师下的所有学生
public Teacher getTeacher(int id);

2、编写接口对应的Mapper配置文件

<mapper namespace="com.kuang.mapper.TeacherMapper">

   <!--
   思路:
       1. 从学生表和老师表中查出学生id,学生姓名,老师姓名
       2. 对查询出来的操作做结果集映射
           1. 集合的话,使用collection!
               JavaType和ofType都是用来指定对象类型的
               JavaType是用来指定pojo中属性的类型
               ofType指定的是映射到list集合属性中pojo的类型。
   -->
   <select id="getTeacher" resultMap="TeacherStudent">
      select s.id sid, s.name sname , t.name tname, t.id tid
      from student s,teacher t
      where s.tid = t.id and t.id=#{id}
   </select>

   <resultMap id="TeacherStudent" type="Teacher">
       <result  property="name" column="tname"/>
       <collection property="students" ofType="Student">
           <result property="id" column="sid" />
           <result property="name" column="sname" />
           <result property="tid" column="tid" />
       </collection>
   </resultMap>
</mapper>

3、将Mapper文件注册到MyBatis-config文件中

<mappers>
   <mapper resource="mapper/TeacherMapper.xml"/>
</mappers>

4、测试

@Test
public void testGetTeacher(){
   SqlSession session = MybatisUtils.getSession();
   TeacherMapper mapper = session.getMapper(TeacherMapper.class);
   Teacher teacher = mapper.getTeacher(1);
   System.out.println(teacher.getName());
   System.out.println(teacher.getStudents());
}

按查询嵌套处理

1、TeacherMapper接口编写方法

public Teacher getTeacher2(int id);

2、编写接口对应的Mapper配置文件

<select id="getTeacher2" resultMap="TeacherStudent2">
select * from teacher where id = #{id}
</select>
<resultMap id="TeacherStudent2" type="Teacher">
   <!--column是一对多的外键 , 写的是一的主键的列名-->
   <collection property="students" javaType="ArrayList" ofType="Student" column="id" select="getStudentByTeacherId"/>
</resultMap>
<select id="getStudentByTeacherId" resultType="Student">
  select * from student where tid = #{id}
</select>

3、将Mapper文件注册到MyBatis-config文件中

4、测试

@Test
public void testGetTeacher2(){
   SqlSession session = MybatisUtils.getSession();
   TeacherMapper mapper = session.getMapper(TeacherMapper.class);
   Teacher teacher = mapper.getTeacher2(1);
   System.out.println(teacher.getName());
   System.out.println(teacher.getStudents());
}

小结

1、关联-association

2、集合-collection

3、所以association是用于一对一和多对一,而collection是用于一对多的关系

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

  • JavaType是用来指定pojo中属性的类型
  • ofType指定的是映射到list集合属性中pojo的类型。

注意说明:

1、保证SQL的可读性,尽量通俗易懂

2、根据实际要求,尽量编写性能更高的SQL语句

3、注意属性名和字段不一致的问题

4、注意一对多和多对一 中:字段和属性对应的问题

5、尽量使用Log4j,通过日志来查看自己的错误

10.动态SQL

什么是动态SQL:动态SQL指的是根据不同的查询条件 , 生成不同的Sql语句.

常用的动态SQL标签包括:

  1. if标签:用于条件判断,可以根据不同情况生成不同的SQL片段。
  2. choose、when、otherwise标签:用于多条件选择,类似于Java中的switch-case语句。
  3. trim标签:用于去除生成SQL时产生的多余的空格和逗号等符号。
  4. where标签:可以自动根据条件添加WHERE关键字,并处理多个条件之间的AND/OR关系。
  5. set标签:用于更新操作,可以自动生成UPDATE语句中SET子句部分。
  6. foreach标签:用于循环迭代,可以根据集合大小生成对应数量的SQL片段。

通过使用动态SQL标签,可以避免手动构建复杂的SQL语句,同时还可以提高代码的可读性和可维护性。需要注意的是,在使用动态SQL时,要尽量避免过于复杂的嵌套结构,以免影响查询性能。

10.1动态SQL的使用

if 语句

需求:根据作者名字和博客名字来查询博客!如果作者名字为空,那么只根据博客名字查询,反之,则根据作者名来查询

1、编写接口类

//需求1
List<Blog> queryBlogIf(Map map);

2、编写SQL语句

<!--需求1:
根据作者名字和博客名字来查询博客!
如果作者名字为空,那么只根据博客名字查询,反之,则根据作者名来查询
select * from blog where title = #{title} and author = #{author}
-->
<select id="queryBlogIf" parameterType="map" resultType="blog">
  select * from blog where
   <if test="title != null">
      title = #{title}
   </if>
   <if test="author != null">
      and author = #{author}
   </if>
</select>

3、测试

@Test
public void testQueryBlogIf(){
   SqlSession session = MybatisUtils.getSession();
   BlogMapper mapper = session.getMapper(BlogMapper.class);

   HashMap<String, String> map = new HashMap<String, String>();
   map.put("title","Mybatis如此简单");
   map.put("author","狂神说");
   List<Blog> blogs = mapper.queryBlogIf(map);

   System.out.println(blogs);

   session.close();
}

这样写我们可以看到,如果 author 等于 null,那么查询语句为 select * from user where title=#{title},但是如果title为空呢?那么查询语句为 select * from user where and author=#{author},这是错误的 SQL 语句,如何解决呢?请看下面的 where 语句!

Where

修改上面的SQL语句;

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

这个“where”标签会知道如果它包含的标签中有返回值的话,它就插入一个‘where’。此外,如果标签返回的内容是以AND 或OR 开头的,则它会剔除掉。

Set

同理,上面的对于查询 SQL 语句包含 where 关键字,如果在进行更新操作的时候,含有 set 关键词,我们怎么处理呢?

1、编写接口方法

int updateBlog(Map map);

2、sql配置文件

<!--注意set是用的逗号隔开-->
<update id="updateBlog" parameterType="map">
  update blog
     <set>
         <if test="title != null">
            title = #{title},
         </if>
         <if test="author != null">
            author = #{author}
         </if>
     </set>
  where id = #{id};
</update>

3、测试

@Test
public void testUpdateBlog(){
   SqlSession session = MybatisUtils.getSession();
   BlogMapper mapper = session.getMapper(BlogMapper.class);

   HashMap<String, String> map = new HashMap<String, String>();
   map.put("title","动态SQL");
   map.put("author","秦疆");
   map.put("id","9d6a763f5e1347cebda43e2a32687a77");

   mapper.updateBlog(map);


   session.close();
}

choose语句

有时候,我们不想用到所有的查询条件,只想选择其中的一个,查询条件有一个满足即可,使用 choose 标签可以解决此类问题,类似于 Java 的 switch 语句

1、编写接口方法

List<Blog> queryBlogChoose(Map map);

2、sql配置文件

<select id="queryBlogChoose" parameterType="map" resultType="blog">
  select * from blog
   <where>
       <choose>
           <when test="title != null">
                title = #{title}
           </when>
           <when test="author != null">
              and author = #{author}
           </when>
           <otherwise>
              and views = #{views}
           </otherwise>
       </choose>
   </where>
</select>

3、测试类

@Test
public void testQueryBlogChoose(){
   SqlSession session = MybatisUtils.getSession();
   BlogMapper mapper = session.getMapper(BlogMapper.class);

   HashMap<String, Object> map = new HashMap<String, Object>();
   map.put("title","Java如此简单");
   map.put("author","狂神说");
   map.put("views",9999);
   List<Blog> blogs = mapper.queryBlogChoose(map);

   System.out.println(blogs);

   session.close();
}

SQL片段

有时候可能某个 sql 语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽取出来,然后使用时直接调用。

提取SQL片段:

<sql id="if-title-author">
   <if test="title != null">
      title = #{title}
   </if>
   <if test="author != null">
      and author = #{author}
   </if>
</sql>

引用SQL片段:

<select id="queryBlogIf" parameterType="map" resultType="blog">
  select * from blog
   <where>
       <!-- 引用 sql 片段,如果refid 指定的不在本文件中,那么需要在前面加上 namespace -->
       <include refid="if-title-author"></include>
       <!-- 在这里还可以引用其他的 sql 片段 -->
   </where>
</select>

注意:

①、最好基于 单表来定义 sql 片段,提高片段的可重用性

②、在 sql 片段中不要包括 where

Foreach

将数据库中前三个数据的id修改为1,2,3;

需求:我们需要查询 blog 表中 id 分别为1,2,3的博客信息

1、编写接口

List<Blog> queryBlogForeach(Map map);

2、编写SQL语句

<select id="queryBlogForeach" parameterType="map" resultType="blog">
  select * from blog
   <where>
       <!--
       collection:指定输入对象中的集合属性
       item:每次遍历生成的对象
       open:开始遍历时的拼接字符串
       close:结束时拼接的字符串
       separator:遍历对象之间需要拼接的字符串
       select * from blog where 1=1 and (id=1 or id=2 or id=3)
     -->
       <foreach collection="ids"  item="id" open="and (" close=")" separator="or">
          id=#{id}
       </foreach>
   </where>
</select>

3、测试

@Test
public void testQueryBlogForeach(){
   SqlSession session = MybatisUtils.getSession();
   BlogMapper mapper = session.getMapper(BlogMapper.class);

   HashMap map = new HashMap();
   List<Integer> ids = new ArrayList<Integer>();
   ids.add(1);
   ids.add(2);
   ids.add(3);
   map.put("ids",ids);

   List<Blog> blogs = mapper.queryBlogForeach(map);

   System.out.println(blogs);

   session.close();
}

小结:其实动态 sql 语句的编写往往就是一个拼接的问题,为了保证拼接准确,我们最好首先要写原生的 sql 语句出来,然后在通过 mybatis 动态sql 对照着改,防止出错。多在实践中使用才是熟练掌握它的技巧。

11缓存

1、什么是缓存 [ Cache ]?

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

2、为什么使用缓存?

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

3、什么样的数据能使用缓存?

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

11.1Mybatis缓存

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

Mybatis提供了一级缓存和二级缓存两种缓存机制。

  • 一级缓存是在SqlSession生命周期内的缓存,多次查询相同的语句第一次会存储到缓存中后面再缓存中取,默认情况下,Mybatis开启了一级缓存,可以通过清除缓存或关闭SqlSession来刷新缓存。

  • 二级缓存是在Mapper映射文件级别的缓存,可跨SqlSession共享,在不同SqlSession中都可以访问到。需要手动开启和配置,并且只缓存Serializable类型的对象。同时要注意缓存对象的更新和失效问题,避免出现脏数据和并发问题。

尽管Mybatis提供了缓存机制,但并不是所有场景都适合使用缓存。例如,当数据量较大,查询频繁且更新频率较高时,缓存可能成为性能瓶颈,反而影响系统性能。因此,在使用缓存时需要谨慎考虑缓存策略和缓存失效机制,以达到最优化的系统性能。

11.1.1一级缓存

一级缓存失效的四种情况

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

一级缓存失效情况:没有使用到当前的一级缓存,效果就是,还需要再向数据库中发起一次查询请求!

1、sqlSession不同

  • 每个sqlSession中的缓存相互独立

2、sqlSession相同,查询条件不同

  • 当前缓存中,不存在这个数据

3、sqlSession相同,两次查询之间执行了增删改操作!

  • 因为增删改操作可能会对当前数据产生影响

4、sqlSession相同,手动清除一级缓存

11.1.2二级缓存

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

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

  • 工作机制

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

使用步骤

1、开启全局缓存 【mybatis-config.xml】

<setting name="cacheEnabled" value="true"/>

2、去每个mapper.xml中配置使用二级缓存,这个配置非常简单;【xxxMapper.xml】

<cache/>

官方示例=====>查看官方文档
<cache
 eviction="FIFO"
 flushInterval="60000"
 size="512"
 readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。

结论

  • 只要开启了二级缓存,我们在同一个Mapper中的查询,可以在二级缓存中拿到数据
  • 查出的数据都会被默认先放在一级缓存中
  • 只有会话提交或者关闭以后,一级缓存中的数据才会转到二级缓存中

11.2缓存原理图

image-20230401010208552