MyBatis映射器文件

映射器文件

mybatis的真正强大之处就在于它的语句映射。

正是因为此映射器文件,才使我们减少了将近95%的JDBC代码,使我们更专注于书写SQL语句

映射器文件只有这些顶级元素

  • cache
  • cache-ref
  • delete
  • insert
  • resultMap
  • select
  • sql
  • update

select

该标签对标SQL语句的DQL语句,也就是select语句

1
2
3
<select id="getStudentById" resultType="student">
select * from student where id = #{id}
</select>

这个select语句映射namespace绑定的接口中的listTeachers方法

resultType定义返回值类型

#{id}是取到方法传入的参数

属性 描述
id 在命名空间中唯一的标识符,可以被用来引用这条语句。
parameterType 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。
resultType 期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。
resultMap 对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。
useCache 将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。
flushCache 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false。
statementType 可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
timeout 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。

insert,update和delete

这三个标签对标SQL语句的DML语句

属性 描述
id 在命名空间中唯一的标识符,可以被用来引用这条语句。
parameterType 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。
parameterMap 用于引用外部 parameterMap 的属性,目前已被废弃。请使用行内参数映射和 parameterType 属性。
flushCache 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:(对 insert、update 和 delete 语句)true。
timeout 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。
statementType 可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
useGeneratedKeys (仅适用于 insert 和 update)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字段),默认值:false。
keyProperty (仅适用于 insert 和 update)指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值,默认值:未设置(unset)。如果生成列不止一个,可以用逗号分隔多个属性名称。

sql

该标签用来定义sql片段,一般用来定义一些可复用的SQL语句片段,使用include标签以在其他语句中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>

<select id="selectUsers" resultType="map">
select
<include refid="userColumns">
<property name="alias" value="t1"/>
</include>,
<include refid="userColumns">
<property name="alias" value="t2"/>
</include>
from some_table t1
cross join some_table t2
</select>

参数

如果传入一个复杂的对象,行为就会有点不一样了。比如:

