MyBatis-03-XML映射文件
标签属性
insert update delete
这个三个标签分别负责实现:插入更新和删除,三者的属性非常接近。
属性设置
属性 | 描述 |
---|---|
id | 此语句绑定接口的方法 |
parameterType | 用来说明方法输入的参数类型,但这个类型通过反射可以拿到,所以不需要写 |
flushCache | true/false:true 此语句被调用之后刷新本地缓存和二级缓存。 |
timeout | 整形等待数据库的最长秒数,这个不用填,交给 Spring 控制 |
statementType | 执行 SQL 使用的语句类型,可选 STATEMENT,PREPARED 或 CALLABLE,表示 Statement,PreparedStatement 和 CallableStatement,默认PREPARED |
useGeneratedKeys(适用于 insert 和 update) | true/false:false 开启之后,执行完 SQL 会在传入的对象中自动填写数据库生成的主键值(需要设置keyProperty) |
keyProperty(适用于 insert 和 update) | 搭配上面的 useGeneratedKeys 使用,指明传入参数的哪个属性对应数据库主键。 |
keyColumn(适用于 insert 和 update) | 对于某些主键列不在第一个的数据库,需要设置这个,指明哪一列是主键的列,才能配合使用上面两项设置。 |
databaseId | 用来指明这条 SQL 语句执行的数据库类型。 |
获取自增主键
在 SQL 语句里面设置 useGeneratedKeys 和 keyProperty:
1 | <insert id="insertUser" keyProperty="id" useGeneratedKeys="true"> |
然后用于新插入的对象 user 执行完 SQL 语句之后,就会被自动赋值 id 属性为生成的主键值。
1 |
|
select
属性设置
这里只说说和上面三条不太一样的:
属性 | 描述 |
---|---|
resultType | 设置返回值类型,非常用类型必须填写。 |
resultMap | 设置结果映射,和 resultType 二选一 |
useCache | true/false:true 查询结果会放在二级缓存中 |
resultSetType | 用于设置 JDBC 获取的结果集的类型 FORWARD_ONLY 指的是只能用 next() 向下读取,SCROLL_SENSITIVE 可以实现结果前后滚动读取和相对坐标跳跃读取,而选项 SCROLL_INSENSITIVE 的结果集不仅可以前后滚动读取,还可以实时感知数据库的更新(不包括插入和删除),也就是获取的结果集在不同时间调用的时候,拿到的都是最新的数据。一般这个选项不需要我们设置,使用默认的 unset 即可。 |
resultOrdered | 和 MyBatis 缓存有关,设置为 true 之后表示数据是根据 key 相同分好组的,MyBatis 就可以进行解析优化。具体用法后面再讨论。 |
resultSet | 由于一些特殊语句(比如两条 selete 查询)会返回多个结果集,为了操作不同结果集,就需要区分。这里是按照顺序设置结果集的名称 resultSet="User,Address" 这样为两条 SQL 返回的不同结果集命名。 |
参数映射
单参数映射
当我们的方法参数为一个单个参的时候,又分为很多种情况。
为 MyBatis 内置的较为基本的类型的时候,MyBatis 会直接将其返回调用 toString() 填充到 SQL 语句中。
为 POJO 类型的时候,会根据
#{VarName}
构造 getVarName() 方法获取 POJO 值,填充 SQL 语句。为 Map 类型的时候,会根据
#{Varname}
名字作为 Key 从 Map 中取出 value,填充到 SQL 语句中。为 List 类型的时候:
为 数组类型的时候:
多参数映射
多参数入参解决方案
这里主要是 SQL 语句中 parameterType 属性和 @Param 注解的解释。
其中因为 Java 基本上所有的版本都是支持反射获取方法参数类型 (Method 类 getParameterTypes()),所以对于 parameterType 这个属性完全可以不写,Mybatis 完全可以通过反射拿到。
但是 Mybatis 拿到的仅仅只是方法参数的类型,是拿不到参数名的,编译之后就成了 var1 var2 这样的,所以对于多个参数进行的映射如 public void insertUser(String userName,String password);
这样,只能用一些特殊的技巧填入 SQL 语句中的 #{} 里面:
- 使用数字表示参数顺序
1 | <insert id="insertUser"> |
通过数字来表示方法中的第 0 个参数,第 1 个参数等等。
- 使用 @Param(“Name”) 来定义参数名
使用 Mybatis 提供的注解 @Param 来对方法中的参数起一个别名。
1 | public void insertUser( String userName, String password); |
然后就可以在 SQL 语句中使用我们在 @Param 中定义的别名了。
1 | <insert id="insertUser"> |
- 将多个参数封装为 Map 之后传入
自己构造一个参数的 参数名->参数值 的 Map。
1 |
|
然后在 SQL 里面直接写 Map 的 key 就能获取自己传入的 value。
1 | <insert id="insertUser"> |
- 封装成 POJO 入参
将所有需要入参的参数封装为一个 POJO,然后就能像使用 Map 一样,用属性名取出参数值。
1 |
|
1 | <insert id="insertUser"> |
多参数入参原理
其实只要你的方法参数数量大于 1 ,那么就会被 MyBatis 封装为 Map 类型,通过 Key->Value 来在 SQL 中填充值。
如果在方法参数的地方标注了 @Param(“Alias”),那么封装 Map 的时候这里的 Key 就是自己设置的 Alias,如果没标注,那么就是 arg1 arg2 这些。
而从 Map 中取出来的值也是原原本本的传入的参数类型,不会改变。
1 | public User getUser( User user, String address); |
这里面需要取值的时候,user 取出来一个 User 对象,address 取出来一个 String 对象。
结果映射
Java 是可以通过反射拿到方法的返回值类型,但是对于 List Map 这样用到了泛型的,是无法拿到泛型的具体类型,也自然无法进行封装,所以才会引入 resultType 和 resultMap 来解决这个问题,两者将作为反射的一个补充。
列名和属性一一对应
当数据库的列名和属性名一一对应的时候,就不需要自定义映射规则了,只需要指定映射结果类型即可。
当设置为 POJO 类型的时候,会根据列名和属性名进行一一映射,如果名字相同,则可以映射成功,如果不相同,则需要引入 resultMap 来自定义映射规则。
当设置为 List 类型的时候,resultType 应该填写
List<Type>
里面的数据类型 Type,多条结果会自动封装为 List。当想要将一条数据的列名作为 Key,值作为 Value,返回值类型为 Map<String,Object>,resultType 应该填写 map。
当想要将主键作为 Key,剩下其余的值封装为 POJO 作为 Value 的时候,返回值类型为 Map<KeyType,POJOType>,resultType 应该填写 POJO 的类型。并在接口方法出添加注解
@MapKey("KeyColumn")
来指明拿哪一列的值作为 Map 中 POJO 的 Key。
列名和属性不对应
这个时候就无法自动映射了,需要我们指定映射规则,也就是填写 resultMap。
需要注意的是,当我们设置了驼峰下划线自动映射之后,也就是 mapUnderScoreToCamelCase 之后。这玩意只能在 自动匹配 的时候生效,也就是使用 resultType 或者 resultMap 设置属性 autoMapping=ture 的时候,会在自动映射的属性-列中自动进行下划线驼峰转化。
当你进行手动映射的时候,只能老老实实写 SQL 语句里面的真实列名进行手动映射,mapUnderScoreToCamelCase 不会生效的!!!
resultMap 标签属性
- id 定义此 resultMap 的 id。
- type 定义此 resultMap 结果映射的类。
- autoMapping
true/false:false
,在 resultMap 中开启自动映射。 - extends 外部继承一个 resultMap,并在它后面补充(概念上是和 Java 的继承是一样的)
resultMap 内部标签
- <id> 定义 ID 映射,有特殊优化。
- <result> 定义一个普通的映射
- <association> 关联,定义一个内联的 resultMap,给是属性的 POJO 赋值。
- <constructor> 构造器,利用构造器给结果赋值。
- <collection> 集合,将一对多的查询结果的多的部分封装为集合类型。
- <discriminator> 鉴别器,
下面主要介绍后三个的用法:
constructor 构造器
指的是使用 POJO 里面的构造器进行封装参数,参数传入顺序是按照标签顺序进行的,只需要写 javaType 和 column 两个属性即可。
1 | <resultMap id="UserMap" type="user" autoMapping="true"> |
如果想要按照参数名来进行传参而不是顺序,则需要在 POJO 的构造方法的参数里面添加 @Param("paramName")
注解,然后在 arg 标签里面指定属性 name=paramName
。
1 | public UserAddress( String userName, String userAddress){ |
1 | <resultMap id="UserMap" type="user" autoMapping="true"> |
collection 集合
collection 主要是用来封装一对多的情况,也就是 POJO 属性有集合类型的情况。
1 | public class User { |
因为是一对多,所以查出来的条数是多的数量,因此在对一使用自动映射的时候,会因为有多条数据无法对应上一而报错。 Expected one result (or null) to be returned by selectOne(), but found: 3
正确的解决方案就是:先手动映射上一列,到后面映射集合元素的时候,就会对应上多条数据,从而避免多条数据无法对应上,手动映射随便一列就行,我这里选的是主键。
1 | <resultMap id="UserMap" type="user" autoMapping="true"> |
collection 和其他标签唯一不同的地方是,标注集合内元素类型使用的是 ofType
属性,而我们在其他标签(比如 association)指明映射类型使用的 javaType 在这里面则是值生成的集合的类型,比如 ArrayList,LinkedList 等等。
discriminator 鉴别器
这个东西用的不多,就先不学了。
分级查询
记得在配置中有 lazyLoadingEnabled aggressiveLazyLoading 两条关于懒加载,按需加载,延迟加载的配置,这些配置就是作用于分级查询的语句的。
分级查询针对对象是内联属性,也就是 collection 和 association 标签,实现方式就是将联合查询的 SQL 语句拆分成两句简单的查询,然后分两次调用。不同的是第二次调用是通过配置来完成的:
还是这样一个 POJO:
1 | public class User { |
然后将内联查询拆分成两条单独的 SQL 语句,第一条 SQL 只处理非内联属性的值获取,第二条 SQL 负责处理内联属性的值获取。然后在 resultMap 对内联属性进行映射处理的时候,添加数据获取源 select=""
表明通过另外一条 SQL 查询获取数据,对于此 SQL 的输入参数,则通过上一条 SQL 查询结果获取,设置为属性 column 将上一条结果的某一列的值传给下一条 SQL。
1 | <resultMap id="UserMap" type="user" autoMapping="true"> |
需要注意的是,指定的数据传递 column 属性之后,回对这一列数据的自动封装有一定影响,需要重新手动映射一下,也就是因为 column="user_name"
多出来的 <result column="user_name" property="userName"/>
。
这样分级查询就构建好了!!!
对于第二级 SQL 的输入为多个参数的情况,使用 column={key1=column1,key2=column2}
这样来解决,key 为我们在 @Param()
定义的别名,column 为上一条查询结果的列名。
构建完分级查询之后,最大的意义就是使用延迟加载来提高数据库性能!!!
只需要将 lazyLoadingEnabled 设置为 true 表明系统可以使用延迟加载,然后将 aggressiveLazyLoading 设置为 false ,关闭延迟加载侵入。这样分级查询的第二级就只会在需要(被调用的时候)进行了!!!