MyBatis

MyBatis

前言

以下内容多为狂神MyBatis课程上课笔记,一起“食用”更加美味哟_

以下实例的所有代码会上传至本人github,欢迎访问!

一、简介

1.1 什么是MyBatis

  1. MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis,2013年11月迁移到Github。

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

    • 所谓持久化,指的是将数据(如内存中的数据)保存到持久化设备中,主要应用是将内存中的数据存储到关系型数据库中,也可存储到硬盘文件或者XML数据文件中。
  3. 支持自定义 SQL、存储过程以及高级映射。

  4. MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。

  5. MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

1.2 为什么用MyBatis

https://juejin.im/post/5aa5c6fb5188255587232e5a

MyBatis、Hibernate等持久化框架都是为了体现ORM的思想,是对JDBC的一种封装。

在这里插入图片描述
MyBatis与Hibernate存在以下区别:

  1. MyBatis是半自动的,Hibernate是全自动的

    MyBatis仅有基本的字段映射,对象数据以及对象实际关系仍然需要通过手写sql来实现和管理。Hibernate完全可以通过对象关系模型实现对数据库的操作,拥有完整的JavaBean对象与数据库的映射结构来自动生成sql。

  2. Hibernate数据库移植性远大于MyBatis

    Hibernate通过它强大的映射结构和hql语言,大大降低了对象与数据库(oracle、mysql等)的耦合性,而MyBatis仍需手写sql,因此与数据库的耦合性直接取决于程序员写sql的方法,如果sql不具通用性而用了很多某数据库特性的sql语句的话,移植性也会随之降低很多,成本很高。

  3. Hibernate拥有完整的日志系统,MyBatis有所欠缺

    Hibernate日志系统非常健全,涉及广泛,包括:sql记录、关系异常、优化警告、缓存提示、脏数据警告等;而mybatis则除了基本记录功能外,功能薄弱很多

  4. MyBatis相比Hibernate需要关心很多细节

    Hibernate配置要比MyBatis复杂的多,学习成本也比MyBatis高。但也正因为MyBatis使用简单,才导致它要比Hibernate关心很多技术细节。MyBatis由于不用考虑很多细节,开发模式上与传统JDBC区别很小,因此很容易上手并开发项目,但忽略细节会导致项目前期bug较多,因而开发出相对稳定的软件很慢,而开发出软件却很快,为了能开发出相对稳定的软件,基于MyBatis开发时,需要更加注重细节。而Hibernate则正好与之相反,但是如果使用Hibernate很熟练的话,实际上开发效率丝毫不差于甚至超越MyBatis。

  5. 缓存机制上,Hibernate要比mybatis更好一些

    MyBatis的二级缓存配置都是在每个具体的表-对象映射中进行详细配置,这样针对不同的表可以自定义不同的缓存机制。并且MyBatis可以在命名空间中共享相同的缓存配置和实例,通过Cache-ref来实现。

    Hibernate对查询对象有着良好的管理机制,用户无需关心sql。所以在使用二级缓存时如果出现脏数据,系统会报出错误并提示。

  6. sql直接优化上,MyBatis要比Hibernate方便很多

    由于MyBatis的sql都是写在xml里,因此优化sql比Hibernate方便很多。而hibernate的sql很多都是自动生成的,无法直接维护sql;虽有hql,但功能还是不及sql强大,见到报表等变态需求时,hql也无能为力,所以在处理复杂业务时hql是有局限的;Hibernate虽然也支持原生sql,但开发模式上却与ORM不同,需要转换思维,因此使用上不是非常方便。总之写sql的灵活度上Hibernate不及MyBatis。

综上所述:Hibernate学习成本较高,但如果熟练掌握了hql,Hibernate框架用起来会特别舒服,不用写sql代码,但是在处理复杂业务时,Hibernate灵活度较差,复杂的hql难写也难以理解。而MyBatis简单易学,灵活度好,sql和代码分离,也提高了可维护性,更重要的是现在使用的公司多呀,那我们必须要学习呗。接下来我们就开始我们的第一个MyBatis程序吧!

二、入门–创建第一个MyBatis程序

2.1 环境搭建

  • JDK 1.8
  • Maven 3.6.3
  • MySQL 5.7.3
  • IDEA

2.2 创建数据库

CREATE DATABASE `mybatis`;

USE `mybatis`;

CREATE TABLE `user`(
  `id` INT(20) NOT NULL PRIMARY KEY,
  `name` VARCHAR(30) DEFAULT NULL,
  `pwd` VARCHAR(30) DEFAULT NULL
)ENGINE=INNODB DEFAULT CHARSET=utf8;

INSERT INTO `user`(`id`,`name`,`pwd`) VALUES 
(1,'FY','123456'),
(2,'张三','123456'),
(3,'李四','123890')

2.3 新建Maven项目

在这里插入图片描述
在这里插入图片描述

  • 创建之后进入File-Settings,将maven版本更改为自己的
    在这里插入图片描述
  • 在pom.xml文件中添加依赖
    <!--导入依赖-->
    <dependencies>
        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.4</version>
        </dependency>
        
        <!--junit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>
  • 删除src目录(因为目前新建的MyBatis要用做父工程,父工程的src用不到,之后要再在父工程下建立子工程)

2.4 创建一个Mdule

在这里插入图片描述在这里插入图片描述

2.5 为子模块编写核心配置文件

  • mybatis-config.xml,放于resources目录下
<?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核心配置文件-->
<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?serverTimezone=GMT&amp;useSSL=false&amp;useUnicode=true&amp;characterEncoding=utf8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

</configuration>

2.6 编写mybatis工具类

  • MybatisUtils,放于com.fy.utils下
package com.fy.utils;

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 {
            //1、获取sqlSessionFactory对象
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //既然有了 SqlSessionFactory,
    // 顾名思义,我们可以从中获得 SqlSession 的实例
    public static SqlSession getsqlSession(){
        SqlSession sqlSession=sqlSessionFactory.openSession();
        return sqlSession;
    }
}

2.7 编写功能代码

  1. 实体类User,放于com.fy.pojo下
package com.fy.pojo;

//实体类
public class User {
    private int id;
    private String name;
    private String pwd;

    public User() {
    }

    public User(int id, String name, String pwd) {
        this.id = id;
        this.name = name;
        this.pwd = pwd;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }
}
  1. 编写Dao接口,放于com.fy.dao下