1
2
3
4
<insert id="insertUser" parameterType="User">
insert into users (id, username, password)
values (#{id}, #{username}, #{password})
</insert>

JDBC 要求,如果一个列允许使用 null 值,并且可能会使用值为 null 的参数,就必须要指定 JDBC 类型(jdbcType)

字符串替换

默认情况下,使用#{}获取参数的值的时候,mybatis会创建一个PreparedStatement对象,通过预编译,生成与#{}出现次数相同的占位符(即 ? )这样更安全并且更迅速。

但是有时候想直接在SQL语句中插入一个字符串,不需要转义此字符串(即不需要占位符),比如ORDER BY语句,这时候可以使用${}语句,这样的话,mybatis就不会转义该字符串了

当 SQL 语句中的元数据(如表名或列名)是动态生成的时候,字符串替换将会非常有用。

举个栗子,如果你想 select 一个表任意一列的数据时,不需要这样写:

1
2
3
4
5
6
7
8
9
10
@Select("select * from user where id = #{id}")
User findById(@Param("id") long id);

@Select("select * from user where name = #{name}")
User findByName(@Param("name") String name);

@Select("select * from user where email = #{email}")
User findByEmail(@Param("email") String email);

// 其它的 "findByXxx" 方法

而是可以只写这样一个方法:

1
2
@Select("select * from user where ${column} = #{value}")
User findByColumn(@Param("column") String column, @Param("value") String value);

其中 ${column} 会被直接替换,而 #{value} 会使用 ? 预处理。 这样,就能完成同样的任务:

1
2
3
User userOfId1 = userMapper.findByColumn("id", 1L);
User userOfNameKid = userMapper.findByColumn("name", "kid");
User userOfEmail = userMapper.findByColumn("email", "noone@nowhere.com");

结果集映射

可以自定义一些复杂的返回值对象

简单结果集映射
1
2
3
4
5
6
7
8
9
@Data
@NoArgsConstructor
@AllArgsConstructor
@Alias("aliasStudent")
public class Student {
private Integer id;
private String name;
private Integer tid;
}
1
2
3
4
5
6
7
8
<select id="getStudentById" resultMap="studentMap">
select student_id,student_name,student_tid from student where id = #{id}
</select>
<resultMap id="studentMap">
<id column="student_id" property="id"/>
<result column="student_name" property="name"/>
<result column="student_tid" property="tid"/>
</resultMap>
高级结果集映射
1
2
3
4
5
6
7
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Teacher {
private int id;
private String name;
}
1
2
3
4
5
6
7
8
9
10
11
12
<resultMap id="teacherMap" type="com.lizhi.pojo.Teacher">
<id property="id" column="tid" />
<result property="name" column="tname"/>
<collection property="students" ofType="student">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="teacher">
<id property="id" column="tid"/>
<result property="name" column="tname"/>
</association>
</collection>
</resultMap>

resultMap的子标签

  • constructor

    用于在实例化类时,注入结果到构造方法中

    • idArg - ID 参数;标记出作为 ID 的结果可以帮助提高整体性能
    • arg - 将被注入到构造方法的一个普通结果
  • id – 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能

  • result – 注入到字段或 JavaBean 属性的普通结果

  • association

    一个复杂类型的关联,许多结果将包装成这种类型

    • 嵌套结果映射 – 关联可以是 resultMap 元素,或是对其它结果映射的引用
  • collection

    一个复杂类型的集合

    • 嵌套结果映射 – 集合可以是 resultMap 元素,或是对其它结果映射的引用
constructor

有些情况下,你会使用一个不可变类,即那些很少改变或者基本不变的类,即可以使用构造方法注入

1
2
3
4
5
<constructor>
<idArg column="id" javaType="int" name="id" />
<arg column="age" javaType="_int" name="age" />
<arg column="username" javaType="String" name="username" />
</constructor>
id & result
1
2
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>

这些元素是结果映射的基础。

idresult 元素都将一个列的值映射到一个简单数据类型(String, int, double, Date 等)的属性或字段。

这两者之间的唯一不同是,id 元素对应的属性会被标记为对象的标识符,在比较对象实例时使用。

这样可以提高整体的性能,尤其是进行缓存和嵌套结果映射(也就是连接映射)的时候。

两个元素都有一些属性:

属性 描述
property 映射到列结果的字段或属性。如果 JavaBean 有这个名字的属性(property),会先使用该属性。否则 MyBatis 将会寻找给定名称的字段(field)。
column 数据库中的列名,或者是列的别名。
javaType 一个 Java 类的全限定名,或一个类型别名(关于内置的类型别名,可以参考上面的表格)。 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。
jdbcType JDBC 类型,所支持的 JDBC 类型参见这个表格之后的“支持的 JDBC 类型”。 只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程,你需要对可以为空值的列指定这个类型。

jdbcType支持的 JDBC 类型

为了以后可能的使用场景,MyBatis 通过内置的 jdbcType 枚举类型支持下面的 JDBC 类型。

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

该标签处理一个对象聚合另一个对象的关系。

mybatis有两种不同方式去加载关联

  1. 嵌套select查询:通过另一个SQL语句去加载复杂对象
  2. 嵌套结果查询:通过连接查询直接将所有的结果查询出来,放在一张表里面
嵌套select查询(不推荐)
1
2
3
4
5
6
7
8
9
10
11
12
13
<resultMap id="StudentMap" type="student">
<result column="tid" property="tid"/>
<association property="teacher" column="tid" javaType="teacher" select="getTeacherById"/>
</resultMap>

<select id="getTeacherById" resultType="teacher">
select * from teacher where id = #{tid}
</select>

<select id="getStudentById" resultMap="StudentMap">
select * from student where id = #{id}
</select>

优点:SQL语句写起来方便

缺点:多层SQL嵌套,不易于维护,使用了mybatis的相关插件会导致select语句爆红,看起来不舒服

对于大型数据上,这里有一个“N+1查询问题”,问题描述如下

  • 执行了一个单独的SQL语句去加载每一条记录的详细信息(就是+1)
  • 对返回的列表的每一条记录,都需要执行这样的一个单独的SQL语句去加载详细信息(就是N)

这样会导致运行成千上万条sql语句,大大增加系统开销,这是我们不希望看到的

嵌套结果查询(推荐)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<resultMap id="studentMap" type="student">
<id property="id" column="sid" javaType="_int"/>
<result property="name" column="sname" javaType="string"/>
<result property="tid" column="tid" javaType="_int"/>
<association property="teacher" javaType="teacher">
<id property="id" column="tid" javaType="_int"/>
<result property="name" column="tname" javaType="string"/>
</association>
</resultMap>
<select id="getStudentById" resultMap="studentMap">
select
s.id sid, s.name sname, tid, t.name tname
from student s inner join teacher t
on s.id = #{id} and tid = t.id
</select>

优点:配置resultMap十分方便,逻辑十分清楚,方便查错和维护

缺点:连接查询的SQL语句写起来难度大,

collection

collection可以说是多个类型的关联,所以与association的处理十分相似

嵌套select查询
1
2
3
4
5
6
7
8
9
<resultMap id="teacherMap" type="teacher">
<collection property="students" javaType="ArrayList" column="id" ofType="student" select="getStudentsByTid"/>
</resultMap>
<select id="getStudentsByTid" resultType="student">
select * from student where tid = #{id}
</select>
<select id="getTeacherById" resultMap="teacherMap">
SELECT * FROM teacher WHERE id = #{id}
</select>

注意这里需要使用ofType来指定集合的泛型

嵌套结果查询
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<resultMap id="teacherMap" type="teacher">
<id property="id" column="tid"/>
<result property="name" column="tname"/>
<collection property="students" ofType="student">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>
<select id="getTeacherById" resultMap="teacherMap">
SELECT
t.name tname,t.id tid,s.id sid, s.name sname
FROM teacher t inner join student s on t.id=#{id} and s.tid=#{id}
</select>

缓存

首先我们先来看一下到底什么是缓存?

缓存这个词,相信大家对其不会陌生,因为我们在b站,腾讯视频这一类视频APP中都会缓存过一些视频。

那我们可以从中归纳出,缓存就是程序以某种形式将数据保存下来,方便下一次的使用。

那我们为什么需要使用缓存呢?

我们都知道,我们程序员要追求三高,当然不会是高血脂,高血糖,高血压。我们所说的三高是高并发,高可用,高性能。众所周知,对于数据库的操作是比较消耗资源的。因此,多次连接数据库不利于我们达到三高,那么缓存就是会帮助我们达到三高的一种技术。

我们将用户经常查询,并且很少发生改变的数据放入缓存中,当用户下一次查询同样的数据时,就不用再去连接数据库查询数据了,减少了访问数据库的次数,减少了系统开销,从而提高了性能。


在mybatis中,存在着两种缓存机制:一级缓存和二级缓存

一级缓存

该级别的缓存,mybatis是默认开启的,并且无法关闭。

该缓存是sqlSession级别的,也就是说作用域和我们之前谈过的sqlSession的作用域一致

因为用户大部分操作都是查询,但是如果出现了DML语句(即增删改)的时候,可能会对缓存中的数据造成了修改,为了安全起见,当发生增删改操作的时候,一级缓存即刻失效。

当然我们也可以手动清理缓存

二级缓存

该级别的缓存,是默认关闭的,需手动打开

  1. 在mapper映射器文件中,添加以下一行代码
1
2
3
4
5
6
7
<cache/>
//或者加一些配置
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
  1. 在mybatis-config.xml文件中
1
2
3
//可能有的小伙伴会说,该设置默认就是true,不需要写这一行代码
//但是,为了显式说明我们开启了二级缓存,最好还是标注出来
<setting name="cacheEnabled" value="true"/>

注:如果在cache标签中没有加入readOnly=“true”,则需要将模型类实现序列化接口(Serializable)

该缓存是namespace级别的,也就是和应用程序的生命周期一致


附上一张关于mybatis的原理图,仅供参考

给作者买杯咖啡吧~~~