1、简介


1.1、什么是Mybasit

image.png

  • Mybasit 是一款优秀的持久层框架
  • 它支持定制化SQL、存储过程以及高级映射
  • Mybatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。Mybatis 可以使用简单的 XML 或者注解来配置和映射原生类型、接口和Java的POJO(Plain Old Java Objects,普通老式Java对象)为数据库中的记录。
  • Mybatis 本是apache的一个开源项目iBatis,2010年这个项目由 apache software foundation 迁移到了 goole code,并且改名为Mybatis
  • 2013年11月迁移到Github

Github:https://github.com/mybatis/mybatis-3/releases
中文文档:https://mybatis.org/mybatis-3/zh/index.html

image.png

1.2、持久层

数据持久化

  • 持久化就是将程序的数据在持久状态和瞬时状态转化的过程
  • 瞬时状态
    • 内存:断电即失
  • 持久化
    • 数据库(jdbc)持久化,io文件持久化

为什么需要持久化?

  • 有一些对象,不能让它丢掉,比如支付宝中的钱
  • 本质:内存太贵了(正是太贵所以容量不大,就需要做到断电即失)

1.3、持久层

Dao层,Service层,Controller层

  • 完成持久化工作的代码块
  • 层界限十分明显

1.4、为什么需要Mybatis?

  • 传统的JDBC代码太复杂了。—> 简化 —> 框架 (自动化)
  • 帮助程序员将数据存入到数据库中
  • 不用Mybatis也可以。但是使用框架的话,更容易上手。技术没有高低之分
  • 优点
    • 简单易学
    • 灵活
    • SQL和代码的分离,提高可维护性
    • 提供映射标签,支持对象与数据库的orm字段关系映射
    • 提供对象关系映射标签,支持对象关系组建维护
    • 提供XML标签,支持编写动态SQL

2、第一个Mybatis程序


思路:搭建环境 —> 导入Mybatis —> 编写代码 —> 测试

  1. 搭建Maven环境,我这里用的是 maven-3.8.4
  2. 创建数据库 ``sql CREATE DATABASEmybatis; USEmybatis`

CREATE TABLE user( id INT 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, ‘Jungle’, ‘123456’), (2, ‘Eric’, ‘123456’), (3, ‘Curry’, ‘123456’)

  1. 3. 导入依赖
  2. ```xml
  3. <!--导入依赖-->
  4. <dependencies>
  5. <!--mysql驱动-->
  6. <dependency>
  7. <groupId>mysql</groupId>
  8. <artifactId>mysql-connector-java</artifactId>
  9. <version>5.1.47</version>
  10. </dependency>
  11. <!--mybatis-->
  12. <dependency>
  13. <groupId>org.mybatis</groupId>
  14. <artifactId>mybatis</artifactId>
  15. <version>3.5.2</version>
  16. </dependency>
  17. <!--junit-->
  18. <dependency>
  19. <groupId>junit</groupId>
  20. <artifactId>junit</artifactId>
  21. <version>4.12</version>
  22. </dependency>
  23. </dependencies>

2.2、创建一个模块

  • 编写mybatis的核心配置文件 mybatis-config.xml ```xml <?xml version=”1.0” encoding=”UTF-8” ?> <!DOCTYPE configuration

      PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
      "http://mybatis.org/dtd/mybatis-3-Config.dtd">
    


- 编写mybatis工具类
```java
package com.jungle.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;

public class MybatisUtils {
    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            // 使用mybatis第一步:获取sqlSessionFactory对象
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 既然有了 SqlSessionFactory,顾名思义,我们就可以从中获得SqlSession的实例了。
    // SqlSession 完全包含了面向数据库执行SQL命令所需的所有方法.
    public static SqlSession getSqlSession(){
        SqlSession sqlSession = sqlSessionFactory.openSession();
        return sqlSession;
    }
}

2.3、编写代码

  • 实体类 ```java package com.jungle.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;
}

}


- DAO接口
```cpp
package com.jungle.dao;

import com.jungle.pojo.User;

import java.util.List;

public interface UserDao {
    List<User> getUserList();
}
  • 接口实现类:由原来的 UserDaoImpl 实现类转换为一个UserMapper.xml文件 ```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">
    


<a name="imoBH"></a>
## 2.4、测试

**Junit测试**
```java
package com.jungle.dao;

