优化:文章目录结构(某分类/YYYY/MM/dd/xxx.md)
This commit is contained in:
255
repos/courses/mybatis/01-MyBatis基础/01-快速入门.md
Normal file
255
repos/courses/mybatis/01-MyBatis基础/01-快速入门.md
Normal file
@@ -0,0 +1,255 @@
|
||||
---
|
||||
title: 快速入门
|
||||
author: 查尔斯
|
||||
date: 2020/12/25 14:49
|
||||
categories:
|
||||
- MyBatis快速入门
|
||||
tags:
|
||||
- MyBatis
|
||||
- ORM框架
|
||||
---
|
||||
|
||||
# 快速入门
|
||||
|
||||
我们将通过一个简单的 Demo 来阐述 MyBatis 的强大功能,在此之前,笔者假设你已经:
|
||||
|
||||
- 拥有 Java 开发环境以及相应 IDE(本 Demo 采用 Eclipse 作为IDE)
|
||||
- 熟悉 Java Web 开发流程
|
||||
- 熟悉至少一个关系型数据库(本 Demo 采用 MySQL 作为数据库)
|
||||
|
||||
## 数据库准备
|
||||
|
||||
现有一张 `User` 表,其表结构如下:
|
||||
|
||||
| 主键 | 姓名 | 年龄 | 邮箱 |
|
||||
| :--: | :----: | :--: | :------------: |
|
||||
| 1 | Jone | 18 | Jone@126.com |
|
||||
| 2 | Jack | 20 | Jack@126.com |
|
||||
| 3 | Tom | 28 | Tom@126.com |
|
||||
| 4 | Sandy | 21 | Sandy@126.com |
|
||||
| 5 | Billie | 24 | Billie@126.com |
|
||||
|
||||
其对应的数据库 结构 脚本如下:
|
||||
|
||||
```sql
|
||||
-- 创建并切换数据库
|
||||
CREATE DATABASE mybatis_demo_db;
|
||||
USE mybatis_demo_db;
|
||||
|
||||
-- 创建用户数据表
|
||||
CREATE TABLE `user` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '姓名',
|
||||
`age` int(11) NULL DEFAULT NULL COMMENT '年龄',
|
||||
`email` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '邮箱',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户表' ROW_FORMAT = Compact;
|
||||
```
|
||||
|
||||
其对应的数据库 数据 脚本如下:
|
||||
|
||||
```sql
|
||||
-- 清空用户表数据
|
||||
TRUNCATE TABLE user;
|
||||
|
||||
-- 向用户表插入测试数据
|
||||
INSERT INTO `user` VALUES (1, 'Jone', 18, 'Jone@126.com');
|
||||
INSERT INTO `user` VALUES (2, 'Jack', 20, 'Jack@126.com');
|
||||
INSERT INTO `user` VALUES (3, 'Tom', 28, 'Tom@126.com');
|
||||
INSERT INTO `user` VALUES (4, 'Sandy', 21, 'Sandy@126.com');
|
||||
INSERT INTO `user` VALUES (5, 'Billie', 24, 'Billie@126.com');
|
||||
```
|
||||
|
||||
## 下载依赖
|
||||
|
||||
要使用 MyBatis 框架,第一步就是下载好 MyBatis 的 jar 包,我们可以从 [MyBatis](https://github.com/mybatis/mybatis-3/releases) 在 GitHub 上的开源地址下载。
|
||||
|
||||

|
||||
|
||||
笔者下载了 MyBatis 的核心压缩包(mybatis-x.x.x.zip)及其源码包(mybatis-x-mybatis-x.x.x.zip)。
|
||||
|
||||

|
||||
|
||||
解压开 **mybatis-3.5.6.zip** 压缩包,目录结构如下:
|
||||
|
||||

|
||||
|
||||
::: tip 笔者说
|
||||
如果 GitHub 下载太慢,可以前往 [FastGit](https://hub.fastgit.org/mybatis/mybatis-3/releases/tag/mybatis-3.5.6) 进行下载,它是 GitHub 的镜像地址,网站界面等各方面与 GitHub 几乎一模一样。
|
||||
但是注意它仅仅是一个镜像网站,可以用于克隆或下载 GitHub 资源,但登录之类的功能是不可用的。
|
||||
:::
|
||||
|
||||
## 创建项目
|
||||
|
||||
下载好依赖之后,我们通过 Eclipse,创建一个动态 Web 项目,并将刚才下载的 jar 包和指定数据库驱动包添加到 WebConent\WEB-INF\lib 目录,效果如下:
|
||||
|
||||

|
||||
|
||||
::: tip 笔者说
|
||||
本次我们不会使用到 Servlet API,所以创建一个普通 Java 工程也没问题。
|
||||
:::
|
||||
|
||||
## 创建POJO类
|
||||
|
||||
在 DAO 模式开发中,第一步就是要创建实体类,而在 MyBatis 项目中,实体类"弱化"为了 POJO,这种类型是专门用于和数据库做映射的 Java 类型,数据表中的列与 POJO 类型的属性一 一对应。
|
||||
|
||||
```java
|
||||
package com.example.pojo;
|
||||
|
||||
/**
|
||||
* 用户POJO类(它是Java和关系数据库表映射的类型)
|
||||
* @author Charles7c
|
||||
*/
|
||||
public class User {
|
||||
private Long id;
|
||||
private String name;
|
||||
private Integer age;
|
||||
private String email;
|
||||
// 省略getter/setter方法
|
||||
// 省略toString方法
|
||||
}
|
||||
```
|
||||
|
||||
::: tip 笔者说
|
||||
POJO(Plain Old Java Objects,普通老式 Java 对象)。一般来讲,将 POJO 简单理解为实体类也无伤大雅。
|
||||
:::
|
||||
|
||||
## 创建SQL映射文件
|
||||
|
||||
在 DAO 模式开发中,实体类创建完之后就是要编写 BaseDao、以及不同实体的 Dao 接口和 Dao 实现类。但这一切的繁琐过程,在现在都被 MyBatis 解决了。
|
||||
|
||||
现在,我们只需要按照 MyBatis 的要求创建好一个编写 SQL 的映射文件,在映射文件中编写好数据库的 CRUD 操作即可。
|
||||
|
||||
```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">
|
||||
<!-- namespace:命名空间,在同一个项目中保持唯一 -->
|
||||
<mapper namespace="userMapper">
|
||||
<!-- SQL 操作(根据 CRUD 的不同选择不同的标签编写 SQL 操作)
|
||||
id:SQL 操作标识
|
||||
resultType: 结果集类型(全类名)
|
||||
下方设定等价于编写了一个方法:List<User> selectList();
|
||||
-->
|
||||
<select id="selectList" resultType="com.example.pojo.User">
|
||||
SELECT * FROM `user`
|
||||
</select>
|
||||
</mapper>
|
||||
```
|
||||
|
||||
::: tip 笔者说
|
||||
SQL 映射文件的命名风格为:POJO类名Mapper.xml,就像命名以前的 Dao接口 一样,你可以将 SQL 映射文件理解为是以前的 Dao 实现类。
|
||||
:::
|
||||
|
||||
## 创建核心配置文件
|
||||
|
||||
MyBatis 为我们简化了非常多的操作,但是一些必须由我们自定义的配置还是少不了的。在 classpath 下创建一个核心配置文件命名为:`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">
|
||||
<configuration>
|
||||
<!-- 环境配置:
|
||||
可以配置多个,但生效的只有一个
|
||||
default属性:指定生效的环境的id
|
||||
-->
|
||||
<environments default="develop">
|
||||
<!-- 单个环境配置 -->
|
||||
<environment id="develop">
|
||||
<!-- 事务管理配置 -->
|
||||
<transactionManager type="JDBC"/>
|
||||
<!-- 数据源配置 -->
|
||||
<dataSource type="POOLED">
|
||||
<property name="driver" value="com.mysql.jdbc.Driver"/>
|
||||
<property name="url" value="jdbc:mysql://localhost:3306/mybatis_demo_db"/>
|
||||
<property name="username" value="root"/>
|
||||
<property name="password" value="root"/>
|
||||
</dataSource>
|
||||
</environment>
|
||||
</environments>
|
||||
|
||||
<!-- 指定要加载的SQL映射文件 -->
|
||||
<mappers>
|
||||
<!-- 注意:该地址不是全类名!!! -->
|
||||
<mapper resource="com/example/mapper/UserMapper.xml"/>
|
||||
</mappers>
|
||||
</configuration>
|
||||
```
|
||||
|
||||
## 添加日志配置文件
|
||||
|
||||
在 MyBatis 中,采用的日志框架是 log4j,所以为了能够查看到日志输出,我们需要在 classpath 下添加一个 log4j.properties 文件。
|
||||
|
||||
```
|
||||
###############################################################
|
||||
# 输出到控制台 #
|
||||
###############################################################
|
||||
# log4j.rootLogger日志输出类别和级别:只输出不低于该级别的日志信息DEBUG < INFO < WARN < ERROR < FATAL
|
||||
# DEBUG:日志级别 CONSOLE:输出位置自己定义的一个名字
|
||||
log4j.rootLogger=DEBUG,CONSOLE
|
||||
# 配置CONSOLE输出到控制台
|
||||
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
|
||||
# 配置CONSOLE设置为自定义布局模式
|
||||
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
|
||||
# 配置CONSOLE日志的输出格式 [demo] 2019-08-22 22:52:12,000 %r耗费毫秒数 %p日志的优先级 %t线程名 %C所属类名通常为全类名 %L代码中的行号 %x线程相关联的NDC %m日志 %n换行
|
||||
log4j.appender.CONSOLE.layout.ConversionPattern=[demo] %d{yyyy-MM-dd HH:mm:ss,SSS} - %-4r %-5p [%t] %C:%L %x - %m%n
|
||||
```
|
||||
|
||||
## 测试
|
||||
|
||||
当一切准备好之后,完整的项目目录结构如下:
|
||||
|
||||

|
||||
|
||||
创建好一个单元测试类,测试一下:
|
||||
|
||||
```java
|
||||
class TestMyBatis {
|
||||
|
||||
@Test
|
||||
void testSelectList() throws IOException {
|
||||
// 1.从classpath加载核心配置文件,构建SqlSession工厂对象
|
||||
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
|
||||
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
|
||||
|
||||
// 2.获取SqlSession对象
|
||||
try (SqlSession sqlSession = sqlSessionFactory.openSession()){
|
||||
|
||||
// 3.执行SQL语句 根据要执行的SQL语句选择合适的API
|
||||
// p1:SQL语句唯一地址 (SQL映射文件的namespace值.SQL语句的id值)
|
||||
List<User> userList = sqlSession.selectList("userMapper.selectList");
|
||||
|
||||
// 4.遍历数据
|
||||
userList.forEach(System.out::println);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
**控制台输出:**
|
||||
|
||||
```
|
||||
User [id=1, name=Jone, age=18, email=Jone@126.com]
|
||||
User [id=2, name=Jack, age=20, email=Jack@126.com]
|
||||
User [id=3, name=Tom, age=28, email=Tom@126.com]
|
||||
User [id=4, name=Sandy, age=21, email=Sandy@126.com]
|
||||
User [id=5, name=Billie, age=24, email=Billie@126.com]
|
||||
```
|
||||
|
||||
## 后记
|
||||
|
||||
**C:** 好了,与 MyBatis 的第一次约会结束了。怎么样?约会体验如何?使用步骤是不是还挺简单的?
|
||||
|
||||
虽然是在学习一个新技术,但是一定要时刻想想当初 DAO 模式你是怎么一个开发步骤,这样对比着会发现 MyBatis 就是在简化、优化原来的每个环节而已。根本上还是那么回事,多想想,脑子里就能留下使用思路。
|
||||
|
||||
::: info 笔者说
|
||||
对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。
|
||||
|
||||
所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。
|
||||
:::
|
||||
185
repos/courses/mybatis/01-MyBatis基础/02-核心对象.md
Normal file
185
repos/courses/mybatis/01-MyBatis基础/02-核心对象.md
Normal file
@@ -0,0 +1,185 @@
|
||||
---
|
||||
title: 核心对象
|
||||
author: 查尔斯
|
||||
date: 2020/12/25 20:02
|
||||
categories:
|
||||
- MyBatis快速入门
|
||||
tags:
|
||||
- MyBatis
|
||||
- ORM框架
|
||||
---
|
||||
|
||||
# 核心对象
|
||||
|
||||
## 前言
|
||||
|
||||
**C:** 在上一篇,笔者带大家对 MyBatis 做了一个快速入门,不知道你是否已经掌握了 MyBatis 的使用步骤呢?本篇,笔者将继续带你学习 MyBatis,掌握对上篇中三个核心对象的概念理解和使用。
|
||||
|
||||
在 API 使用层面,MyBatis 的核心类型有三个,分别是:SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession。
|
||||
|
||||

|
||||
|
||||
## SqlSessionFactoryBuilder
|
||||
|
||||
SqlSessionFactoryBuilder 是 MyBatis 中应用构建者模式的一个类,它的作用就是用来 **读取 MyBatis 的核心配置文件信息,然后来构建一个 SqlSessionFactory 对象** 。
|
||||
|
||||
对于 SqlSessionFactoryBuilder 这个类,使用完就没有什么价值了,所以它的生命周期只存在于方法体内即可,也就是作为一个局部变量存在。
|
||||
|
||||
**以下是它的使用方式:**
|
||||
|
||||
```java
|
||||
// Resources 类是 MyBatis 中的一个资源加载工具类
|
||||
// 1.从 classpath 下将 MyBatis 核心配置文件内容加载为一个输入流对象
|
||||
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
|
||||
// 2.构建 SqlSessionFactory 对象
|
||||
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
|
||||
```
|
||||
|
||||
**以下是它的常用方法:**
|
||||
|
||||
- build(InputStream inputStream) : SqlSessionFactory 通过字节输入流构建
|
||||
- build(Reader reader) : SqlSessionFactory 通过字符输入流构建
|
||||
- build(Configuration config) : SqlSessionFactory 通过Configuration构建
|
||||
- ....
|
||||
|
||||
::: tip 笔者说
|
||||
因为 SqlSessionFactory 对象构建需要的配置参数很多,且不能保证每个参数都是正确的或者不能一次性得到构建所需的所有参数(有些参数是初始化时需要的,如数据源配置,延迟加载配置,事务配置等,有些是框架运行过程中需要的,如SQL映射等[1]),所以不能采用简单的 new 方式来创建对象。
|
||||
:::
|
||||
|
||||
## SqlSessionFactory
|
||||
|
||||
SqlSessionFactory 对象比较重要,它的作用是 **创建 SqlSession 对象** 。
|
||||
|
||||
**以下是它的常用方法:**
|
||||
|
||||
- openSession() : SqlSession 获取 SqlSession对象
|
||||
|
||||
该方法是我们最常用的方法,同时也是开启事务处理的(参见下方源码)
|
||||
|
||||
- openSession(boolean autoCommit) : SqlSession 获取SqlSession对象,自行指定是否开启事务
|
||||
|
||||
autoCommit 参数是用来指定是否开启自动提交的
|
||||
|
||||
如果 autoCommit 的值为 true,代表关闭事务处理。反之,代表开启事务处理。
|
||||
|
||||
```java
|
||||
// DefaultSqlSessionFactory 是 SqlSessionFactory的实现类
|
||||
public class DefaultSqlSessionFactory implements SqlSessionFactory {
|
||||
// ...
|
||||
@Override
|
||||
public SqlSession openSession() {
|
||||
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
|
||||
}
|
||||
@Override
|
||||
public SqlSession openSession(boolean autoCommit) {
|
||||
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit);
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
每一个 MyBatis 的应用程序都以一个 SqlSessionFactory 对象为核心。SqlSessionFactory 一旦被创建, **它的生命周期应该与应用的生命周期相同** ,所以在应用运行期间不要重复创建 SqlSessionFactory 对象。
|
||||
|
||||
为此,我们可以使用单例模式来改造获取 SqlSessionFactory 对象的方式。
|
||||
|
||||
```java
|
||||
/**
|
||||
* MyBatis 工具类
|
||||
* @author Charles7c
|
||||
*/
|
||||
public class MyBatisUtils {
|
||||
|
||||
// 私有化构造函数
|
||||
private MyBatisUtils() {}
|
||||
|
||||
// 静态对象
|
||||
private static volatile SqlSessionFactory SQL_SESSION_FACTORY = null;
|
||||
|
||||
/**
|
||||
* 获取 SqlSessionFactory 对象
|
||||
* @return 单例的 SqlSessionFactory 对象
|
||||
* @throws IOException /
|
||||
*/
|
||||
public static SqlSessionFactory getSqlSessionFactory() throws IOException {
|
||||
if (SQL_SESSION_FACTORY == null) {
|
||||
// 同步锁
|
||||
synchronized(MyBatisUtils.class) {
|
||||
// 双重检测机制
|
||||
if (SQL_SESSION_FACTORY == null) {
|
||||
// 从 classpath 加载核心配置文件,构建 SqlSession 工厂对象
|
||||
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
|
||||
SQL_SESSION_FACTORY = new SqlSessionFactoryBuilder().build(is);
|
||||
}
|
||||
}
|
||||
}
|
||||
return SQL_SESSION_FACTORY;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## SqlSession
|
||||
|
||||
SqlSession 是 MyBatis 执行持久化操作的关键对象,类似于 JDBC 中的 Connection。 **SqlSession 对象包含了执行 SQL 所需的所有方法** ,它的底层封装了 JDBC 连接,可以用 SqlSession 实例来直接执行被映射的 SQL 语句。
|
||||
|
||||
**以下是它的常用方法:**
|
||||
|
||||
- insert(String statement, Object parameter) : int 增加操作
|
||||
- delete(String statement, Object parameter) : int 删除操作
|
||||
- update(String statement, Object parameter) : int 修改操作
|
||||
- selectOne(String statement) : T 单个查询
|
||||
- selectOne(String statement, Object parameter) : T 带参数单个查询
|
||||
- selectList(String statement) : List\<E> 集合查询
|
||||
- selectList(String statement, Object parameter) : List\<E> 带参数集合查询
|
||||
- commit() : void 提交事务
|
||||
- rollback() : void 回滚事务
|
||||
- getMapper(Class\<T> type) : T 获取Mapper接口(Mapper接口开发)
|
||||
- ...
|
||||
|
||||
每个线程都应该有它自己的 SqlSession 实例, SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的HttpSession。如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。[2] **使用完 SqlSeesion 之后一定记得关闭,可以采用 finally 块或 try-with-resources** 。在 SqlSession 里可以执行多次 SQL 语句,但一旦关闭了 SqlSession 就需要重新创建。
|
||||
|
||||
结合 SqlSessionFactory 的获取方式改造,下方是在 MyBatisUtils 工具类中又增加了 SqlSession 的获取方式改造。
|
||||
|
||||
```java
|
||||
/**
|
||||
* 获取 SqlSession 对象(开启事务处理)
|
||||
* @return SqlSession 对象
|
||||
* @throws IOException /
|
||||
*/
|
||||
public static SqlSession openSession() throws IOException {
|
||||
return getSqlSessionFactory().openSession();
|
||||
}
|
||||
```
|
||||
|
||||
## 参考文献
|
||||
|
||||
[1]哲雪君!. 第五篇 mybatis的运行原理(2):构建者模式, SqlSessionFactoryBuilder类解析[EB/OL]. https://www.cnblogs.com/zhexuejun/p/11285206.html. 2019-08-14
|
||||
|
||||
[2]MyBatis官网. MyBatis 入门 | 作用域(Scope)和生命周期[EB/OL]. https://mybatis.org/mybatis-3/zh/getting-started.html. 2020-12-26
|
||||
|
||||
## 后记
|
||||
|
||||
到此为止,笔者就介绍完了在使用 MyBatis 时,所遇到的三个核心对象。同样经过上述对这些核心对象使用的改造后,我们也看一下到底它优化到了什么程度,开开眼吧。
|
||||
|
||||
```java
|
||||
@Test
|
||||
void testSelectList() throws IOException {
|
||||
|
||||
// 获取SqlSession对象
|
||||
try (SqlSession sqlSession = MyBatisUtils.openSession()){
|
||||
|
||||
// 执行SQL语句
|
||||
List<User> userList = sqlSession.selectList("userMapper.selectList");
|
||||
|
||||
// 遍历数据
|
||||
userList.forEach(System.out::println);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
::: info 笔者说
|
||||
对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。
|
||||
所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。
|
||||
:::
|
||||
484
repos/courses/mybatis/01-MyBatis基础/03-核心配置文件.md
Normal file
484
repos/courses/mybatis/01-MyBatis基础/03-核心配置文件.md
Normal file
@@ -0,0 +1,484 @@
|
||||
---
|
||||
title: 核心配置文件
|
||||
author: 查尔斯
|
||||
date: 2020/12/26 14:48
|
||||
categories:
|
||||
- MyBatis快速入门
|
||||
tags:
|
||||
- MyBatis
|
||||
- ORM框架
|
||||
---
|
||||
|
||||
# 核心配置文件
|
||||
|
||||
## 前言
|
||||
|
||||
**C:** 在上一篇,笔者带大家对 MyBatis 的核心对象做了介绍。本篇,笔者将继续带你学习 MyBatis,掌握对核心配置文件的使用。
|
||||
|
||||
MyBatis 的核心/全局配置文件 mybatis-config.xml ,顾名思义就是对 MyBatis 系统的核心设置文件。包含有 MyBatis 运行时行为配置、类型别名配置、环境配置等。
|
||||
|
||||
下方是核心配置文件的标签模板,笔者将对其中常用的一些标签的常用使用方式进行介绍。
|
||||
|
||||
configuration 根节点
|
||||
- **properties** 属性配置
|
||||
|
||||
- settings 运行时行为配置
|
||||
- **typeAliases** 类型别名配置
|
||||
- typeHandlers 类型处理器
|
||||
|
||||
- objectFactory 对象工厂
|
||||
- plugins 插件配置
|
||||
- **environments** 环境配置
|
||||
- environment 单个环境配置
|
||||
- transactionManager 事务管理器配置
|
||||
- dataSource 数据源配置
|
||||
- databaseIdProvider 数据库厂商标识
|
||||
- **mappers** 映射器配置
|
||||
|
||||
::: tip 笔者说
|
||||
这些标签在使用时一定要注意标签的顺序和允许使用次数。Eclipse 中可以通过在标签上按 F2 查看该标签下的内容模型,即标签的顺序和允许使用次数。你看下图中画圈处就是各个标签的顺序,后面的 ?号 代表指定标签最多允许使用一次。
|
||||
:::
|
||||

|
||||
|
||||
## properties元素
|
||||
|
||||
如果你学过 Maven,那 properties 元素应该不难理解。在 MyBatis 的核心配置文件中,有很多配置是可能经常需要变动或复用的,如果直接将值硬编码在对应位置,将不利于统一维护管理和复用。
|
||||
|
||||
properties 元素的作用就体现出来了,它的使用方式有两种。
|
||||
|
||||
### 内部编写
|
||||
|
||||
**第一种使用方式,是内部编写配置** ,示例如下:
|
||||
|
||||
```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">
|
||||
<configuration>
|
||||
<!-- 要配置哪些属性是根据你项目需求来定的,笔者仅仅做一个示例而已 -->
|
||||
<properties>
|
||||
<property name="mysql.driver" value="com.mysql.jdbc.Driver"/>
|
||||
<property name="mysql.url" value="jdbc:mysql://localhost:3306/mybatis_demo_db"/>
|
||||
<property name="mysql.username" value="root"/>
|
||||
<property name="mysql.password" value="root"/>
|
||||
<property name="txType" value="JDBC"/>
|
||||
</properties>
|
||||
<!-- ...略... -->
|
||||
<environments default="develop">
|
||||
<!-- 单个环境配置 -->
|
||||
<environment id="develop">
|
||||
<!-- 事务管理配置 -->
|
||||
<transactionManager type="${txType}"/>
|
||||
<!-- 数据源配置 -->
|
||||
<dataSource type="POOLED">
|
||||
<property name="driver" value="${mysql.driver}"/>
|
||||
<property name="url" value="${mysql.url}"/>
|
||||
<property name="username" value="${mysql.username}"/>
|
||||
<property name="password" value="${mysql.password}"/>
|
||||
</dataSource>
|
||||
</environment>
|
||||
</environments>
|
||||
<!-- ...略... -->
|
||||
</configuration>
|
||||
```
|
||||
|
||||
### 外部引入
|
||||
|
||||
**第二种使用方式,是在外部配置文件编写配置,然后通过 properties 引入外部配置** ,示例如下:
|
||||
|
||||
在 classpath 下 添加 一个 properties 配置文件,记录各种配置信息。(此处笔者记录的是数据源信息)
|
||||
|
||||
```
|
||||
# MySQL
|
||||
mysql.driver=com.mysql.jdbc.Driver
|
||||
mysql.url=jdbc:mysql://localhost:3306/mybatis_demo_db
|
||||
mysql.username=root
|
||||
mysql.password=root
|
||||
```
|
||||
```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">
|
||||
<configuration>
|
||||
<!-- 加载外部的配置文件 -->
|
||||
<properties resource="db.properties"/>
|
||||
<!-- ...略... -->
|
||||
<environments default="develop">
|
||||
<!-- 单个环境配置 -->
|
||||
<environment id="develop">
|
||||
<!-- 事务管理配置 -->
|
||||
<transactionManager type="JDBC"/>
|
||||
<!-- 数据源配置 -->
|
||||
<dataSource type="POOLED">
|
||||
<property name="driver" value="${mysql.driver}"/>
|
||||
<property name="url" value="${mysql.url}"/>
|
||||
<property name="username" value="${mysql.username}"/>
|
||||
<property name="password" value="${mysql.password}"/>
|
||||
</dataSource>
|
||||
</environment>
|
||||
</environments>
|
||||
<!-- ...略... -->
|
||||
</configuration>
|
||||
```
|
||||
|
||||
::: tip 笔者说
|
||||
大家猜一下,如果这两种方式在同时使用时遇到了相同配置,那么哪种方式的配置会生效呢?
|
||||
|
||||
测试思路: 可以先故意改错内部配置方式的 mysql.password 值,如果测试运行正常,说明外部配置生效了,反之则内部配置生效了。 也可以再故意调错外部的值试试。
|
||||
:::
|
||||
|
||||
```xml
|
||||
<properties resource="db.properties">
|
||||
<property name="mysql.password" value="root"/>
|
||||
<property name="txType" value="JDBC"/>
|
||||
</properties>
|
||||
```
|
||||
|
||||
## settings元素(下篇讲解)
|
||||
|
||||
settings 元素是用来设置和改变 MyBatis 在运行时的一些行为的。
|
||||
|
||||
| **设置项** | **描述** | **允许值** | **默认值** |
|
||||
| ------------------- | :----------------------------------------------------------: | :-------------------: | :--------: |
|
||||
| cacheEnabled | 对在此配置文件下的所有cache进行全局性开/关设置 | true \| false | true |
|
||||
| lazyLoadingEnabled | 全局性设置懒加载。如果设为false,则所有相关联的都会被初始化加载 | true \| false | true |
|
||||
| autoMappingBehavior | MyBatis对于resultMap自动映射匹配级别 | NONE\|PARTIAL \|FULL | PARTIAL |
|
||||
| **……(9个)** | **......** | **......** | **......** |
|
||||
|
||||
## typeAlias元素
|
||||
|
||||
在 SQL 映射文件中,我们在使用到某些类型时,需要编写好对应的全类名,大量的使用时,繁琐不说还容易错,如下 resultType 属性示例。
|
||||
|
||||
```xml
|
||||
<select id="selectList" resultType="com.example.pojo.User">
|
||||
SELECT * FROM `user`
|
||||
</select>
|
||||
```
|
||||
|
||||
而typeAlias 元素就可以解决此问题,通过它的配置,可以为指定类型配置好别名,这样在 SQL 映射文件中就可以不用写全限定类名,而是直接使用配置的类型别名了。它的使用方式也有两种。
|
||||
|
||||
### 单个配置
|
||||
|
||||
**第一种使用方式:挨个对不同类型进行别名配置。**
|
||||
|
||||
```xml
|
||||
<typeAliases>
|
||||
<!--
|
||||
单个类型的别名配置(如果类型多,需要配置大量的该标签)
|
||||
type:指定类型的全类名
|
||||
alias:该类型的别名
|
||||
-->
|
||||
<typeAlias type="com.example.pojo.User" alias="User"/>
|
||||
</typeAliases>
|
||||
```
|
||||
|
||||
### 包扫描
|
||||
|
||||
**第二种使用方式:当要配置别名的类型都在指定的 package 下时,可以直接开启包扫描,批量实现别名自动配置。**
|
||||
|
||||
```xml
|
||||
<!-- 类型别名配置 -->
|
||||
<typeAliases>
|
||||
<!--
|
||||
会自动对指定包(包含其子包)下的类进行别名注册
|
||||
注册的别名是:该类型的类名小写
|
||||
例如:User类自动注册的别名是user
|
||||
-->
|
||||
<package name="com.example.pojo"/>
|
||||
</typeAliases>
|
||||
```
|
||||
|
||||
### 使用效果
|
||||
|
||||
下方是有了类型别名配置之后,SQL 映射文件内使用类型的效果。
|
||||
|
||||
```xml
|
||||
<!-- 有了别名配置,使用指定类型时,直接用它的别名即可,而且不区分大小写(原因见下方) -->
|
||||
<select id="selectList" resultType="User">
|
||||
SELECT * FROM `user`
|
||||
</select>
|
||||
```
|
||||
|
||||
::: tip 笔者说
|
||||
在 MyBatis 中有一个类 TypeAliasRegistry ,它的作用就是进行类型别名注册和解析,Java 中常见的类型都已经被它注册好了别名。
|
||||
:::
|
||||
|
||||
```java
|
||||
package org.apache.ibatis.type;
|
||||
// ...略...
|
||||
public class TypeAliasRegistry {
|
||||
/** Map<类型别名, 对应类型的Class对象> */
|
||||
private final Map<String, Class<?>> typeAliases = new HashMap<>();
|
||||
/** 在创建对象时进行常用 Java 类型的别名注册 */
|
||||
public TypeAliasRegistry() {
|
||||
// String 类型注册的别名为 string
|
||||
registerAlias("string", String.class);
|
||||
|
||||
registerAlias("byte", Byte.class);
|
||||
registerAlias("long", Long.class);
|
||||
registerAlias("short", Short.class);
|
||||
registerAlias("int", Integer.class);
|
||||
registerAlias("integer", Integer.class);
|
||||
registerAlias("double", Double.class);
|
||||
registerAlias("float", Float.class);
|
||||
registerAlias("boolean", Boolean.class);
|
||||
|
||||
registerAlias("byte[]", Byte[].class);
|
||||
registerAlias("long[]", Long[].class);
|
||||
registerAlias("short[]", Short[].class);
|
||||
registerAlias("int[]", Integer[].class);
|
||||
registerAlias("integer[]", Integer[].class);
|
||||
registerAlias("double[]", Double[].class);
|
||||
registerAlias("float[]", Float[].class);
|
||||
registerAlias("boolean[]", Boolean[].class);
|
||||
|
||||
registerAlias("_byte", byte.class);
|
||||
registerAlias("_long", long.class);
|
||||
registerAlias("_short", short.class);
|
||||
registerAlias("_int", int.class);
|
||||
registerAlias("_integer", int.class);
|
||||
registerAlias("_double", double.class);
|
||||
registerAlias("_float", float.class);
|
||||
registerAlias("_boolean", boolean.class);
|
||||
|
||||
registerAlias("_byte[]", byte[].class);
|
||||
registerAlias("_long[]", long[].class);
|
||||
registerAlias("_short[]", short[].class);
|
||||
registerAlias("_int[]", int[].class);
|
||||
registerAlias("_integer[]", int[].class);
|
||||
registerAlias("_double[]", double[].class);
|
||||
registerAlias("_float[]", float[].class);
|
||||
registerAlias("_boolean[]", boolean[].class);
|
||||
|
||||
registerAlias("date", Date.class);
|
||||
registerAlias("decimal", BigDecimal.class);
|
||||
registerAlias("bigdecimal", BigDecimal.class);
|
||||
registerAlias("biginteger", BigInteger.class);
|
||||
registerAlias("object", Object.class);
|
||||
|
||||
registerAlias("date[]", Date[].class);
|
||||
registerAlias("decimal[]", BigDecimal[].class);
|
||||
registerAlias("bigdecimal[]", BigDecimal[].class);
|
||||
registerAlias("biginteger[]", BigInteger[].class);
|
||||
registerAlias("object[]", Object[].class);
|
||||
|
||||
registerAlias("map", Map.class);
|
||||
registerAlias("hashmap", HashMap.class);
|
||||
registerAlias("list", List.class);
|
||||
registerAlias("arraylist", ArrayList.class);
|
||||
registerAlias("collection", Collection.class);
|
||||
registerAlias("iterator", Iterator.class);
|
||||
|
||||
registerAlias("ResultSet", ResultSet.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析别名
|
||||
* @param string 要解析的别名
|
||||
* @return 该别名对应的类型的Class对象
|
||||
*/
|
||||
public <T> Class<T> resolveAlias(String string) {
|
||||
try {
|
||||
if (string == null) {
|
||||
return null;
|
||||
}
|
||||
// MyBatis 在【别名自动配置】和【解析映射文件中别名】时,对别名进行了小写转换。
|
||||
// 所以在使用别名的时候才不区分大小写。
|
||||
String key = string.toLowerCase(Locale.ENGLISH);
|
||||
Class<T> value;
|
||||
if (typeAliases.containsKey(key)) {
|
||||
value = (Class<T>) typeAliases.get(key);
|
||||
} else {
|
||||
value = (Class<T>) Resources.classForName(string);
|
||||
}
|
||||
return value;
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e);
|
||||
}
|
||||
}
|
||||
// ...略...
|
||||
}
|
||||
```
|
||||
|
||||
## environments元素
|
||||
|
||||
MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中,现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema的多个生产数据库中使用相同的 SQL 映射。还有许多类似的使用场景。
|
||||
|
||||
**不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。**
|
||||
|
||||
所以,如果你想连接两个数据库,就需要创建两个 SqlSessionFactory 实例,每个数据库对应一个。而如果是三个数据库,就需要三个实例,依此类推,记起来很简单:
|
||||
|
||||
- **每个数据库对应一个 SqlSessionFactory 实例** [1]
|
||||
|
||||
```xml
|
||||
<!-- 环境配置:
|
||||
可以配置多个,但生效的只能有一个
|
||||
default属性:指定生效的环境的id
|
||||
-->
|
||||
<environments default="develop">
|
||||
<!-- 单个环境配置 id 值须保证唯一 -->
|
||||
<environment id="develop">
|
||||
<!-- 事务管理配置
|
||||
type属性:事务管理器的类型
|
||||
可选值有 JDBC 和 MANAGED
|
||||
JDBC:这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
|
||||
MANAGED:让容器来管理事务的整个生命周期
|
||||
-->
|
||||
<transactionManager type="JDBC"/>
|
||||
<!-- 数据源配置
|
||||
type属性:数据源的类型
|
||||
可选值有 POOLED 、UNPOOLED、JNDI
|
||||
POOLED:使用连接池来管理连接对象,降低连接的开销
|
||||
UNPOOLED:不采用连接池,每次都会进行连接打开和关闭
|
||||
JNDI:了解,采用 JNDI 来获取数据源,这种方式不用配置下方的 property。
|
||||
它当初出现的目的就是为了能在同一个应用服务器内的不同应用间共享数据源
|
||||
-->
|
||||
<dataSource type="POOLED">
|
||||
<!-- 下方只是最重要的四项配置,不同类型数据源有不同额外配置 -->
|
||||
<property name="driver" value="${mysql.driver}"/>
|
||||
<property name="url" value="${mysql.url}"/>
|
||||
<property name="username" value="${mysql.username}"/>
|
||||
<property name="password" value="${mysql.password}"/>
|
||||
</dataSource>
|
||||
</environment>
|
||||
</environments>
|
||||
```
|
||||
|
||||
## mappers元素
|
||||
|
||||
mappers 元素的作用就是用来告诉 MyBatis 去哪找我们编写的 SQL 语句,它的使用方式有两大类。
|
||||
|
||||
### 指定映射文件
|
||||
|
||||
这类方式主要是告诉 MyBatis 我们所编写的 SQL 映射文件的地址,我们之前在 [《快速入门》](./01-快速入门) 中使用的就是属于这类方式。它有两种实现:
|
||||
|
||||
```xml
|
||||
<!-- 使用相对于类路径的资源引用,有多少 SQL 映射文件就写多少个 mapper 配置 -->
|
||||
<mappers>
|
||||
<mapper resource="com/example/mapper/UserMapper.xml"/>
|
||||
</mappers>
|
||||
```
|
||||
|
||||
```xml
|
||||
<!-- 使用完全限定资源定位符(URL) (很少使用) -->
|
||||
<mappers>
|
||||
<mapper url="file:///E:/eclipse-workspace/mybatis-1/src/com/example/mapper/UserMapper.xml"/>
|
||||
</mappers>
|
||||
```
|
||||
|
||||
### 指定Mapper接口[重要]
|
||||
|
||||
这类方式还要涉及到 SqlSession 对象的另一个使用形式:**Mapper接口开发**
|
||||
|
||||
我们之前使用 SqlSession 是让它来直接执行指定的 SQL 语句。这种方式需要指明 SQL 映射文件 namespace的名字以及 SQL 语句的 id。因为是硬编码在代码中,维护时有诸多不便,例如:容易写错不说,还不利于我们在持久层采用面向接口编程思想。
|
||||
|
||||
```java
|
||||
// 执行 SQL 语句
|
||||
List<User> userList = sqlSession.selectList("userMapper.selectList");
|
||||
```
|
||||
|
||||
而 Mapper 接口开发就可以有效解决此问题,实现方式如下:
|
||||
|
||||
**第一步:先创建一个 Mapper 接口。** 前期就养成一个开发习惯,保持 Mapper 接口和对应 SQL 映射文件同名同包(虽然目前不同名也没事,但是先听话,养成习惯)。
|
||||
|
||||
```java
|
||||
public interface UserMapper {
|
||||
|
||||
/**
|
||||
* 查询用户列表
|
||||
* @return
|
||||
*/
|
||||
List<User> selectList();
|
||||
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
**第二步:将 SQL 映射文件的 namespace 值改为对应 Mapper 接口的全限定类名。**
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.example.mapper.UserMapper">
|
||||
</mapper>
|
||||
```
|
||||
|
||||
**第三步:将 SQL 映射文件中的 SQL 语句和 Mapper 接口中的方法进行绑定。**
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.example.mapper.UserMapper">
|
||||
<!-- 与 Mapper 接口的方法绑定
|
||||
id 就是 Mapper 接口中的对应方法名
|
||||
resultType 就是 Mapper 接口中的返回值类型,如果返回值类型时集合,则写它的泛型
|
||||
-->
|
||||
<select id="selectList" resultType="User">
|
||||
SELECT * FROM `user`
|
||||
</select>
|
||||
</mapper>
|
||||
```
|
||||
|
||||
**最后,我们在核心配置文件中,再配置好 Mapper 接口的全限定类名即可。**
|
||||
|
||||
```xml
|
||||
<!-- 使用 Mapper 接口的完全限定类名 -->
|
||||
<mappers>
|
||||
<mapper class="com.example.mapper.UserMapper"/>
|
||||
</mappers>
|
||||
```
|
||||
|
||||
**测试一下。**
|
||||
|
||||
```java
|
||||
@Test
|
||||
void testSelectList() throws IOException {
|
||||
|
||||
// 获取SqlSession对象
|
||||
try (SqlSession sqlSession = MyBatisUtils.openSession()){
|
||||
|
||||
// 获取 Mapper 接口,而不再直接执行 SQL 语句
|
||||
// 和以前 DAO 模式就非常相像了,只不过是 DAO 实现类变为了 SQL 映射文件
|
||||
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
|
||||
// 执行方法
|
||||
List<User> userList = userMapper.selectList();
|
||||
|
||||
// 遍历数据
|
||||
userList.forEach(System.out::println);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
另外,在核心配置文件中,大量的配置 Mapper 接口全限定类名,还是有些麻烦,所以 MyBatis 在这也支持包扫描配置。
|
||||
|
||||
```xml
|
||||
<!-- 包扫描,将指定包(及其子包)下的 Mapper 接口全部注册为映射器 -->
|
||||
<mappers>
|
||||
<package name="com.example.mapper"/>
|
||||
</mappers>
|
||||
```
|
||||
|
||||
::: warning 笔者说
|
||||
Mapper 接口开发是我们以后主要使用的方式,必须掌握!
|
||||
:::
|
||||
|
||||
## 参考文献
|
||||
|
||||
[1]MyBatis 官网. MyBatis 配置[EB/OL]. https://mybatis.org/mybatis-3/zh/configuration.html. 2020-12-26
|
||||
|
||||
## 后记
|
||||
|
||||
::: info 笔者说
|
||||
对于技术的学习,笔者一贯遵循的步骤是:先用最最简单的 demo 让它跑起来,然后学学它的最最常用 API 和 配置让自己能用起来,最后熟练使用的基础上,在空闲时尝试阅读它的源码让自己能够洞彻它的运行机制,部分问题出现的原因,同时借鉴这些技术实现来提升自己的代码高度。
|
||||
|
||||
所以在笔者的文章中,前期基本都是小白文,仅仅穿插很少量的源码研究。当然等小白文更新多了,你们还依然喜欢,后期会不定时专门对部分技术的源码进行解析。
|
||||
:::
|
||||
Reference in New Issue
Block a user