标签属性

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
2
3
<insert id="insertUser" keyProperty="id" useGeneratedKeys="true">
SQL statement...
<insert>

然后用于新插入的对象 user 执行完 SQL 语句之后,就会被自动赋值 id 属性为生成的主键值。

1
2
3
4
5
6
7
8
9
@Test
public void insertUser() {
UserMapper mapper = session.getMapper(UserMapper.class);
User user=new User("Xorex", "Tempest", new UserAddress("Xorex","China"));
mapper.insertUser(user);
System.out.println(user.getId());
session.commit();
session.close();
}

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 返回的不同结果集命名。

参数映射

单参数映射

当我们的方法参数为一个单个参的时候,又分为很多种情况。

  1. 为 MyBatis 内置的较为基本的类型的时候,MyBatis 会直接将其返回调用 toString() 填充到 SQL 语句中。

  2. 为 POJO 类型的时候,会根据 #{VarName} 构造 getVarName() 方法获取 POJO 值,填充 SQL 语句。

  3. 为 Map 类型的时候,会根据 #{Varname} 名字作为 Key 从 Map 中取出 value,填充到 SQL 语句中。

  4. 为 List 类型的时候:

  5. 为 数组类型的时候:

多参数映射

多参数入参解决方案

这里主要是 SQL 语句中 parameterType 属性和 @Param 注解的解释。

其中因为 Java 基本上所有的版本都是支持反射获取方法参数类型 (Method 类 getParameterTypes()),所以对于 parameterType 这个属性完全可以不写,Mybatis 完全可以通过反射拿到。

但是 Mybatis 拿到的仅仅只是方法参数的类型,是拿不到参数名的,编译之后就成了 var1 var2 这样的,所以对于多个参数进行的映射如 public void insertUser(String userName,String password); 这样,只能用一些特殊的技巧填入 SQL 语句中的 #{} 里面:

  1. 使用数字表示参数顺序
