Mybaits(二)——查询参数问题(参数绑定、模糊查询、主键回填)、ORM映射(ResultMap结果集、TypeHandler类型转换器)、一对多映射(ResultMap的继承、懒加载)
一、查询参数问题
1、前期准备
依赖导入:
mybatis-config.xml:
然后实体类:
然后 bookMapper:
测试:
2、参数绑定
a、默认一个参数
测试代码:
结果:
b、注解参数绑定(多个参数的情况下)
这里的多个参数比如集合、数组等或者直接就是三四个参数,不可以直接用参数名去引用,只能用 mybatis 原生提供的,原生提供的是 arg0,arg1,arg2…这种按顺序来,或者 param1,param2,param3…这种。如果要用参数名,需要用注解 @Param(“xx”) 。
测试:
c、map 参数绑定
如果是 map 类型的参数,可以当成一个普通对象来处理,比如里面有什么属性,可以直接写属性名。
如果 map 类型加了别名,比如 m,则调用这个参数的时候需要 m.xxx 来调用。调用方式直接变了。
测试:
结果:
3、模糊查询
a、方式一—— #符号(推荐)
模糊查询数据库写法是 x%,这里不仅要处理百分号 % 的问题,还要处理字符传入的问题。
通过这种方式,传入数据,然后拼接百分号 %,即可完成模糊查询。
测试:
结果:
通过查看日志,看看这种方式怎么实现的:
可以看到是通过占位符的方式来完成。这种就可以防止 sql 语句注入。
b、方式二——$符号(不推荐,不过有时候只能用这种方式)
结果:
可以看到这个是直接通过拼接的方式完成的。这种就有 sql 注入的风险。
# 和 $ 的区别:
# 用的是 PreparedStatement,参数有占位符,后来再为占位符提供具体的参数,可以防止 sql 注入。
$ 用的是 Statement,参数直接通过字符拼接加到 sql 中。
c、分组排序时只能用 $ 符号
也有不得不用 $ 符号的时候,比如分组排序的时候,用的 order by id desc ,如果用的是 # 符号,则明显起不了作用,用 $ 才有作用。
如果想把列的参数当做名字传进来,基本只能用 $ 符号。
4、主键回填
以前增加数据时要取到 ID,用的是主键回填的方式,这里有几种方式可以:
a、last_insert_id()
在数据库中执行 last_insert_id()这个函数,就可以取到,这个函数的意思是获取刚刚插入的数据 ID:
然后测试:
结果:
b、useGeneratedKeys —— 主键回填
c、使用 UUID 生成随机数作为 ID
还有一种需求就是 UUID 作为 ID。一般情况下,都是建议主键自增,主键自增存储效率高一些,主键索引是一个 B+ 树,有顺序的话都是一直往后面追加的。如果主键不是自增的话,主键就需要先找个合适的位置再插入,这种插入效率就会比较低。
如果用的是 UUID 来作为主键,这个时候就有两种需求,一种是 Java 生成 UUID ,再插入;还有一种是使用数据库的函数生成 UUID ,作为主键:
用 Java 代码生成的 UUID ,就没有主键回填的问题;用数据库的函数生成 UUID ,就需要主键回填:
二、ORM映射
1、MyBatis自动ORM失效
自动 ORM 映射有条件,就是属性名跟字段名要一一对应,或者说推断出来的要对应。
如果字段名跟属性名不对应,还可以在 sql 语句中使用别名 as 的方式来对应,但这种方式不算好的办法,不好复用。
一般用的是 mybatis 中的结果映射集:resultMap。
2、resultMap 结果集
使用这个结果集,以后映射关系会方便很多。
3、有参构造与无参构造
如果有这三种构造方法,会优先使用哪一种呢?
答案是:先是无参,如果没有无参就完整的有参,接着就是残缺的有参构造。
但是映射关系并不走有参构造方法的对应,走的是 get、set 方法。
然后去掉无参。
然后去掉这个有参。
可以看到顺序就是这样。
如果想要指定某个构造方法,可以通过下面这种方式:
4、TypeHandler——类型转换器
现在想要一种效果:在数据插入的时候,将 List 集合转为字符串,在数据读取的时候,将字符串自动转为 List。
比如一年级有两个班级,list 存储两个班级对象,存到数据库中时,只显示这两个班级的名字;那么这里就是 对象转为字符串了。取出来的时候,根据这个班级的名字取出对应的班级对象。
a、插入转换跟查询转换
那么此时可以用到 TypeHandler——类型转换器:
先测试一下:
可以看到输出没什么问题。
虽然最后多了个 逗号,不过不影响。
设置好后接着就是 mapper 文件设置:
然后测试结果:
查看数据库:
可以看到成功插入。
接着就是查询
// 下面的三个方法都是用于查询,虽然有三个方法,但干的都是一件事情。先看前两个,第一个方法是根据列名获取。
// 第二个方法时根据下标去获取。所以这里提供了两种方式
/**
* 查询的时候调用
* @param resultSet
* @param columnName 列名
* @return 手动解析查询结果,返回 list 集合。
* @throws SQLException
*/
public List<String> getResult(ResultSet resultSet, String columnName) throws SQLException {
String s = resultSet.getString(columnName);
// 查询到的 足球,篮球, 按照 “ ,” 拆分成数组,再将数组转换为集合。
if (s !=null && "".equals(s)){
List<String> list = Arrays.asList(s.split(","));
return list;
}
return null;
}
public List<String> getResult(ResultSet resultSet, int columnIndex) throws SQLException {
// 这里的写法跟上面是通用的。
String s = resultSet.getString(columnIndex);
// 查询到的 足球,篮球, 按照 “ ,” 拆分成数组,再将数组转换为集合。
if (s !=null && !"".equals(s)){
List<String> list = Arrays.asList(s.split(","));
return list;
}
return null;
}
/**
* 如果调用的是存储过程的话,会触发该方法。
* @param cs
* @param i
* @return
* @throws SQLException
*/
public List<String> getResult(CallableStatement cs, int i) throws SQLException {
// 这里的写法跟上面是通用的,只需要改动一点点地方
String s = cs.getString(i);
// 查询到的 足球,篮球, 按照 “ ,” 拆分成数组,再将数组转换为集合。
if (s !=null && "".equals(s)){
List<String> list = Arrays.asList(s.split(","));
return list;
}
return null;
}
然后就是配置 mapper 文件:
接着就是测试:
可以看到,测试没问题。
b、全局指定(全局转换)
在 mybatis-config.xml 文件里面配置全局:
全局配置了这个,原先配置 typehandler 的地方 都可以去掉了。
按照这个测试,没有问题。
同样的,全局配置也可以不指定后面那两个,通过注解来指定:
然后注解,在类上面配置:
三、一对多映射
1、一对多
一对多跟一对一用法近似。
首先是年级类,然后是班级,一个年级有好几个班级。
先看下实体类:
然后是 mapper:
然后测试结果:
2、ResultMap的继承——extends
在一个 mapper 文件中,可能会有很多个 ResultMap ,不可避免的会发生很多重复的代码,比如:
红色框框中就很明显是重复了的代码,所以这里就需要通过继承,来完成代码的复用:
这样就能一段代码就能多次使用,不用写重复部分的代码,后面的只需要写不同部分的代码即可。
3、根据 xx 去查询 xx(不推荐,好处是懒加载)
还有个用法,这个用法用于查询,但是不太推荐。可以先获取年级,再根据年级ID获取班级;但是这么一来会执行很多次查询语句,但是有一个好处就是有懒加载,就是调用到查询班级的方法才会去查询班级,不然就不会调用这个方法。 这个功能是上面所没有的,因为上面的方法是一下子就把所有数据查出来了。
看代码:
接口:
clazz mapper 文件:
grade mapper 文件:
看结果:
第一种是查询所有,可以看到执行了好几条 sql 语句:
另一种结果是只查询年级名字,但是结果中不会去查询到班级的数据: