小白 の MyBatis 学习笔记

596 阅读14分钟

1. MyBatis 简介

  • MyBatis 是一个优秀的基于 java 的持久层框架,它内部封装了 JDBC,使开发者只需要关注 sql 语句本身,而不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。
  • MyBatis 通过 xml注解的方式将要执行的各种 statement 配置起来,并通过 java 对象和 statement 中 sql 的动态参数进行映射生成最终执行的 sql 语句。
  • MyBatis 框架执行 sql 并将结果映射为 java 对象并返回。采用 ORM 思想解决了实体和数据库映射的问题,对 jdbc 进行了封装,屏蔽了 JDBC API 底层访问细节,使我们不用与 JDBC API 打交道,就可以完成对数据库的持久化操作。

MyBatis 官网地址:mybatis.org/mybatis-3/z…

2. MyBatis 开发步骤

2.1 添加 MyBatis 的相关依赖

2.2 创建数据库表 user & 实体类 User

public class User {
    private int id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;
    // setter、getter 略
}

2.3 编写 UserDao 接口

package com.one.dao;

public interface UserDao {
    // select 方法
    List<User> findAllUsers();
}

2.3 编写映射文件 UserMapper.xml

Mapper 映射文件约束头

XML 文档定义的两种形式(DTDSCHEMA),此处使用的是 DTD;在约束头的作用下,当我们书写标签时能起到约束提示的作用。

<?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

2.4 编写核心文件 SqlMapConfig.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">

SqlMapConfig.xml

2.5 编写测试类

3. MyBatis の CRUD

3.1 <select>

常用属性:

  • id
  • parameterType
  • resultType
  • resultMap:外部 resultMap 的命名引用
  • flushCache:表示在调用 SQL 语句之后,是否需要 MyBatis 清空之前查询的本地缓存和二级缓存
  • useCache:用于控制二级缓存的启停
  • timeout:用于设置超时参数,单位为秒

UserDao.java

public interface UserDao {
    // parameterType: Integer
    // resultType: com.one.domain.User
    User searchById(Integer id);
}

UserMapper.xml

<!--namespace: UserMapper.xml实现的接口UserDao的全限定名-->
<mapper namespace="com.one.dao.UserDao">
    <!--parameterType: searchById()的参数类型-->
    <!--resultType: searchById()的返回值类型-->
    <select id="searchById" parameterType="Integer" resultType="com.one.domain.User">
        select id, username, sex from user where id = #{id};
    </select>
</mapper>

3.2 <insert>

<insert id=""
        parameterType=""
        flushCache="true"
        statementType="PREPARED"
        keyProperty=""
        keyColumn=""
        useGeneratedKeys=""
        timeout="20">
</insert>

相对于 <select> 的 3 个特有属性说明:

  • keyProperty:(仅对 insert 和 update 有效)该属性将插入或更新操作时的返回值赋值给 PO 类的某个属性,通常会设置为主键对应的属性。
  • keyColumn:(仅对 insert 和 update 有效)设置第几列是主键,当且仅当主键不是第一列时需要设置。
  • useGeneratedKeys:(仅对 insert 和 update 有效)

UserDao.java

public interface UserDao {
    // parameterType: com.one.domain.User
    void saveUser(User user);
}

UserMapper.xml

<mapper namespace="com.one.dao.UserDao">
    <insert id="saveUser" parameterType="com.one.domain.User">
        insert into user(username, birthday, sex, address) values(#{username}, #{birthday}, #{sex}, #{address});
    </insert>
</mapper>

3.3 <update>

与上文提及的 <select> 中的属性基本相同,除了 resultMapresultType

UserDao.java

public interface UserDao {
	// parameterType: com.one.domain.User
    void updateUser(User user);
}

UserMapper.xml

<mapper namespace="com.one.dao.UserDao">
    <update id="updateUser" parameterType="com.one.domain.User">
        update user set birthday=#{birthday}, sex=#{sex}, username=#{username}, address=#{address} where id=#{id};
    </update>
</mapper>

3.4 <delete>

与上文提及的 <select> 中的属性基本相同,除了 resultMapresultType

UserDao.java

public interface UserDao {
    // parameterType: Integer/int
	void deleteUser(Integer id);
}

UserMapper.xml

<mapper namespace="com.one.dao.UserDao">
    <delete id="deleteUser" parameterType="int">
        delete from user where id=#{id};
    </delete>
</mapper>

综上:

测试代码

4. MyBatis 核心配置文件

4.1 核心配置文件层级关系

⭐注意:<configuration> 的子元素必须按照如下表由上到下的顺序进行配置,否则 MyBatis 在解析 XML 的配置文件的时候会报错。

  • configuration 配置
    • properties 属性
    • settings 设置
    • typeAliases 类型别名
    • typeHandlers 类型处理器
    • plugins 插件
    • environments 环境
      • environment 环境变量
        • transactionManager 事务管理器
        • dataSource 数据源
    • mappers 映射器

4.2 标签详解

4.2.1 <properties>

实际开发中,习惯将数据源的配置信息单独抽取成一个 properties 文件,该标签可以加载额外配置properties 文件。

4.2.2 <settings>

<setting> 元素主要用于改变 MyBatis 运行时的行为,例如开启二级缓存、开启延迟加载等。虽然不配置 <setting> 元素也可以正常运行 MyBatis,但是熟悉 <setting> 的配置内容以及它们的作用还是十分必要的。<setting> 元素中的常见配置如下:

<setting>
	<setting name="cacheEnabled" value="true"/>
	<setting name="lazyLoadingEnabled" value="false"/>
	<setting name="aggressiveLazyLoading" value="true"/>
	<setting name="multipleResultSetsEnabled" value="true"/>
	<setting name="useColumnLabel" value="true"/>
	<setting name="jdbcTypeForNull" value="OTHER"/>
	<setting name="mapUnderscoreToCamelCase" value="false"/>
    ...
</setting>
日志工厂

这里就简单介绍下 MyBatis 所用日志的具体实现 STDOUT_LOGGINGLOG4J,设置日志有助于我们更好地查看实现过程及排错。

1)STDOUT_LOGGING

SqlMapConfig.xml

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

未设置日志的输出结果:

image.png

设置日志后的输出结果:

image.png


2)LOG4J

导入 log4j 的坐标:

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

SqlMapConfig.xml(MyBatis 核心文件):

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

log4j.properties

#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file

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

#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/w.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

测试

// 注意导包:org.apache.log4j.Logger
static Logger logger = Logger.getLogger(MyTest.class);

@Test
public void textLog4j(){
    logger.info("info...");
    logger.debug("debug...");
    logger.error("error...");
}

4.2.3 <typeAliases>

<!-- 定义别名 -->
<typeAliases>
    <!-- 单个定义 -->
    <typeAlias type="com.one.domain.User" alias="user"/>
    <!-- 整个包定义:别名为其类名小写(Account => account) -->
    <package name="com.one.domain"/>
</typeAliases>

上面我们是自定义的别名,MyBatis 框架已经为我们设置好的一些常用的类型的别名:

别名数据类型
stringString
longLong
intInteger
doubleDouble
booleanBoolean
dateDate
decimalBigDecimal
mapMap
hashmapHashMap
listList
collectionCollection
arraylistArrayList
iteratoriterator
......

4.2.4 <typeHandlers>


自定义类型处理器:你可以重写已有的类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。 具体做法为:实现 org.apache.ibatis.type.TypeHandler 接口, 或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler, 并且可以(可选地)将它映射到一个 JDBC 类型。比如将 Java 类型 Date 转换为数据库类型 bigint

1)继承 BaseTypeHandler

2)在 MyBatis 核心文件中进行注册

3)测试

4.2.5 <plugins>

MyBatis 允许在已映射语句执行过程中的某一点进行拦截调用,这种拦截调用是通过插件来实现的。<plugins> 元素的作用就是配置用户所开发的插件。如果用户想要进行插件开发,必须要先了解其内部运行原理,因为在试图修改或重写已有方法的行为时,很可能会破坏 MyBatis 原有的核心模块。

以下以 PageInterceptor 作为例子进行演示,分页助手 PageHelper 是将分页的复杂操作进行封装,使用简单的方式即可获得分页的相关数据。

开发步骤

  1. 导入通用 PageHelper 的坐标
  2. 在 MyBatis 核心配置文件中配置 PageInterceptor 插件

3)使用分页

4.2.6 <environments>

在配置文件中,<environments> 元素用于对环境进行配置。MyBatis 的环境配置实际上就是数据源的配置,我们可以通过 <environment> 元素配置多种数据源,即配置多种数据库。

对数据库进行多环境配置示例如下:

其中,事务管理器transactionManager)类型有两种:

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

其中,数据源dataSource)类型有三种:

  • UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接。
    • driver
    • url
    • username
    • password
    • defaultTransactionIsolationLevel
  • POOLED:这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来。
    • driverurlusernamepassworddefaultTransactionIsolationLevel
    • poolMaximumActiveConnections:在任意时间可以存在的活动(也就是正在使用)连接数量,默认值 10
    • poolMaximumIdleConnections:任意时间可能存在的空闲连接数
    • poolMaximumCheckoutTime:在被强制返回之前,池中连接被检出时间,默认值 200000ms,即 20s
    • ...
  • JNDI:这个数据源的实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。
    • initial_context
    • data_source

4.2.7 <mapper>

该标签的作用是加载映射的,加载方式有如下几种:

  • 使用相对于类路径的资源引用,例如:<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  • 使用映射器接口实现类的完全限定类名,例如:<mapper class="org.mybatis.builder.AuthorMapper"/>
  • 使用完全限定资源定位符(本地文件路径),例如:<mapper url="file://D:com/one/mappers/AuthorMapper.xml"/>
  • 内的映射器接口实现全部注册为映射器,例如:<package name="org.mybatis.builder"/>

5. MyBatis 相关 API

5.1 SqlSessionFactoryBuilder

常用 API :SqlSessionFactory build(InputStream inputStream)

通过加载 MyBatis 的核心文件的输入流的形式构建一个 SqlSessionFactory 对象

InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml"); 
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); 
SqlSessionFactory factory = builder.build(inputStream);

其中, Resources 工具类,这个类在 org.apache.ibatis.io 包中;Resources 类帮助你从类路径下、文件系统或一个 web URL 中加载资源文件。

5.2 SqlSessionFactory

SqlSessionFactory 有多个个方法创建 SqlSession 实例。常用的有如下两个:

方法解释
openSession()会默认开启一个事务,但事务不会自动提交,也就意味着需要手动提交该事务,更新操作数据才会持久化到数据库中
openSession(boolean autoCommit)参数为是否自动提交,如果设置为 true,那么不需要手动提交事务

5.3 SqlSession

SqlSession 实例在 MyBatis 中是非常强大的一个类。在这里你会看到所有执行语句、提交或回滚事务和获取映射器实例的方法。

执行语句的方法主要有:

<T> T selectOne(String statement, Object parameter) 
<E> List<E> selectList(String statement, Object parameter) 
int insert(String statement, Object parameter) 
int update(String statement, Object parameter) 
int delete(String statement, Object parameter)

操作事务的方法主要有:

void commit()  
void rollback() 

6. MyBatis 的 Dao 层实现方式

传统开发方式:手动对 Dao 进行实现

代理方式对 Dao 进行实现:UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

采用 MyBatis 的代理开发方式实现 DAO 层的开发,这种方式是我们后面进入企业的主流。Mapper 接口开发方法只需要程序员编写Mapper 接口(相当于Dao 接口),由 MyBatis 框架根据接口定义创建接口的动态代理对象。

Mapper 接口开发需要遵循以下规范

  1. Mapper.xml 文件中的 namespacemapper 接口的全限定名相同
  2. Mapper 接口方法名Mapper.xml 中定义的每个 statement 的 id 相同
  3. Mapper 接口方法的输入参数类型mapper.xml 中定义的每个 sql 的 parameterType 的类型相同
  4. Mapper 接口方法的输出参数类型mapper.xml 中定义的每个 sql 的 resultType 的类型相同

实例:

7. 动态 SQL

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

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

  • <if>
  • <foreach>
  • <sql>、<foreach>

7.1 if

7.2 foreach

7.3 sql、include

7.4 choose、when、otherwise

  • <choose>switch
  • <when>if
  • <otherwise>default

image.png

8. MyBatis 的多表查询

8.1 一对一的配置

8.1.1 第一种配置形式

8.1.2 第二种配置形式

查询结果:

8.2 一对多的配置

查询结果:

8.3 多对多的配置

对比一对多配置

相同点:resultMap 配置

差异点:sql 语句

8.4 小结

MyBatis 多表配置方式:

  • 一对一配置:使用 <resultMap> & <association>作配置
  • 一对多配置:使用 <resultMap> & <collection> 作配置
  • 多对多配置:使用 <resultMap> & <collection> 作配置

9. MyBatis 注解开发

这几年来注解开发越来越流行,Mybatis 也可以使用注解开发方式,这样我们就可以减少编写 Mapper 映射文件了。常用注解如下:

  • @Insert:实现新增
  • @Update:实现更新
  • @Delete:实现删除
  • @Select:实现查询
  • @Result:实现结果集封装
  • @Results:可以与 @Result 一起使用,封装多个结果集
  • @One:实现一对一结果集封装
  • @Many:实现一对多结果集封装

9.1 CRUD 注解

SqlMapConfig.xml

<!-- 加载映射关系: 寻找注解 -->
<mappers>
    <package name="com.one.dao"/>
</mappers>

9.2 多表查询

SqlMapConfig.xml

<!-- 加载映射关系: 寻找注解 -->
<mappers>
    <package name="com.one.dao"/>
</mappers>

9.2.1 一对一查询

1)Method_1:

2)Method_2:

可以将 SELECT * FROM user, account WHERE user.id=account.UID; 拆分成两个语句:

SELECT * FROM account + SELECT * FROM user WHERE id=#{id},于是便有了如下第二种一对一注解查询方式。

9.2.2 一对多查询

9.2.3 多对多查询

10. MyBatis 缓存机制

MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。

MyBatis 系统中定义了两级缓存:

  • 一级缓存
  • 二级缓存

10.1 一级缓存

默认情况下,只启用了本地的会话缓存,它仅仅对一个会话(sqlSession)中的数据进行缓存;一级缓存是 SqlSession 级别缓存,也称为本地缓存;一个 sqlSession 对应一个一级缓存,一旦执行 sqlSession.close(),则一级缓存失效。

与数据库同一次会话期间查询到的数据会存放到本地缓存中,以后如果需要获取相同的数据,直接从缓存中获取,没必要再去连接并查询数据库,从而提高了查询效率。

10.1.1 测试一级缓存

@Test
public void testCache() {
    InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    
    UserDao mapper = sqlSession.getMapper(UserDao.class);

    User user1 = mapper.searchById(45);
    System.out.println(user1);

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

    User user2 = mapper.searchById(45);
    System.out.println(user2);

    // 对比两个对象
    System.out.println(user1 == user2);
    
    // 关闭 sqlSession,同时: 一级缓存在此之后失效
    sqlSession.close();
}

测试结果:

10.1.2 可能导致缓存失效的几种情况

  • 查询不同的对象
  • 增删改操作,改变了数据,刷新了缓存
  • 查询不同的 **Mapper.xml
  • 手动清理缓存:sqlSession.clearCache();

10.2 二级缓存

10.2.1 二级缓存简介

一级缓存作用域太低了,所以诞生了二级缓存;一个 namespace 对应一个二级缓存。二级缓存也称全局缓存,需要手动开启和配置,是基于 namespace 级别的缓存。要启用全局的二级缓存,只需要在你的 SQL 映射文件(**Mapper.xml)中添加一行:

<!-- 开启二级缓存 -->
<cache/>

SqlMapConfig.xml

<settings>
    <!-- 显式地开启二级缓存:可以不写,仅为了可读性 -->
    <setting name="cacheEnabled" value="true"/>
</settings>

基本上就是这样。这个简单语句 <cache/> 的效果如下:

  • 映射语句文件中的所有 select 语句的结果将会被缓存。
  • 映射语句文件中的所有 insertupdatedelete 语句会刷新缓存
  • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
  • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
  • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
  • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

提示缓存只作用于 cache 标签所在的映射文件中的语句。

这些属性可以通过 cache 标签的属性来修改。比如:

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"	
  readOnly="true"/>
  • flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。

  • size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。

  • readOnly(只读)属性可以被设置为 truefalse。只读的缓存会给所有调用者返回缓存对象的相同实例, 因此这些对象不能被修改,这就提供了可观的性能提升,虽然速度上会慢一些,但是更安全,因此默认值是 false而可读写的缓存会(通过序列化)返回缓存对象的拷贝 —— 所以当 readOnly = false 时,读写的对象必须实现序列化接口 class User implements Serializable

这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。

可用的清除策略有:

  • LRU – 最近最少使用:移除最长时间不被使用的对象(默认的清除策略)。
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
  • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

提示二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=trueinsert/delete/update 语句时,缓存会获得更新。二级缓存在实际开发中基本不会使用,在一些笔试题会出现。

10.2.2 测试二级缓存

@Test
public void testSecondCache() throws IOException {
    InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
    /* 创建两个SqlSession */
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();

    UserDao mapper1 = sqlSession1.getMapper(UserDao.class);
    User user1 = mapper1.searchById(45);
    System.out.println(user1);
    // 将结果写入到二级缓存中
    sqlSession1.close();

    System.out.println("========================= sqlSession1 关闭后 ===========================");

    UserDao mapper2 = sqlSession2.getMapper(UserDao.class);
    User user2 = mapper2.searchById(45);
    System.out.println(user2);

    System.out.println(user1 == user2);
    sqlSession2.close();
}

image.png

测试结果:

小结:

  • 一级缓存是默认开启的,只在一次 SqlSession 对象中生效!
  • 二级缓存需要手动开启 <cache/>,可在多个 SqlSession 对象中奏效——当且仅当在同一个 Mapper.xml 中才不会失效!

OK,以上就是 MyBatis 笔记的所有内容!

原创不易🧠 转载请标明出处🌊
若本文对你有所帮助,点赞支持嗷🔥