1
2
3
<insert id="insertUser">
insert into user (username,password) values (#{0},#{1})
</insert>

通过数字来表示方法中的第 0 个参数,第 1 个参数等等。

  1. 使用 @Param(“Name”) 来定义参数名

使用 Mybatis 提供的注解 @Param 来对方法中的参数起一个别名。

1
public void insertUser(@Param("userNameVar") String userName,@Param("passwordVar") String password);

然后就可以在 SQL 语句中使用我们在 @Param 中定义的别名了。

1
2
3
<insert id="insertUser">
insert into user (username,password) values (#{userNameVar},#{passwordVar})
</insert>
  1. 将多个参数封装为 Map 之后传入

自己构造一个参数的 参数名->参数值 的 Map。

1
2
3
4
5
6
7
8
9
10
11
 @Test
public void insertUser() {
UserMapper mapper = session.getMapper(UserMapper.class);
HashMap<String, String> map = new HashMap<>();
map.put("userName", "Xorex");
map.put("password", "Tempest");
map.put("userAddress","China");
mapper.insertUser(map);
session.commit();
session.close();
}

然后在 SQL 里面直接写 Map 的 key 就能获取自己传入的 value。

1
2
3
4
5
6
<insert id="insertUser">
insert into user (user_name,password)
values(#{userName},#{password});
insert into address (user_name,user_address)
values(#{userName},#{userAddress});
</insert>
  1. 封装成 POJO 入参

将所有需要入参的参数封装为一个 POJO,然后就能像使用 Map 一样,用属性名取出参数值。

1
2
3
4
5
6
7
@Test
public void insertUser() {
UserMapper mapper = session.getMapper(UserMapper.class);
mapper.insertUser(new User("Xorex", "Tempest", new UserAddress("Xorex","China")));
session.commit();
session.close();
}
1
2
3
4
5
6
<insert id="insertUser">
insert into user (user_name,password)
values(#{userName},#{password});
insert into address (user_name,user_address)
values(#{userAddress.userName},#{userAddress.userAddress});
</insert>

多参数入参原理

其实只要你的方法参数数量大于 1 ,那么就会被 MyBatis 封装为 Map 类型,通过 Key->Value 来在 SQL 中填充值。

如果在方法参数的地方标注了 @Param(“Alias”),那么封装 Map 的时候这里的 Key 就是自己设置的 Alias,如果没标注,那么就是 arg1 arg2 这些。

而从 Map 中取出来的值也是原原本本的传入的参数类型,不会改变。

1
public User getUser(@Param("user") User user,@Param("address") String address);

这里面需要取值的时候,user 取出来一个 User 对象,address 取出来一个 String 对象。

结果映射

Java 是可以通过反射拿到方法的返回值类型,但是对于 List Map 这样用到了泛型的,是无法拿到泛型的具体类型,也自然无法进行封装,所以才会引入 resultType 和 resultMap 来解决这个问题,两者将作为反射的一个补充。

列名和属性一一对应

当数据库的列名和属性名一一对应的时候,就不需要自定义映射规则了,只需要指定映射结果类型即可。

  1. 当设置为 POJO 类型的时候,会根据列名和属性名进行一一映射,如果名字相同,则可以映射成功,如果不相同,则需要引入 resultMap 来自定义映射规则。

  2. 当设置为 List 类型的时候,resultType 应该填写 List<Type> 里面的数据类型 Type,多条结果会自动封装为 List。

  3. 当想要将一条数据的列名作为 Key,值作为 Value,返回值类型为 Map<String,Object>,resultType 应该填写 map。

  4. 当想要将主键作为 Key,剩下其余的值封装为 POJO 作为 Value 的时候,返回值类型为 Map<KeyType,POJOType>,resultType 应该填写 POJO 的类型。并在接口方法出添加注解 @MapKey("KeyColumn") 来指明拿哪一列的值作为 Map 中 POJO 的 Key。

列名和属性不对应

这个时候就无法自动映射了,需要我们指定映射规则,也就是填写 resultMap。

需要注意的是,当我们设置了驼峰下划线自动映射之后,也就是 mapUnderScoreToCamelCase 之后。这玩意只能在 自动匹配 的时候生效,也就是使用 resultType 或者 resultMap 设置属性 autoMapping=ture 的时候,会在自动映射的属性-列中自动进行下划线驼峰转化。

当你进行手动映射的时候,只能老老实实写 SQL 语句里面的真实列名进行手动映射,mapUnderScoreToCamelCase 不会生效的!!!

resultMap 标签属性

  1. id 定义此 resultMap 的 id。
  2. type 定义此 resultMap 结果映射的类。
  3. autoMapping true/false:false,在 resultMap 中开启自动映射。
  4. extends 外部继承一个 resultMap,并在它后面补充(概念上是和 Java 的继承是一样的)

resultMap 内部标签

  1. <id> 定义 ID 映射,有特殊优化。
  2. <result> 定义一个普通的映射
  3. <association> 关联,定义一个内联的 resultMap,给是属性的 POJO 赋值。
  4. <constructor> 构造器,利用构造器给结果赋值。
  5. <collection> 集合,将一对多的查询结果的多的部分封装为集合类型。
  6. <discriminator> 鉴别器,

下面主要介绍后三个的用法:

constructor 构造器

指的是使用 POJO 里面的构造器进行封装参数,参数传入顺序是按照标签顺序进行的,只需要写 javaType 和 column 两个属性即可。

1
2
3
4
5
6
7
8
<resultMap id="UserMap" type="user" autoMapping="true">
<association property="userAddress" javaType="useraddress">
<constructor>
<arg javaType="string" column="user_name" />
<arg javaType="string" column="user_address" />
</constructor>
</association>
</resultMap>

如果想要按照参数名来进行传参而不是顺序,则需要在 POJO 的构造方法的参数里面添加 @Param("paramName") 注解,然后在 arg 标签里面指定属性 name=paramName

1
2
3
4
public UserAddress(@Param("userName") String userName, @Param("userAddress") String userAddress) {
this.userName = userName;
this.userAddress = userAddress;
}
1
2
3
4
5
6
7
8
<resultMap id="UserMap" type="user" autoMapping="true">
<association property="userAddress" autoMapping="false" javaType="useraddress">
<constructor> <!--更换位置之后也不需要担心顺序哦-->
<arg javaType="string" column="user_address" name="userAddress" />
<arg javaType="string" column="user_name" name="userName" />
</constructor>
</association>
</resultMap>
collection 集合

collection 主要是用来封装一对多的情况,也就是 POJO 属性有集合类型的情况。

1
2
3
4
5
6
public class User {
private Integer id;
private String userName;
private String password;
private List<UserAddress> userAddress;
}

因为是一对多,所以查出来的条数是多的数量,因此在对一使用自动映射的时候,会因为有多条数据无法对应上一而报错。 Expected one result (or null) to be returned by selectOne(), but found: 3

正确的解决方案就是:先手动映射上一列,到后面映射集合元素的时候,就会对应上多条数据,从而避免多条数据无法对应上,手动映射随便一列就行,我这里选的是主键。

1
2
3
4
<resultMap id="UserMap" type="user" autoMapping="true">
<result column="password" property="password"/>
<collection property="userAddress" javaType="arraylist" ofType="useraddress" autoMapping="true" />
</resultMap>

collection 和其他标签唯一不同的地方是,标注集合内元素类型使用的是 ofType 属性,而我们在其他标签(比如 association)指明映射类型使用的 javaType 在这里面则是值生成的集合的类型,比如 ArrayList,LinkedList 等等。

discriminator 鉴别器

这个东西用的不多,就先不学了。

分级查询

记得在配置中有 lazyLoadingEnabled aggressiveLazyLoading 两条关于懒加载,按需加载,延迟加载的配置,这些配置就是作用于分级查询的语句的。

分级查询针对对象是内联属性,也就是 collection 和 association 标签,实现方式就是将联合查询的 SQL 语句拆分成两句简单的查询,然后分两次调用。不同的是第二次调用是通过配置来完成的:

还是这样一个 POJO:

1
2
3
4
5
6
public class User {
private Integer id;
private String userName;
private String password;
private List<UserAddress> userAddress;
}

然后将内联查询拆分成两条单独的 SQL 语句,第一条 SQL 只处理非内联属性的值获取,第二条 SQL 负责处理内联属性的值获取。然后在 resultMap 对内联属性进行映射处理的时候,添加数据获取源 select="" 表明通过另外一条 SQL 查询获取数据,对于此 SQL 的输入参数,则通过上一条 SQL 查询结果获取,设置为属性 column 将上一条结果的某一列的值传给下一条 SQL。

1
2
3
4
5
6
7
8
9
10
11
12
<resultMap id="UserMap" type="user" autoMapping="true">
<result column="user_name" property="userName"/>
<collection property="userAddress" ofType="useraddress" autoMapping="true" select="getUserAddressByUserName" column="user_name" />
</resultMap>

<select id="getUserByUserName" resultMap="UserMap">
select * from user where user_name=#{userName}
</select>

<select id="getUserAddressByUserName" resultType="useraddress">
select * from address where user_name=#{userName}
</select>

需要注意的是,指定的数据传递 column 属性之后,回对这一列数据的自动封装有一定影响,需要重新手动映射一下,也就是因为 column="user_name" 多出来的 <result column="user_name" property="userName"/>

这样分级查询就构建好了!!!

对于第二级 SQL 的输入为多个参数的情况,使用 column={key1=column1,key2=column2} 这样来解决,key 为我们在 @Param() 定义的别名,column 为上一条查询结果的列名。


构建完分级查询之后,最大的意义就是使用延迟加载来提高数据库性能!!!

只需要将 lazyLoadingEnabled 设置为 true 表明系统可以使用延迟加载,然后将 aggressiveLazyLoading 设置为 false ,关闭延迟加载侵入。这样分级查询的第二级就只会在需要(被调用的时候)进行了!!!