文章摘要
GPT 4
此内容根据文章生成,仅用于文章内容的解释与总结
投诉

回顾 JDBC开发

  1. 优点:简单易学,上手快, 非常灵活构建SQL,效率高

  2. 缺点:代码繁琐,难以写出高质量的代码(例如:资源的释放,SQL注入安全性等)

开发者既要写业务逻辑,又要写对象的创建和销毁,必须管底层具体数据库的语法

(例如:分页)。

  1. 适合于超大批量数据的操作,速度快

性能:jdbc(80%代码只完成20%的事) > mybatis > hibernate:(from Employee) > jpa

什么是mybatis,有什么特点

  1. 基于上述二种支持,我们需要在中间找到一个平衡点呢?结合它们的优点,摒弃它们的缺点,这就是myBatis,现今myBatis被广泛的企业所采用。

  2. MyBatis,前身ibatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。

  3. iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAO)

  4. 持久层技术:

jdbc/dbutils/springDAO

​ ORM ( Object Relationship Mapping)框架:ORM 框架是持久化框架有mybatis、hibernate、springORM、Java Persistence API (JPA)、toplink、EJB3 EntityBean

MyBatis中文官网

1.准备Maven Pom.xml

在IntelliJ IDEA中配置 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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<!-- 常量声明 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- jdk版本 -->
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<!-- 模块的依赖 -->
<!-- mysql driver -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.11</version>
</dependency>
<!-- 日志库 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
<!-- apache libraries -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.5</version>
</dependency>
<!-- 类字节增强库:getter/setter, constructor method etc... -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
</dependencies>

2. 创建POJO实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Data // lombok类库 生成getter, setter, toString
// @ToString //生成toString()
// @AllArgsConstructor //全参数构造
// @NoArgsConstructor //默认构造
public class Employee {
//lombok类库:用注解为实体类的属性生成getter/setter及构造函数等
private Integer empno; //Integer=null, int=0
private String ename;
private String job;
private Integer mgr;
private Date hiredate;
private Double sal;
private Double comm;
private Integer deptno;
}

3. 创建 mybatis-config.xml配置文件

src/main/resources 目录下新建mybatis-config.xml 配置文件,内容如下:

  1. (可选)配置相关属性:<properties resource="jdbc.properties"/> 引入连接信息

  2. (可选)配置实体类别名:<typeAliases> ... </typeAliases>

  1. 配置连接池环境: <environments default="..."> ... </environments>

  2. 注册SQL Mapper映射文件:

jdbc.properties

1
2
3
4
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/scott?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
jdbc.username=root
jdbc.password=root

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
<?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="jdbc.properties"/>
<!-- 配置不同的数据库环境 -->
<environments default="development">
<!-- 开发环境 -->
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- 注册实体映射文件,里面写SQL -->
<!-- <mapper resource="org/mybatis/example/BlogMapper.xml"/>-->
</mappers>
</configuration>

**<typeAliases>**类型别名

类型别名是为 Java 类型设置一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。例如:

1
2
3
4
5
6
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/>
<typeAlias alias="Blog" type="domain.blog.Blog"/>
<!-- 为包内的类命名别名:Employee 或 employee -->
<package name="org.glut.mybatismapper.entity"/>
</typeAliases>

注意:加在元素的后面,元素的前面。

**<settings>**特性配置

MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。具体配置项参考 Mybatis中文参考手册。

1
2
3
4
5
<settings>
<setting name="logImpl" value="LOG4J"/>
<!-- 二级缓存开关; 这个配置使全局的映射器启用或禁用二级缓存 -->
<setting name="cacheEnabled" value="true"/>
</settings>

注意:加在元素的后面,元素的前面。

4. 创建 SqlSessionFactory

  • 通过加载核心配置文件创建SqlSessionFactory实例,SqlSessionFactory是一个应用程序内全局单一的实例,支持多线程安全操作

  • 通过SqlSessionFactory 获得 SqlSession 操作数据库,所有的数据库操作通过SqlSession 完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) {
try {
String resource = "mybatis-config.xml";
// 加载核心配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
// sql会话工厂,此对象类似 DataSource
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

// 得到sql会话对象,它封装了Connection, 此对象类似 java.sql.Connection
SqlSession sqlSession = sqlSessionFactory.openSession();
System.out.println(sqlSession.getConnection());

// 执行查询
List list = sqlSession.selectList("名字空间 + 语句 ID");

} catch (IOException e) {
e.printStackTrace();
}
}

5. 编写XML映射文件

SQL语句元素有:

  • select
  • insert
  • update
  • delete
  • sql

Mybatis XML映射文件中可用来生成动态 SQL 的 XML 元素有:

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

创建一个包:com.lanqiao.mapper

  1. 创建 XML 映射文件,编写sql语句

  2. 编写结果映射配置:

  3. 主要的XML元素:

5.1 **<mapper>**根元素

  • namespace: 命名空间,通常是接口映射的全类名。
1
2
3
4
5
<?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="mapper.EmployeeMapper">
</mapper>

5.2 **<select> ** 与 **<where>**元素

<select>元素用来编写select语句。

<where>元素会生成一个 WHERE关键字。

  • <select resultType="POJO全类名">: 自动查询结果映射,要求结果集的列与实体类的属性名一致不区分大小写。

  • <select resultMap="REF_ResultMap_ID">:手动结果映射,这种是推荐结果映射方式,这种方式比较灵活,而合适多表查询的结果映射。

  • <bind name="变量名" 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
<select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
SELECT * FROM EMP WHERE EMPNO=#{empno,jdbcType=INTEGER}
</select>

<select id="select" parameterType="Employee" resultMap="baseResultMap">
SELECT * FROM EMP
<!-- 动态WHERE, 会生成一个 WHERE关键字,并且会消除条件中多余的 AND 或 OR关键字 -->
<where>
<!-- 在Java 里把 %JAMES% 拼好 -->
<!-- <if test="ename != null">AND ENAME like #{ename}</if> -->
<if test="ename != null">
<bind name="enamePattern" value="'%' + ename + '%'"/>
AND ENAME like #{enamePattern}
</if>
<if test="job != null and job.trim().length()>0">AND JOB=trim(#{job})</if>
<if test="sal != null"><![CDATA[ AND SAL<=#{sal} ]]></if>
<if test="deptnoList != null">
<foreach item="deptno" collection="deptnoList"
open="AND DEPTNO in("
close=")"
separator=",">
#{deptno}
</foreach>
</if>
</where>
</select>

注意:以上where 条件中SAL <=#{sal} 的小于号是 XML 的无字符,需要放在XML CDATA 节点内,

<![CDATA[ AND SAL<=#{sal} ]]>

5.3 **<resultMap>**结果映射元素

<resultMap>元素用来将查询结果集映射到实体。<resultMap>内部有以下几个主要元素:

  • <id>元素专门用于映射主键字段

    • javaType: 映射的 Java 属性类型,可以mybatis 预定义的类型别名(在第 8 节)或类型的全类名。
    • jdbcType: jdbc的类型名称(在第 7 节)。
    • typeHandler:类型处理器。无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。Mybatis 已定义了一些默认的类型处理器。
  • <result>元素用于映射普通字段。元素的属性与元素的的属性一样。

  • <association>元素用于M:1多对一关联中映射 1方的实体

  • <collection> 元素用于1:M一对多关联中映射 M方的实体

1
2
3
4
5
6
7
8
9
10
<resultMap id="BaseResultMap" type="com.lanqiao.domain.Employee">
<id property="empno" column="empno" jdbcType="INTEGER"/>
<result property="ename" column="ename" jdbcType="VARCHAR"/>
<result property="job" column="job" jdbcType="VARCHAR"/>
<result property="mgr" column="mgr" jdbcType="INTEGER"/>
<result property="hiredate" column="hiredate" jdbcType="DATE"/>
<result property="sal" column="sal" jdbcType="DECIMAL"/>
<result property="comm" column="comm" jdbcType="DECIMAL"/>
<result property="deptno" column="deptno" jdbcType="INTEGER"/>
</resultMap>

5.3.1 **<association>**多对一关联映射

常用的两种实体关联映射有:

  • M:1(多对一关联映射)
  • 1:1(一对一关联映射)

实体的 M:1(多对一)关联中使用<association></association>映射,关联元素处理“有一个”类型的关系,如一个员工属于一个部门。

多对一关联有以下两种映射方式:

  1. (推荐)嵌套结果

​ 使用嵌套结果映射来处理重复的联合结果的子集。首先让我们来查看这个元素的属性。所有的结果集你都会看到,它和普通的只由 select resultMap 属性的结果映射不同。

嵌套结果要求使用一条SQL语句查询出实体与关联实体(关联表)的数据,效率好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<resultMap id="baseResultMap" type="Employee">
省略其它字段的映射....
<!-- 1.嵌套结果(推荐使用): -->
<association property="dept" javaType="Department">
<id property="deptno" column="DEPTNO" />
<result property="dname" column="DNAME"/>
<result property="loc" column="LOC"/>
</association>

<!-- 或者也可以引用另一个名字空间下的结果映射,这样可以减少一些映射配置
<association property="dept"
resultMap="com.lanqiao.mapper.DepartmentMapper.baseResultMap">
</association>
-->
</resultMap>
  1. 嵌套查询

嵌套查询 (性能差): 通过执行另外一个 SQL 映射语句来返回预期的复杂类型(JavaBean)。也就是说每个关联的实体都通过一条单独的 SQL来查询。

这种方式很简单, 但是对于大型数据集合和列表将不会表现很好。问题就是我们熟知的 “N+1 查询问题”

(面试题)在mybatis的查询中什么是N+1次查询,即嵌套查询中的N+1问题:这种关联会发生N+1次查询问题。N指的是执行了多少条SQL语句获取关联实体,1指的是主查询。如下映射所示:

  • 一条语句为查询员工实体:

    select * from scott.emp

    你执行了一个单独的 SQL 语句来获取员工结果列表(就是“+1”)。

  • N条语句为查询每个员工关联的部门实体

    对返回的每条记录, 你执行了一个查询语句来为每个实体加载细节(就是“N”)。

    当一条语句查询出员工的结果集时,员工结果集中关联的每个部门 (10,20,30,40) 都会导致执行了N 条 SQL语句查询部门实体,已被查询出的部门实体会被缓存在mybatis的一级缓存 SqlSession中

    select * from scott.dept=10;

    select * from scott.dept=20;

    ......

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <resultMap id="baseResultMap_2" type="employee">
    <!-- 使用单独的语句查询关联实体,column="deptno"员工表外键 -->
    <association property="dept" select="selectDeptByPrimaryKey"
    column="deptno"/>
    </resultMap>

    <select id="selectDeptByPrimaryKey" parameterType="int"
    resultMap="deptResultMap">
    select * from dept where deptno=#{id}
    </select>

什么是N+1问题的第二个例子:

例如,假设有一个用户(User)和多个订单(Order)的关系,如果你先查询出所有用户,然后再对每个用户查询他们的订单,那么查询的次数将是用户数量加1。

如果用户先查询出所有用户,然后再对每个用户查询他们的订单,那么总的查询次数将是用户数量加1。这里的“加1”指的是最初查询所有用户的那一次查询。

接下来,我们分析为什么总的查询次数会是用户数量加1。

  1. 首次查询:首先,系统会执行一次查询来获取所有的用户。这是首次查询,也是“加1”中的“1”所代表的那一次查询。
  2. 后续查询:在获取了所有用户之后,系统需要对每个用户执行一次查询来获取他们的订单。因为用户数量是未知的,所以我们用“用户数量”来表示这部分的查询次数。

将首次查询和后续查询的次数相加,就得到了总的查询次数:用户数量 + 1。

5.3.2 **<collection>**一对多关联映射

1:M(一对多)关联使用<collection ofType="元素类型" column="外键字段"></collection>元素,要映射Many端结果到List集合中,我们使用集合元素。

映射集合也有两种方式:

  • 1(推荐使用)嵌套结果

    <collection property="属性名" ofType="集合元素的类型" column="外键列">

    </collection>

    ​ 结果映射引用了EmployeeMapper.xml 中的 baseResultMap 结果映射配置.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     <resultMap id="baseResultMap" type="departemnt">
    <id property="deptno" column="deptno"/>
    <result property="dname" column="dname"/>
    <result property="loc" column="loc"/>
    <collection property="employeeList"
    ofType="employee"
    column="deptno"
    resultMap="com.lanqiao.mapper.EmployeeMapper.baseResultMap">
    </collection>
    </resultMap>
  • 2. 嵌套查询

    通过在<collection></collection>元素上指定select="查询 Many端实体的语句" 属性,

    该属性会使用当前实体的主键(Many端为外键)来执行另一条查询语句获取Many端实体,并将获取到的Many端实体存入集合中。

    <collection property="属性名" ofType="属性类型" column="外键列" select="查询 Many端实体的语句">

    </collection>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<resultMap id="baseResultMap_2" type="departemnt">
<id property="deptno" column="deptno"/>
<result property="dname" column="dname"/>
<result property="loc" column="loc"/>
<collection property="employeeList" ofType="employee" column="deptno"
select="selectEmployeeByDeptno">
</collection>
</resultMap>

<select id="selectEmployeeByDeptno" parameterType="int"
resultMap="com.lanqiao.mapper.EmployeeMapper.baseResultMap">
select *
from emp
where deptno=#{id};
</select>

优点:编写SQL简单,无需做多表的连接查询;关联的实体通过单独的SQL语句查询并单独封装。
缺点:执行了N+1条件语句。性能差

5.4 **<sql>**通用 SQL 片段

通用 SQL片段,可被其他语句引用的可重用语句块。

1
2
3
4
5
6
7
8
9
10
<sql id="Base_Column_List">
empno,ename,job,mgr,hiredate,sal,comm,deptno,header
</sql>

<select id="selectByPrimaryKey" parameterType="java.lang.Long"
resultMap="BaseResultMap">
select <include refid="Base_Column_List" />
from emp
where empno = #{empno,jdbcType=INTEGER}
</select>

5.5 **<if>**元素

<if>元素用以根据条件判断生成动态SQL。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<where>
<!-- 1.在程序里把 %JAMES% 拼好
emp.setEname("%J%");
-->
<if test="ename != null">
AND ENAME like #{ename}
</if>

<!-- 2.在XML里把 % 拼好 -->
<if test="ename != null">
<bind name="enamePattern" value="'%' + ename + '%'"/>
AND ENAME like #{enamePattern}
</if>

<!-- SQL中遇到小于号用 CDATA 元素包裹起来。大于号可以直接用 -->
<if test="sal != null">
<![CDATA[ AND SAL<=#{sal} ]]>
</if>

<!-- trim(#{job}) 是mysql的trim()函数 -->
<if test="job != null and job.trim().length()>0">
AND JOB=trim(#{job})
</if>
</where>

5.6 **<choose>**元素

分支结构

1
2
3
4
5
6
7
8
9
<!-- 类似 java的 switch -->
<choose>
<when test="ename != null"> <!-- java switch case -->
sql语句
</when>
<otherwise> <!-- java switch default -->
sql语句
</otherwise>
</choose>

5.7**<foreach>**元素

<foreach>元素通过遍历集合来产生动态 SQL。

  • java 代码
1
2
3
4
5
6
7
8
9
10
@NoArgsConstructor
@AllArgsConstructor
@Data // @Data = @Getter + @Setter
@ToString
public class Employee {
//省略其它属性

//用于查询条件的多个部门号
private List<Integer> deptnoList;
}
  • xml 映射
1
2
3
4
5
6
<if test="deptnoList != null">
<foreach item="deptno" collection="deptnoList"
open="AND DEPTNO in(" close=")" separator=",">
#{deptno}
</foreach>
</if>

5.8**<trim>**元素

<trim> 元素用于删除空格或删除指定字符,并添加上指定的前缀字符 和 后缀字符。

1
2
3
4
5
6
7
<trim prefix="加上前缀字符" suffix="加上的后缀字符" 
prefixOverrides="被删除的前缀字符" suffixOverrides="被删除的后缀字符">
<if test="ename != null">ename,</if>
<if test="job != null">job,</if>
<if test="mgr != null">mgr,</if>
......
</trim>

示例:用来前置SET关键字和清除最后的逗号

1
2
3
4
5
6
7
8
9
10
11
12
<update id="update_2" parameterType="employee">
update emp
<trim prefix="set" suffixOverrides=",">
<if test="ename != null">ENAME=#{ename},</if>
<if test="job != null">JOB=#{job},</if>
<if test="sal != null">SAL=#{sal},</if>
<if test="comm != null">COMM=#{comm},</if>
</trim>
<where>
<if test="empno != null">EMPNO=#{empno}</if>
</where>
</update>

5.9**<update>**元素、set元素

<set>元素会前置一个SET关键字,同时会删除最后多余的逗号

1
2
3
4
5
6
7
8
9
10
11
12
<update id="update" parameterType="employee">
update emp
<set>
<if test="ename != null">ENAME=#{ename},</if>
<if test="job != null">JOB=#{job},</if>
<if test="sal != null">SAL=#{sal},</if>
<if test="comm != null">COMM=#{comm},</if>
</set>
<where>
<if test="empno != null">EMPNO=#{empno}</if>
</where>
</update>

5.10**<insert>**元素

insert语句没有resultMap,默认返回整数即插入的行数.
如果某些字段允许为 null 时,安全的做法就是对这样的列做 空值处理,这不是 mybaits 需要的,而是不同数据的驱动需要。

  • useGeneratedKeys="true" : 说明主键值由数据库生成

  • keyProperty="empno" : 自动生成的主键值赋给 POJO 的哪个属性

  • keyColumn="empno": 自动生成主键值的列

示例 1:使用 JDBC null 值处理

1
2
3
4
<insert id="insert" parameterType="employee" useGeneratedKeys="true" keyProperty="empno" keyColumn="empno">
insert into emp(ename, job, mgr, hiredate, sal, comm, deptno)
values (#{ename},#{job,jdbcType=VARCHAR},#{mgr,jdbcType=INTEGER},#{hiredate,jdbcType=DATE},#{sal,jdbcType=DOUBLE},#{comm,jdbcType=DOUBLE},#{deptno,jdbcType=INTEGER})
</insert>

示例 2:使用<trim>元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- 动态 insert语句,就是说页面过来有值的字段生成SQL,无值的字段不生成SQL -->
<insert id="insert_2" parameterType="employee">
insert into emp(
<trim suffixOverrides=",">
<if test="ename != null">ename,</if>
<if test="job != null">job,</if>
<if test="mgr != null">mgr,</if>
<if test="hiredate != null">hiredate,</if>
<if test="sal != null">sal,</if>
<if test="comm != null">comm,</if>
<if test="deptno != null">deptno,</if>
</trim>)
values(
<trim suffixOverrides=",">
<if test="ename != null">#{ename},</if>
<if test="job != null">#{job},</if>
<if test="mgr != null">#{mgr},</if>
<if test="hiredate != null">#{hiredate},</if>
<if test="sal != null">#{sal},</if>
<if test="comm != null">#{comm},</if>
<if test="deptno != null">#{deptno},</if>
</trim>)
</insert>

6. 接口映射器

映射器接口执行SQL语句的规则:

  1. XML 映射文件 *.xml的名字空间(namespace)必须和映射器接口全类名一致,必须放在与映射器接口相同的包下。

    接口映射器类全名:org.glut.mybatis.mapper.EmployeeMapper (名字空间)

  • 接口映射器 Java
1
2
3
4
package org.glut.mybatis.mapper;
public interface EmployeeMapper {
public Employee selectByPrimaryKey(Integer empno);
}
  • XML映射文件
1
2
3
4
5
6
<mapper namespace="org.glut.mybatis.mapper.EmployeeMapper">
<select id="selectByPrimaryKey" parameterType="int"
resultType="Employee">
select * from emp where empno=#{id}
</select>
</mapper>
  1. selectByPrimaryKey(Integer empno) 方法名对应 *.xml中的语句ID
1
2
3
<select id="selectByPrimaryKey" ....>
SQL....
</select>
  1. selectByPrimaryKey(Integer empno)参数类型对应SQL语句所需的参数类型
1
2
3
<select parameterType="int" ....>
SQL....
</select>
  1. Employee selectByPrimaryKey(Integer empno)返回值 对应 SQL语句的查询结果类型
1
2
3
<select resultType="Employee" ....>
SQL....
</select>

如果是 insert,update,delete 操作那么方法声明返回 int 类型

1
2
3
int insert(Employee parameter);
int update(Employee parameter);
int delete(Integer empno);
  1. mybatis-config.xml 配置文件中配置接口映射器
1
2
3
4
<mappers>
<mapper resource="mapper/EmployeeMapper.xml"/>
<!-- <package name="XML映射文件所在包"/> -->
</mappers>
  1. 在程序中使用接口映射器
1
2
3
4
5
6
7
EmployeeMapper mapper = SqlSession.getMapper(EmployeeMapper.class);
/*
* 通过EmployeeMapper接口全类名 与 EmployeeMapper.xml文件中的命名空间匹配.
* 调用的select() 方法其方法名“select”为 EmployeeMapper.xml文件中的语句ID.
* select()方法返回类型与 sql语句的查询结果匹配
*/
Employee emp = mapper.selectByPrimaryKey(1234);

7.支持的 JDBC 类型

为了未来的参考,MyBatis 通过包含的 jdbcType枚举型,支持下面的 JDBC 类型。

java.sql.Types中定义的常量。

BIT FLOAT CHAR TIMESTAMP OTHER UNDEFINED
TINYINT REAL VARCHAR BINARY BLOG NVARCHAR
SMALLINT DOUBLE LONGVARCHAR VARBINARY CLOB NCHAR
INTEGER NUMERIC DATE LONGVARBINARY BOOLEAN NCLOB
BIGINT DECIMAL TIME NULL CURSOR ARRAY

8. 支持的 Java类型

mybatis已经为许多常见的 Java 类型内建了相应的类型别名。它们都是大小写不敏感的,需要注意的是由基本类型名称重复导致的特殊处理“带下划线”

别名 映射的Java类型
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
object Object
map Map
hashmap HashMap
list List
arraylist ArrayList
collection Collection
iterator Iterator

9. #{expr}与 ${expr} 的区别

1
2
#{ename}: mybatis会将该表达解析成预译语句的 ?号(占位符)。JDBC中的preparedStatement的Set方法
${ename}: mybatis不将这种表达式解析成预编译语句的 ?号,而是将它直接作为值来处理。这种语法不安全会有SQL注入攻击的风险。
1
2
and ename like '${ename}'  #生成--> and ename like '%J%'
select * from employee order by ${orderBy} # orderBy是 Java 里的一个属性

10. SqlSession 持久化操作

1
2
3
4
5
6
SqlSession.selectList("语句ID" [, parameter]);
SqlSession.selectOne("语句ID" [, parameter]);
SqlSession.insert("语句ID", parameter);
SqlSession.delete("语句ID", parameter);
SqlSession.update("语句ID", parameter);
T getMapper(Class<T> cls); //获取接口映射器

parameter 参数对象为调用 SQL语句时所需的占位符参数,一般为 POJO对象。

11.(重要)mybatis工作流程

  1. 通过Reader对象读取mybatis-config.xml配置文件(该文本的位置和名字可任意)

  2. 通过SqlSessionFactoryBuilder对象创建SqlSessionFactory(类似javax.sql.DataSource)对象。

注:如果要在应用程序中使用二级缓存那么可利用第三方的缓存(ehcache等),mybatis提供集成方案。

  1. SqlSessionFactory中获取SqlSession(封装了java.sql.Connection)对象,将sqlSession称为一级缓存。

说明:

SessionFactoryBuilder

这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但是最好还是不要让其一直存在以保证所有的 XML 解析资源开放给更重要的事情。

SqlSessionFactory

SqlSessionFactory的缓存称为:二级(全局)缓存

SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由对它进行清除或重建。使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏味道(bad smell)”。因此 SqlSessionFactory 的最佳作用域是应用程序作用域级。有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。

SqlSession

SqlSession的缓存称为:一级(线程级、事务级)缓存

每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。也绝不能将 SqlSession 实例的引用放在任何类型的管理作用域中,比如 Servlet 架构中的 HttpSession。如果你现在正在使用一种 Web 框架,要考虑 SqlSession 放在一个和 HTTP 请求对象相似的作用域中。换句话说,每次收到的 HTTP 请求,就可以打开一个 SqlSession,返回一个响应,就关闭它。这个关闭操作是很重要的,你应该把这个关闭操作放到 finally 块中以确保每次都能执行关闭。

  1. 事务开始,在mybatis中事务是默认开启的, 但自动提交是关闭的。

  2. 通过SqlSession对象读取EmployeeMapper.xml映射文件中的操作语句ID,从而读取sql语句。

  3. 事务提交。

    • sqlSession.commit() : 提交事务

    • sqlSession.rollback():回滚事务

  4. 关闭SqlSession对象,释放数据库连接,提高数据库连接的重用性。让mybatis能响应更多的用户请求。

    1
    2
    3
    4
    5
    //使用try-finally 结构自动关闭 SqlSession
    try ( SqlSession sqlSession = MybatisUtil.getSqlSession() ) {

    } catch (Exception e) {
    }

12. 二级缓存配置

MyBatis缓存看这一篇就够了(一级缓存+二级缓存+缓存失效+缓存配置+工作模式+测试)_直接修改数据库数据会使mybatis一级缓存和二级缓存的使用场景失效吗-CSDN博客

springboot中打开二级缓存

咱们得知道,做数据查询是可以进行缓存的。注意,是查询,dml语句没办法缓存的吧?

SqlSession级的缓存称为一级缓存、事务级缓存、线程级缓存。

SqlSessionFactory级的缓存称为二级缓存、应用级缓存、全局缓存。Mybatis除了自身提供二级缓存的实现外,同时也提供对第三方缓存的支持,如:Redis, Ehcache, Memory Cache等。

12.1 修改mybatis-config.xml

默认二级缓存是开启的,如果要启用二级缓存,需要在mybatis-config.xml文件中加入下面设置:

1
2
3
4
5
6
<settings>
<!-- 设置日志的实现方式 -->
<setting name="logImpl" value="LOG4J"/>
<!-- 这个配置使全局的映射器启用或禁用二级缓存 -->
<setting name="cacheEnabled" value="true"/>
</settings>

12.2 在SQL Mapper映射文件中

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 缓存实体,此元素只对当前名字空间内的实体查询有效 -->
<cache eviction="LRU"
flushInterval="60000"
size="1024"
readOnly="true"/>

<!--也可让某查询不使用二经缓存,只需在<selec>元素中使用useCache=“true”
flushCache="false"
-->
<select id="select" parameterType="string" resultType="integer"
flushCache="false" useCache="true">
Select_statement
</select>

这个简单语句的效果如下:

  • 映射语句文件中的所有 select 语句将会被缓存

  • 映射语句文件中的所有 insert,updatedelete 语句会刷新缓存

  • 缓存会使用 Least Recently Used (LRU,最近最少使用的)算法来收回。

  • 根据时间表(比如 no Flush Interval,没有刷新间隔), 缓存不会以任何时间顺序来刷新。

  • 缓存会存储列表集合或对象(无论查询方法返回什么)的 1024 个引用。

  • 缓存会被视为是 read/write(可读/可写)的缓存,意味着对象检索不是共享的,而且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

可用的收回策略(eviction)有:

  • LRU – 最近最少使用的:移除最长时间不被使用的对象。

  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。

  • SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。

  • WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。

默认的是 LRU收回策略。

  • flushInterval(刷新间隔)可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。

  • size(引用数目)可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的可用内存资源数目。默认值1024。

  • readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false。

12.3 实体类实现java.io.Serializable接口

如果二级缓存想要命中实现,则必须要将上一次sqlSession commit之后才能生效

缓存实体,只对当前名字空间内的实体查询有效。默认数据被缓存在java.io.tmpdir (JVM的一个属性)

注意:

  1. 多表连接查询时关联实体不做缓存

  2. mybatis的缓存是基于SQL语句id(namespace + select_id)做标识的

  3. 查询后要调用SqlSession.commit(); 数据才会被缓存