import com.jungle.pojo.User;
import com.jungle.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class UserDaoTest {
    @Test
    public void test(){
        // 通过MybatisUtils获得SqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        // 方式一:getMapper
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        List<User> userList = mapper.getUserList();
        for (User user : userList){
            System.out.println(user);
        }


        // 关闭sqlSession
        sqlSession.close();
    }
}

注意点:Mybatis绑定异常
org.apache.ibatis.binding.BindingException: Type interface com.jungle.dao.UserDao is not known to the MapperRegistry.

MapperRegistry是什么?

  • 核心配置文件中注册 mappers
    <!--每一个Mapper.xml都需要在Mybatis的核心文件中注册,不然会报注册异常-->
    <mappers>
    <mapper resource="com/jungle/dao/UserMapper.xml"/>
    </mappers>
    

maven由于它的约定大于配置,我们之后可能遇到我们写的配置文件无法被导出或者无法生效的问题,解决方案:
在pom.xml中增加静态资源过滤

<!--在build中配置resource,来防止我们资源导出失败的问题-->
<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>

可能遇到的问题:

  1. 配置文件没有注册
  2. 绑定接口错误
  3. 方法名不对
  4. 返回类型不对(记住,是全限定名)
  5. maven导出资源问题

3、CRUD


3.1、namespace

namespace中的包名要和Dao/mapper 的接口包名相同

3.2、select

选择,查询语句;

  • id:就是对应namespace中的方法名
  • resultType:Sql语句执行的返回值
  • parameterType:参数类型!
  1. 编写接口
  2. 编写对应的mapper中的sql语句
  3. 测试

insert、update、delete
注意点:增删改需要提交事务!

3.3、万能的Map

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

Map的好处是,传递给SQL语句的变量名不需要同数据库的字段完全相同,因为参数类型是map,
只有当参数类型是pojo层中的对象,那么久必须与数据库中的字段名一一对应,这是mybatis的约定俗称。而使用Map,我们可以灵活的使用key值
但是频繁的强转类型,可能效率会比较低。
image.png

3.4、模糊查询以及SQL注入问题

模糊查询怎么写?
image.png

关于 #{value},其低层实现是 Preparedstatement,有防止SQL注入的功能,具体原理看这篇文章
Preparedstatement是如何防止SQL注入的?
防注入的低层原理:我们可以看到输出的SQL文是把整个参数用引号包起来,并把参数中的引号作为转义字符,从而避免了参数也作为条件的一部分

4、配置解析

4.1、核心配置文件

  • mybatis-config.xml
  • mybatis的配置文件包含了会深深影响mybatis行为的设置和属性信息
    configuration(配置)
    properties(属性)
    settings(设置)
    typeAliases(类型别名)
    typeHandlers(类型处理器)
    objectFactory(对象工厂)
    plugins(插件)
    environments(环境配置)
        environment(环境变量)
        transactionManager(事务管理器)
        dataSource(数据源)
    databaseIdProvider(数据库厂商标识)
    mappers(映射器)
    

4.2、环境配置(environment)

Mybatis可以配置成适应多种环境
不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
要学会使用配置多套运行环境!

mybatis的默认事务管理器是JDBC,还有一个Manager,但是基本不用
mybatis的默认的连接池:POOLED,还有UNPOOLED,JNDI

4.3、属性(properties)

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

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

注意:mybatis在XML文件中,所有的配置都要遵循mybatis的核心文件规定的配置顺序进行配置,否则报错。

image.png

4.4、类型别名

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

<typeAliases>
  <typeAlias alias="Author" type="domain.blog.Author"/>
  <typeAlias alias="Blog" type="domain.blog.Blog"/>
  <typeAlias alias="Comment" type="domain.blog.Comment"/>
  <typeAlias alias="Post" type="domain.blog.Post"/>
  <typeAlias alias="Section" type="domain.blog.Section"/>
  <typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>

当这样配置时,Blog 可以用在任何使用 domain.blog.Blog 的地方。
也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:

<typeAliases>
  <package name="domain.blog"/>
</typeAliases>

每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author 的别名为 author;若有注解,则别名为其注解值。见下面的例子:

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

4.5、设置(settings)

image.png
image.png

4.6、映射器(mappers)

mapperRegister:注册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文件绑定注册

<!-- 使用映射器接口实现类的完全限定类名 -->
<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必须同名!
  • 接口和它的mapper必须在同一个包下!

4.8、生命周期和作用域(scope)

理解我们之前讨论过的不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。
image.png
SqlSessionFactoryBuilder:

  • 一旦创建了SqlSessionFactory,SqlSessionFactoryBuilder 就不再需要了。
  • 最好设置成一个局部变量

SqlSessionFactory:

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

SqlSession

  • 可以认为是连接到连接池的一个请求!
  • SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳作用域是请求或者方法作用域
  • 用完之后需要赶紧关闭,否则资源被占用!

image.png
这里面的每一个Mapper,都代表一个具体的业务!

5、解决属性名和字段名不一致的问题


5.1、问题

当实体类中的属性名为:id、name、password
而数据库中的字段名为:id、name、pwd
这时候属性名和字段名不一致

select * from mybatis.user where id = #{id}
-- 等价于 ---
select id, name, pwd from mybatis.user where id = #{id}
-- java有个叫类型处理器的东西,会自动去实体类中匹配SQL语句中对应的字段名

image.png

解决方法:

  • 起别名
    <select id="getUserById" resultType="com.kuang.pojo.User">
      select id, name, pwd as password from mybatis.user where id = #{id}
    </select>
    

5.2、resultMap

结果集映射

id        name        pwd
id        name        password

具体实现

<!--结果集映射-->
<resultMap>
        <!--column数据库中的字段,property实体类中的属性-->
    <result column="id" property="id"/>
    <result column="name" property="name"/>
    <result column="pwd" property="password"/>
</resultMap>

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

resultMap 元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC 不支持的操作。实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份 resultMap 能够代替实现同等功能的数千行代码。ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。

6、日志


6.1、日志工厂

如果一个数据库操作出现了异常,我们需要排错。日志就是mybatis提供的一个最好的助手。
曾经:sout、debug
现在:日志工厂!

  • SLF4J
  • LOG4J 【掌握】
  • LOG4J2
  • JDK_LOGGING
  • COMMONS_LOGGING
  • STDOUT_LOGGING 【掌握】
  • NO_LOGGING

在mybatis中具体使用哪个日志实现,在设置中设定!
STDOUT_LOGGING标准日志输出
在mybatis核心配置文件中,配置我们的日志!

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

image.png

6.2、Log4j

什么是Log4j?

  • Log4j 是 Apache 的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件
  • 我们也可以控制每一条日志的输出格式
  • 通过定义每一条日志信息的级别,我们能够更加细致地控制日志生成的过程
  • 通过一个配置文件来灵活地进行配置,而不需要修改应用的代码
  1. 先导入log4j的包

    <!-- https://mvnrepository.com/artifact/log4j/log4j -->
    <dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
    </dependency>
    
  2. log4j.properties ```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/jungle.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


3. 配置mybatis-config.xml
```xml
<settings>
  <!--标准的日志工厂实现-->
  <!--        <setting name="logImpl" value="STDOUT_LOGGING"/>-->
  <setting name="logImpl" value="LOG4J"/>
</settings>
  1. Log4j的使用,直接测试运行刚才的查询

image.png

简单使用

  1. 在要使用Log4j 的类中,导入包 import.org.apache.log4j.Logger;
  2. 日志对象,参数为当前类的class

    static Logger logger = Logger.getLogger(UserDaoTest.class);
    
  3. 日志级别

    @Test
    public void testLog4j(){
     logger.info("info: 进入了testLog4j");
     logger.debug("debug: 进入了testLog4j");
     logger.error("error: 进入了testLog4j");
    }
    

7、分页


思考:为什么要分页?

  • 减少数据的处理量

7.1、Limit分页

使用Limit分页

select * from user limit startIndex, pageSize;
select * from user limit 3;            # [0, 3]

Mysql,之前可以使用 select * from user limit 0, -1
意为从第一条语句查询到最后一条语句,但是被MySql视为一个BUG,已经被修复了。

使用Mybatis实现分页,核心SQL

  1. 接口
  2. Mapper.xml
  3. 测试

7.2、RowBounds(不推荐)

通过Java程序来实现分页的功能,而不是通过SQL层面
即SQL语句查询全部,然后在Java代码中对齐进行分页。

  1. 接口

    // 分页RowBound
    List<User> getUserByRowBounds();
    
  2. Mapper.xml

    <!--RowBound分页-->
    <select id="getUserByRowBounds" resultType="User">
    select * from user
    </select>
    
  3. 测试

    @Test
    public void getUserByRowBounds(){
     SqlSession sqlSession = MybatisUtils.getSqlSession();
     Object mapper = sqlSession.getMapper(UserMapper.class);
    
     // RowBounds实现分页
     RowBounds rowBounds = new RowBounds(0, 2);
    
     // 通过Java代码层面实现分页
     List<User> UserList = sqlSession.selectList("com.jungle.dao.UserMapper.getUserByRowBounds", null, rowBounds);
    
     for(User user : UserList){
         System.out.println(user);
     }
    
     sqlSession.close();
    }
    

7.3、分页插件(了解即可)

image.png

8、使用注解开发


8.1、面向接口编程

  1. 注解在接口上实现

    @Select("select * from user")
    List<User> getUsers();
    
  2. 需要在核心配置文件中绑定接口!

    <mappers>
    <!--绑定接口-->
    <mapper class="com.jungle.dao.UserMapper"/>
    </mappers>
    
  3. 测试

    @Test
    public void getUsers(){
     // 通过MybatisUtils获得SqlSession对象
     SqlSession sqlSession = MybatisUtils.getSqlSession();
     UserMapper mapper = sqlSession.getMapper(UserMapper.class);
     List<User> userList = mapper.getUsers();
     for (User user : userList) {
         System.out.println(user);
     }
    
     // 关闭sqlSession
     sqlSession.close();
    }
    

本质:反射机制实现
低层:动态代理!

8.2、使用注解开发

Mybatis详细的执行流程 【这一块需要去读源码!!!】
Mybatis-9.28 - 图16

8.3、CRUD

我们可以在工具类创建的时候实现自动提交事务!

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

CRUD的注解操作
image.png

测试类
【注意:我们必须要将接口注册绑定到mybatis的核心配置文件中!】

8.4、关于 @Param()注解

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

#{ } ${ } 的区别
image.png

它们的区别就相当于是 PreparedStatement 和 Statement 之间的区别。

9、Lombok

使用步骤:

  1. 下载Lombok插件
  2. 在项目中导入Lombok的 jar 包 ```xml org.projectlombok lombok 1.18.22 provided

3. 在实体类上加注解
```xml
Features
  @Getter and @Setter
  @FieldNameConstants
  @ToString
  @EqualsAndHashCode
  @AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor
  @Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog
  @Data
  @Builder
  @SuperBuilder
  @Singular
  @Delegate
  @Value
  @Accessors
  @Wither
  @With
  @SneakyThrows
  @val
  @var
  experimental @var
  @UtilityClass
  Lombok config system
  Code inspections
  Refactoring actions (lombok and delombok)

说明:

@Data:无参构造、get、set、toString、hashCode、equals
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
@ToString
@Getter

总结:虽然方便,但是尽量少用,以免失去了编写源码的能力。

10、多对一处理

  • 多个学生,对应一个老师
  • 对于学生这边而言,多个学生关联一个老师【多对一】 关联
  • 对于老师而言,一个老师有很多学生【一对多】 集合

image.png
SQL语句:

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');

测试环境搭建

  • 导入lombok
  • 新建实体类Teacher、Student
  • 建立Mapper接口
  • 建立Mapper.xml文件
  • 在核心配置文件中绑定注册我们的Mapper接口或者文件
  • 测试查询是否成功

多对一查询代码:

  • 为什么不能都使用简单的 来做为映射?
    • 因为 只适用于简单的字段,当字段为一个对象的时候,它就无法满足了。所以需要用 来表示关联的关系。这里已经理解了,如果后面的复习如果忘记了,看狂神,Mybatis P20,多对一的处理。

