MyBatis

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

Reference

1. 简介

1.1 什么是MyBatis

MyBatis logo

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

1.2 获取

  • maven 仓库

    • ```xml
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.13</version>
      
      </dependency>
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33

      - GitHub: [mybatis/mybatis-3](https://github.com/mybatis/mybatis-3)

      ## 1.3 持久化

      - 数据持久化

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

      - 内存:**断电即失**

      - 数据库(Jdbc),io文件持久化

      - 作用
      - 节省内存

      ## 1.4 持久层

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

      ## 1.5 优点

      - 将数据存入到数据库中
      - 操作方便
      - 传统的JDBC代码太复杂,简化–>框架–>自动化
      - 使用的人多 ==(技术没有高低之分!)==

      # 2. 第一个Mybatis程序

      ```mermaid
      graph LR
      搭建环境 --> 导入MyBatis --> 编写代码 --> 测试
  • 参照官方文档进行配置:mybatis – MyBatis 3

2.1 搭建环境

  • 新建maven项目

  • MySQL

    • ``sql CREATE DATABASEmybatis`;

      USE mybatis;

      CREATE TABLE user (

      `id` INT(20) NOT NULL,
      `name` VARCHAR(30) DEFAULT NULL,
      `pwd` VARCHAR(30) DEFAULT NULL,
      PRIMARY KEY(`id`)
      

      )ENGINE=INNODB DEFAULT CHARSET=utf8

      INSERT INTO user(id, name, pwd) VALUES
      (1, ‘bayyy’, ‘123456’),
      (2, ‘dnnner’, ‘123456’),
      (3, ‘BAY’, ‘123465’)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24

      - 导入maven依赖
      - mysql - `mysql-connector-java`
      - mybatis
      - junit

      - pom.xml

      ```xml
      <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.13</version>
      </dependency>
      <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.19</version>
      </dependency>
      <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.28</version>
      </dependency>
  • db.properties

    1
    2
    3
    4
    driver=com.mysql.cj.jdbc.Driver
    url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username=root
    password=123456
  • mybatis-config.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "https://mybatis.org/dtd/mybatis-3-config.dtd">
    <!--核心配置文件-->
    <configuration>
    <!-- 引入外部配置文件 -->
    <properties resource="db.properties">
    <!-- 也可以在内部增加配置, 若存在相同字段, 优先使用外部配置文件 -->
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
    </properties>

    <!-- 设置 -->
    <settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <!-- 别名 -->
    <typeAliases>
    <!-- 给实体类起别名 -->
    <typeAlias type="com.bayyy.pojo.User" alias="User"/>
    <!-- 给包起别名: 扫描实体类的包, 默认别名就是类名首字母小写 -->
    <package name="com.bayyy.pojo"/>
    </typeAliases>

    <!-- 环境配置(default-选择默认环境) -->
    <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"></transactionManager>
    <dataSource type="POOLED"></dataSource>
    </environment>
    </environments>

    <!-- 指定映射文件 -->
    <mappers>
    <mapper resource="com/bayyy/dao/UserMapper.xml"/>
    </mappers>
    </configuration>
  • IDUtils.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @SuppressWarnings("all")    // 抑制警告
    public class IDUtils {
    public static String getId() {
    return UUID.randomUUID().toString().replace("-", "");
    }

    @Test
    public void test() {
    System.out.println(IDUtils.getId());
    }
    }

  • MyBatisUtils.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // sqlSessionFactory --> sqlSession
    public class MyBatisUtils {
    private static SqlSessionFactory sqlSessionFactory;

    static {
    try {
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    } catch (IOException e) {
    throw new RuntimeException(e);
    }
    }

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    #将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
    log4j.rootLogger = DEBUG,console ,file

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

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

    #日志输出级别
    log4j.logger.org.mybatis=DEBUG
    log4j.logger.java.sql=DEBUG
    log4j.logger.java.sql.Statement=DEBUG
    log4j.logger.java.sql.ResultSet=DEBUG
    log4j.logger.java.sql.PreparedStatement=DEBUG

2.2 创建一个模块

2.2.1 编写mybatis的核心配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="UTF-8" ?>
<!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="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- 写完XXXMapper.xml一定要进行配置 -->
</mappers>
</configuration>

image-20230715210825743

2.2.2 编写mybatis工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// sqlSessionFactory --> sqlSession
public class MyBatisUtils {
private static SqlSessionFactory sqlSessionFactory;

static {
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

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

2.3 编写代码

2.3.1 实体类

1
2
3
4
5
6
7
8
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
}

2.3.2 Dao接口

1
2
3
public interface UserDao {
List<User> getUserList();
}

2.3.3 Dao接口实现类

1)mybatis-xml配置(*)

  • UserMapper.xml
1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace->绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.bayyy.dao.UserDao">
<!-- select 查询语句 -->
<select id="getUserList" resultType="com.bayyy.pojo.User">
select * from mybatis.user
</select>
</mapper>

2)UserDaoImpl 实现

