映射器文件
mybatis的真正强大之处就在于它的语句映射。
正是因为此映射器文件,才使我们减少了将近95%的JDBC代码,使我们更专注于书写SQL语句
映射器文件只有这些顶级元素
- cache
- cache-ref
- delete
- insert
- resultMap
- select
- sql
- update
select
该标签对标SQL语句的DQL语句,也就是select语句
1 | <select id="getStudentById" resultType="student"> |
这个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 | <sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql> |
参数
如果传入一个复杂的对象,行为就会有点不一样了。比如:
1 | <insert id="insertUser" parameterType="User"> |
JDBC 要求,如果一个列允许使用 null 值,并且可能会使用值为 null 的参数,就必须要指定 JDBC 类型(jdbcType)
字符串替换
默认情况下,使用#{}获取参数的值的时候,mybatis会创建一个PreparedStatement对象,通过预编译,生成与#{}出现次数相同的占位符(即 ? )这样更安全并且更迅速。
但是有时候想直接在SQL语句中插入一个字符串,不需要转义此字符串(即不需要占位符),比如ORDER BY语句,这时候可以使用${}语句,这样的话,mybatis就不会转义该字符串了
当 SQL 语句中的元数据(如表名或列名)是动态生成的时候,字符串替换将会非常有用。
举个栗子,如果你想
select
一个表任意一列的数据时,不需要这样写:
1 |
|
而是可以只写这样一个方法:
1 |
|
其中 ${column}
会被直接替换,而 #{value}
会使用 ?
预处理。 这样,就能完成同样的任务:
1 | User userOfId1 = userMapper.findByColumn("id", 1L); |
结果集映射
可以自定义一些复杂的返回值对象
简单结果集映射
1 |
|
1 | <select id="getStudentById" resultMap="studentMap"> |
高级结果集映射
1 |
|
1 | <resultMap id="teacherMap" type="com.lizhi.pojo.Teacher"> |
resultMap的子标签
constructor
用于在实例化类时,注入结果到构造方法中
idArg
- ID 参数;标记出作为 ID 的结果可以帮助提高整体性能arg
- 将被注入到构造方法的一个普通结果
id
– 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能result
– 注入到字段或 JavaBean 属性的普通结果association
一个复杂类型的关联,许多结果将包装成这种类型
- 嵌套结果映射 – 关联可以是
resultMap
元素,或是对其它结果映射的引用
- 嵌套结果映射 – 关联可以是
collection
一个复杂类型的集合
- 嵌套结果映射 – 集合可以是
resultMap
元素,或是对其它结果映射的引用
- 嵌套结果映射 – 集合可以是
constructor
有些情况下,你会使用一个不可变类,即那些很少改变或者基本不变的类,即可以使用构造方法注入
1 | <constructor> |
id & result
1 | <id property="id" column="post_id"/> |
这些元素是结果映射的基础。
id 和 result 元素都将一个列的值映射到一个简单数据类型(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有两种不同方式去加载关联
- 嵌套select查询:通过另一个SQL语句去加载复杂对象
- 嵌套结果查询:通过连接查询直接将所有的结果查询出来,放在一张表里面
嵌套select查询(不推荐)
1 | <resultMap id="StudentMap" type="student"> |
优点:SQL语句写起来方便
缺点:多层SQL嵌套,不易于维护,使用了mybatis的相关插件会导致select语句爆红,看起来不舒服
对于大型数据上,这里有一个“N+1查询问题”,问题描述如下
- 执行了一个单独的SQL语句去加载每一条记录的详细信息(就是+1)
- 对返回的列表的每一条记录,都需要执行这样的一个单独的SQL语句去加载详细信息(就是N)
这样会导致运行成千上万条sql语句,大大增加系统开销,这是我们不希望看到的
嵌套结果查询(推荐)
1 | <resultMap id="studentMap" type="student"> |
优点:配置resultMap十分方便,逻辑十分清楚,方便查错和维护
缺点:连接查询的SQL语句写起来难度大,
collection
collection可以说是多个类型的关联,所以与association的处理十分相似
嵌套select查询
1 | <resultMap id="teacherMap" type="teacher"> |
注意:这里需要使用ofType来指定集合的泛型
嵌套结果查询
1 | <resultMap id="teacherMap" type="teacher"> |
缓存
首先我们先来看一下到底什么是缓存?
缓存这个词,相信大家对其不会陌生,因为我们在b站,腾讯视频这一类视频APP中都会缓存过一些视频。
那我们可以从中归纳出,缓存就是程序以某种形式将数据保存下来,方便下一次的使用。
那我们为什么需要使用缓存呢?
我们都知道,我们程序员要追求三高,当然不会是高血脂,高血糖,高血压。我们所说的三高是高并发,高可用,高性能。众所周知,对于数据库的操作是比较消耗资源的。因此,多次连接数据库不利于我们达到三高,那么缓存就是会帮助我们达到三高的一种技术。
我们将用户经常查询,并且很少发生改变的数据放入缓存中,当用户下一次查询同样的数据时,就不用再去连接数据库查询数据了,减少了访问数据库的次数,减少了系统开销,从而提高了性能。
在mybatis中,存在着两种缓存机制:一级缓存和二级缓存
一级缓存
该级别的缓存,mybatis是默认开启的,并且无法关闭。
该缓存是sqlSession级别的,也就是说作用域和我们之前谈过的sqlSession的作用域一致
因为用户大部分操作都是查询,但是如果出现了DML语句(即增删改)的时候,可能会对缓存中的数据造成了修改,为了安全起见,当发生增删改操作的时候,一级缓存即刻失效。
当然我们也可以手动清理缓存
二级缓存
该级别的缓存,是默认关闭的,需手动打开
- 在mapper映射器文件中,添加以下一行代码
1 | <cache/> |
- 在mybatis-config.xml文件中
1 | //可能有的小伙伴会说,该设置默认就是true,不需要写这一行代码 |
注:如果在cache标签中没有加入readOnly=“true”,则需要将模型类实现序列化接口(Serializable)
该缓存是namespace级别的,也就是和应用程序的生命周期一致
附上一张关于mybatis的原理图,仅供参考