第三章 映射器
映射器的主要元素
SQL 映射文件只有很少的几个顶级元素(按照应被定义的顺序列出):
cache
– 该命名空间的缓存配置。cache-ref
– 引用其它命名空间的缓存配置。resultMap
– 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。parameterMap
– 老式风格的参数映射。此元素已被废弃,并可能在将来被移除!请使用行内参数映射。文档中不会介绍此元素。sql
– 可被其它语句引用的可重用语句块。insert
– 映射插入语句。update
– 映射更新语句。delete
– 映射删除语句。select
– 映射查询语句。
select元素
select元素帮助我们从数据库读取数据,组装数据给业务层。
select允许配置很多属性来配置每条语句的行为细节
<select
id="selectPerson"
parameterType="int"
parameterMap="deprecated"
resultType="hashmap"
resultMap="personResultMap"
flushCache="false"
useCache="true"
timeout="10"
fetchSize="256"
statementType="PREPARED"
resultSetType="FORWARD_ONLY">
性 | 描述 |
---|---|
id | 在命名空间中唯一的标识符,可以被用来引用这条语句。 |
parameterType | 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。 |
parameterMap | 用于引用外部 parameterMap 的属性,目前已被废弃。请使用行内参数映射和 parameterType 属性。 |
resultType | 期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。 |
resultMap | 对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。 |
flushCache | 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false。 |
useCache | 将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。 |
timeout | 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。 |
fetchSize | 这是一个给驱动的建议值,尝试让驱动程序每次批量返回的结果行数等于这个设置值。 默认值为未设置(unset)(依赖驱动)。 |
statementType | 可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。 |
resultSetType | FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个,默认值为 unset (依赖数据库驱动)。 |
databaseId | 如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。 |
resultOrdered | 这个设置仅针对嵌套结果 select 语句:如果为 true,将会假设包含了嵌套结果集或是分组,当返回一个主结果行时,就不会产生对前面结果集的引用。 这就使得在获取嵌套结果集的时候不至于内存不够用。默认值:false 。 |
resultSets | 这个设置仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔。 |
说明:
(1)传递多个参数
①使用Map传递参数,可读性比较差
②使用注解的方式传递,多参数调用比较困难
@Param
③使用JavaBean,参数多于5个建议使用
(2)使用resultMap映射结果集
简单使用
<resultMap id="userMapper" type="user">
<result javaType="string" jdbcType="VARCHAR" column="name" property="name" typeHandler="mybatis.MyStringTypeHandler"/>
<result column="age" property="age" />
</resultMap>
insert元素,update元素和delete元素
使用比较类似
<insert
id="insertAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
keyProperty=""
keyColumn=""
useGeneratedKeys=""
timeout="20">
<update
id="updateAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
timeout="20">
<delete
id="deleteAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
timeout="20">
属性 | 描述 |
---|---|
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 )。如果生成列不止一个,可以用逗号分隔多个属性名称。 |
keyColumn | (仅适用于 insert 和 update)设置生成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列不是表中的第一列的时候,是必须设置的。如果生成列不止一个,可以用逗号分隔多个属性名称。 |
databaseId | 如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。 |
使用:
(1)user表的id必须时自增字段
(2)useGeneratedKeys=“true” keyProperty=“id”
<insert id="insertUser" parameterType="user" useGeneratedKeys="true" keyProperty="id">
insert into user(name,age) value (#{name},#{age});
</insert>
sql元素
可重用的SQL代码块,以便在其他语句中使用
(1)定义
<sql id="userColumns">name,age </sql>
(2)使用
<select id="selectUsers" resultType="map">
select
<include refid="userColumns"></include>,
from user
</select>
resultMap结果映射器
resultMap
元素是 MyBatis 中最重要最强大的元素.ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
(1)resultMap元素的构成
<resultMap id="userMapper1" type="user">
<constructor/>
<id/>
<result/>
<association property=""/>
<collection property=""/>
<discriminator javaType="">
<case value=""></case>
</discriminator>
</resultMap>
- constructor - 用于在实例化类时,注入结果到构造方法中
idArg
- ID 参数;标记出作为 ID 的结果可以帮助提高整体性能arg
- 将被注入到构造方法的一个普通结果
id
– 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能result
– 注入到字段或 JavaBean 属性的普通结果association
– 一个复杂类型的关联;许多结果将包装成这种类型- 嵌套结果映射 – 关联可以是
resultMap
元素,或是对其它结果映射的引用
- 嵌套结果映射 – 关联可以是
collection
– 一个复杂类型的集合- 嵌套结果映射 – 集合可以是
resultMap
元素,或是对其它结果映射的引用
- 嵌套结果映射 – 集合可以是
discriminator
– 使用结果值来决定使用哪个resultMap- case – 基于某些值的结果映射
- 嵌套结果映射 –
case
也是一个结果映射,因此具有相同的结构和元素;或者引用其它的结果映射
- 嵌套结果映射 –
- case – 基于某些值的结果映射
id & result属性
属性 | 描述 |
---|---|
property | 映射到列结果的字段或属性。如果 JavaBean 有这个名字的属性(property),会先使用该属性。否则 MyBatis 将会寻找给定名称的字段(field)。 无论是哪一种情形,你都可以使用常见的点式分隔形式进行复杂属性导航。 比如,你可以这样映射一些简单的东西:“username”,或者映射到一些复杂的东西上:“address.street.number”。 |
column | 数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样。 |
javaType | 一个 Java 类的全限定名,或一个类型别名(关于内置的类型别名,可以参考上面的表格)。 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。 |
jdbcType | JDBC 类型,所支持的 JDBC 类型参见这个表格之后的“支持的 JDBC 类型”。 只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程,你需要对可以为空值的列指定这个类型。 |
typeHandler | 我们在前面讨论过默认的类型处理器。使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的全限定名,或者是类型别名。 |
(2)使用map存储结果集
<select id="selectUsers" resultType="map">
select id, name
from user
where id = #{id}
</select>
(3)使用POJO存储结果集
<select id="selectUsers" resultType="bases.User">
select id, name
from user
where id = #{id}
</select>
(4)级联
1》如果我们获取用户信息的时候,还想获取用户的身份证信息,我们把这种情况叫做级联
2》级联中存在三种对应关系:一对一,一对多,多对多
3》在mybatis中级联分为三种:
association、代表一对一,和身份证就是一对一的级联关系
①User有一个SelfIdCard的属性
package bases;
/**
* @author PitterWang
* @create 2020/6/9
* @since 1.0.0
*/
public class User {
private Long id;
private String name;
private Long age;
private SelfIdCard selfIdCard;
public User() {
}
public User(String name, Long age) {
this.name = name;
this.age = age;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getAge() {
return age;
}
public void setAge(Long age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
public SelfIdCard getSelfIdCard() {
return selfIdCard;
}
public void setSelfIdCard(SelfIdCard selfIdCard) {
this.selfIdCard = selfIdCard;
}
}
package bases;
/**
* 〈身份表〉
*
* @author PitterWang
* @create 2020/6/12
* @since 1.0.0
*/
public class Position {
private Long userId;
private String name;
public Position() {
}
public Position(Long userId, String name) {
this.userId = userId;
this.name = name;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
②配置文件中使用association
<resultMap id="userMapper" type="user">
<id column="id" property="id"></id>
<result javaType="string" jdbcType="VARCHAR" column="name" property="name"/>
<result column="age" property="age" />
<association property="selfIdCard" column="id" select="mybatis.UserMapper.getUserSelfIdCard"/>
</resultMap>
<resultMap id="selfIdCardMapper" type="selfIdCard">
<result column="idcard" property="idcard"/>
<result column="atWhere" property="atWhere" />
</resultMap>
<select id="getUserByName" parameterType="java.lang.String" resultMap="userMapper">
select * from user where name = #{name }
</select>
<select id="getUserSelfIdCard" parameterType="java.lang.Long" resultMap="selfIdCardMapper">
select * from selfidcard where user_id = #{userId }
</select>
collection、代表一对多
①User有一个Position List的属性
package bases;
import java.util.List;
/**
* @author PitterWang
* @create 2020/6/9
* @since 1.0.0
*/
public class User {
private Long id;
private String name;
private Long age;
private SelfIdCard selfIdCard;
private List<Position> positions;
public User() {
}
public User(String name, Long age) {
this.name = name;
this.age = age;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getAge() {
return age;
}
public void setAge(Long age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
public SelfIdCard getSelfIdCard() {
return selfIdCard;
}
public void setSelfIdCard(SelfIdCard selfIdCard) {
this.selfIdCard = selfIdCard;
}
public List<Position> getPositions() {
return positions;
}
public void setPositions(List<Position> positions) {
this.positions = positions;
}
}
package bases;
/**
* 〈身份表〉
*
* @author PitterWang
* @create 2020/6/12
* @since 1.0.0
*/
public class Position {
private Long userId;
private String name;
public Position() {
}
public Position(Long userId, String name) {
this.userId = userId;
this.name = name;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
②配置文件中使用collection
<resultMap id="userMapper" type="user">
<id column="id" property="id"></id>
<result javaType="string" jdbcType="VARCHAR" column="name" property="name"/>
<result column="age" property="age" />
<association property="selfIdCard" column="id" select="mybatis.UserMapper.getUserSelfIdCard"/>
<collection property="positions" column="id" select="mybatis.UserMapper.getPosition"/>
</resultMap>
discriminator 鉴别器,它可以根据实际选择采用那个类作为实例,允许你根据特定的条件去关联不同的结果集。比如人分为男人和女人,但是,实例化一个人的时候,要根据情况去去实例化时男人还时女人
<resultMap id="vehicleResult" type="Vehicle">
<id property="id" column="id" />
<result property="vin" column="vin"/>
<result property="year" column="year"/>
<result property="make" column="make"/>
<result property="model" column="model"/>
<result property="color" column="color"/>
<discriminator javaType="int" column="vehicle_type">
<case value="1" resultMap="carResult"/>
<case value="2" resultMap="truckResult"/>
</discriminator>
</resultMap>
<resultMap id="carResult" type="Car">
<result property="doorCount" column="door_count" />
</resultMap>
<resultMap id="truckResult" type="Car1">
<result property="doorCount" column="door_count" />
</resultMap>
或者可以使用如下方法
<resultMap id="vehicleResult" type="Vehicle">
<id property="id" column="id" />
<result property="vin" column="vin"/>
<result property="year" column="year"/>
<result property="make" column="make"/>
<result property="model" column="model"/>
<result property="color" column="color"/>
<discriminator javaType="int" column="vehicle_type">
<case value="1" resultType="carResult">
<result property="doorCount" column="door_count" />
</case>
<case value="2" resultType="truckResult">
<result property="boxSize" column="box_size" />
<result property="extendedCab" column="extended_cab" />
</case>
<case value="3" resultType="vanResult">
<result property="powerSlidingDoor" column="power_sliding_door" />
</case>
<case value="4" resultType="suvResult">
<result property="allWheelDrive" column="all_wheel_drive" />
</case>
</discriminator>
</resultMap>
4》使用级联的问题
性能和N+1问题:可以方便的处理获取数据了,多层关联时,超过三层就尽快少用,因为不利于维护,并且复杂度增加。
如果我们我们去一个用户信息,就把这个用户下的所有的信息都查出来了,这样就照成了N+1问题,为了避免这样问题发生,考虑延迟加载
延迟加载,当我们不使用的时候,我们不去加载,但使用时才去加载。
开启延迟加载的方法时再mybatis全局配置中加入
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
缓存cache
(1)系统缓存(一级缓存和二级缓存)
Mybatis的默认开启一级缓存是相对于sqlSession而已。在参数和SQL完全一样的情况下,我们使用一个sqlSession对应调用同一个Mapper方法,往往只执行一次Sql,因为第一次查完反正缓存里,以后再查时去缓存的数据
MyBatis默认不开启二级缓存是相对于SqlSessionFactory,二级缓存开启需要配置。,主要,如果开启二级缓存,返回的POJO必须实现Serializable接口。
开启这个简单语句的效果如下:
-
映射语句文件中的所有 select 语句的结果将会被缓存。
-
映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
-
缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
-
缓存不会定时进行刷新(也就是说,没有刷新间隔)。
-
缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
-
缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
cache的几个属性
<cache eviction="FIFO" //清除策略 flushInterval="60000" size="512" readOnly="true"/>
①清除策略
LRU – 最近最少使用:移除最长时间不被使用的对象。
FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。②flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
③size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
④readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
(2)自定义缓存
①需要实现 org.apache.ibatis.cache.Cache 接口
②开启使用自定义缓存,并设置参数
cache type="com.domain.something.MyCustomCache"> <property name="cacheFile" value="/tmp/my-custom-cache.tmp"/> </cache>