MyBatis 是一款优秀的持久层框架,它支持定制化 SQL存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录。本文简单记录一些关于MyBatis的问题。

参考文章:

Maven基础&MyBatis

【spring boot】 mybatis配置双数据源/多数据源

Mybatis概述

Mybatis、持久层、框架简介

MyBatis中文网

  • MyBatis 是一款优秀的持久层框架,用于简化 JDBC 开发
  • MyBatis 本是 Apache 的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github
  • 官网:mybatis – MyBatis 3 | 简介

持久层:

  • 负责将数据保存到数据库的那一层代码。

    以后开发我们会将操作数据库的Java代码作为持久层。而Mybatis就是对jdbc代码进行了封装

  • JavaEE三层架构:表现层做页面展示、业务层做逻辑处理、持久层对数据持久化

  • 下图是持久层框架的使用占比。

    image-20250728215016340

框架:

  • 框架就是一个半成品软件,是一套可重用的、通用的、软件基础代码模型
  • 在框架的基础之上构建软件编写更加高效、规范、通用、可扩展

Mybatis对比JDBC

image-20250729124941788


核心配置文件mybatis-config.xml

核心配置文件标签必须有向后顺序(相比之下,SQL映射配置文件的同级标签没有先后顺序),如下

  • configuration(配置)
    • properties(属性)
    • settings(设置)
    • typeAliases(类型别名)
    • typeHandlers(类型处理器)
    • objectFactory(对象工厂)
    • plugins(插件)
    • environments(环境配置)
      • environment(环境变量)
        • transactionManager(事务管理器)
        • dataSource(数据源)
    • databaseldProvider(数据库厂商标识)
    • 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
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"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--根标签configuration-->
<configuration>
<!-- typeAliases类型别名-->
<!-- 指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean-->
<typeAliases>
<package name="package1.pojo"/>
</typeAliases>

<!--
environments:配置数据库连接环境信息。可以配置多个environment,通过default属性切换不同的environment
尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
-->
<!-- default是每个environment默认使用的环境 ID-->
<environments default="development">
<!-- 指定每个 environment 元素定义的环境 ID-->
<!-- 注意:环境可以随意命名,但务必保证默认的环境 ID 要匹配其中一个环境 ID。-->
<environment id="development">
<!-- 事务管理器transactionManager的配置,不用太在意,事务管理之后是用Spring接管-->
<!-- 在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"):-->

<!-- JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。-->
<!-- MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。-->
<transactionManager type="JDBC"/>
<!-- 数据源dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。-->

<!-- 大多数 MyBatis 应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。-->
<!-- 有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]"):-->
<!-- POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。-->
<!-- UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。-->
<!-- JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。-->
<dataSource type="POOLED">
<!--数据库连接信息-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--加载sql映射文件,告诉MyBatis 去哪寻找映射SQL 的语句。-->
<!-- Maven项目编译后,java和resources下的目录和文件都在同一个跟目录下,所以路径是这样的,注意路径是/,不是.-->
<!-- <mapper resource="UserMapper.xml"/>-->
<!-- <mapper resource="package1/mapper/UserMapper.xml"/>-->
<!-- 扫描包指定name的包下所有mapper配置文件-->
<package name="package1.mapper"/>
</mappers>
</configuration>

properties【属性标签】

作用:将数据库配置属性从dataSource标签内部提取到外部
属性:
resource:设置外部属性文件类路径
url:设置外部属性文件真实路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    <!--    属性标签-->
<properties resource="druid.properties"></properties>
<!-- 设置数据库环境-->
<environments default="development">
<environment id="development">
<!--设置事务管理器-->
<transactionManager type="JDBC"/>
<!--设置数据源-->
<dataSource type="POOLED">
<property name="driver" value="${driverClassName}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
<!-- <property name="initialSize" value="${initialSize}"/>-->
</dataSource>
</environment>
</environments>

settings【设置标签】

作用: 是mybatis中极为重要的调整设置,他们会改变mybatis的运行时行为

mapUnderscoreToCamelCase:开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。
默认值为false,当设置为true时开启驼峰命名
注意:只能将a_bc与aBc自动映射,不能将a_b与aBc自动映射

1
2
3
4
<settings>
<!--开启驼峰式命名自动映射-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

typeAliases【类型别名】

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

1
2
3
4
5
6
7
8
<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>

也可以指定一个包名进行扫描,MyBatis 会在包名下面搜索需要的 Java Bean,比如:

1
2
3
4
5
<!--    typeAliases类型别名-->
<!-- 给指定name包下的所有类起一个别名,-->
<typeAliases>
<package name="package1.pojo"/>
</typeAliases>

这样sql映射文件resultType属性就可以省略路径package1.pojo:

1
2
3
4
5
6
7
8
<!-- 映射配置文件 UserMapper.xml-->
<!-- namespace名称空间,该命名空间和对应mapper接口的全限定名一致 -->
<mapper namespace="package1.mapper.UserMapper">
<!-- resultType="User"或"user"也可以,因为核心配置文件中有给package1.pojo起别名,且有别名后不区分大小写 -->
<select id="selectAll" resultType="package1.pojo.User">
select * from tb_user;
</select>
</mapper>

typeHandlers【类型处理器】

objectFactory【对象工厂】

plugins(插件)

environments【数据库环境设置】

作用:配置数据库连接环境信息。

  • 可以配置多个environment。
  • 环境可以随意命名,但务必保证默认的环境 ID 要匹配其中一个环境 ID。这样通过default属性切换不同的environment环境
  • 尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

属性说明

environments属性default是每个environment默认使用的环境 ID

environment属性id是指定该 environment 元素定义的环境 ID。

事务管理器transactionManager属性type有两种(type=”[JDBC|MANAGED]”)):
JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。

数据源 dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。
type有三种(type=”[UNPOOLED|POOLED|JNDI]”):
POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。
UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。
JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- default是每个environment默认使用的环境 ID-->
<environments default="development">
<!-- 指定每个environment元素定义的环境ID,注意:环境可随意命名,但务必保证默认环境ID要匹配其中一个环境 ID。-->
<environment id="development">
<!-- 事务管理器transactionManager的配置,不用太在意,事务管理之后是用Spring接管 -->
<transactionManager type="JDBC"/>
<!-- 大多 MyBatis 应用程序会按示例中例子配置数据源。数据源配置可选,但要启用延迟加载特性就必须配置数据源。-->
<dataSource type="POOLED">
<!-- 数据库连接信息 -->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
</environments>

databaseIdProvider(数据库厂商标识)

mappers 【映射器】

作用:加载映射文件

1
2
3
4
5
6
<mappers>
<!-- 加载sql映射文件,告诉MyBatis 去哪寻找映射SQL 的语句。-->
<mapper resource="UserMapper.xml"/>
<!-- 或者也可以扫描包寻找sql映射文件文件,推荐这种方法 -->
<package name="package1.mapper"/>
</mappers>

Mybatis入门案例(不用mapper)

代码实现

需求:查询user表中所有的数据

  1. 创建user表,添加数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    create database mybatis;
    use mybatis;
    drop table if exists tb_user;
    create table tb_user(
    id int primary key auto_increment,
    username varchar(20),
    password varchar(20),
    gender char(1),
    addr varchar(30)
    );
    INSERT INTO tb_user VALUES (1, 'zhangsan', '123', '男', '北京');
    INSERT INTO tb_user VALUES (2, '李四', '234', '女', '天津');
    INSERT INTO tb_user VALUES (3, '王五', '11', '男', '西安');
  2. 创建模块,导入坐标

    在创建好的模块中的 pom.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
    <dependencies>
    <!--mybatis 依赖-->
    <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.5</version>
    </dependency>
    <!--mysql 驱动-->
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.46</version>
    </dependency>
    <!-- 添加slf4j日志api -->
    <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.20</version>
    </dependency>
    <!-- 添加logback-classic依赖 -->
    <dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
    </dependency>
    <!-- 添加logback-core依赖 -->
    <dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>1.2.3</version>
    </dependency>
    </dependencies>

    其实依赖只需要导入mybatis,mysql即可,其他是日志、测试之类的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <configuration>
    <!-- CONSOLE :表示当前的日志信息是可以输出到控制台的。 -->
    <appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
    <pattern>[%level] %cyan([%thread]) %boldGreen(%logger{15}) - %msg %n</pattern>
    </encoder>
    </appender>
    <!-- 这里要改,包名-->
    <logger name="package1" level="DEBUG" additivity="false">
    <appender-ref ref="Console"/>
    </logger>
    <!-- level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF , 默认debug
    <root>可以包含零个或多个<appender-ref>元素,标识这个输出位置将会被本日志级别控制。
    -->
    <root level="DEBUG">
    <appender-ref ref="Console"/>
    </root>
    </configuration>

    注意:需要在项目的 resources 目录下创建logback的配置文件

    slf4j,simple logging facade for java的缩写,翻译为java的简单日志外观。slf4j是一个开源项目,它提供我们一个一致的API来使用不同的日志框架,比如: java.util.logging,logback,log4j等。slf4j使用户可以在运行时嵌入他们想使用的日志框架。从名字中可以看出,它其实使用的是facade设计模式来实现的。

    Logback是SpringBoot内置的日志处理框架,你会发现spring-boot-starter其中包含了spring-boot-starter-logging,该依赖内容就是 Spring Boot 默认的日志框架 logback。官方文档:http://logback.qos.ch/manual/

  3. 编写 MyBatis 核心配置文件 – > 替换连接信息 解决硬编码问题

    在模块下的 resources 目录下创建mybatis的配置文件 mybatis-config.xml,内容如configuration【根标签】

  4. 编写 SQL 映射文件 –> 统一管理sql语句,解决硬编码问题

    在模块的 resources 目录下创建映射配置文件 UserMapper.xml,内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <?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">
    <!-- 映射配置文件 UserMapper.xml-->
    <!-- namespace名称空间,该命名空间和对应mapper接口的全限定名一致-->
    <mapper namespace="test">
    <select id="selectAll" resultType="package1.pojo.User">
    select * from tb_user;
    </select>
    </mapper>

    另外,可通过resultMap实现对数据库列名起别名,以解决数据库列名和User类属性命名法不对应问题(数据库标识符不区分大小写,采用下划线命名法,java标识符常用驼峰命名法):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <!-- 统一起别名,resultMap标签的id为自定义的唯一标识,type为package1.pojo.User,
    若核心配置有typeAliases扫描pojo包,user -->
    <resultMap id="userResultMap" type="user">
    <!-- 对数据库的指定列起别名-->
    <result column="username" property="userName"/>
    <result column="password" property="passWord"/>
    </resultMap>
    <!-- 起别名了,resultType改成resultMap,值为上面resultMap的id-->
    <select id="selectAll" resultMap="userResultMap">
    select * from tb_user;
    </select>

    根标签【mapper】
    作用:所用子标签均需书写在mapper内部
    namespace与接口全路径类名【类的全限定名】一致

    八大子标签

    insert:定义增加SQL语句
    delete:定义删除SQL语句
    update:定义修改语句
    select:定义查询SQL语句
    sql:定义SQL语句块
    resultMap:定义结果集映射【resultType解决不了时,使用resultMap】
    cache:定义缓冲类
    cache-ref:定义引用缓存

  5. 在package1.pojo包下创建 User类

    POJO(Plain Old Java Objects)译为,简单的Java对象,其实就是没有从任何类继承、也没有实现任何接口,更没有被其它框架侵入的,没有遵从特定的Java对象模型、约定或框架(如EJB)的不受任何限制的java对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class User {
    private int id;
    private String username;
    // 下面两个passWord改成password
    private String passWord;
    private String gender;
    private String addr;
    // 省略了 setter 和 getter
    @Override
    public String toString() {
    return "User{" +
    "id=" + id +
    ", username='" + username + '\'' +
    ", passWord='" + password + '\'' +
    ", gender='" + gender + '\'' +
    ", addr='" + addr + '\'' +
    '}';
    }
    }
  6. 在package1包下编写 MybatisDemo 测试类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
     public class Demo {
    public static void main(String[] args) throws IOException {
    // 1. 加载mybatis的核心配置文件,获取 SqlSessionFactory,session译为“会话”,factory译为“工厂”。
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    // 2. 获取SqlSession对象,用它来执行sql
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // sqlSession = sqlSessionFactory.openSession(true); // 设置自动提交事务,这种情况不需要手动提交事务了
    // 3. 执行sql,这步不用记,一般这步都是使用mapper代理开发
    sqlSession.getMapper(UserMapper.class);
    // 参数是一个字符串,该字符串必须是映射配置文件的namespace.id
    List<User> users = sqlSession.selectList("test.selectAll");
    System.out.println(users);
    // 4.要手动提交,不然会回滚事务。如果前面sqlSessionFactory.openSession(true),就不用再提交事务了
    sqlSession.commit();
    // 5. 释放资源
    sqlSession.close();
    }
    }

    第三步执行SQL前的代码在所有案例中都是一样的,直接复制粘贴即可。而且整合Spring后就只剩mapper优化后的执行方法

  7. 回顾对比一下jdbc:

    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
    public class Demo {
    public static void main(String[] args) throws Exception {
    // 1. 注册驱动
    // Class.forName("com.mysql.jdbc.Driver");// 通过反射获取Driver实现类对象,从而加载驱动
    // 注册驱动语句DriverManager.registerDriver(driver)在Driver的静态代码块里做过了。
    // 此句仅在mysql可省略,其他数据库不能省略

    // 2. 获取连接
    String url = "jdbc:mysql://127.0.0.1:3306/db1?useSSL=false";
    String username = "root";
    String passWord = "1234";
    Connection conn = DriverManager.getConnection(url, username, passWord);
    // 3. 定义sql
    String sql = "update student set age = 80 where name='xiaohua'";
    // 4. 获取执行sql的对象 Statement
    Statement stmt = conn.createStatement();
    // 5. 执行sql
    int count = stmt.executeUpdate(sql);//受影响的行数
    // 6. 处理结果
    System.out.println(count);
    // 7. 释放资源
    stmt.close();
    conn.close();
    }
    }

SqlSessionFactory工具类抽取

1
2
3
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

第三步执行SQL前的代码在所有案例中都是一样的,这些重复代码就会造成一些问题:

  1. 重复代码不利于后期的维护

  2. SqlSessionFactory工厂类进行重复创建

  3. 资源消耗非常大但性能却非常低。

解决方案

  • 代码重复可以抽取工具类
  • 对指定代码只需要执行一次可以使用静态代码块
1
2
SqlSession sqlSession = sqlSessionFactory.openSession();
// sqlSession = sqlSessionFactory.openSession(true); // 设置自动提交事务,这种情况不需要手动提交事务了

虽然上面语句也重复,但不能抽取到工具类里。因为SqlSession是一个连接、会话,每次连接数据库时候创建一次会话是合适,如果所有连接都共用一个会话会互相影响。

SqlSession是一个会话,相当于JDBC中的一个Connection对象,Mybatis中所有的数据库交互都由SqlSession来完成。

抽取工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class SqlSessionFactoryUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
// 静态代码块会随着类的加载而自动执行,且只执行一次,静态代码块不能抛异常,要用try-catch
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSessionFactory getSqlSessionFactory(){
return sqlSessionFactory;
}
}

使用工具类

1
SqlSessionFactory sqlSessionFactory = SqlSessionFactoryUtils.getSqlSessionFactory();

IDEA连接数据库

解决SQL映射文件的警告提示:

在入门案例映射配置文件中存在报红的情况:数据表显示红色

  • 产生的原因:Idea和数据库没有建立连接,不识别表信息。但是大家一定要记住,它并不影响程序的执行。
  • 解决方式:在Idea中配置MySQL数据库连接。

IDEA中配置MySQL数据库连接

  • 点击IDEA右边框的 Database ,在展开的界面点击 + 选择 Data Source ,再选择 MySQL

  • 在弹出的界面进行基本信息的填写

  • 点击完成后就能看到数据库编译器界面

  • 而此界面就和 navicat 工具一样可以进行数据库的操作。也可以编写SQL语句

如果发现写表名时没有提示:打开File的settings,把SQL Dialects选项的右边那个None改为你的默认数据库。


Mapper代理开发

目的

  • 解决原生方式中的硬编码
  • 简化后期执行SQL
1
2
3
4
5
6
7
8
// 3.执行sqL
List<User> users = sqlSession.selectList("test.selectAll");
System.out.println(users);

// 3.获取接口代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 4.执行方法,其实就是执行sql语句
List<User> users = userMapper.selectAll();

使用mapper实现查询

  1. 定义与SQL映射文件同名的Mapper接口,并且将Mapper接口和SQL映射文件放置在同一目录下。如下图放就可以,因为编译Maven后java和resources下的目录和文件都在同一个根目录下。

    注意:在resources下建多层文件夹要用斜杠,例如aa/bb,不能aa.bb。如果直接建aa.bb,则只建了一个名为aa.bb的文件夹,而不是两个。

  2. 设置SQL映射文件的namespace属性为Mapper接口全限定名:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <?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">
    <!-- 映射配置文件 UserMapper.xml-->
    <!--namespace名称空间,该命名空间和对应mapper接口的全限定名一致-->
    <mapper namespace="package1.mapper.UserMapper">
    <select id="selectAll" resultType="package1.pojo.User">
    select * from tb_user;
    </select>
    </mapper>
  3. 在 Mapper 接口中定义方法,方法名就是SQL映射文件中sql语句的id,并保持参数类型和返回值类型一致

  4. Mapper代理开发

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class Demo {
    public static void main(String[] args) throws IOException {
    // 1. 加载mybatis的核心配置文件,获取 SqlSessionFactory
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

    // 2. 获取SqlSession对象,用它来执行sql
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // 3. 执行sql
    // 参数是一个字符串,该字符串必须是映射配置文件的namespace.id
    // List<User> users = sqlSession.selectList("test.selectAll");
    // 3.1获取UserMapper接口的代理对象
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    // 3.2调用sql方法
    List<User> users = userMapper.selectAll();
    System.out.println(users);
    // 4. 释放资源
    sqlSession.close();
    }
    }

    注意:如果Mapper接口名称和SQL映射文件名称相同,并在同一目录下,则可以使用在核心配置文件mybatis-config.xml中用包扫描的方式简化SQL映射文件的加载。示例:

    1
    2
    3
    4
    5
    6
    <mappers>
    <!-- 加载sql映射文件,告诉MyBatis 去哪寻找映射SQL 的语句。-->
    <!-- Maven项目编译后,java和resources下的目录和文件都在同一个跟目录下,所以路径是这样的,注意路径是/,不是.-->
    <!-- <mapper resource="UserMapper.xml"/>-->
    <mapper resource="package1/mapper/UserMapper.xml"/>
    </mappers>

    简化成

    1
    2
    3
    4
    <mappers>
    <!-- 扫描包指定name的包下所有mapper配置文件-->
    <package name="package1.mapper"/>
    </mappers>

配置文件实现增删改查

MybatisX插件、占位符、XML特殊字符转义

安装MybatisX插件:Setting–Plugins–MybatisX

作用是通过小鸟图标,方便mapper接口和mapper配置文件之间代码的统一。

参数占位符:参数占位符里的内容为传入的参数名,或者参数的成员变量名,或map的键名。

mybatis提供了两种参数占位符:

  • #{} :执行SQL时,会将 #{} 占位符替换为?,将来自动设置参数值。底层使用的是 PreparedStatement
  • ${} :拼接SQL。底层使用的是 Statement,会存在SQL注入问题。
1
2
3
<select id="selectById" resultMap="userResultMap">
select * from tb_user where id=#{id};
</select>

#{}和 ${} 的区别

\#{}是预编译处理,${}是字符替换。在使用 #{}时,MyBatis 会将 SQL 中的 #{}替换成“?”,配合 PreparedStatement 的 set 方法赋值,这样可以有效防止 SQL 注入,保证程序的运行安全。

特殊字符处理:

  1. 转义字符。XML中,需要转义的字符有:

    &   &amp;

    <   &lt;

    >   &gt;

    "  &quot;

    '  &apos;

  2. CDATA区

    1
    2
    3
    4
    5
    <select id="selectById" resultMap="userResultMap">
    select * from tb_user where id <![CDATA[
    >
    ]]> #{id};
    </select>

resultMap给列起别名并查询所有

适用于数据库字段名和实体类属性名不同的情况。

SQL语句下划线命名法的列名和java实体类驼峰命名法的成员变量不同,会导致数据库给成员变量无法赋值的问题。通过resultMap标签给数据库列名起别名,可以解决这个问题。

多条件动态查询(动态SQL)

动态SQL:SQL语句随着用户的输入或外部条件的变化而变化,称为动态SQL。

Mybatis关于动态SQL的标签

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

if 标签的test 属性:逻辑表达式

为了防止出现SQL语句where 后直接跟and的情况,给所有条件加了and,并且where后跟了1=1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<select id="selectByCondition" resultMap="brandResultMap">
select * from tb_brand
<!-- 这里1=1是防止where后直接跟and的情况发生,更好的办法是用where标签,根据语法动态去and或where关键字 -->
where 1=1
<if test="status != null">
and status = #{status}
</if>
<if test="companyName != null and companyName != '' ">
and company_name like #{companyName}
</if>
<if test="brandName != null and brandName != '' ">
and brand_name like #{brandName}
</if>
</select>

where 标签作用:

  • 替换where关键字
  • 会动态的去掉第一个条件前的 and
  • 如果所有的参数没有值则不加where关键字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 <select id="selectByCondition" resultMap="brandResultMap">
select * from tb_brand
<where>
<!-- where标签下注意所有if都要加and-->
<if test="status != null">
and status = #{status}
</if>
<if test="companyName != null and companyName != '' ">
and company_name like #{companyName}
</if>
<if test="brandName != null and brandName != '' ">
and brand_name like #{brandName}
</if>
</where>
</select>

单条件动态查询(动态SQL)

先选择,再输入查询。

choose(when,otherwise)标签类似于Java 中的switch语句,自带break。

注意:

choose-when是多选一,一项满足条件后其他就不判断了。

可以多层判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<select id="selectByConditionSingle" resultMap="brandResultMap">
select * from tb_brand
<where>
<!-- 相当于switch -->
<choose>
<!-- 相当于case -->
<when test="status != null">
status = #{status}
</when>
<when test="companyName != null and companyName != '' ">
company_name like #{companyName}
</when>
<when test="brandName != null and brandName != ''">
brand_name like #{brandName}
</when>
</choose>
</where>
</select>

添加数据

返回主键:在 insert 标签上添加如下属性

  • useGeneratedKeys:是够获取自动增长的主键值。true表示获取
  • keyProperty :指定将获取到的主键值封装到哪儿个属性里
1
2
3
4
<insert id="add" useGeneratedKeys="true" keyProperty="id">
insert into tb_brand (brand_name, company_name, ordered, description, status)
values (#{brandName}, #{companyName}, #{ordered}, #{description}, #{status});
</insert>
1
2
brandMapper.add(brand);
System.out.println(brand.getId());

修改字段

获取到修改后的数据和id,修改此id对应的一行数据。在修改界面用户可能只修改部分属性, 所以加条件判断每个属性修改框用户有没有填写,用防止更新语句最后一行有逗号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<update id="update">
update tb_brand
<set>
<if test="brandName != null and brandName != ''">
brand_name = #{brandName},
</if>
<if test="companyName != null and companyName != ''">
company_name = #{companyName},
</if>
<if test="ordered != null">
ordered = #{ordered},
</if>
<if test="description != null and description != ''">
description = #{description},
</if>
<if test="status != null">
status = #{status},
</if>
</set>
where id = #{id};
</update>

批量删除

编写SQL时需要遍历数组来拼接SQL语句。Mybatis 提供了 foreach 标签供我们使用

foreach 标签

用来迭代任何可迭代的对象(如数组,集合)。

  • collection 属性:指定遍历的数组
    • mybatis会将数组参数,封装为一个Map集合。
      • 默认:array = 数组,key是array而不是数组名,如collection=”array”和void deleteByIds(int[] ids);
      • 用@Param注解改变map集合的默认key的名称为数组名,如collection=”ids”和void deleteByIds(@Param(“ids”) int[] ids);
  • item 属性:本次迭代获取到的元素,如item=”id”。
  • separator 属性:集合项迭代之间的分隔符。foreach 标签不会错误地添加多余的分隔符。也就是最后一次迭代不会加分隔符。如separator=”,”
  • open 属性:该属性值是在拼接SQL语句之前拼接的语句,只会拼接一次。如open=”(“
  • close 属性:该属性值是在拼接SQL语句拼接后拼接的语句,只会拼接一次。如close=”)”
1
2
// 批量删除
void deleteByIds(int[] ids);
1
2
3
4
5
6
<delete id="deleteByIds">
delete from tb_brand where id in
<foreach collection="array" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>

Mybatis参数传递

Mybatis 接口方法中可以接收各种各样的参数,如下:

  • 多个参数
  • 单个参数:单个参数又可以是如下类型
    • POJO 类型,传入对象
    • Map 集合类型,传入map
    • Collection 集合类型
    • List 集合类型
    • Array 类型
    • 其他类型

注意:单个参数不用@Param注解起别名,会自动识别要传的参数。

mapper传参数,如果参数包括对象和散装参数,那么对象必须也注解,写SQL语句时候不能忘记对象.属性,例如brand.status.

  1. 散装参数:使用 @Param("参数名称") 标记每一个参数,在映射配置文件中就需要使用 #{参数名称} 进行占位

    1
    2
    3
    List<Brand> selectByPageAndCondition(@Param("begin") int begin,
    @Param("size") int size,
    @Param("brand") Brand brand);
    1
    2
    3
    4
    5
    6
    <select id="selectByCondition" resultMap="brandResultMap">
    select *
    from tb_brand
    where status = #{brand.status}
    limit #{begin}, #{size}
    </select>
  2. 实体类封装参数:将多个参数封装成一个 实体对象 ,将该实体对象作为接口的方法参数。该方式要求在映射配置文件的SQL中使用 #{内容} 时,里面的内容必须和实体类属性名保持一致

    1
    List<Brand> selectByCondition(Brand brand);
  3. map集合:将多个参数封装到map集合中,将map集合作为接口的方法参数。该方式要求在映射配置文件的SQL中使用 #{内容} 时,里面的内容必须和map集合中键的名称一致

    1
    List<Brand> selectByCondition(Map map);

注解底层,了解即可

我们在接口方法中定义多个参数,Mybatis 会将这些参数封装成 Map 集合对象,值就是参数值,而键在没有使用 @Param 注解时有以下命名规则:

  • 以 arg 开头 :第一个参数就叫 arg0,第二个参数就叫 arg1,以此类推。如:

    map.put(“arg0”,参数值1);

    map.put(“arg1”,参数值2);

  • 以 param 开头 : 第一个参数就叫 param1,第二个参数就叫 param2,依次类推。如:

    map.put(“param1”,参数值1);

    map.put(“param2”,参数值2);

  • 在映射配合文件的SQL语句中使用用 arg 开头的和 param 书写,代码的可读性会变的特别差,此时可以使用 @Param 注解。在接口方法参数上使用 @Param 注解,Mybatis 会将 arg 开头的键名替换为对应注解的属性值。

    1
    User select(@Param("username") String username, String password);

    Mybatis 在封装 Map 集合时,键名就会变成如下:

    map.put(“username”,参数值1);

    map.put(“arg1”,参数值2);

    map.put(“param1”,参数值1);

    map.put(“param2”,参数值2);

    1
    2
    3
    <select id="select" resultType="user">
    select * from tb_user where username=#{username} and password=#{param2}
    </select>

    运行程序结果没有报错。而如果将 #{} 中的 username 还是写成 arg0

    1
    2
    3
    <select id="select" resultType="user">
    select * from tb_user where username=#{arg0} and password=#{param2}
    </select>

    运行程序则可以看到报错:Parameter ‘arg0’ not found.

结论:以后接口参数是多个时,在每个参数上都使用 @Param 注解。


注解实现增删改查

如果SQL语句简单,使用注解开发会比配置文件开发更加方便

1
2
@Select(value = "select * from tb_user where id = #{id}")
public User select(int id);

注意:

  • 注解后是没有分号的。
  • 注解是用来替换映射配置文件方式配置的,所以使用了注解,就不需要再映射配置文件中书写对应的 statement

Mybatis 针对 CURD 操作都提供了对应的注解,已经做到见名知意。如下:

  • 查询 :@Select
  • 添加 :@Insert
  • 修改 :@Update
  • 删除 :@Delete

注意:在官方文档中 入门 中有这样的一段话:使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。

所以注解完成简单功能,配置文件完成复杂功能。

而我们之前写的动态 SQL 就是复杂的功能,如果用注解,就需要使用到 Mybatis 提供的SQL构建器来完成,而对应的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
string sql = new SQL() {{
SELECT("P.ID,P.USERNAME,P.PASSWORD, P.FIRST NAME, P.LAST NAME")FROM("PERSON P");
if (id != null) {
WHERE("P.ID like #{id}");
}
if (firstName != null) {
WHERE("P.FIRST NAME like #{firstName}");
}
if (lastName != null) {
WHERE("P.LAST NAME like #{lastName}");
}
ORDER BY("P.LAST NAME");
}}.tostring();

上述代码将java代码和SQL语句融到了一块,使得代码的可读性大幅度降低。


MyBatis 多数据源

一、配置双数据源

  1. 禁用Spring Boot数据源自动装配,在启动类@SpringBootApplication添加exclude = {DataSourceAutoConfiguration.class}

    1
    2
    3
    4
    5
    @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, MybatisAutoConfiguration.class})
    public class Application {
    public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
    }
  2. application.properties配置文件添加两个数据源配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    spring.datasource.db1.driverClassName =oracle.jdbc.OracleDriver
    spring.datasource.db1.jdbcUrl=jdbc:oracle:thin:@<db1 ip>/db1
    spring.datasource.db1.username=xxx
    spring.datasource.db1.password=xxx
    spring.datasource.db1.platform=oracle

    spring.datasource.db2.driverClassName =oracle.jdbc.OracleDriver
    spring.datasource.db2.jdbcUrl=jdbc:oracle:thin:@<db2 ip>/db2
    spring.datasource.db2.username=xxx
    spring.datasource.db2.password=xxx
    spring.datasource.db2.platform=oracle
  3. 添加oracle数据源配置类PrimaryDataSourceConfig,下面的pg数据源配置类DmpDataSourceConfig,添加对应数据源的bean

    • PrimaryDataSourceConfig

      1
      2
      3
      4
      5
      6
      7
      @Bean(name = "primaryDataSource")
      @ConfigurationProperties(prefix = "spring.datasource.db1")
      @Primary
      public DataSource dataSource() {
      log.info("config primaryDataSource success.");
      return DataSourceBuilder.create().build();
      }
    • DmpDataSourceConfig

      1
      2
      3
      4
      5
      6
      @Bean(name = "db2DataSource")
      @ConfigurationProperties(prefix = "spring.datasource.db2")
      public DataSource dataSource() {
      log.info("config db2DataSource success.");
      return DataSourceBuilder.create().build();
      }