1
2
3
4
5
6
7
8
9
10
public class UserDaoImpl implements UserDao{
@Override
public List<User> getUserList() {
// 连接
// 执行sql
// 结果集
// 关闭连接
return null;
}
}

2.4 测试

2.4.1 测试语句

  • 测试在 test 对应文件下使用 Junit 进行, 此处:

image-20230715214456442

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
public void test() {
// 1. 获取sqlSession对象
SqlSession sqlSession = MyBatisUtils.getSqlSession();
// 2. 执行sql
// 方式一:getMapper
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = userMapper.getUserList();
for (User user : userList) {
System.out.println(user);
}

// 方式二:不推荐
List<User> userList2 = sqlSession.selectList("com.bayyy.dao.UserMapper.getUserList");
for (User user : userList2) {
System.out.println(user);
}

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

2.4.2 try-with

使用try-with-resources代替常规try-catch-finally

  • 多条声明 ‘;’ 分割,且后声明的先释放
1
2
3
4
5
6
7
8
9
10
// try-with-resources: try内部的资源会自动释放(且是先声明的资源先释放)
try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = userMapper.getUserList();
for (User user : userList) {
System.out.println(user);
}
} catch (Exception e) {
e.printStackTrace();
}

2.4.3 易错点

1)配置文件

第一个易错点, MapperRegistry, ==配置文件没有注册==:

  • org.apache.ibatis.binding.BindingException: Type interface com.bayyy.dao.UserDao is not known to the MapperRegistry.

image-20230715214631891

  • ==mybatis-config.xml 中配置mappers:==

  • image-20230715214755340

    1
    2
    3
    4
    <mappers>
    <!-- 使用 '/' 表示路径 -->
    <mapper resource="com/bayyy/dao/UserMapper.xml"/>
    </mappers>

2)导出资源

第二个错误点, ExceptionInInitializerError, ==导出资源路径存在问题==:

image-20230715214956980

  • 问题原因:==放在java文件夹下的.xml/.properties不会自动在target目录中生成==

  • 解决方法

    • 在xml中使用resources加入这些文件

    • ```xml

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

      </build>

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27

      - ![image-20230715215825830](https://s2.loli.net/2023/07/15/Xl9dvouy8qQ4DVU.png)

      # 3. CRUD

      ==增删改需要提交错误==

      ## 3.1 namespace

      - namespace中的包名要和 Mapper(Dao) 接口的包名一致

      ## 3.2 select

      > 查询语句

      ![image-20230716131850778](https://s2.loli.net/2023/07/16/VNAJnZIHCmt3zbs.png)

      - `id`: 对应 namespace 中的方法名
      - `resultType`: sql 语句执行的返回值
      - `parameterType`: 参数类型

      ### 3.2.1 编写接口

      ```java
      /* UserMapper.java */
      // 根据id查询用户
      User getUserById(int id);

3.2.2 Mapper.xml配置

1
2
3
4
5
6
7
8
<!-- UserMapper.xml -->
<!-- 根据 id 查询
// 根据id查询用户
User getUserById(int id);
-->
<select id="getUserById" parameterType="int" resultType="com.bayyy.pojo.User">
select * from mybatis.user where id=#{id}
</select>

3.2.3 测试

1
2
3
4
5
6
7
8
@Test
public void test2() {
try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserById(1);
System.out.println(user);
}
}
  • 测试结果:image-20230716132155882

3.3 Insert

:key: 对象中的属性可以直接用 #{属性名} 取出

  • Mapper.xml
1
2
3
4
5
6
7
8
<!-- insert 插入语句
// 插入一个用户
int addUser(User user);
注意: 对象中的属性可以直接用 #{属性名} 取出
-->
<insert id="addUser" parameterType="com.bayyy.pojo.User">
insert into mybatis.user(id, name, pwd) values (#{id}, #{name}, #{pwd})
</insert>

:warning: ==增删改==需要事务处理 ==SqlSession.commit();==

1
2
3
4
5
6
7
8
9
10
11
12
13
try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
int add = sqlSession.getMapper(UserMapper.class).addUser(new User(4, "长江四号", "123456"));
User user = sqlSession.getMapper(UserMapper.class).getUserById(4);
System.out.println(user);
}
try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
User user = sqlSession.getMapper(UserMapper.class).getUserById(4);
System.out.println(user);
}

>>>
User(id=4, name=长江四号, pwd=123456)
null
  • 测试

    • ```java
      @Test
      public void test3() {
      try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
          int add = sqlSession.getMapper(UserMapper.class).addUser(new User(4, "长江四号", "123456"));
          if (add > 0) {
              User user = sqlSession.getMapper(UserMapper.class).getUserById(4);
              System.out.println(user);
          } else {
              System.out.println("插入失败");
          }
          sqlSession.commit();
      }
      
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13

      ## 3.4 Update

      - Mapper.xml

      ```xml
      <!-- update 更新语句
      // 修改一个用户
      int updateUser(User user);
      -->
      <update id="updateUser" parameterType="com.bayyy.pojo.User">
      update mybatis.user set name=#{name}, pwd=#{pwd} where id=#{id}
      </update>
  • 测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void testUpdate() {
try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int update = mapper.updateUser(new User(4, "长江四号", "654321"));
if (update > 0) {
System.out.println("修改成功");
User user = mapper.getUserById(4);
System.out.println(user);
} else {
System.out.println("修改失败");
}
sqlSession.commit();
}
}
  • 测试结果:image-20230716141119624

3.5 Delete

  • Mapper.xml
1
2
3
4
5
6
7
<!-- delete 删除语句
// 删除一个用户
int deleteUser(int id);
-->
<delete id="deleteUser" parameterType="int">
delete from mybatis.user where id=#{id}
</delete>
  • 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void testDelete() {
try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
int delete = sqlSession.getMapper(UserMapper.class).deleteUser(4);
if (delete > 0) {
System.out.println("删除成功");
sqlSession.getMapper(UserMapper.class).getUserList().forEach(System.out::println);
} else {
System.out.println("删除失败");
}
sqlSession.commit();
}
}
  • 测试结果:image-20230716141232311

3.6 万能Map

实体类或数据库表,字段或参数过多时,应当考虑用Map

1
2
// Map 方式修改一个用户
int mapUpdate(Map<String, Object> map);
1
2
3
4
5
6
7
<!-- Map 方式传参
// Map 方式修改一个用户
int mapUpdate(Map<String, Object> map);
-->
<update id="mapUpdate" parameterType="map">
update mybatis.user set name=#{mapName} where id=#{mapId}
</update>
1
2
3
4
5
6
7
8
9
10
@Test
public void testMapUpdate() {
try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("mapId", 4);
map.put("mapName", "黄河四号");
System.out.println(sqlSession.getMapper(UserMapper.class).mapUpdate(map));
sqlSession.commit();
}
}
  • 基本类型传参 parameterType="int" 可以直接取到
  • 对象传参 parameterType='User' 直接取对象属性
  • Map传参 parameterType='map' 直接取对应的 key 得到对应的 value

3.8 模糊查询

  • java代码中使用通配符

    1
    List<User> userList = mapper.getUserLike("%李%");
  • sql拼接中使用通配符

    1
    select * from mybatis.user where name like "%"#{value}"%"

4. 配置解析

  • mybatis-config.xml

4.1 核心配置文件

MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:

image-20230716150749394

  • configuration 配置顺序需要遵循 -

    • The content of element type "configuration" must match "(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?)".
  • 示例:

    • ```xml
      <?xml version=”1.0” encoding=”UTF-8” ?>
      <!DOCTYPE configuration

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

      <!-- 引入外部配置文件 -->
      <properties resource="db.properties">
          <!-- 也可以在内部增加配置, 若存在相同字段, 优先使用外部配置文件 -->
          <property name="username" value="root"/>
          <property name="password" value="123456"/>
      </properties>
      
      <!-- 别名 -->
      <typeAliases>
          <!-- 给实体类起别名 -->
          <typeAlias type="com.bayyy.pojo.User" alias="User"/>
          <!-- 给包起别名: 扫描实体类的包, 默认别名就是类名首字母小写 -->
          <package name="com.bayyy.pojo"/>
      </typeAliases>
      
      <!-- 环境配置(default-选择默认环境) -->
      <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"></transactionManager>
              <dataSource type="POOLED"></dataSource>
          </environment>
      </environments>
      
      <!-- 指定映射文件 -->
      <mappers>
          <mapper resource="com/bayyy/dao/UserMapper.xml"/>
      </mappers>
      

      </configuration>

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41

      ## 4.2 环境配置(environments)

      - MyBatis 可以配置成适应多种环境,**但每个 SqlSessionFactory 实例只能选择一种环境**
      - DEFAULT
      - 事务管理器 - JDBC
      - 数据源 - POOLED

      ### 4.2.1 事务管理器(transactionManager)

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

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

      2. MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)

      > :pushpin: 如果使用 Spring + MyBatis,就没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置。

      ### 4.2.2 数据源(dataSource)

      - 有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]"):
      1. **UNPOOLED**– 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择
      2. ==**POOLED**==– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,**能使并发 Web 应用快速响应请求** (==使用后不会关闭==)
      3. **JNDI** – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用

      ## 4.3 属性(properties)

      ### 4.3.1 描述

      - 可以通过properties属性来实现引用配置文件
      - 这些属性可以在外部进行配置,并可以进行**动态替换**。你既可以在典型的 **Java 属性文件**中配置这些属性,也可以在 **properties 元素**的子元素中设置。==db.properties==

      ### 4.3.2 过程

      - db.properties

      ```properties
      driver=com.mysql.cj.jdbc.Driver
      url=jjdbc:mysql://localhost:3306/mybatis?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=UTC
      username=root
      password=123456
  • mybatis-config.xml : 使用 value="${...}" 获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<!--核心配置文件-->