10.1、按查询嵌套处理

image.png

<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jungle.dao.StudentMapper">

    <!--思路
        1.查询所有学生的信息
        2.根据查询出来的学生的信息的tid,寻找对应的老师
    -->
    <select id="getStudent" resultMap="StudentTeacher">
        select * from student;
    </select>
    <!--结果集映射-->
    <resultMap id="StudentTeacher" type="Student">
        <result property="id" column="id"/>
        <result property="name" column="name"/>
        <!--复杂的属性,我们需要单独处理
            关联:association
            集合:collection
        -->
        <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
    </resultMap>

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

</mapper>

10.2、按照结果嵌套处理

image.png
代码:

<!--按结果嵌套查询-->
<select id="getStudent2" resultMap="StudentTeacher2">
    select s.id as sid, s.name as sname, t.name as tname
    from student as s, teacher as t
    where s.tid = t.id;
</select>
<resultMap id="StudentTeacher2" type="Student">
    <result property="id" column="sid"/>
    <result property="name" column="sname"/>
    <association property="teacher" javaType="Teacher">
        <result property="name" column="tname"/>
    </association>
</resultMap>

回顾Mysql多对一查询:

  • 子查询
    • select id name tname from student, teacher where (select tname from teacher……)
  • 联表查询
    • select s.id as sid, s.name as sname, t.name as tname
      from student as s, teacher as t
      where s.tid = t.id;

11、一对多处理

比如:一个老师拥有多个学生!
对于老师而言就是一对多的关系!

  1. 环境搭建,和刚才一样

11.1、按照结果嵌套处理

<!--按结果嵌套查询-->
    <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 = #{tid}
    </select>
    <resultMap id="TeacherStudent" type="Teacher">
        <result property="id" column="tid"/>
        <result property="name" column="tname"/>
        <!--复杂的属性,我们需要单独处理  关联:association  集合:collection
        javaType="" 指定属性的类型!
        集合中的泛型信息,我们适用ofType,但其实他们在本质上没有多大的区别
        -->
        <collection property="students" ofType="Student">   <!--这里不用javaType是因为查询出来的结果就是一个个Student的对象了-->
            <result property="id" column="sid"/>
            <result property="name" column="sname"/>
            <result property="tid" column="tid"/>
        </collection>
    </resultMap>

11.2、按照查询嵌套处理

<!--按查询嵌套处理-->
    <select id="getTeacher2" resultMap="TeacherStudent2">
        select * from teacher where id = #{tid};
    </select>
    <resultMap id="TeacherStudent2" type="Teacher">
        <result property="id" column="id"/>
        <result property="name" column="name"/>
        <collection property="students" column="id" javaType="ArrayList" ofType="Student" select="getStudentByTeacherId"/>
    </resultMap>
    <select id="getStudentByTeacherId" resultType="Student">
        select * from student where tid = #{id};
    </select>

小结

  1. 关联 — associate 【多对一】
  2. 集合 — collection 【一对多】
  3. javaType & ofType
    1. javaType 用来指定实体类中类型的属性
    2. ofType 用来指定映射到List或者集合,里面的pojo类型,说白了就是泛型中的约束类型!

注意点:

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

慢SQL 别人查一条数据 0.01s,你查一条数据 100s!
以上写的SQL全都是有问题的,都属于慢SQL,即没有经过任何的优化!!!

面试高频

  • Mysql引擎
  • InnoDB低层原理
  • 索引
  • 索引优化!

12、动态SQL

什么是动态SQL?

  • 动态SQL就是根据不同的条件生成不同的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

搭建环境

SQL语句

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 '创建时间',
    view3 INT(30) NOT NULL COMMENT '浏览量'
)ENGINE=INNODB DEFAULT CHARSET=utf8

创建一个基础工程

  1. 导包
  2. 编写配置文件
  3. 编写实体类
  4. 编写实体类对应Mapper接口 和 Mapper.xml 文件

12.1、if

Mapper.xml

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

测试

@Test
public void queryBlogIf(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

    HashMap map = new HashMap();
    // 往map添加两个参数,实现动态SQL查询
    map.put("title", "Java如此简单");
    map.put("author", "狂神说");
    List<Blog> blogs = mapper.queryBlogIf(map);
    for (Blog blog : blogs) {
        System.out.println(blog);
    }

    sqlSession.close();
}

12.2、choose(when,otherwise)

Choose标签

Mapper.xml

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

测试

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

        HashMap map = new HashMap();
        // 往map添加两个参数,实现动态SQL查询
        map.put("title", "Java如此简单");
        map.put("author", "狂神说");
        map.put("view3", 9999);
        List<Blog> blogs = mapper.queryBlogChoose(map);
        for (Blog blog : blogs) {
            System.out.println(blog);
        }
        sqlSession.close();
    }

12.3、trim(where、set)

where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:

<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ...
</trim>

prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。
用于动态更新语句的类似解决方案叫做 setset 元素可以用于动态包含需要更新的列,忽略其它不更新的列。比如:

<update id="updateAuthorIfNecessary">
  update Author
    <set>
      <if test="username != null">username=#{username},</if>
      <if test="password != null">password=#{password},</if>
      <if test="email != null">email=#{email},</if>
      <if test="bio != null">bio=#{bio}</if>
    </set>
  where id=#{id}
</update>

这个例子中,set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。
来看看与 set 元素等价的自定义 trim 元素吧:

<trim prefix="SET" suffixOverrides=",">
  ...
</trim>

注意,我们覆盖了后缀值设置,并且自定义了前缀值。

Where标签

适用 if 标签 会有 where 1 = 1 的问题,where标签就是为了解决这一情况出现的

Mapper.xml

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

测试

@Test
    public void queryBlogWhere(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

        HashMap map = new HashMap();
        // 往map添加两个参数,实现动态SQL查询
        map.put("title", "Java如此简单");
        map.put("author", "狂神说");
        List<Blog> blogs = mapper.queryBlogWhere(map);
        for (Blog blog : blogs) {
            System.out.println(blog);
        }

        sqlSession.close();
    }

SQL被解析之后:
[com.jungle.dao.BlogMapper.queryBlogWhere] - ==> Preparing: select * from mybatis.blog WHERE title = ? and author = ?

set标签

Mapper.xml

<insert 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}
</insert>

测试

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

    HashMap map = new HashMap();
    // 往map添加两个参数,实现动态SQL查询
    map.put("title", "Java如此简单22");
    map.put("author", "狂神说");
    map.put("id", "2c8ae8e2c0af4cb8b3f4c29fb8bb659a");
    int count = mapper.updateBlog(map);

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

所谓的动态SQL,本质还是SQL语句,只是我们可以在SQL层面上去执行一些逻辑代码而已!

12.3、SQL片段

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

  1. 使用SQL标签抽取公共部分

    <!--SQL片段-->
    <sql id="IF-title-author">
     <if test="title != null">
         and title = #{title}
     </if>
     <if test="author != null">
         and author = #{author}
     </if>
    </sql>
    
  2. 在需要引用的地方使用include标签引用即可

    <select id="queryBlogWhere" parameterType="map" resultType="Blog">
     select * from mybatis.blog
     <where>
         <include refid="IF-title-author"></include>
     </where>
    </select>
    

注意事项:

  • 最好基于单表来定义SQL片段
  • 不要存在where标签,否则很难实现SQL复用

12.4、Foreach

  1. SQL语句

    <!--通过Foreach查询id=1 || id=2 || id=3-->
    <select id="queryBlogForeach" parameterType="map" resultType="Blog">
     select * from mybatis.blog
     <where>
         <foreach collection="ids" item="id" open="and (" separator="or" close=")">
             id = #{id}
         </foreach>
     </where>
    </select>
    

    测试

    @Test
    public void queryBlogForeach(){
     SqlSession sqlSession = MybatisUtils.getSqlSession();
     BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    
     HashMap map = new HashMap();
     ArrayList list = new ArrayList();
     list.add(1);
     list.add(2);
     list.add(3);
     map.put("ids", list);
     List<Blog> blogForeach = mapper.queryBlogForeach(map);
     for (Blog foreach : blogForeach) {
         System.out.println(foreach);
     }
    
     sqlSession.close();
    }
    

13、缓存


13.1、简介

查询 : 连接数据库,但是消耗资源!
        一次查询的结果,给它暂存在一个可以直接渠道的地方 --> 内存,把这个操作叫缓存

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

image.png

读写分离,主从复制
image.png

13.2、Mybatis缓存

  • Mybatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。
  • Mybatis系统中默认定义了两级缓存:一级缓存二级缓存
    • 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也成为本地缓存)
    • 二级缓存需要手动开启和配置,它是基于 namespace 级别的缓存。
    • 为了提高拓展性,Mybatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存

13.2.1、一级缓存

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

测试

  1. 开启日志

    <settings>
     <!--标准的日志工厂实现-->
     <!--<setting name="logImpl" value="STDOUT_LOGGING"/>-->
     <setting name="logImpl" value="LOG4J"/>
    </settings>
    
  2. 测试在一个Session中查询两次相同的记录

    @Test
    public void test(){
     SqlSession sqlSession = MybatisUtils.getSqlSession();
     UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    
     User user1 = mapper.queryUser(1);
     System.out.println(user1);
     System.out.println("=====================");
     User user2 = mapper.queryUser(1);
     System.out.println(user2);
    
     if (user1 == user2){
         System.out.println("true");
     }else{
         System.out.println("false");
     }
    
     sqlSession.close();
    }
    
  3. 查看日志输出

image.png

一级缓存失效的情况:

  1. 查询不同的数据记录
  2. 增删改操作。因为增删改操作可能会改变原来的数据,所以必定会刷新缓存!
  3. 查询不同的Mapper.xml(这里连二级缓存都失效)
  4. 手动清理缓存!

image.png
image.png

一级缓存默认是开启的,只在一次SqlSession中有效,也就是拿到连接到关闭连接这个区间段。
一级缓存就相当于是一个Map,同样都是拥有键值对,也是通过key去查询value。(从编译器的debug可以看出来)

13.2.2、二级缓存

image.png
步骤:

  1. 开启全局缓存,在Mybatis的核心配置文件

    <!--显示的开启全局缓存, 默认就是开启的-->
    <setting name="cacheEnabled" value="true"/>
    
  2. 在当前Mapper.xml中使用二级缓存

    <cache/>
    

    还可以自定义缓存

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

    还可以为一条单独的select标签添加缓存,但是增删改对应的是刷新缓存选项 flushCache

    <select id="" resultType="" useCache="true">
    ......
    </select>
    <!-- ================================================== -->
    <update id="" resultType="" flushCache="true">
    ......
    </update>
    
  3. 测试

    1. 问题:我们需要将实体类序列化!否则就会报错!(说明存在缓存中的数据也是需要经过序列化的)

      Cause by: java.io.NotSerializableException: com.kuang.pojo.User
      

      测试程序

      @Test
      // 测试二级缓存
      public void test2(){
        SqlSession sqlSession1 = MybatisUtils.getSqlSession();
        SqlSession sqlSession2 = MybatisUtils.getSqlSession();
      
        UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
        User user1 = mapper1.queryUser(1);
        System.out.println(user1);
        sqlSession1.close();
      
        System.out.println("=====================");
        UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
        User user2 = mapper2.queryUser(1);
        System.out.println(user2);
      
        if (user1 == user2){
            System.out.println("true");
        }else{
            System.out.println("false");
        }
        sqlSession2.close();
      }
      

      image.png

小结:

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

注意:用注解写SQL结果不会被二级缓存。

13.3、Mybatis缓存原理

缓存顺序:

  1. 先看二级缓存中有没有
  2. 再看一级缓存中有没有
  3. 最后才回去查看数据库

image.png

13.4、自定义缓存-ehCache

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

要在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.xml中指定使用我们的 ehcache 缓存实现!

<!--使用第三方缓存ehcache-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

ORM框架(对象、关系、映射框架):

  • Mybatis:半自动持久化框架,可定制性更高,SQL更灵活。
  • Hibernate:全自动持久化框架、全自动SQL,SQL的编写就是个暗箱操作,开发人员不需要自己编写SQL,很方便,但是随之而来的就是SQL的灵活性不高,不能处理复杂的业务,因此慢慢不用了
  • ……还有其他三种等等