二、配置两个Mybatis

  1. 禁用Mybatis自动装配,启动类@SpringBootApplication注解添加exclude = {DataSourceAutoConfiguration.class, MybatisAutoConfiguration.class},如上

  2. 在application.properties配置文件添加两个Mybatis的配置和对应的配置类

    • 主数据源的mybatis配置

      1
      spring.datasource.db1.extend.mybatisMapperLocations=classpath:config/mybatis/mapper/*.xml
    • 主数据源的配置类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      @Configuration
      @ConfigurationProperties(prefix = "spring.datasource.db1.extend")
      public class PrimaryMybatisProperties {

      private String mybatisMapperLocations;

      public String getMybatisMapperLocations() {
      return mybatisMapperLocations;
      }

      public void setMybatisMapperLocations(String mybatisMapperLocations) {
      this.mybatisMapperLocations = mybatisMapperLocations;
      }
      }
    • 数据源2的配置

      1
      spring.datasource.db2.extend.mybatisMapperLocations=classpath:config/mybatis/mapper2/*.xml
    • 数据源2的配置类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      @Configuration
      @ConfigurationProperties(prefix = "spring.datasource.db1.extend")
      public class DmpMybatisProperties {

      private String mybatisMapperLocations;

      public String getMybatisMapperLocations() {
      return mybatisMapperLocations;
      }

      public void setMybatisMapperLocations(String mybatisMapperLocations) {
      this.mybatisMapperLocations = mybatisMapperLocations;
      }
      }
  3. 配置两个Mybatis的SqlSessionFactory

    主数据源

    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
    @Slf4j
    @Configuration
    @MapperScan(basePackages = { "xxx.db.mapper" },sqlSessionFactoryRef="primarySqlSessionFactory")
    public class PrimaryMybatisConfiguration {

    @Autowired
    private PrimaryMybatisProperties property;

    @Bean(name = "primaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.db1")
    @Primary
    public DataSource dataSource() {
    log.info("config primaryDataSource success.");
    return DataSourceBuilder.create().build();
    }

    @Bean(name = "primarySqlSessionFactory")
    @Primary
    public SqlSessionFactory sqlSessionFactory(
    @Qualifier("primaryDataSource") DataSource dataSource) {
    try {
    SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
    sessionFactory.setDataSource(dataSource);
    sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
    .getResources(this.property.getMybatisMapperLocations()));
    log.info("config primarySqlSessionFactory success.");
    return sessionFactory.getObject();
    } catch (Exception e) {
    log.error("config primarySqlSessionFactory failure.", e);
    return null;
    }
    }

    @Bean(name = "primarySqlSessionTemplate")
    @Primary
    public SqlSessionTemplate testSqlSessionTemplate(
    @Qualifier("primarySqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
    return new SqlSessionTemplate(sqlSessionFactory);
    }

    @Bean(name = "primaryTransactionManager")
    @Primary
    public DataSourceTransactionManager transactionManager(
    @Qualifier("primaryDataSource") DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
    }
    }

    数据源2

    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
    @Slf4j
    @Configuration
    @MapperScan(basePackages = { "xxx.db2.mapper" },sqlSessionFactoryRef="db2SqlSessionFactory")
    public class Db2MybatisConfiguration {

    @Autowired
    private Db2MybatisProperties property;

    @Bean(name = "db2DataSource")
    @ConfigurationProperties(prefix = "spring.datasource.db2")
    public DataSource dataSource() {
    log.info("config db2DataSource success.");
    return DataSourceBuilder.create().build();
    }

    @Bean(name = "db2SqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(
    @Qualifier("db2DataSource") DataSource dataSource) {
    try {
    SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
    sessionFactory.setDataSource(dataSource);
    sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
    .getResources(this.property.getMybatisMapperLocations()));
    log.info("config db2SqlSessionFactory success.");
    return sessionFactory.getObject();
    } catch (Exception e) {
    log.error("config db2SqlSessionFactory failure.", e);
    return null;
    }
    }

    @Bean(name = "db2SqlSessionTemplate")
    public SqlSessionTemplate testSqlSessionTemplate(
    @Qualifier("db2SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
    return new SqlSessionTemplate(sqlSessionFactory);
    }

    @Bean(name = "db2TransactionManager")
    public DataSourceTransactionManager transactionManager(
    @Qualifier("db2DataSource") DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
    }
    }

三、配置两个mybatis PageHelper

pagehelper spring boot AutoConfigure提供的PageHelperAutoConfiguration类,自动配置Mybatis的分页插件。因此,先禁用PageHelperAutoConfiguration类。

1
2
3
4
5
6
7
8
9
10
@SpringBootApplication(exclude = { 
DataSourceAutoConfiguration.class,
MybatisAutoConfiguration.class,
PageHelperAutoConfiguration.class })
public class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

要配置两个mybatis的分页插件,需要做如下操作:

  1. application.properties配置文件中添加如下两个mybatis分页插件的配置和对应的@Configuration类。
    使用db1数据源的mybatis分页插件的配置文件

    1
    2
    3
    4
    5
    6
    spring.datasource.db1.extend.pagehelperDialect=Oracle
    spring.datasource.db1.extend.pagehelperReasonable=true
    spring.datasource.db1.extend.pagehelperSupportMethodsArguments=true
    spring.datasource.db1.extend.pagehelperOffsetAsPageNum=true
    spring.datasource.db1.extend.pagehelperRowBoundsWithCount=true
    spring.datasource.db1.extend.pagehelperParams=count=countSql;pageNum=pageNumKey;pageSize=pageSizeKey;

    使用db1数据源的mybatis分页插件的@Configuration类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Configuration
    @ConfigurationProperties(prefix = "spring.datasource.db1.extend")
    public class PrimaryMybatisProperties {

    private String mybatisMapperLocations;
    private String pagehelperDialect;
    private String pagehelperReasonable;
    private String pagehelperSupportMethodsArguments;
    private String pagehelperParams;
    private String pagehelperOffsetAsPageNum;
    private String pagehelperRowBoundsWithCount;

    }

    使用db2数据源的mybatis分页插件的配置文件

    1
    2
    3
    4
    5
    6
    spring.datasource.db2.extend.pagehelperDialect=Oracle
    spring.datasource.db2.extend.pagehelperReasonable=true
    spring.datasource.db2.extend.pagehelperSupportMethodsArguments=true
    spring.datasource.db2.extend.pagehelperOffsetAsPageNum=true
    spring.datasource.db2.extend.pagehelperRowBoundsWithCount=true
    spring.datasource.db2.extend.pagehelperParams=count=countSql;pageNum=pageNumKey;pageSize=pageSizeKey;

    使用db2数据源的mybatis分页插件的@Configuration类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Configuration
    @ConfigurationProperties(prefix = "spring.datasource.db2.extend")
    public class Db2MybatisProperties {

    private String mybatisMapperLocations;
    private String pagehelperDialect;
    private String pagehelperReasonable;
    private String pagehelperSupportMethodsArguments;
    private String pagehelperParams;
    private String pagehelperOffsetAsPageNum;
    private String pagehelperRowBoundsWithCount;

    }
  2. 配置两个mybatis的SqlSessionFactory,增加分页插件
    PrimaryMybatisConfiguration,添加PageHelper,其余不变

    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
    public class PrimaryMybatisConfiguration {

    @Bean(name = "primarySqlSessionFactory")
    @Primary
    public SqlSessionFactory sqlSessionFactory(
    @Qualifier("primaryDataSource") DataSource dataSource) {
    try {
    SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
    sessionFactory.setDataSource(dataSource);
    sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
    .getResources(this.property.getMybatisMapperLocations()));

    // 分页插件
    Interceptor interceptor = new PageInterceptor();
    Properties properties = new Properties();
    properties.setProperty("helperDialect", this.property.getPagehelperDialect());
    properties.setProperty("reasonable", this.property.getPagehelperReasonable());
    properties.setProperty("supportMethodsArguments",
    this.property.getPagehelperSupportMethodsArguments());
    properties.setProperty("params",this.property.getPagehelperParams());
    interceptor.setProperties(properties);
    sessionFactory.setPlugins(new Interceptor[] {interceptor});

    log.info("config primarySqlSessionFactory success.");
    return sessionFactory.getObject();
    } catch (Exception e) {
    log.error("config primarySqlSessionFactory failure.", e);
    return null;
    }
    }
    }

    Db2MybatisConfiguration,添加PageHelper,其余不变

    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
    public class Db2MybatisConfiguration {

    @Bean(name = "db2SqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(
    @Qualifier("db2DataSource") DataSource dataSource) {
    try {
    SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
    sessionFactory.setDataSource(dataSource);
    sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
    .getResources(this.property.getMybatisMapperLocations()));

    //分页插件
    Interceptor interceptor = new PageInterceptor();
    Properties properties = new Properties();
    properties.setProperty("helperDialect", this.property.getPagehelperDialect());
    properties.setProperty("reasonable", this.property.getPagehelperReasonable());
    properties.setProperty("supportMethodsArguments",
    this.property.getPagehelperSupportMethodsArguments());
    properties.setProperty("params",this.property.getPagehelperParams());
    interceptor.setProperties(properties);
    sessionFactory.setPlugins(new Interceptor[] {interceptor});

    log.info("config db2SqlSessionFactory success.");
    return sessionFactory.getObject();
    } catch (Exception e) {
    log.error("config db2SqlSessionFactory failure.", e);
    return null;
    }
    }
    }

四、配置两个mybatis generator

  1. 在pom.xml中引入mybatis generator

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <!-- mybatis generator 自动生成代码插件 -->
    <plugin>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-maven-plugin</artifactId>
    <version>1.3.2</version>
    <configuration>
    <overwrite>true</overwrite>
    <verbose>true</verbose>
    </configuration>
    <dependencies>
    <dependency>
    <groupId>com.oracle.jdbc</groupId>
    <artifactId>ojdbc7</artifactId>
    <version>12.1.0.1</version>
    </dependency>
    </dependencies>
    </plugin>
  2. 配置2个mybatis generator配置文件。
    使用db1数据源的mybatis generator的配置文件generatorConfig.xml
    使用db1数据源的mybatis generator的配置文件generatorConfig2.xml

  3. 使用mvn执行mybatis generator命令。

    1
    2
    3
    4
    # 使用db1数据源的mybatis generator生成代码命令
    mvn -Dmybatis.generator.configurationFile=src/main/resources/generatorConfig.xml mybatis-generator:generate
    # 使用db2数据源的mybatis generator生成代码命令
    mvn -Dmybatis.generator.configurationFile=src/main/resources/generatorConfig2.xml mybatis-generator:generate

MyBatis 编写自定义插件

MyBatis 分页插件的实现原理

分页插件的基本原理是使用 MyBatis 提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的 SQL,然后重写 SQL,根据 dialect 方言,添加对应的物理分页语句和物理分页参数

自定义插件实现原理

MyBatis 自定义插件针对 MyBatis 四大对象(ExecutorStatementHandlerParameterHandlerResultSetHandler)进行拦截:
Executor:拦截内部执行器,它负责调用 StatementHandler 操作数据库,并把结果集通过 ResultSetHandler 进行自动映射,另外它还处理了二级缓存的操作
StatementHandler:拦截 SQL 语法构建的处理,它是MyBatis直接和数据库执行 SQL脚本的对象,它也实现了 MyBatis 的一级缓存
ParameterHandler:拦截参数的处理
ResultSetHandler:拦截结果集的处理

自定义插件实现关键

MyBatis插件要实现 Interceptor 接口,接口包含的方法,如下代码所示:
setProperties 方法:在 MyBatis 进行配置插件的时候可配置自定义相关属性,即:接口实现对象的参数配置
plugin 方法:插件用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理,可以决定是否要进行拦截进而决定要返回一个什么样的目标对象,官方提供了示例:return Plugin. wrap(target, this)
intercept 方法:是要进行拦截的时候要执行的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public interfaceInterceptor{
object intercept(Invocation invocation) throws Throwable;
object plugin(object target);
voidsetProperties(Properties properties);
}

@Intercepts({@signature(type = Executor.class, method = "query",
args = {Mappedstatement.class, Object.class, RowBounds.class, ResultHandler.class})})
publicclassTestInterceptorimplementsInterceptor {
public Object intercept(Invocation invocation) throws Throwable {
Object target = invocation.getTarget(); //被代理对象
Method method = invocation.getMethod();//代理方法
Object[] args = invocation.getArgs() // 方法参数
// do something ...... 方法拦截前执行代码块
Object result =invocation.proceed():
// do something ...... 方法拦截后执行代码块
return result:
}
public object plugin(object target){
return Plugin.wrap(target, this);
}
}


相关问题

MyBatis 的分页方式

分页方式:逻辑分页和物理分页。

  • 逻辑分页:使用 MyBatis 自带的 RowBounds 进行分页,它是一次性查询很多数据,然后在数据中再进行检索
  • 物理分页:手写 SQL 分页或使用分页插件 PageHelper,去数据库查询指定条数的分页数据的形式。

MyBatis 逻辑分页和物理分页的区别

逻辑分页是一次性查询很多数据,然后在结果中检索分页的数据。这样需要消耗大量的内存、有内存溢出的风险、对数据库压力较大
物理分页是从数据库查询指定条数的数据,弥补了一次性全部查出的所有数据的缺点,如需要大量内存,数据库查询压力较大等问题。

RowBounds 是一次性查询全部结果吗?为什么?

RowBounds 表面是在“所有”数据中检索数据,其实并非是一次性查询出所有数据,因为 MyBatis 是对 jdbc 的封装在 jdbc 驱动中有一个 Fetch Size 的配置,它规定了每次最多从数据库查询多少条数据,假如你要查询更多数据,它会在你执行 next()的时候,去查询更多的数据
就好比去自动取款机取 10000 元,但取款机每次最多能取 2500 元,所以你要取 4 次才能把钱取完。只是对于 jdbc 来说,当你调用 next()时会自动完成查询工作。这样做的好处可以有效防止内存溢出

MyBatis 是否支持延迟加载及延迟加载的原理

MyBatis 支持延迟加载,设置 lazyLoadingEnabled=true
延迟加载的原理调用的时候触发加载,而不是在初始化的时候就加载信息。
比如调用 a. getB(). getName(),这个时候发现 a. getB() 的值为 null,此时会单独触发事先保存好的关联 B 对象的 SQL,先查询出来 B,然后再调用 a. setB(b),而这时候再调用 a. getB(). getName() 就有值了,这就是延迟加载的基本原理。

MyBatis 的一级缓存和二级缓存

一级缓存:基于PerpetualCacheHashMap本地缓存,其声明周期和 SQLSession 一致,有多个 SQLSession 或者分布式的环境中数据库操作,可能会出现脏数据Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认一级缓存开启。
二级缓存:也是基于 PerpetualCacheHashMap 本地缓存,不同在于其存储作用域为 Mapper 级别的,如果多个SQLSession之间需要共享缓存,则需要使用到二级缓存,并且二级缓存可自定义存储源,如 Ehcache。默认不打开二级缓存,**要开启二级缓存,使用二级缓存属性类需要实现 Serializable 序列化接口(可用来保存对象的状态)**。
开启二级缓存数据查询流程:二级缓存 -> 一级缓存 -> 数据库
缓存更新机制:当某一作用域(一级缓存Session/二级缓存Mapper)进行C/U/D 操作后,默认该作用域下所有select中的缓存将被clear

MyBatis 和 hibernate 的区别

灵活性:MyBatis 更加灵活,自己可以写 SQL 语句,使用起来比较方便。
可移植性:MyBatis 有很多自己写的 SQL,因为每个数据库的 SQL 可以不相同,所以可移植性比较差
学习和使用门槛:MyBatis 入门比较简单,使用门槛也更低。
二级缓存:hibernate 拥有更好的二级缓存,可以自行更换为第三方的二级缓存

MyBatis 有哪些执行器(Executor)

MyBatis 有三种基本的Executor执行器:
SimpleExecutor:每执行一次 update 或 select 就开启一个 Statement 对象,用完立刻关闭 Statement 对象;
ReuseExecutor:执行 update 或 select以 SQL 作为 key 查找 Statement 对象,存在就使用,不存在就创建,用完后不关闭 Statement 对象,而是放置于 Map 内供下一次使用。简言之,就是重复使用 Statement 对象
**BatchExecutor:*执行 update(没有 select,jdbc 批处理不支持 select),将所有 SQL 都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个 Statement 对象,每个 Statement 对象都是 addBatch()完毕后,等待逐一执行 executeBatch()批处理*,与 jdbc 批处理相同。