<configuration>
<!-- 引入外部配置文件 -->
<properties resource="db.properties">
<!-- 也可以在内部增加配置, 若存在相同字段, 优先使用外部配置文件 -->
<property name="username" value="root"/>
<property name="password" value="123456"/>
</properties>

<!-- 环境配置(default-选择默认环境) -->
<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"></transactionManager>
<dataSource type="POOLED"></dataSource>
</environment>
</environments>

<!-- 指定映射文件 -->
<mappers>
<mapper resource="com/bayyy/dao/UserMapper.xml"/>
</mappers>
</configuration>

4.4 类型别名(typeAliases)

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

  • image-20230716153937797
1
2
3
4
5
6
7
8
// mybatis-config.xml
<!-- 别名 -->
<typeAliases>
<!-- 给实体类起别名 -->
<typeAlias type="com.bayyy.pojo.User" alias="User"/>
<!-- 给包起别名: 扫描实体类的包, 默认别名就是类名首字母小写 -->
<package name="com.bayyy.pojo"/>
</typeAliases>
  • 实体类起别名
    • <typeAlias type="com.bayyy.pojo.User" alias="User"/>User 可以用在任何使用 com.bayyy.pojo.User 的地方
  • 包起别名
    • MyBatis 会在包名下面搜索需要的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 com.bayyy.pojo.User 的别名为 user;若有注解,则别名为其注解值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// UserMapper.xml
// 实体类起别名 -> 自定义别名
<!-- select 查询语句 -->
<select id="getUserList" resultType="User">
select * from mybatis.user
</select>
// 包起别名 -> Java Bean 小写
<!-- select 查询语句 -->
<select id="getUserList" resultType="user">
select * from mybatis.user
</select>
// 包起别名 -> @Alias("hello") 注解
<!-- select 查询语句 -->
<select id="getUserList" resultType="hello">
select * from mybatis.user
</select>

4.5 设置

MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为

  • 现介绍几个重要的设置
设置名 描述 有效值 默认值
cacheEnabled 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 true \ false true
lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 true \ false false
useGeneratedKeys 允许 JDBC 支持自动生成主键,需要数据库驱动支持。如果设置为 true,将强制使用自动生成主键。尽管一些数据库驱动不支持此特性,但仍可正常工作(如 Derby)。 true \ false False
mapUnderscoreToCamelCase 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 true \ false False
logImpl 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 SLF4J \ LOG4J(3.5.9 起废弃) \ LOG4J2 \ JDK_LOGGING \ COMMONS_LOGGING \ STDOUT_LOGGING \ NO_LOGGING 未设置

4.6 其他配置

  • typeHandlers(类型处理器)
  • objectFactory(对象工厂)
  • plugins(插件)
    1. mybatis-generator-core
    2. mybatis-plus
    3. 通用mapper

4.7 映射器(mappers)

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

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

    1
    2
    3
    4
    5
    <mappers>
    <mapper resource="com/bayyy/dao/UserMapper.xml"/>
    <!-- 将此目录下的通配 -->
    <mapper resource="com/bayyy/dao/*Mapper.xml"/>
    </mappers>
  • 方式二:使用映射器接口实现类的完全限定类名

    :warning: 接口和它的Mapper配置文件必须同名

    :warning: 接口和它的Mapper配置文件必须在同一个包下!

    1
    2
    3
    <mappers>
    <mapper class="com.bayyy.dao.UserMapper"/>
    </mappers>
  • 方式三:将包内的映射器接口全部注册为映射器

    :warning: 接口和它的Mapper配置文件必须同名

    :warning: 接口和它的Mapper配置文件必须在同一个包下!

    1
    2
    3
    <mappers>
    <package name="com.bayyy.dao"/>
    </mappers>
  • 方式四:使用完全限定资源定位符(URL)

    1
    2
    3
    4
    5
    <mappers>
    <mapper url="file:///var/mappers/AuthorMapper.xml"/>
    <mapper url="file:///var/mappers/BlogMapper.xml"/>
    <mapper url="file:///var/mappers/PostMapper.xml"/>
    </mappers>

4.8 生命周期和作用域 Scope

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

1
2
3
4
5
6
7
8
9
flowchart LR

subgraph 配置文件
direction TB
id3("mybatis-config.xml配置文件") --> id1(SqlSessionFactoryBuilder)
end
开始 --> id1
id1 --> SqlSessionFactory --> SqlSession --> id2("Sql Mapper") --> 结束
SqlSession -.不推荐.-> 结束
  • SqlSessionFactoryBuilder
    • 一旦创建了 SqlSessionFactory,就不再需要它了
    • ==(局部变量)==
  • SqlSessionFactory
    • 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例 (数据库连接池)
    • 因此 SqlSessionFactory 的最佳作用域是应用作用域
    • ==单例模式或者静态单例模式==
  • SqlSession
    • 每个线程都应该有它自己的 SqlSession 实例 (连接请求)
    • SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域
    • 用完后需要赶紧关闭,否则资源被占用!

image-20230716165650546

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

5.1 问题

image-20230716165957060

  • 运行结果:image-20230716170025707

  • 解决方法:

    1. 起别名 select id,name,pwd as password from mybatis.user where id = #{id}
    2. resultMap

5.2 resultMap

1
2
3
4
5
6
7
8
9
10
11
12
13
<resultMap id="userMap" type="User">
<!-- name -> sql中的字段名 -->
<!-- 明确的指定映射关系可以不用显示的写出来 -->
<!-- <result property="id" column="id"/> -->
<!-- <result property="name" column="name"/> -->
<result property="password" column="pwd"/>
</resultMap>

<!-- select 查询语句 -->
<!-- resultMap -> 返回结果集的映射 -->
<select id="getUserList" resultMap="userMap">
select * from mybatis.user
</select>
  • resultMap 元素是 MyBatis 中最重要最强大的元素
  • ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
  • ResultMap 的优秀之处——你完全可以不用显式地配置它们。

6. 日志

6.1 日志工厂

logImpl 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 SLF4J \ LOG4J(3.5.9 起废弃) \ LOG4J2 \ JDK_LOGGING \ COMMONS_LOGGING \ STDOUT_LOGGING \ NO_LOGGING 未设置
  • SLF4J
  • LOG4J 【掌握】
  • LOG4J2
  • JDK_LOGGING
  • COMMONS_LOGGING
  • STDOUT_LOGGING【掌握】
  • NO_LOGGING
1
2
3
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
  • 测试结果:image-20230716173723471

6.2 Log4j

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

6.2.1 配置过程

  1. 导入依赖包

    1
    2
    3
    4
    5
    6
    <!-- https://mvnrepository.com/artifact/log4j/log4j -->
    <dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
    </dependency>
  2. 在resources文件夹下建立log4j.properties文件进行配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    #将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
    log4j.rootLogger = DEBUG,console ,file

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

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

    #日志输出级别
    log4j.logger.org.mybatis=DEBUG
    log4j.logger.java.sql=DEBUG
    log4j.logger.java.sql.Statement=DEBUG
    log4j.logger.java.sql.ResultSet=DEBUG
    log4j.logger.java.sql.PreparedStatement=DEBUG
  3. mybatis-config.xml 配置

    1
    2
    3
    <settings>
    <setting name="logImpl" value="LOG4J"/>
    </settings>
  4. 测试结果:image-20230716181725525

6.2.2 简单使用

  1. 导包及生成日志对象

    1
    2
    3
    4
    5
    import org.apache.log4j.Logger;

    public class UserMapperTest {
    static Logger logger = Logger.getLogger(UserMapperTest.class);
    }
  2. 日志级别

    1
    2
    3
    logger.info("info:进入了testLog4j");
    logger.debug("DEBUG:进入了testLog4j");
    logger.error("erro:进入了testLog4j");
  3. 可在对应位置查看日志文件信息

    image-20230716182504767

7. 分页

作用:减少数据的处理量

7.1 使用Limit分页

1
2
SELECT * from user limit startIndex,pageSize
SELECT * from user limit 3 #[0,n]

7.1.1 接口

1
2
// 分页查询
List<User> getUserByLimit(Map<String, Integer> map);

7.1.2 Mapper.xml

1
2
3
4
5
6
<!-- 分页查询 -->
<!-- // 分页查询
List<User> getUserByLimit(Map<String, Integer> map); -->
<select id="getUserByLimit" parameterType="map" resultType="User">
select * from mybatis.user limit #{startIndex}, #{pageSize}
</select>

7.1.3 测试

1
2
3
4
5
6
7
8
9
@Test
public void testGetUserByLimit() {
try (SqlSession sqlSession=MyBatisUtils.getSqlSession()){
HashMap<Object, Integer> map = new HashMap<Object, Integer>();
map.put("startIndex", 0);
map.put("pageSize", 2);
sqlSession.getMapper(UserMapper.class).getUserByLimit(map).forEach(System.out::println);
}
}
  • 测试结果:image-20230716183842267

7.2 RowBounds分页

不推荐

7.2.1 接口

1
2
// 分页查询: RowBounds
List<User> getUserByRowBounds();

7.2.2 Mapper.xml

1
2
3
4
5
6
<!-- 分页查询: RowBounds -->
<!-- // 分页查询
List<User> getUserByRowBounds(); -->
<select id="getUserByRowBounds" resultType="User">
select * from mybatis.user
</select>

7.2.3 测试

1
2
3
4
5
6
7
8
@Test
public void testGetUserByRowBounds() {
try (SqlSession sqlSession=MyBatisUtils.getSqlSession()){
// RowBounds实现
RowBounds rowBounds = new RowBounds(1, 2);
sqlSession.selectList("com.bayyy.dao.UserMapper.getUserByRowBounds", null, rowBounds).forEach(System.out::println);
}
}

7.3 分页插件

了解即可

image-20230716184453361

8. 使用注解开发

8.1 面向接口编程

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

8.1.1 面向接口的理解

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

8.1.2 三个面向的区别

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

8.2 使用注解开发

  • 本质:反射机制实现
  • 底层:动态代理!
1
2
3
4
// UserMapper.java
// 查询全部用户
@Select("select * from user")
List<User> getUserList();
  • 无需 Mapper.xml 文件

  • mybatis-config.xml

    1
    2
    3
    4
    <!-- 绑定接口 -->
    <mappers>
    <mapper class="com.bayyy.dao.UserMapper"/>
    </mappers>
  • 测试

    1
    2
    3
    4
    5
    6
    7
    @Test
    public void testSelectAll() {
    // try-with-resources: try内部的资源会自动释放(且是先声明的资源先释放)
    try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
    sqlSession.getMapper(UserMapper.class).getUserList().forEach(System.out::println);
    }
    }
  • 测试结果:image-20230716190116868

8.3 MyBatis执行流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
---
title: MyBatis执行流程
---
flowchart TB
id1("Resources获取加载全局配置文件") --> id2("实例化SqlSessionFactoryBuilder构造器")
id2 --> id3[["解析配置文件流XMLConfigBuilder"]]
id3 --> id4[["Configuration所有的配置信息"]]
id4 --> id5("SqlSessionFactory实例化")
id5 --> id7(["transactional事物管理"])
id7 --> id8("创建executor执行器")
id8 --> id9("创建SqlSession")
id9 --> id10("实现CRUD")
id10 --> id7
id10 --> id11{"查看是否执行成功?"}
id11 --> id7
id11 --> id12("提交事务")
id12 --> 关闭

8.4 CRUD注解

8.4.1 自动提交事务

1
2
3
4
// MybatisUtils.java
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession(true);
}

image-20230716192244707

8.4.2 使用

  • UserMapper.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 查询全部用户
@Select("select * from user")
List<User> getUserList();

// 根据id查询用户
// @Param("id") 与 #{id} 对应
// 如果存在多个参数,必须需要使用 @Param
@Select("select * from user where id = #{id}")
User getUserById(@Param("id") int id);

// 插入一个用户
// 引用对象的属性名可以直接使用 #{属性名}
@Insert("insert into user(id, name, pwd) values(#{id}, #{name}, #{pwd})")
int addUser(User user);

// 修改一个用户
@Update("update user set name = #{name} where id = #{id}")
int updateUser(User user);

// 删除一个用户
@Delete("delete from user where id = #{id}")
int deleteUser(int id);
  • 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    @Test
    public void testSelectAll() {
    // try-with-resources: try内部的资源会自动释放(且是先声明的资源先释放)
    try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    mapper.getUserList().forEach(System.out::println);
    }
    }

    @Test
    public void testSelectById() {
    try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
    System.out.println(sqlSession.getMapper(UserMapper.class).getUserById(1));
    }
    }

    @Test
    public void testAddUser() {
    try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
    sqlSession.getMapper(UserMapper.class).addUser(new User(5, "长江五号", "123456"));
    // openSession(true) 会自动提交事务
    }
    }

    @Test
    public void testUpdate() {
    try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
    sqlSession.getMapper(UserMapper.class).updateUser(new User(5, "长江五号", ""));
    System.out.println(sqlSession.getMapper(UserMapper.class).getUserById(5));
    }
    }

    @Test
    public void testDelete() {
    try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
    sqlSession.getMapper(UserMapper.class).deleteUser(5);
    sqlSession.getMapper(UserMapper.class).getUserList().forEach(System.out::println);
    }
    }

8.4.3 @Param

  • 基本类型的参数或者String类型,需要加上
  • 引用类型不需要加
  • 如果只有一个基本类型的话,可以忽略,但是建议都加上!
  • 我们在SQL中引用的就是我们这里的@Param(“”)中设定的属性名!
    • #{}${} 的区别:#{} 可以很大程度上防止sql注入

9. Lombok

Lombok是一款Java开发插件,使得Java开发者可以通过其定义的一些注解来消除业务工程中穴长和繁琐的代码,尤其对于简单的Java模型对象(POJO)。在开发环境中使用Lombok插件后,Java开发人员可以节省出重复构建,诸如hashCode和equals这样的方法以及各种业务对象模型的accessor和ToString等方法的大量时间。对于这些方法,它能够在编译源代码期间自动帮我们生成这些方法,并没有如反射那样降低程序的性能。

  • IDE 安装 Lombok 插件
1
2
3
4
5
6
7
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
<scope>provided</scope>
</dependency>

9.1 典型的注解

1
2
3
4
5
6
@Data:	无参构造、get、set、toString、hashCode、equals
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
@ToString
@Getter and @Setter

9.2 插件简介中更多注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@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

10. 多对一处理

多对一

  • 多个学生,对应一个老师
  • 对于学生而言,关联–多个学生,关联一个老师【多对一】
  • 对于老师而言,集合–一个老师,有很多个学生【一对多】
  • sql 建表

    • ``sql CREATE TABLEteacher` (

      `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’);

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29

      ## 10.1 测试环境搭建

      1. 实体类 Teacher, Student
      2. Mapper 接口
      3. Mapper.xml
      4. mybatis-config.xml 核心配置文件绑定注册
      - 此处放在 `resources/com/bayyy/dao` 中,要注意建包是否出问题
      - ![image-20230716202205735](https://s2.loli.net/2023/07/16/nAN6SatmwxvWsgD.png)
      5. 测试查询是否能够成功

      ## 10.2 按照查询嵌套处理

      ```xml
      <!-- 按照查询嵌套处理 -->
      <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>

10.3 按照结果嵌套处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 按照结果嵌套处理 -->
<select id="getStudent2" resultMap="studentTeacher2">
select s.id sid, s.name sname, t.id tid, t.name tname
from student s, teacher t
where s.tid = t.id
</select>
<resultMap id="studentTeacher2" type="student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<!-- 复杂的属性: 需要单独处理
对象: association
集合: collection -->
<association property="teacher" javaType="teacher">
<result property="name" column="tname"/>
<result property="id" column="tid"/>
</association>
</resultMap>

11 . 一对多处理

11.1 环境搭建

1
2
3
4
5
6
7
@Data
public class Teacher {
private int id;
private String name;
// 一个老师拥有多个学生
private List<Student> students;
}

11.2 按照结果嵌套处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- 按照结果嵌套查询 -->
<select id="getTeacherAll" resultMap="teacherMap">
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="teacherMap" type="teacher">
<!-- column: 列名
property: 属性名
-->
<result column="tid" property="id"/>
<result column="tname" property="name"/>
<!-- 复杂的属性要单独处理
collection: 集合
association: 对象
javaType: 指定类型
ofType: 指定类型(List 的泛型)
-->
<collection property="students" ofType="student">
<result column="sid" property="id"/>
<result column="sname" property="name"/>
</collection>
</resultMap
  • 测试结果:image-20230717230304210

11.3 按照查询嵌套处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- 按照查询嵌套结果 -->
<select id="getTeacherAll2" resultMap="teacherMap2">
select * from teacher where id = #{tid};
</select>
<resultMap id="teacherMap2" type="teacher">
<result property="id" column="id"/>
<result property="name" column="name"/>
<collection property="students" javaType="ArrayList" ofType="student" select="getStudent" column="id">
<id column="id" property="id"/>
<result column="name" property="name"/>
</collection>
</resultMap>
<select id="getStudent" resultType="student">
select * from student where tid = #{tid};
</select>
  • 测试结果:image-20230717231032809

12. 动态SQL

根据不同的条件生成不同的SQL语句

在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。

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

12.1 搭建环境

  • 建表
1
2
3
4
5
6
7
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
  • pojo

    1
    2
    3
    4
    5
    6
    7
    8
    @Data
    public class Blog {
    private int id;
    private String title;
    private String author;
    private Date createTime; // 此处与数据库不完全相同 create_time(通过配置文件解决:mapUnderscoreToCamelCase)
    private int views;
    }
    1
    2
    3
    4
    5
    // mybatis-config.xml
    <settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
  • IDutils: 生成UUID

    1
    2
    3
    4
    5
    6
    @SuppressWarnings("all")    // 抑制警告
    public class IDutils {
    public static String getId() {
    return UUID.randomUUID().toString().replace("-", "");
    }
    }

12.2 IF

这条语句提供了可选的查找文本功能

==where== 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

1
2
3
// 查询博客
List<Blog> selectBlogIf(HashMap map);
// List<Blog> selectBlogIf(Blog blog);
1
2
3
4
5
6
7
8
9
10
11
<select id="selectBlogIf" 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>
1
2
3
4
5
6
7
8
9
10
11
HashMap map = new HashMap();
map.put("author", "Bayyy");
map.put("title", "Mybatis如此简单");
mapper.selectBlogIf(map).forEach(System.out::println);

/*
Blog blog = new Blog();
blog.setAuthor("Bayyy");
blog.setTitle("Mybatis如此简单");
mapper.selectBlogIf(blog).forEach(System.out::println);
*/

12.3 choose (when, otherwise)

choose 元素,它有点像 Java 中的 switch 语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<select id="selectBlogChoose" 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>

12.4 trim (where, set)

可以通过自定义 ==trim== 元素来定制 where 元素的功能

  • 覆盖前缀值设置

    1
    2
    3
    <trim prefix="WHERE" prefixOverrides="AND |OR ">
    ...
    </trim>
  • 覆盖后缀值设置

    1
    2
    3
    <trim prefix="SET" suffixOverrides=",">
    ...
    </trim>

用于动态更新语句的类似解决方案叫做 ==set==。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列

  • set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<update id="updateBlogSet" parameterType="map">
update blog
<set>
<if test="title != null">
title = #{title},
</if>
<if test="author != null">
author = #{author},
</if>
<if test="views != null">
views = #{views},
</if>
</set>
where id = #{id}
</update>

12.5 Foreach

对集合进行遍历(尤其是在构建 IN 条件语句的时候)

foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符

  • :pushpin: 可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach
    • 当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素
    • 当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<select id="selectBlogForeach" parameterType="map" resultType="blog">
select * from blog
<where>
<foreach collection="ids" item="id"
open="id in (" close=")" separator=",">
#{id}
</foreach>
</where>
</select>

<!-- 或者 -->
<select id="selectBlogForeach" parameterType="map" resultType="blog">
select * from blog
<where>
<foreach collection="list" item="item" index="index"
open="id in (" close=")" separator=",">
#{item}
</foreach>
</where>
</select>
1
2
3
4
5
6
7
8
9
10
@Test
public void testSelectBlogForeach() {
try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

HashMap map = new HashMap();
map.put("ids", new Integer[]{1, 2, 3});
mapper.selectBlogForeach(map).forEach(System.out::println);
}
}

12.6 SQL片段

允许插入一种语言驱动,并基于这种语言来编写动态 SQL 查询语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<sql id="if-title-author">
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</sql>

<select id="queryBlogIF" parameterType="map" resultType="Blog">
select * from mybatis.blog
<where>
<include refid="if-title-author"></include>
</where>
</select>
  • :warning: 不要存在where标签

13. 缓存(了解)

13.1 简介

13.1.1 什么是缓存[Cache]?

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

13.1.2 为什么使用缓存?

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

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

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

    13.2 Mybatis缓存

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

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

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

    13.3 一级缓存

13.3.1 简介

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

13.3.2 缓存失效情况

  1. 查询不同的东西;
  2. 增删改操作,可能会改变原来的数据,所以必定会刷新缓存!
  3. 查询不同的Mapper.xml
  4. 手动清理缓存!

一级缓存相当于一个Map

13.4 二级缓存

13.4.1 简介

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

13.4.2 开启步骤

  1. 在mybatis-config.xml开启全局缓存

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

    1
    2
    <!--在当前Mapper.xml中使用二级缓存-->
    <cache/>
    • 也可以自定义参数

      1
      2
      3
      4
      5
      6
      <!--在当前Mapper.xml中使用二级缓存: 以下为默认配置-->
      <cache
      eviction="FIFO"
      flushInterval="60000"
      size="512"
      readOnly="true"/>
  3. 实体类序列化

13.4.3 二级缓存

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

    13.5 自定义缓存-ehcache

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

  • 要在程序中使用ehcache,先要导包!
  • 在mapper中指定使用我们的ehcache缓存实现!