package com.fy.dao;

import com.fy.pojo.User;
import java.util.List;

public interface UserDao {
    //查询全部用户
    List<User> getUserList();
}
  1. 接口实现类由原来的UserDaoImpl转变为一个 Mapper配置文件,放于com.fy.dao 下
  • namespace中的包名要和 Dao/mapper 接口的包名一致
  • id : 就是对应的namespace中的方法名
  • resultType:sql语句执行的返回值
  • parameterType : 参数类型
<?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="com.fy.dao.UserDao">
<!--select查询语句-->
   <select id="getUserList" resultType="com.fy.pojo.User">
       select * from mybatis.user
   </select>

</mapper>
  1. 核心配置文件mybatis-config.xml下注册Mapper
<!--每一个Mapper,XML都需要在Mybatis核心配置文件中注册-->
    <mappers>
        <mapper resource="com/fy/dao/UserMapper.xml"/>
    </mappers>

注意:UserMapper.xml现在放于com.fy.dao下,idea无法加载,需在父工程的pom.xml中加入以下代码,也可在resources目录下新建Mapper文件夹,将UserMapper.xml放入其中

 <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>

2.8 测试

  • test目录下新建测试类
    @Test
    public  void test(){
        //第一步:获得sqlsession对象
        SqlSession sqlSession= MybatisUtils.getsqlSession();
        try{
            //执行sql:方式1-->getMapper
            UserMapper mapper=sqlSession.getMapper(UserMapper.class);
            //mapper.getUserList();
            List<User> userList=mapper.getUserList();

            //方式2-->不推荐
            //List<User> userList1=sqlSession.selectList("com.fy.dao.UserMapper.getUserList");

            for (User user: userList) {
                System.out.println(user);
            }

        }catch (Exception e){
            e.printStackTrace();

        }finally {
            //关闭sqlsession
            sqlSession.close();
        }
    }

子模块mybatis-01的整体目录为:
在这里插入图片描述

三、核心配置文件解析

3.1 配置文件的整体结构

XML 配置文件中包含了对 MyBatis 系统的核心设置,包括获取数据库连接实例的数据源(DataSource)以及决定事务作用域和控制方式的事务管理器(TransactionManager)等,MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:

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

3.2 properties(属性)

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

这些属性都是可外部配置且可动态替换的,既可以在典型的 Java 属性文件中配置,也可通过 properties 元素的子元素来传递。

如:我们可以在外部构建一个配置文件【db.properties】

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT&useSSL=false&useUnicode=true&characterEncoding=utf8
username=root
password=123456

然后在核心配置文件中映入

<!--引入外部配置文件-->
    <properties resource="db.properties"/>

设置好的属性可以在整个配置文件中用来替换需要动态配置的属性值,【db.properties】中的属性值对应的是dataSource中的属性值,所以dataSource中对应的属性可以更改为动态属性值, 那么driver,url,username 和 password 将会由 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>

属性值也可通过 SqlSessionFactoryBuilder.build() 方法中传入属性值,如:

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, props);

// ... 或者 ...

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, props);

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

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

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

3.3 settings(设置)

settings(设置)MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。具体每个设置对应的名称、描述、有效值和默认值可查看官网

如:日志的设置

 <!--配日志-SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING-->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

3.4 typeAliases(类型别名)

类型别名是为 Java 类型设置一个短的名字,存在的意义仅在于用来减少类完全限定名的冗余。

<!--起别名-->
    <!--方式一,实体类少的时候使用,一个个起别名-->
    <typeAliases>
        <typeAlias type="com.fy.pojo.User" alias="User"/>
    </typeAliases>
<!--方式二,实体类多的时候使用,但这一种不能DIY,如果非要改,需要在实体类上加注解-->
    <typeAliases>
        <package name="com.fy.pojo.User" />
    </typeAliases>  
@Alias("user")
public class User {}

3.4 其他配置

3.5 environments(环境配置)

MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中,或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射。

**不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。**所以,如果想连接两个数据库,就需要创建两个 SqlSessionFactory 实例,每个数据库对应一个。而如果是三个数据库,就需要三个实例。如:我们在环境配置下配置两个环境变量

<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="test">
            <!--事务管理器-->
            <transactionManager type="JDBC"/>
            <!--连接池-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT&amp;useSSL=false&amp;useUnicode=true&amp;characterEncoding=utf8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

其中默认的环境变量为id=“development”,若要指定创建哪种环境,只要将它作为可选的参数传递给 SqlSessionFactoryBuilder 即可,如果忽略环境参数则为默认值。可以接受环境配置的两个方法签名是:

//更改对应的environment字段即可
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment);

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, properties);
  • 事务管理器(transactionManager)

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

<!--事务管理器-->
<transactionManager type="JDBC"/>

JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。

MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。例如:

<transactionManager type="MANAGED">
  <property name="closeConnection" value="false"/>
</transactionManager>
  • 数据源(dataSource)

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

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

有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]"),我们使用的是POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。

常配的属性如下:

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

还有更多属性用来配置 POOLED 的数据源:

  • poolMaximumActiveConnections – 在任意时间可存在的活动(正在使用)连接数量,默认值:10
  • poolMaximumIdleConnections – 任意时间可能存在的空闲连接数。
  • poolMaximumCheckoutTime – 在被强制返回之前,池中连接被检出(checked out)时间,默认值:20000 毫秒(即 20 秒)
  • poolTimeToWait – 这是一个底层设置,如果获取连接花费了相当长的时间,连接池会打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直失败且不打印日志),默认值:20000 毫秒(即 20 秒)。
  • poolMaximumLocalBadConnectionTolerance – 这是一个关于坏连接容忍度的底层设置, 作用于每一个尝试从缓存池获取连接的线程。 如果这个线程获取到的是一个坏的连接,那么这个数据源允许这个线程尝试重新获取一个新的连接,但是这个重新尝试的次数不应该超过 poolMaximumIdleConnectionspoolMaximumLocalBadConnectionTolerance 之和。 默认值:3(新增于 3.4.5)
  • poolPingQuery – 发送到数据库的侦测查询,用来检验连接是否正常工作并准备接受请求。默认是“NO PING QUERY SET”,这会导致多数数据库驱动出错时返回恰当的错误消息。
  • poolPingEnabled – 是否启用侦测查询。若开启,需要设置 poolPingQuery 属性为一个可执行的 SQL 语句(最好是一个速度非常快的 SQL 语句),默认值:false。
  • poolPingConnectionsNotUsedFor – 配置 poolPingQuery 的频率。可以被设置为和数据库连接超时时间一样,来避免不必要的侦测,默认值:0(即所有连接每一时刻都被侦测 — 当然仅当 poolPingEnabled 为 true 时适用)。

其他数据源类型,参考

3.6 mappers(映射器)

既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。

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

方式一: 使用相对于类路径的资源引用

<!--每一个Mapper.XML都需要在Mybatis核心配置文件中注册!-->
<mappers>
    <mapper resource="com/kuang/dao/UserMapper.xml"/>
</mappers>

方式二:使用class文件绑定注册, 映射器接口实现类的完全限定类名

<!--每一个Mapper.XML都需要在Mybatis核心配置文件中注册!-->
<mappers>
    <mapper class="com.kuang.dao.UserMapper"/>
</mappers>

注意点:

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

方式三:使用扫描包进行注入绑定,包内的映射器接口实现全部注册为映射器

<!--每一个Mapper.XML都需要在Mybatis核心配置文件中注册!-->
<mappers>
    <package name="com.kuang.dao"/>
</mappers>

注意点:

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

四、生命周期和作用域

在这里插入图片描述
生命周期,和作用域,是至关重要的,因为错误的使用会导致非常严重的并发问题

4.1 SqlSessionFactoryBuilder

这个类可以在任何时候被实例化、使用和销毁。一旦创建了SqlSessionFactory 就不需要

再保留它了。所以SqlSessionFactoryBuilder 实例的最好的作用域是方法体内(即一个本地方法

变量)。我们可以从SqlSessionFactoryBuilder 创建多个SqlSessionFactory 实例,但最好不要把

时间、资源放在解析XML 文件上,而是要从中解放出来做最重要事情。

4.2 SqlSessionFactory

SqlSessionFactory 可以想象为数据库连接池,SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 因此 SqlSessionFactory 的最佳作用域是一个应用的生命周期

范围。 最简单的就是使用单例模式或者静态单例模式

4.3 SqlSession

每个线程都有一个SqlSession 实例,SqlSession 实例是不被共享的,并且不是线程安全的因此最好的作用域是request 或者方法体作用域。不要用一个静态字段或者一个类的实例字段来保存SqlSession 实例引用。也不要用任何一个管理作用域,如Servlet 框架中的HttpSession,来保存SqlSession 的引用。如果正在用一个WEB 框架,可以把SqlSession 的作用域看作类似于HTTP 的请求范围。也就是说,在收到一个HTTP 请求,我们可以打开一个SqlSession,当您把response 返回时,就可以把SqlSession 关闭。关闭会话是非常重要的,应该要确保会话在一个finally 块中被关闭。

4.4 Mapper实例

Mapper 是一种创建的用于绑定映射语句的接口,Mapper 接口的实例是用 SqlSession 来获得的。同样,从技术上来说,最广泛的 Mapper 实例作用域像 SqlSession 一样,使用请求作用域。确切地说,在方法被调用的时候调用 Mapper 实例,然后使用后,就自动销毁掉。不需要使用明确的注销。当一个请求执行正确无误的时候,像 SqlSession 一样,你可以轻而易举地操控这一切。保持简单性,保持 Mapper 在方法体作用域内。

五、增加CRUD操作

新建子模块----->导需要的包----->编写mybatis-config核心配置文件(这几个过程不再赘述)

5.1 Select

  1. 编写接口
//根据ID查询用户
User getUserById(int id);
  1. 编写对应的mapper中的sql语句,注意要写在对应的mapper下
  • resultType:sql语句执行的返回值
  • parameterType : 参数类型
<!--因为在核心配置文件中起了别名,所以resultType可以用别名User,完整的是com.fy.pojo.user-->
<select id="getUserById" parameterType="int" resultType="User">
        select * from mybatis.user where id=#{id}
</select>
  1. 测试
@Test
public void getUserById(){
    SqlSession sqlSession=MybatisUtils.getsqlSession();
    UserMapper mapper=sqlSession.getMapper(UserMapper.class);

    User user=mapper.getUserById(1);
    System.out.println(user);

    sqlSession.close();
}

5.2 Insert

  1. 编写接口
//insert 一个用户
int addUser(User user);
  1. 编写对应的mapper中的sql语句
<insert id="addUser" parameterType="User">
    insert into mybatis.user (id,name,pwd) values (#{id},#{name},#{pwd});
</insert>
  1. 测试,注意增删改操作需要提交事务
//增删改需要提交事务
@Test
public void addUser(){
    SqlSession sqlSession=MybatisUtils.getsqlSession();
    UserMapper mapper=sqlSession.getMapper(UserMapper.class);
    mapper.addUser(new User(4,"FYYI","654321"));

    sqlSession.commit();
    sqlSession.close();
}

5.3 Update

  1. 编写接口
//insert 一个用户
int addUser(User user);
  1. 编写对应的mapper中的sql语句
<update id="updateUser" parameterType="User">
    update mybatis.user set name=#{name},pwd=#{pwd}  where id=#{id};
</update>
  1. 测试
@Test
public void updateUser(){
    SqlSession sqlSession=MybatisUtils.getsqlSession();
    UserMapper mapper=sqlSession.getMapper(UserMapper.class);
    mapper.updateUser(new User(4,"FYTest","123123"));

    sqlSession.commit();
    sqlSession.close();
}

5.4 Delete

  1. 编写接口
//删除一个用户
int deleteUser(int id);
  1. 编写对应的mapper中的sql语句
<delete id="deleteUser" parameterType="int">
    delete from mybatis.user where id=#{id};
</delete>
  1. 测试
@Test
public void deleteUser(){
    SqlSession sqlSession=MybatisUtils.getsqlSession();
    UserMapper mapper=sqlSession.getMapper(UserMapper.class);
    mapper.deleteUser(4);

    sqlSession.commit();
    sqlSession.close();
}

5.5 万能Map

当我们的实体类,或者数据库中的表,字段或者参数过多,我们应当考虑使用Map

使用map完成增加用户操作,新建子模块----->导需要的包----->编写mybatis-config核心配置文件(这几个过程不再赘述)

  1. 编写接口
//万能Map
int addUser2(Map<String,Object> map);
  1. 编写对应的mapper中的sql语句
<insert id="addUser2" parameterType="map">
   insert into mybatis.user (id,name,pwd) values (#{userid},#{username},#{passward});
</insert>
  1. 测试
@Test
public void addUser2(){
    SqlSession sqlSession=MybatisUtils.getsqlSession();
    UserMapper mapper=sqlSession.getMapper(UserMapper.class);

    Map<String,Object> map=new HashMap<String, Object>();
    map.put("userid",5);
    map.put("username","FYER");
    map.put("passward","654321");

    mapper.addUser2(map);

    sqlSession.commit();
    sqlSession.close();
}

5.6 模糊查询

  1. 编写接口
//模糊查询
List<User> getUserLike(String value);
  1. 编写对应的mapper中的sql语句
<select id="getUserLike" resultType="com.fy.pojo.User">
    select * from mybatis.user where name like "%"#{value}"%";
</select>
  1. 测试
@Test
public void getUserLike(){
    SqlSession sqlSession=MybatisUtils.getsqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    //mapper中sql语句里的%%,也可加在
    //List<User> userList = mapper.getUserLike("%FY%");实现模糊查询
    List<User> userList = mapper.getUserLike("FY");
    for (User user: userList) {
        System.out.println(user);
    }
    sqlSession.close();
}

六、数据库字段和实体类字段不一致解决方法

id   name   pwd
id   name   password

当数据库字段名和实体字段名不一致时,我们在查询的时候回查到null值,为解决这个问题,有两个方法:

6.1 起别名

<select id="getUserById" parameterType="int" resultMap="User">
        <!--数据库字段和实体类字段不一致解决方式一:-->
        select id,name ,pwd as password from mybatis.user where id=#{id}
</select>

6.2 结果集映射

<!--数据库字段和实体类字段不一致解决方式二:resultMap,结果集映射-->
    <resultMap id="UserMap" type="User">
        <!--column数据库中的字段,property实体类中的属性-->
        <!--因为这个字段跟数据库是相同的所以可以不映射<result column="id" property="id"/>-->
        <!--
        因为这个字段跟数据库是相同的所以可以不映射<result column="name" property="name"/>
        -->
        <result column="pwd" property="password"/>
    </resultMap>

    <select id="getUserById" parameterType="int" resultMap="UserMap">     
        select * from mybatis.user where id=#{id}
    </select>

七、日志

7.1 日志工厂

如果一个数据库操作,出现了异常,我们需要排错。日志就是最好的助手!

曾经:system.out.println()、debug

现在:日志工厂

在mybatis-config.xml中我们已经设置了日志的默认值为STDOUT_LOGGING标准日志输出

 <!--配日志-SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING-->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

通过控制台,可以看到反馈的信息,比如是否建立数据库连接,执行的sql,输入的参数,返回的结果等

"E:\Program Files\Java\jdk1.8.0_241\bin\java.exe"...
Opening JDBC Connection
Created connection 1885996206.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@706a04ae]
==>  Preparing: select * from mybatis.user; 
==> Parameters: 
<==    Columns: id, name, pwd
<==        Row: 1, FY, 123456
<==        Row: 2, 张三, 123456
<==        Row: 3, 李四, 987654
<==        Row: 4, FYYI, 654321
<==      Total: 4
User{id=1, name='FY', pwd='123456'}
User{id=2, name='张三', pwd='123456'}
User{id=3, name='李四', pwd='987654'}
User{id=4, name='FYYI', pwd='654321'}
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@706a04ae]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@706a04ae]
Returned connection 1885996206 to pool.

Process finished with exit code 0

7.2 Log4j

  • Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件;
  • 我们也可以控制每一条日志的输出格式;
  • 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程;
  • 通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
  1. 在当前子模块的pom.xml中导入log4j的包
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
  1. resources目录下建立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/fy.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. 在mybatis-config.xml中我们已经设置了日志的默认值为
 <!--配日志-SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING-->
    <settings>
        <setting name="logImpl" value="LOG4J"/>
    </settings>
  1. 测试反馈的信息,以下信息还会根据我们在配置文件中设置的格式输出到指定的file中
[org.apache.ibatis.logging.LogFactory]-Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
[org.apache.ibatis.logging.LogFactory]-Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
[org.apache.ibatis.datasource.pooled.PooledDataSource]-PooledDataSource forcefully closed/removed all connections.
[org.apache.ibatis.datasource.pooled.PooledDataSource]-PooledDataSource forcefully closed/removed all connections.
[org.apache.ibatis.datasource.pooled.PooledDataSource]-PooledDataSource forcefully closed/removed all connections.
[org.apache.ibatis.datasource.pooled.PooledDataSource]-PooledDataSource forcefully closed/removed all connections.
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 756185697.
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@2d127a61]
[com.fy.dao.UserMapper.getUserById]-==>  Preparing: select * from mybatis.user where id=? 
[com.fy.dao.UserMapper.getUserById]-==> Parameters: 1(Integer)
[com.fy.dao.UserMapper.getUserById]-<==      Total: 1
User{id=1, name='FY', password='123456'}
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@2d127a61]
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@2d127a61]
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 756185697 to pool.

Process finished with exit code 0
  1. 简单使用
  • 在要使用Log4j 的类中,导入包 import org.apache.log4j.Logger;

  • 日志对象,参数为当前类的class

static Logger logger = Logger.getLogger(UserDaoTest.class);
  • 日志级别
logger.info("info:进入了testLog4j");
logger.debug("debug:进入了testLog4j");
logger.error("error:进入了testLog4j");

八、分页

目的:减少数据的处理量

8.1 使用limit分页

语法:
SELECT * from user limit startIndex,pageSize;
SELECT * from user limit 3;  #[0,3)

新建子模块----->导需要的包----->编写mybatis-config核心配置文件(这几个过程不再赘述)

  1. 编写接口
//分页
List<User> getUserByLimit(Map<String,Integer> map);
  1. 编写对应的mapper中的sql语句
<!--分页-->
<select id="getUserByLimit" parameterType="map" resultMap="User">
    select * from mybatis.user limit #{startIndex},#{pageSize}
 </select>
  1. 测试
//分页
@Test
public void getUserByLimit(){
    SqlSession sqlSession=MybatisUtils.getsqlSession();
    UserMapper mapper =sqlSession.getMapper(UserMapper.class);
    Map<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();
}

8.2 RowBounds分页

  1. 编写接口
//分页2
List<User> getUserByRowBounds();
  1. 编写对应的mapper中的sql语句
<select id="getUserByRowBounds" resultMap="User">
    select * from mybatis.user
</select>
  1. 测试
//分页2
@Test
public void getUserByRowBounds(){
    SqlSession sqlSession=MybatisUtils.getsqlSession();

    //RowBounds
    RowBounds rowBounds = new RowBounds(1, 2);

    List<User> userList = sqlSession.selectList("com.fy.dao.UserMapper.getUserByRowBounds",null,rowBounds);

    for (User user: userList) {
        System.out.println(user);
    }
    sqlSession.close();
}

8.3 使用分页插件

在这里插入图片描述

九、注解开发

9.1 面向接口编程

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

关于接口的理解

  • 接口从更深层次的理解,应是定义(规范,约束)与实现(名实分离的原则)的分离。
  • 接口的本身反映了系统设计人员对系统的抽象理解。
  • 接口应有两类:
    - 第一类是对一个个体的抽象,它可对应为一个抽象体(abstract class);
    - 第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface);
  • 一个体有可能有多个抽象面。抽象体与抽象面是有区别的。

三个面向区别

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

9.2 使用注解开发

本质:反射机制的实现

底层:动态代理

执行流程:
在这里插入图片描述

9.3 注解实现CRUD

新建子模块----->导需要的包----->编写mybatis-config核心配置文件(这几个过程不再赘述)

  1. 编写接口
public interface UserMapper {
    @Select("select * from user")
    List<User> getUser();

    //方法存在多个阐述,所有参数前面必须加@Param
    @Select("select * from user where id =#{id} and name=#{name}")
    User getUserById(@Param("id") int id,@Param("name") String name);

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

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

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

}
  1. 将接口绑定到核心配置mybatis-config文件中,使用注解就没有Mapper.xml配置文件了
<!--绑定接口-->
<mappers>
    <mapper class="com.fy.dao.UserMapper"/>
</mappers>

3.测试

public class UserMapperTest {
    @Test
    public void getUser(){
        SqlSession sqlSession= MybatisUtils.getsqlSession();
        //底层主要应用反射
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = mapper.getUser();

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

        }

        sqlSession.close();
    }

    @Test
    public void getUserById(){
        SqlSession sqlSession= MybatisUtils.getsqlSession();
        //底层主要应用反射
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.getUserById(1, "FY");

        System.out.println(user);

        sqlSession.close();
    }

    @Test
    public void addUser(){
        SqlSession sqlSession=MybatisUtils.getsqlSession();
        UserMapper mapper=sqlSession.getMapper(UserMapper.class);
        mapper.addUser(new User(6,"haha","123456"));

        //sqlSession.commit();
        sqlSession.close();
    }

    @Test
    public void updateUser(){
        SqlSession sqlSession=MybatisUtils.getsqlSession();
        UserMapper mapper=sqlSession.getMapper(UserMapper.class);
        mapper.updateUser(new User(6,"hehe","123456"));

        //sqlSession.commit();
        sqlSession.close();
    }

    @Test
    public void deleteUser(){
        SqlSession sqlSession=MybatisUtils.getsqlSession();
        UserMapper mapper=sqlSession.getMapper(UserMapper.class);
        mapper.deleteUser(6);

        //sqlSession.commit();
        sqlSession.close();
    }
}

关于@Param() 注解

  • 基本类型的参数或者String类型,需要加上
  • 引用类型不需要加
  • 如果只有一个基本类型的话,可以忽略,但是建议大家都加上!
  • 我们在SQL中引用的就是我们这里的 @Param() 中设定的属性名!

#与$的区别:

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

对于 : INSERT INTO user (name) VALUES (#{name}); ==> INSERT INTO user (name) VALUES (?);

${} 符号的作用是直接进行字符串替换:

对于 : INSERT INTO user (name) VALUES ('${name}'); ==> INSERT INTO user (name) VALUES ('FY');

十、多对一处理

多对一:多个对象对应一个对象,比如:多个学生由一个老师管理,所以站在学生角度,多个学生关联(association)一个老师,是多对以一的关系。

所以接下来我们就完成查询所有学生的信息以及他们对应的老师的信息这个任务吧!

10.1 创建对应的数据库

CREATE TABLE `teacher` (
  `id` INT(10) NOT NULL,
  `name` VARCHAR(30) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

INSERT INTO teacher(`id`, `name`) VALUES (1, '秦老师'); 

CREATE TABLE `student` (
  `id` INT(10) NOT NULL,
  `name` VARCHAR(30) DEFAULT NULL,
  `tid` INT(10) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `fktid` (`tid`),
  CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;


INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('1', '小明', '1'); 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('2', '小红', '1'); 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('3', '小张', '1'); 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('4', '小李', '1'); 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5', '小王', '1');

10.2 实例实现多对一

新建子模块----->导需要的包----->编写mybatis-config核心配置文件(这几个过程不再赘述)

  1. 新建实体类Teacher,Student,并添加get,set,toString,有参构造,无参构造方法(此处用到lombok简化操作,大家可以了解一下)

    package com.fy.pojo;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Teacher {
        private int id;
        private String name;
    }
    package com.fy.pojo;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Student {
        private int id;
        private String name;
    
        //学生需要关联一个老师
        private Teacher teacher;
    }
  2. 新建对应的Mapper接口

package com.fy.dao;

import com.fy.pojo.Teacher;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

public interface TeacherMapper {

    @Select("select * from teacher where id=#{tid}")
    Teacher getTeacher(@Param("tid") int id);
}
package com.fy.dao;

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

public interface StudentMapper {
    //查询所有的学生的信息以及对应的老师的信息

    List<Student> getStudent();

    List<Student> getStudent2();
}
  1. 新建Mapper接口的配置文件,TeacherMapper.xml和StudentMapper.xml,此处我们把Mapper配置文件放于resources目录下
<?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">

<mapper namespace="com.fy.dao.TeacherMapper">

</mapper>

为了能实现多对一关联查询,有两种思路:

  • 按照查询嵌套处理(sql语句简单,但过程复杂)
  • 按照结果嵌套处理(sql复杂,相当于用我们之前写的原生sql,但过程容易理解)
<?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">

<mapper namespace="com.fy.dao.StudentMapper">
<!--
  思路一:
  1、查询所有学生的信息
  2、根据查询出来的学生的tid,寻找对应的老师
  -->
    <select id="getStudent" resultMap="StudentTeacher">
        select * from student
    </select>
    <resultMap id="StudentTeacher" type="Student">
        <result column="id" property="id"/>
        <result column="name" property="name"/>
        <!--复杂的属性,需要单独处理
            对象:association(多对一)
            集合:collection(一对多)
        -->
        <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
    </resultMap>

   <select id="getTeacher" resultType="Teacher">
       select * from teacher where id=#{id}
   </select>

<!--
思路二:
按照结果嵌套处理
-->
    <select id="getStudent2" 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">
        <result column="sid" property="id"/>
        <result column="name" property="sname"/>
        <association property="teacher" javaType="Teacher">
            <result property="name" column="tname"/>
        </association>
    </resultMap>

</mapper>
  1. 在核心配置文件中绑定Mapper接口
<mappers>
    <mapper class="com.fy.dao.TeacherMapper"/>
    <mapper class="com.fy.dao.StudentMapper"/>
</mappers>
  1. 测试
public class MyTest {

    @Test
    public void getTeacher(){
        SqlSession sqlSession= MybatisUtils.getsqlSession();
        TeacherMapper teacherMapper = sqlSession.getMapper(TeacherMapper.class);

        Teacher teacher = teacherMapper.getTeacher(1);
        System.out.println(teacher);

        sqlSession.close();
    }

    @Test
    public void testStudent(){
        SqlSession sqlSession= MybatisUtils.getsqlSession();
        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
        
        
        //List<Student> studentList = studentMapper.getStudent();
        List<Student> studentList = studentMapper.getStudent2();

        for (Student student : studentList) {
            System.out.println(student);

        }
        sqlSession.close();
    }
}

十一、一对多处理

一对多:一个对象对应多个对象,比如:一个老师拥有多个学生,所以站在老师角度,一个老师拥有很多学生(是一个集合Collection),是一对多的关系。

所以接下来我们就完成获取指定老师下的所有学生及学生的信息这个任务吧!

11.1 实例实现一对多

新建子模块----->导需要的包----->编写mybatis-config核心配置文件(这几个过程不再赘述)

  1. 新建实体类Teacher,Student
package com.fy.pojo;

import lombok.Data;

@Data
public class Student {
    private int id;
    private String name;
    private int tid;
}
package com.fy.pojo;

import lombok.Data;

import java.util.List;

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

    //一个老师拥有多个学生
    private List<Student> students;
}
  1. 新建对应的Mapper接口
package com.fy.dao;

import com.fy.pojo.Teacher;
import org.apache.ibatis.annotations.Param;
import java.util.List;

public interface TeacherMapper {
    //获取老师
    List<Teacher> getTeacher();

    //获取指定老师下的所有学生及学生的信息

    Teacher getTeacher2(@Param("tid") int id);

    Teacher getTeacher3(@Param("tid") int id);

}

学生的这个接口不用实现功能,所以是个空壳子

package com.fy.dao;

public interface StudentMapper {

}
  1. 新建Mapper接口的配置文件,TeacherMapper.xml和StudentMapper.xml

同样的,为了能实现一对多查询,有两种思路:

  • 按照查询嵌套处理(sql语句简单,但过程复杂)
  • 按照结果嵌套处理(sql复杂,相当于用我们之前写的原生sql,但过程容易理解)
<?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">

<mapper namespace="com.fy.dao.TeacherMapper">
    <select id="getTeacher" resultType="Teacher">
        select * from mybatis.teacher;
    </select>


    <!--按结果嵌套查询-->
    <select id="getTeacher2" resultMap="TeacherStudent">
        select s.id sid,s.name sname,t.name tname,tid
        from student s, teacher t
        where s.tid=t.id and t.id=#{tid};
    </select>
    
    <resultMap id="TeacherStudent" type="Teacher">
        <result column="tid" property="id"/>
        <result column="tname" property="name"/>
        <!--复杂的属性,需要单独处理,
        对象(多对一):association
        集合(一对多):collection-->
        <!--javaType="" 指定属性的类型
            集合中的泛型信息,使用ofType来获取
        -->
        <collection property="students" ofType="Student">
            <result property="id" column="sid"/>
            <result property="name" column="sname"/>
            <result property="tid" column="tid"/>

        </collection>
    </resultMap>

    <!--子查询-->
    <select id="getTeacher3" resultMap="TeacherStudent2">
        select * from mybatis.teacher where id=#{tid}
    </select>
    <resultMap id="TeacherStudent2" type="Teacher">
        <collection property="students" javaType="ArrayList" ofType="Student" select="getStudentByTeacherId" column="id"/>
        
    </resultMap>
    
    <select id="getStudentByTeacherId" resultType="Student">
        select * from mybatis.student where tid=#{tid};
    </select>
    
</mapper>
<?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">

<mapper namespace="com.fy.dao.StudentMapper">

</mapper>
  1. 在核心配置文件中绑定Mapper接口
<mappers>
    <mapper class="com.fy.dao.TeacherMapper"/>
    <mapper class="com.fy.dao.StudentMapper"/>
</mappers>
  1. 测试
public class MyTest {
    @Test
    public void getTeacher(){
        SqlSession sqlSession= MybatisUtils.getsqlSession();
        TeacherMapper teacherMapper = sqlSession.getMapper(TeacherMapper.class);
        List<Teacher> teacherList = teacherMapper.getTeacher();
        for (Teacher teacher : teacherList) {
            System.out.println(teacher);
        }

        sqlSession.close();
    }

    @Test
    public void getTeacher2(){
        SqlSession sqlSession= MybatisUtils.getsqlSession();
        TeacherMapper teacherMapper = sqlSession.getMapper(TeacherMapper.class);
        Teacher teacher1 = teacherMapper.getTeacher2(1);
        System.out.println(teacher1);
        
        sqlSession.close();
    }

    @Test
    public void getTeacher3(){
        SqlSession sqlSession= MybatisUtils.getsqlSession();
        TeacherMapper teacherMapper = sqlSession.getMapper(TeacherMapper.class);
        Teacher teacher1 = teacherMapper.getTeacher3(1);
        System.out.println(teacher1);


        sqlSession.close();
    }
}

11.2 小结

  1. 关联 - association 【多对一】

  2. 集合 - collection 【一对多】

  3. javaType & ofType

    • JavaType 用来指定实体类中属性的类型

    • ofType 用来指定映射到List或者集合中的 pojo类型,泛型中的约束类型!

注意点:

  • 保证SQL的可读性,尽量保证通俗易懂
  • 注意一对多和多对一中,属性名和字段的问题!
  • 如果问题不好排查错误,可以使用日志 , 建议使用 Log4j

十二、动态SQL

动态 SQL 是 MyBatis 的强大特性之一。如果使用过 JDBC 或其它类似的框架,应该能体会到根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。

使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。

如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

12.1 实例体验动态SQL

  1. 创建对应的数据库
CREATE TABLE `blog` (
  `id` varchar(50) NOT NULL COMMENT '博客id',
  `title` varchar(100) NOT NULL COMMENT '博客标题',
  `author` varchar(30) NOT NULL COMMENT '博客作者',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `views` int(30) NOT NULL COMMENT '浏览量'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  1. 新建子模块----->导需要的包----->编写mybatis-config核心配置文件(这几个过程不再赘述)
  2. 编写实体类
package com.fy.pojo;

import lombok.Data;
import java.util.Date;

@Data
public class Blog {
    private String id;
    private String title;
    private String author;
    private Date createTime;//属性名和字段名不一致
    private int views;
}

上面存在一个属性名和字段名不一致的问题,这个问题我们可以通过核心配置文件中的setting将经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn

<setting name="mapUnderscoreToCamelCase" value="true"/>
  1. 编写Mapper接口
package com.fy.dao;

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

public interface BlogMapper {
    //插入数据

    int addBlog(Blog blog);

    //查询博客
    List<Blog> queryBlogIF(Map map);

    List<Blog> queryBlogChoose(Map map);

    //更新博客
    int updateBlog(Map map);

    //查询第1-2-3号记录的博客
    List<Blog> queryBlogForeach(Map map);
}
  1. 编写对应的Mapper.xml

首先因为建立的表,没有数据,所以先把addBlog方法对应的sql加上

<insert id="addBlog" parameterType="blog">
    insert into mybatis.Blog(id,title,author,create_time,views)
    values (#{id},#{title},#{author},#{createTime},#{views})
</insert>

IF

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

</select>

choose (when, otherwise)

<select id="queryBlogChoose" parameterType="map" resultType="blog">
    select * from mybatis.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>

trim (where,set)

<update id="updateBlog" parameterType="map">
    update mybatis.blog
    <set>
        <if test="title!=null">
            title=#{title},
        </if>
        <if test="author!=null">
            author=#{author}
        </if>
    </set>
    where id=#{id}
</update>

SQL片段

有的时候,我们可能会将一些功能的部分抽取出来,方便复用!

  • 使用SQL标签抽取公共的部分
<sql id="if-title-author">
    <if test="title != null">
        title = #{title}
    </if>
    <if test="author != null">
        and author = #{author}
    </if>
</sql>
  • 在需要使用的地方使用Include标签引用即可
<select id="queryBlogIF" parameterType="map" resultType="blog">
    select * from mybatis.blog
    <where>
        <include refid="if-title-author"></include>
    </where>
</select>

注意事项:

  • 最好基于单表来定义SQL片段!
  • 不要存在where标签

Foreach

 <!--
    select * from mybatis.blog where i=i and (id=1 or id=2 or id=3)
    现在传递一个万能的map,这个map可以存在一个集合
    -->
<select id="queryBlogForeach" parameterType="map" resultType="blog">
    select * from mybatis.blog
    <where>
        <foreach collection="ids" item="id"
                 open="and (" close=")" separator="or">

        id=#{id}
        </foreach>

    </where>
</select>
  1. 核心配置文件中绑定接口
<mappers>
    <mapper class="com.fy.dao.BlogMapper"/>
</mappers>
  1. 测试
public class MyTest {

    @Test
    public void addBlog(){
        SqlSession sqlSession= MybatisUtils.getsqlSession();
        BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
        Blog blog = new Blog();
        blog.setId(IDUtils.getId());
        blog.setTitle("Mybatis");
        blog.setAuthor("狂神说");
        blog.setCreateTime(new Date());
        blog.setViews(9999);

        blogMapper.addBlog(blog);

        blog.setId(IDUtils.getId());
        blog.setTitle("Java");
        blogMapper.addBlog(blog);

        blog.setId(IDUtils.getId());
        blog.setTitle("Spring");
        blogMapper.addBlog(blog);

        blog.setId(IDUtils.getId());
        blog.setTitle("微服务");
        blog.setViews(1000);
        blogMapper.addBlog(blog);

        sqlSession.close();
    }

    @Test
    public void queryBlogIF(){
        SqlSession sqlSession=MybatisUtils.getsqlSession();
        BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);

        HashMap map=new HashMap();
        map.put("title","Java");
        map.put("author","狂神说");

        List<Blog> blogs = blogMapper.queryBlogIF(map);
        for (Blog blog : blogs) {
            System.out.println(blog);
        }

        sqlSession.close();
    }

    @Test
    public void queryBlogChoose(){
        SqlSession sqlSession=MybatisUtils.getsqlSession();
        BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);

        HashMap map=new HashMap();
        map.put("title","微服务");
        //map.put("author","狂神说");
        map.put("views","9999");

        List<Blog> blogs = blogMapper.queryBlogChoose(map);
        for (Blog blog : blogs) {
            System.out.println(blog);
        }

        sqlSession.close();
    }

    @Test
    public void updateBlog(){
        SqlSession sqlSession=MybatisUtils.getsqlSession();
        BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);

        HashMap map=new HashMap();
        //map.put("title","微服务2");
        map.put("author","狂神说2");
        map.put("id","4b99e57080324f348cf1851138dfa649");
        //map.put("views","9999");

        blogMapper.updateBlog(map);


        sqlSession.close();
    }

    @Test
    public void queryBlogForeach(){
        SqlSession sqlSession=MybatisUtils.getsqlSession();
        BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);

        HashMap map=new HashMap();

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



        List<Blog> blogs = blogMapper.queryBlogForeach(map);
        for (Blog blog : blogs) {
            System.out.println(blog);
        }

        sqlSession.close();
    }
}

动态SQL就是在拼接SQL语句,我们只要保证SQL的正确性,按照SQL的格式,去排列组合就可以了

建议:

  • 先在Mysql中写出完整的SQL,再对应的去修改成为我们的动态SQL实现通用即可!

十三、缓存

如果每次查询都连接数据库 ,耗资源!一次查询的结果,给他暂存在一个可以直接取到的地方!

–> 内存 : 缓存
我们再次查询相同数据的时候,直接走缓存,就不用走数据库了

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

所以经常查询又不常改变的数据可以使用缓存,减少和数据库的交互次数,减少系统开销,提高系统效率

13.1 Mybatis缓存

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

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

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

13.2 一级缓存

一级缓存也叫本地缓存: SqlSession

  • 与数据库同一次会话期间查询到的数据会放在本地缓存中。
  • 以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库

测试步骤:

  1. 开启日志!
  2. 测试在一个Sesion中查询两次相同记录
  3. 查看日志输出

在这里插入图片描述

缓存失效的情况:

  1. 查询不同的东西
    在这里插入图片描述

  2. 增删改操作,可能会改变原来的数据,所以必定会刷新缓存!
    在这里插入图片描述

  3. 查询不同的Mapper.xml

  4. 手动清理缓存!

@Test
public void queryUsersById(){
    SqlSession sqlSession= MybatisUtils.getsqlSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    User user1 = userMapper.queryUsersById(1);
    System.out.println(user1);
    // 关闭以及缓存
    sqlSession.clearCache();//一级缓存默认是开启的,只在一次SqlSession中有效

    System.out.println("================================");

    User user2 = userMapper.queryUsersById(1);
    System.out.println(user2);

    System.out.println(user1==user2);

    sqlSession.close();
}

小结:

  • 一级缓存默认是开启的,只在一次SqlSession中有效,也就是拿到连接到关闭连接这个区间段!

  • 一级缓存就是一个Map

  • 在同一个 SqlSession 中, Mybatis 会把执行的方法和参数通过算法生成缓存的键值, 将键值和结果存放在一个 Map 中, 如果后续的键值一样, 则直接从 Map 中获取数据;

  • 不同的 SqlSession 之间的缓存是相互隔离的;

  • 用一个 SqlSession, 可以通过配置使得在查询前清空缓存;

  • 任何的 UPDATE, INSERT, DELETE 语句都会清空缓存。

13.2 二级缓存

  • 级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
  • 基于namespace级别的缓存,一个名称空间,对应一个二级缓存;
  • 工作机制
    • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
    • 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;
    • 新的会话查询信息,就可以从二级缓存中获取内容;
    • 不同的mapper查出的数据会放在自己对应的缓存(map)中;

步骤:

  1. 开启全局缓存

    <!--显示的开启全局缓存-->
    <setting name="cacheEnabled" value="true"/>
  2. 在要使用二级缓存的Mapper中开启

    <!--在当前Mapper.xml中使用二级缓存-->
    <cache/>

    也可以自定义参数

    <!--在当前Mapper.xml中使用二级缓存-->
    <cache  eviction="FIFO"
           flushInterval="60000"
           size="512"
           readOnly="true"/>
  3. 测试

    1. 问题:我们需要将实体类序列化!否则就会报错!

      Caused by: java.io.NotSerializableException: com.fy.pojo.User
      

小结:

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

13.3 自定义缓存-ehcache

Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存

要在程序中使用ehcache,先要导包!

<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.2.1</version>
</dependency>

在mapper中指定使用我们的ehcache缓存实现!

<!--在当前Mapper.xml中使用二级缓存-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false">
    <!--
       diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
       user.home – 用户主目录
       user.dir  – 用户当前工作目录
       java.io.tmpdir – 默认临时文件路径
     -->
    <diskStore path="./tmpdir/Tmp_EhCache"/>
    
    <defaultCache
            eternal="false"
            maxElementsInMemory="10000"
            overflowToDisk="false"
            diskPersistent="false"
            timeToIdleSeconds="1800"
            timeToLiveSeconds="259200"
            memoryStoreEvictionPolicy="LRU"/>
 
    <cache
            name="cloud_user"
            eternal="false"
            maxElementsInMemory="5000"
            overflowToDisk="false"
            diskPersistent="false"
            timeToIdleSeconds="1800"
            timeToLiveSeconds="1800"
            memoryStoreEvictionPolicy="LRU"/>
    <!--
       defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
     -->
    <!--
      name:缓存名称。
      maxElementsInMemory:缓存最大数目
      maxElementsOnDisk:硬盘最大缓存个数。
      eternal:对象是否永久有效,一但设置了,timeout将不起作用。
      overflowToDisk:是否保存到磁盘,当系统当机时
      timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
      timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
      diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
      diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
      diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
      memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
      clearOnFlush:内存数量最大时是否清除。
      memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
      FIFO,first in first out,这个是大家最熟的,先进先出。
      LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
      LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
   -->

</ehcache>

org.mybatis.caches.ehcache.EhcacheCache"/>


ehcache.xml

```xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false">
    <!--
       diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
       user.home – 用户主目录
       user.dir  – 用户当前工作目录
       java.io.tmpdir – 默认临时文件路径
     -->
    <diskStore path="./tmpdir/Tmp_EhCache"/>
    
    <defaultCache
            eternal="false"
            maxElementsInMemory="10000"
            overflowToDisk="false"
            diskPersistent="false"
            timeToIdleSeconds="1800"
            timeToLiveSeconds="259200"
            memoryStoreEvictionPolicy="LRU"/>
 
    <cache
            name="cloud_user"
            eternal="false"
            maxElementsInMemory="5000"
            overflowToDisk="false"
            diskPersistent="false"
            timeToIdleSeconds="1800"
            timeToLiveSeconds="1800"
            memoryStoreEvictionPolicy="LRU"/>
    <!--
       defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
     -->
    <!--
      name:缓存名称。
      maxElementsInMemory:缓存最大数目
      maxElementsOnDisk:硬盘最大缓存个数。
      eternal:对象是否永久有效,一但设置了,timeout将不起作用。
      overflowToDisk:是否保存到磁盘,当系统当机时
      timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
      timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
      diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
      diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
      diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
      memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
      clearOnFlush:内存数量最大时是否清除。
      memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
      FIFO,first in first out,这个是大家最熟的,先进先出。
      LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
      LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
   -->

</ehcache>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值