1、增删改查
- 修改Mapper接口,增加方法
- 测试
- 增删改中允许定义为Integer、Long、Boolean、void等返回类型
- 不同的session,对是否需要手动提交请求有不同要求
- sqlSessionFactory.openSession();===》手动提交
- sqlSessionFactory.openSession(true);===》自动提交
2、获取主键
2.1、获取自增主键
- 在操作标签中使用useGeneratedKeys参数和keyProperty参数
- useGeneratedKeys="true";使用自增主键获取主键值策略
- keyProperty;指定对应的主键属性,也就是mybatis获取到主键值以后,将这个值封装给javaBean的哪个属性
- 修改EmployeeMapper.xml文件
- 测试结果,直接将id封装到Employee对象中
2.2、获取非自增主键值
- 使用selectKey标签,主要用于没有自增主键功能的数据库(oracle)
- selectKey 元素的属性:
属性 | 描述 |
---|---|
keyProperty | selectKey 语句结果应该被设置到的目标属性。如果生成列不止一个,可以用逗号分隔多个属性名称。 |
keyColumn | 返回结果集中生成列属性的列名。如果生成列不止一个,可以用逗号分隔多个属性名称。 |
resultType | 结果的类型。通常 MyBatis 可以推断出来,但是为了更加准确,写上也不会有什么问题。MyBatis 允许将任何简单类型用作主键的类型,包括字符串。如果生成列不止一个,则可以使用包含期望属性的 Object 或 Map。 |
order | 可以设置为 BEFORE 或 AFTER。如果设置为 BEFORE,那么它首先会生成主键,设置 keyProperty 再执行插入语句。如果设置为 AFTER,那么先执行插入语句,然后是 selectKey 中的语句 - 这和 Oracle 数据库的行为相似,在插入语句内部可能有嵌入索引调用。 |
statementType | 和前面一样,MyBatis 支持 STATEMENT,PREPARED 和 CALLABLE 类型的映射语句,分别代表 Statement, PreparedStatement 和 CallableStatement 类型。 |
3、参数处理
3.1、单个参数
- 单个参数:mybatis不会做特殊处理
#{参数名/任意名}
:取出参数值
3.2、多个参数
- 多个参数:mybatis会做特殊处理
- 多个参数会被封装成 一个map,使用key来获取值
- key:key:param1...paramN,或者参数的索引0、1、2...
- value:传入的参数值
- 明确指定封装参数时map的key;@Param("id")
3.3、POJO&Map&TO
3.3.1、POJO
- 如果多个参数正好是我们业务逻辑的数据模型,我们就可以直接传入pojo(就是直接传入对象)
#{属性名}
:取出传入的pojo的属性值
3.3.2、Map
- 如果多个参数不是业务模型中的数据,没有对应的pojo,不经常使用,为了方便,我们也可以传入map(就是用一个map来封装多个参数,其实MyBatis也会这么干)
#{key}
:取出map中对应的值
3.3.3、TO
- 如果多个参数不是业务模型中的数据,但是经常要使用,推荐来编写一个TO(Transfer Object)数据传输对象
3.4、参数封装取值
- 注解+无注解
- public Employee getEmp(@Param("id")Integer id,String lastName);
- 取值:id==>#{id/param1}, lastName==>#{param2}
- 无注解+注解(对象)
- public Employee getEmp(Integer id,@Param("e")Employee emp);
- 取值:id==>#{param1} ,lastName===>#{param2.lastName/e.lastName}
- 传入Collection或数组
- 会特殊处理。把传入的list或者数组封装在map中
- 如果是Collection,那么key是collection,如果是List,那么key是list
- public Employee getEmpById(List<Integer> ids),取值:取出第一个id的值: #{list[0]}
3.5、源码分析-参数封装map的过程
- (@Param("id")Integer id,@Param("lastName")String lastName,String str);
- ParamNameResolver解析参数封装map的;
- names:{0=id, 1=lastName, 2=2};构造器的时候就确定好了
- args【1,"Tom",'hello'】:
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
//1、参数为null直接返回
if (args == null || paramCount == 0) {
return null;
//2、如果只有一个元素,并且没有Param注解;args[0]:单个参数直接返回
} else if (!hasParamAnnotation && paramCount == 1) {
return args[names.firstKey()];
//3、多个元素或者有Param标注
} else {
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;
//4、遍历names集合;{0=id, 1=lastName,2=2}
for (Map.Entry<Integer, String> entry : names.entrySet()) {
//names集合的value作为key; names集合的key又作为取值的参考args[0]:args【1,"Tom"】:
//eg:{id=args[0]:1,lastName=args[1]:Tom,2=args[2]}
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)param
//额外的将每一个参数也保存到map中,使用新的key:param1...paramN
//效果:有Param注解可以#{指定的key},或者#{param1}
final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
// ensure not to overwrite parameter named with @Param
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
}
- 总结:参数多时会封装map,为了不混乱,我们可以使用@Param来指定封装时使用的key;#{key}就可以取出map中的值;
3.6、#与$取值区别
#{}
: 是以预编译的形式,将参数设置到sql语句中;PreparedStatement;防止sql注入;同时使用#{}可以支持表名、排序关键字等等作为占位符(jdbc不支持)
${}
: 取出的值直接拼装在sql语句中;会有安全问题;- 所以一般使用#{}
3.7、#取值时指定参数相关规则
- 可以指定参数的规则:javaType、jdbcType、mode(存储过程)、numericScale、resultMap、typeHandler、jdbcTypeName、expression(未来准备支持的功能);
- 例如jdbcType:
- 在我们数据为null的时候,有些数据库可能不能识别mybatis对null的默认处理。比如Oracle DB(报错);
- JdbcType OTHER:无效的类型;因为mybatis对所有的null都映射的是原生Jdbc的OTHER类型,Oracle DB中null对应的是原生JDBC的NULL类型,所以Oracle DB不能正确处理
- 解决方法1:在mapper文件中写
#{email,jdbcType=NULL},指定属性值的空类型为null
- 解决方法2:在全局配置文件
<setting name="jdbcTypeForNull" value="NULL"/>
4、select查询
4.1、返回List集合
- EmployeeMapper3.xml,返回类型为实体类即可(MyBatis直接将返回的对象存储到List中)
- 测试:
4.2、记录封装map
4.2.1、返回一条记录
- EmployeeMapper3.xml,返回类型为map,这样会将返回记录的列名作为key,值作为value
- 测试:
4.2.2、返回多条记录
- EmployeeMapper3.xml,返回类型为实体类即可
- EmployeeMapper3.java,定义返回的Map,使用@MapKey注解指定Map的key(在这里,使用Employee的id作为key),map的value为Employee对象
- 测试:
4.3、自定义结果映射规则
- EmployeeMapper3.xml,首先在<select>标签中,不再使用resultType参数来指定返回类型,而是使用resultMap参数,参数内容为自定义的<resultMap>标签的id
- <resultMap>标签中使用<id>标签指定主键,当然主键也可以使用 <result>标签指定,但是主键使用<id>的话MyBatis内部有一些针对主键的底层优化
- <id>和<result>标签中column参数代表数据库列名,property代表实体类属性名
- 测试:
4.4、关联查询
4.4.1、普通关联查询
- 提前准备工作,新建部门类Department,并且在Employee类中添加部门属性,同时修改数据库
- EmployeeMapper3.xml,同样使用<resultMap>标签来自定义返回类型,同时在<result>标签中匹配好数据库返回数据和实体类属性,在级联的部门中,使用department.id对应实体属性即可(department是Employee类中部门属性名,id部门类Department类中的id属性)
- 测试:
4.4.2、association定义关联对象封装规则
- EmployeeMapper3.xml,在<resultMap>标签中使用<association>标签来指明员工类中的部门属性。property参数指定联合属性(在这里是部门),javaType参数指定联合属性指向的类。
- 测试
4.4.3、association分步查询(分段查询)
- association用于一个员工对应一个部门
- DepartmentMapper3.java
- EmployeeMapper3.xml,使用<association>标签来查询部门信息,<association>标签中的property参数值指员工类中部门属性,select指向部门DAO接口中对应查询部门的方法,column指定将哪一列的值传给这个方法
- 测试
4.4.4、association分步查询&延迟加载
- 我们每次查询Employee对象的时候,都将一起查询出来。部门信息可以在我们需要使用的时候再去查询,即延迟加载
- lazyLoadingEnabled:全局性设置懒加载,为true时启动懒加载;为false时所有相关联的都会被一起加载
- aggressiveLazyLoading:true表示如果对具有懒加载特性的对象的任意调用会导致这个对象的完整加载,false表示每种属性按照需要加载。
- 所以,如果设置lazyLoadingEnabled=false时,aggressiveLazyLoading就无效了;当lazyLoadingEnabled=true时,aggressiveLazyLoading才有效
设置项 | 描述 | 允许值 | 默认值 |
lazyLoadingEnabled | 全局性设置懒加载。如果设为‘false’,则所有相关联的都会被初始化加载。 | true | false | false |
aggressiveLazyLoading | 当设置为‘true’的时候,懒加载的对象可能被任何懒属性全部加载。否则,每个属性都按需加载。 | true | false | true |
lazyLoadingEnabled | aggressiveLazyLoading | 加载情况(仅打印员工姓名) |
true | false | 仅执行员工查询,不执行部分查询 |
true | true | 都查询 |
false | false | 都查询 |
false | true | 都查询 |
- 在全局配置文件mybatis-config.xml中设置,根据上表,所以一般设置lazyLoadingEnabled=true,aggressiveLazyLoading=false
4.4.5、collection定义关联集合封装规则
- 查询出部门所有员工
- DepartmentMapper3.java
- DepartmentMapper3.xml,在<resultMap>标签中,使用<collection>标签来表示集合,集合内的类型由ofType参数决定,取值为实体类
- 测试
4.4.6、collection分步查询&延迟加载
- 查询部门所属全部员工
- 在全局配置文件mybatis-config.xml中设置懒加载
- EmployeeMapper3.java,根据部门id属性来查询员工
- DepartmentMapper3.xml,在<collection>标签中,使用参数select去调用员工方法getEmpsByDeptId,id代表传入的参数,传入部门id去查询所属该部门的员工
- 测试,可以看到,在控制台输出中,查询部门名字时,没有去查询员工信息;只有当真的要使用员工信息时采取执行<collection>标签中的查询内容
4.4.7、分步查询传递多列值&fetchType
- 4.4.6中仅给<colloction>标签中传入一个id参数,其实可以传入多个参数
- 将多列的值封装map传递;
column="{key1=column1,key2=column2}"
- <colloction>标签中,参数fetchType="lazy":表示使用延迟加载。取值有lazy(延迟加载)和eager(立即加载)
- 在4.4.6的基础上,修改DepartmentMapper3.xml文件:
- 修改<collection>标签中的column参数,原来4.4.6中的column参数仅为id(数据库列名,传入部门列名作为参数);在这里要求传多个参数,则column参数要使用大括号括起来,使用
"{department_id=id}"
的方式,其中department_id
是员工表中员工部门外键列名,id是部门表中的id列名 - fetchType参数,使用了eager的话,不管全局配置是怎么样,都要立即加载
4.4.8、鉴别器
- mybatis可以使用discriminator判断某列的值,然后根据某列的值改变封装行为
- 比如现在查询员工,如果查询出来的是女生就需要查询部门信息,男生不需要不查询部门信息
- EmployeeMapper3.java
- <discriminator>标签中的javaType参数代表性别属性的java类型;
- column指的是根据什么来分类(这里是性别);
- <case>标签标示分类,value代表性别的值;
- <case>标签中的resultType参数和<association>标签中的property参数共同决定,子查询返回的类型是员工类中的部门类型;
- <case>标签中的select参数指向部门Mapper接口中查询的方法
- <case>标签中的column参数指向的是数据库中员工类指向部门的列名,是传参的类型
- 测试: