1、MyBatis-映射文件
环境准备
创建部门表,员工表添加外键,并往部门表和员工表插入测试数据:
CREATE TABLE t_dept(
id INT PRIMARY KEY AUTO_INCREMENT,
department_name VARCHAR(200)
);
INSERT INTO t_dept VALUES (1,'开发部');
INSERT INTO t_dept VALUES (2,'测试部');
#修改员工表,添加一个部门id字段
ALTER TABLE t_employee ADD COLUMN d_id INT ;
#添加外键约束
ALTER TABLE t_employee ADD CONSTRAINT fk_emp_dept FOREIGN KEY (d_id) REFERENCES t_dept(id);
#往员工表中添加几条记录
INSERT INTO t_employee VALUES ('2','jack',33,'长沙',1),('3','jack2',34,'北京',2),('4','jerry',36,'上海',1),('5','bob',31,'深圳',1);
在上一篇的工程中,添加部门相关的实体和接口以及映射文件,同时全局配置文件中,映射文件通过包扫描的方式进行引入,具体参考上一篇:工程的结构如下:
• 映射文件指导着MyBatis如何进行数据库增删改查,有着非常重要的意义;
•cache –命名空间的二级缓存配置
•cache-ref – 其他命名空间缓存配置的引用。
•resultMap – 自定义结果集映射
•parameterMap – 已废弃!老式风格的参数映射
•sql –抽取可重用语句块。
•insert – 映射插入语句
•update – 映射更新语句
•delete – 映射删除语句
•select – 映射查询语句
1.1 insert, update 和 delete
数据变更语句 insert,update 和 delete 的实现非常接近;
Insert, Update, Delete 元素的属性
属性 | 描述 |
---|---|
id | 命名空间中的唯一标识符,可被用来代表这条语句。 |
parameterType | 将要传入语句的参数的完全限定类名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器推断出具体传入语句的参数,默认值为未设置(unset)。 |
flushCache | 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:true(对于 insert、update 和 delete 语句)。 |
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 的语句;如果带或者不带的语句都有,则不带的会被忽略。 |
下面就是 insert,update 和 delete 语句的示例:
<insert id="insertEmployee">
insert into t_employee (id,last_name,age,addr,d_id)
values (#{id},#{lastName},#{age},#{addr},#{dId})
</insert>
<update id="updateEmployee">
update t_employee set
last_name= #{lastName},
age = #{age},
addr = #{addr},
d_id = #{dId}
where id = #{id}
</update>
<delete id="deleteEmployee">
delete from t_employee where id = #{id}
</delete>
1.2 获取主键值
1.2.1 获取自增主键的值
首先,如果你的数据库支持自动生成主键的字段(比如 MySQL 和 SQL Server),那么你可以设置 useGeneratedKeys=”true”,然后再把 keyProperty 设置到目标属性上就 OK 了。例如,如果上面的 t_employee 表已经对 id 使用了自动生成的列类型,那么语句可以修改为:
<insert id="insertEmployee" useGeneratedKeys="true" keyProperty="id">
insert into t_employee (id,last_name,age,addr,d_id)
values (#{id},#{lastName},#{age},#{addr},#{dId})
</insert>
如果你的数据库还支持多行插入, 你也可以传入一个 Employee 数组或集合,并返回自动生成的主键。
<insert id="insertEmployee" useGeneratedKeys="true" keyProperty="id">
insert into Author (last_name, age, addr, d_id) values
<foreach item="item" collection="list" separator=",">
(#{item.lastName}, #{item.age}, #{item.addr}, #{item.dId})
</foreach>
</insert>
1.2.2 获取非自增主键值
对于不支持自动生成类型的数据库或可能不支持自动生成主键的 JDBC 驱动,MyBatis 有另外一种方法来生成主键。
比如:Oracle,可以支持使用序列来作为主键:
<insert id="insertEmployee">
<selectKey keyProperty="id" resultType="int" order="BEFORE">
select employees_seq.nextval from dual
</selectKey>
insert into t_employee
(id, last_name, age, addr,d_id)
values
(#{id}, #{username}, #{password}, #{email}, #{bio}
</insert>
在上面的示例中,selectKey 元素中的语句将会首先运行,t_employee 的 id 会被设置,然后插入语句会被调用。这可以提供给你一个与数据库中自动生成主键类似的行为,同时保持了 Java 代码的简洁。
属性 | 描述 |
---|---|
keyProperty | selectKey 语句结果应该被设置的目标属性。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。 |
keyColumn | 匹配属性的返回结果集中的列名称。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。 |
resultType | 结果的类型。MyBatis 通常可以推断出来,但是为了更加精确,写上也不会有什么问题。MyBatis 允许将任何简单类型用作主键的类型,包括字符串。如果希望作用于多个生成的列,则可以使用一个包含期望属性的 Object 或一个 Map。 |
order | 这可以被设置为 BEFORE 或 AFTER。如果设置为 BEFORE,那么它会首先生成主键,设置 keyProperty 然后执行插入语句。如果设置为 AFTER,那么先执行插入语句,然后是 selectKey 中的语句 - 这和 Oracle 数据库的行为相似,在插入语句内部可能有嵌入索引调用。 |
statementType | 与前面相同,MyBatis 支持 STATEMENT,PREPARED 和 CALLABLE 语句的映射类型,分别代表 PreparedStatement 和 CallableStatement 类型。 |
1.3 参数的传递和取值
单个参数:mybatis不会做特殊处理,
#{参数名/任意名}:取出参数值。
多个参数:mybatis会做特殊处理。
多个参数会被封装成 一个map,
key:param1...paramN,或者参数的索引也可以
value:传入的参数值
#{}就是从map中获取指定的key的值;
异常:
org.apache.ibatis.binding.BindingException:
Parameter 'id' not found.
Available parameters are [1, 0, param1, param2]
操作:
方法:public Employee getEmpByIdAndLastName(Integer id,String lastName);
取值:#{id},#{lastName}
【命名参数】:明确指定封装参数时map的key;@Param("id")
多个参数会被封装成 一个map,
key:使用@Param注解指定的值
value:参数值
#{指定的key}取出对应的参数值
POJO:
如果多个参数正好是我们业务逻辑的数据模型,我们就可以直接传入pojo;
#{属性名}:取出传入的pojo的属性值
Map:
如果多个参数不是业务模型中的数据,没有对应的pojo,不经常使用,为了方便,我们也可以传入map
#{key}:取出map中对应的值
TO:
如果多个参数不是业务模型中的数据,但是经常要使用,推荐来编写一个TO(Transfer Object)数据传输对象
========================思考================================
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、Set)类型或者是数组,
也会特殊处理。也是把传入的list或者数组封装在map中。
key:Collection(collection),如果是List还可以使用这个key(list)
数组(array)
public Employee getEmpById(List<Integer> ids);
取值:取出第一个id的值: #{list[0]}
========================结合源码,mybatis怎么处理参数==========================
总结:参数多时会封装map,为了不混乱,我们可以使用@Param来指定封装时使用的key;
#{key}就可以取出map中的值;
(@Param("id")Integer id,@Param("lastName")String lastName);
ParamNameResolver解析参数封装map的;
//1、names:{0=id, 1=lastName};构造器的时候就确定好了
确定流程:
1.获取每个标了param注解的参数的@Param的值:id,lastName; 赋值给name;
2.每次解析一个参数给map中保存信息:(key:参数索引,value:name的值)
name的值:
标注了param注解:注解的值
没有标注:
1.全局配置:useActualParamName(jdk1.8):name=参数名
2.name=map.size();相当于当前元素的索引
{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;
参数值的获取两种方式
#{}:可以获取map中的值或者pojo对象属性的值;
${}:可以获取map中的值或者pojo对象属性的值;
select * from tbl_employee where id=${id} and last_name=#{lastName}
Preparing: select * from tbl_employee where id=2 and last_name=?
区别:
#{}:是以预编译的形式,将参数设置到sql语句中;PreparedStatement;防止sql注入
${}:取出的值直接拼装在sql语句中;会有安全问题;
大多情况下,我们去参数的值都应该去使用#{};
原生jdbc不支持占位符的地方我们就可以使用${}进行取值
比如分表、排序。。。;按照年份分表拆分
select * from ${year}_salary where xxx;
select * from tbl_employee order by ${f_name} ${order}
#{}:更丰富的用法:
规定参数的一些规则:
javaType、 jdbcType、 mode(存储过程)、 numericScale、
resultMap、 typeHandler、 jdbcTypeName、 expression(未来准备支持的功能);
jdbcType通常需要在某种特定的条件下被设置:
在我们数据为null的时候,有些数据库可能不能识别mybatis对null的默认处理。比如Oracle(报错);
JdbcType OTHER:无效的类型111;因为mybatis对所有的null都映射的是原生Jdbc的OTHER类型,oracle不能正确处理,而mysql可以处理;
由于全局配置中:jdbcTypeForNull=OTHER;oracle不支持;两种办法
1、#{email,jdbcType=OTHER};
2、jdbcTypeForNull=NULL,在全局配置文件mybatis-config.xml中设置
<settings>
<setting name="jdbcTypeForNull" value="NULL"/>
</settings>
1.4 select详解
1.4.1 Select 元素的属性
Select 元素的属性
属性 | 描述 |
---|---|
id | 在命名空间中唯一的标识符,可以被用来引用这条语句。 |
parameterType | 将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler) 推断出具体传入语句的参数,默认值为未设置(unset)。 |
resultType | 从这条语句中返回的期望类型的类的完全限定名或别名。 注意如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身。可以使用 resultType 或 resultMap,但不能同时使用。 |
resultMap | 外部 resultMap 的命名引用。结果集的映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂映射的情形都能迎刃而解。可以使用 resultMap 或 resultType,但不能同时使用。 |
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 | 这个设置仅对多结果集的情况适用。它将列出语句执行后返回的结果集并给每个结果集一个名称,名称是逗号分隔的。 |
示例:
<select
id="selectPerson"
parameterType="int"
parameterMap="deprecated"
resultType="hashmap"
resultMap="personResultMap"
flushCache="false"
useCache="true"
timeout="10"
fetchSize="256"
statementType="PREPARED"
resultSetType="FORWARD_ONLY">
SELECT * FROM PERSON WHERE ID = #{id}
</select>
• Select元素来定义查询操作。
• Id:唯一标识符。
– 用来引用这条语句,需要和接口的方法名一致
• parameterType:参数类型。
– 可以不传,MyBatis会根据TypeHandler自动推断
• resultType:返回值类型。
– 别名或者全类名,如果返回的是集合,定义集合中元素的类型。不能和resultMap同时使用
1.4.2 自动映射
1、全局setting设置
– autoMappingBehavior默认是PARTIAL,开启自动映射的功能。唯一的要求是列名和javaBean属性名一致
– 如果autoMappingBehavior设置为null则会取消自动映射
– 数据库字段命名规范,POJO属性符合驼峰命名法,如A_COLUMN——》aColumn,我们可以开启自动驼峰命名规则映射功能,mapUnderscoreToCamelCase=true。
2、resultType的使用
2.1 如果是查询到的结果是封装成java对象或对象的集合,那么resultType的值是java类的全类名或别名;
<select id="selectById" resultType="com.mybatis.pojo.Employee">
select id,last_name,age,addr from t_employee where id = #{id}
</select>
2.2 如果查询到的一条记录封装成Map,那么resultType的值就是map(内置别名),key就是列名,value就是对应的值;
<!--查询的结果封装成map-->
<select id="selectByIdForMap" resultType="map">
select id,last_name from t_employee where id = #{id}
</select>
EmployeeMapper中添加方法:
//封装成map返回
public Map<String,Object> selectByIdForMap(String id);
测试方法:
@Test
public void test3(){
SqlSession sqlSession = null;
try {
sqlSession = sessionFactory.openSession();
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
Map<String,Object> resultMap = employeeMapper.selectByIdForMap("1");
System.out.println(resultMap.get("id")+"----名称:"+resultMap.get("last_name"));
}finally {
sqlSession.close();
}
}
2.3 如果查询的多条记录要封装成的Map<Integer,Java对象>,比如:
<!--查询的结果封装成map-->
<select id="selectListByIdForMap" resultType="com.mybatis.pojo.Employee">
select id,last_name,age,addr from t_employee where last_name like #{lastName}
</select>
EmployeeMapper中添加方法:
//查询到的对象集合封装成map返回
@MapKey("lastName")//@MapKey:告诉mybatis封装这个map的时候使用哪个属性作为map的key
public Map<String,Employee> selectListByIdForMap(String lastName);
测试:
@Test
public void test4(){
SqlSession sqlSession = null;
try {
sqlSession = sessionFactory.openSession();
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
Map<String,Employee> resultMap = employeeMapper.selectListByIdForMap("%o%");
System.out.println(resultMap);
}finally {
sqlSession.close();
}
}
结果:{tom=Employee{id='1', lastName='tom', age=22, addr='changshashi'}, bob=Employee{id='5', lastName='bob', age=31, addr='深圳'}}
2 自定义映射规则(resultMap)
如果我们的sql查询返回的结果,能和我们的Java类的属性一一对应的话,那么resultType就可以解决问题。但是当进行多表关联查询的时候,一般是没有现成的Java对象可以进行结果映射的。因此,就需要使用强大的resultMap,来自定义映射规则。
resultMap的属性如下:
- constructor - 用于在实例化类时,注入结果到构造方法中
- idArg - ID 参数;标记出作为 ID 的结果可以帮助提高整体性能
- arg - 将被注入到构造方法的一个普通结果
- id – 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能
- result – 注入到字段或 JavaBean 属性的普通结果
- association – 一个复杂类型的关联;许多结果将包装成这种类型
- 嵌套结果映射 – 关联本身可以是一个 resultMap 元素,或者从别处引用一个
- collection – 一个复杂类型的集合
- 嵌套结果映射 – 集合本身可以是一个 resultMap 元素,或者从别处引用一个
- discriminator – 使用结果值来决定使用哪个 resultMap
- case – 基于某些值的结果映射
- 嵌套结果映射 – case 本身可以是一个 resultMap 元素,因此可以具有相同的结构和元素,或者从别处引用一个
- case – 基于某些值的结果映射
属性 | 描述 |
---|---|
id | 当前命名空间中的一个唯一标识,用于标识一个结果映射。 |
type | 类的完全限定名, 或者一个类型别名(关于内置的类型别名,可以参考上面的表格)。 |
autoMapping | 如果设置这个属性,MyBatis将会为本结果映射开启或者关闭自动映射。 这个属性会覆盖全局的属性 autoMappingBehavior。默认值:未设置(unset)。 |
2.1 一对一映射
修改Employee,添加一个属性dept(部门信息),一个员工属于一个部门;
public class Employee {
private String id;
private String lastName;
private int age;
private String addr;
private Department dept;
//get/set方法以及toString方法
}
2.1.1 级联绑定
需求:根据员工id查询员工信息,同时也要查询出对应部门信息
mapper.xml中的sql
<resultMap id="empDept1" type="com.mybatis.pojo.Employee">
<id property="id" column="id"/>
<result property="lastName" column="last_name"/>
<result property="age" column="age"/>
<result property="addr" column="addr"/>
<!--部门信息的封装,使用级联封装,dept是Employee类中的属性名称-->
<result property="dept.id" column="did"/>
<result property="dept.departmentName" column="department_name"/>
</resultMap>
<select id="selectEmpAndDept" resultMap="empDept1">
SELECT e.id,e.last_name,e.age,e.addr,d.id did,d.department_name FROM t_employee e, t_dept d WHERE e.d_id=d.id and e.id = #{id}
</select>
mapper接口新增一个方法
//级联封装
public Employee selectEmpAndDept(String id);
测试
@Test
public void test5(){
SqlSession sqlSession = null;
try {
sqlSession = sessionFactory.openSession();
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
Employee employee = employeeMapper.selectEmpAndDept("2");
System.out.println("员工信息:"+employee);
System.out.println("部门信息:"+employee.getDept());
}finally {
sqlSession.close();
}
}
结果:
员工信息:Employee{id='2', lastName='jack', age=33, addr='长沙'}
部门信息:Department{id=1, departmentName='开发部'}
可以看到,resultMap中,通过属性名来进行级联,也可以把查询结果封装成我们想要的结果。
2.1.2 association绑定单个对象
使用association来封装,跟级联的唯一不同只是resultMap的不同,修改resultMap定义如下:
<resultMap id="empDept1" type="com.mybatis.pojo.Employee">
<id property="id" column="id"/>
<result property="lastName" column="last_name"/>
<result property="age" column="age"/>
<result property="addr" column="addr"/>
<!--使用association来进行单一对应的封装
dept:是Employee中的部门属性名称
javaType:指定对应的类型
-->
<association property="dept" javaType="com.mybatis.pojo.Department">
<id property="id" column="did"/>
<result property="departmentName" column="department_name"/>
</association>
</resultMap>
执行程序,结果分上面的级联是一样的。
2.1.3 association 分步查询
上面的2.1.2相当于是使用的嵌套结果集,一次性查询出了员工以及部门信息。其实我们可以先查询出员工信息,就能知道他的部门id,再根据部门id去查询部门信息就可以了。
1、编写DepartmentMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--<mapper namespace="com.mybatis.EmployeeMapper">-->
<mapper namespace="com.mybatis.mapper.DepartmentMapper">
<select id="selectById" resultType="com.mybatis.pojo.Department">
SELECT id,department_name FROM t_dept WHERE id = #{id}
</select>
</mapper>
2、DepartmentMapper.java添加方法
package com.mybatis.mapper;
import com.mybatis.pojo.Department;
public interface DepartmentMapper {
//根据id查询
public Department selectById(int id);
}
3、修改EmpoyeeMapper.xml的sql
<resultMap id="empDept1" type="com.mybatis.pojo.Employee">
<id property="id" column="id"/>
<result property="lastName" column="last_name"/>
<result property="age" column="age"/>
<result property="addr" column="addr"/>
<!--使用association来进行单一对应的封装
dept:是Employee中的部门属性名称
select:表明当前属性dept是调用select指定的方法查出的结果
column:指定将哪一列的结果传给select当作参数
-->
<association property="dept" select="com.mybatis.mapper.DepartmentMapper.selectById" column="d_id">
</association>
</resultMap>
<select id="selectEmpAndDept" resultMap="empDept1">
select * from t_employee where id = #{id}
</select>
4、继续执行之前的测试方法
@Test
public void test5(){
SqlSession sqlSession = null;
try {
sqlSession = sessionFactory.openSession();
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
Employee employee = employeeMapper.selectEmpAndDept("2");
System.out.println("员工信息:"+employee);
System.out.println("部门信息:"+employee.getDept());
}finally {
sqlSession.close();
}
}
结果:
员工信息:Employee{id='2', lastName='jack', age=33, addr='长沙'}
部门信息:Department{id=1, departmentName='开发部'}
2.1.4 association 分步查询实现延迟加载
延迟加载:顾名思义就是真正使用的时候才去做查询。比如我们查询员工信息,但是当我们没有使用部门相关的信息时,不会发起查询部门的sql,只有在使用部门信息的时候,才会发sql进行查询操作。
实现延迟加载:在上面的2.1.3的分步查询基础上,只要在全局配置文件mybatis-config.xml中配置两个参数就可以了。
<settings>
<!--开启驼峰命名-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!--延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。
特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。默认false-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--当开启时,任何方法的调用都会加载该对象的所有属性。
否则,每个属性会按需加载(参考 lazyLoadTriggerMethods)。默认false-->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
效果:
没有延迟加载的时候,sql是一起发送的:
2.2 一对多映射
association是用来关联单个对象的,如果是一个对象关联一个集合,那就要使用collection了。
修改Department实体,添加一个List<Employee> emps 属性;
public class Department {
private Integer id;
private String departmentName;
private List<Employee> emps;
//get/set方法以及toString方法
}
需求:查询部门的时候,把部门下面所有的员工也查询出来。
2.2.1 collection关联集合对象
1、修改DepartmentMapper.xml
<resultMap id="MyDeptEpml" type="com.mybatis.pojo.Department">
<id property="id" column="did"/>
<result property="departmentName" column="department_name"/>
<!--collection 可以封装集合
emps:部门对象中的员工集合的属性名称
ofType:要把查询的数据封装成哪个对象
-->
<collection property="emps" ofType="com.mybatis.pojo.Employee">
<id property="id" column="id"/>
<result property="lastName" column="last_name"/>
<result property="age" column="age"/>
<result property="addr" column="addr"/>
</collection>
</resultMap>
<select id="selectDeptAndEmployees" resultMap="MyDeptEpml">
SELECT d.id did,d.department_name,e.id,e.last_name,e.age,e.addr
FROM t_dept d LEFT JOIN t_employee e ON d.id=e.d_id
WHERE d.id=#{id}
</select>
2、DempartmentMapper接口中加上方法
//根据部门id查询,同时查询出员工信息
public Department selectDeptAndEmployees(int id);
3、测试
@Test
public void test6(){
SqlSession sqlSession = null;
try {
sqlSession = sessionFactory.openSession();
DepartmentMapper departmentMapper = sqlSession.getMapper(DepartmentMapper.class);
Department department = departmentMapper.selectDeptAndEmployees(1);
System.out.println("员工信息:"+department);//如果直接打印employee,就不会有延迟加载效果
System.out.println("部门信息:"+department.getEmps());
}finally {
sqlSession.close();
}
}
结果:
DEBUG 02-19 21:22:03,756 ==> Preparing: SELECT d.id did,d.department_name,e.id,e.last_name,e.age,e.addr FROM t_dept d LEFT JOIN t_employee e ON d.id=e.d_id WHERE d.id=? (BaseJdbcLogger.java:145)
DEBUG 02-19 21:22:03,819 ==> Parameters: 1(Integer) (BaseJdbcLogger.java:145)
DEBUG 02-19 21:22:03,857 <== Total: 3 (BaseJdbcLogger.java:145)
员工信息:Department{id=1, departmentName='开发部'}
部门信息:[Employee{id='2', lastName='jack', age=33, addr='长沙'}, Employee{id='4', lastName='jerry', age=36, addr='上海'}, Employee{id='5', lastName='bob', age=31, addr='深圳'}]
2.2.2 collection分步查询
collection的分步查询原理和association分步查询是一样的。下面来实现:
1、在EmployeeMapper.xml中添加根据部门id查询员工信息的方法
<!--根据部门id查询员工信息-->
<select id="selectByDeptId" resultType="com.mybatis.pojo.Employee">
SELECT * FROM t_employee WHERE d_id=#{deptId}
</select>
2、在EmployeeMapper接口中添加根据部门id查询员工信息的方法
//根据部门id查询员工的方法
public List<Employee> selectByDeptId(String id);
3、在DepartmentMapper.xml中添加根据id查询部门的方法
<select id="selectDeptAndEmployees" resultMap="MyDeptEpml">
SELECT d.id did,d.department_name,e.id,e.last_name,e.age,e.addr
FROM t_dept d LEFT JOIN t_employee e ON d.id=e.d_id
WHERE d.id=#{id}
</select>
<resultMap id="MyDeptEpml2" type="com.mybatis.pojo.Department">
<id property="id" column="id"/>
<result property="departmentName" column="department_name"/>
<!--collection 可以封装集合
emps:部门对象中的员工集合的属性名称
ofType:要把查询的数据封装成哪个对象
-->
<collection property="emps" select="com.mybatis.mapper.EmployeeMapper.selectByDeptId" column="id">
</collection>
</resultMap>
<!--根据部门id查询部门信息,封装成map-->
<select id="selectByDeptId" resultMap="MyDeptEpml2">
SELECT id,department_name departmentName FROM t_dept WHERE id = #{id}
</select>
4、在DepartmentMapper.java接口中添加方法
public Department selectByDeptId(int id);
5、测试
@Test
public void test6(){
SqlSession sqlSession = null;
try {
sqlSession = sessionFactory.openSession();
DepartmentMapper departmentMapper = sqlSession.getMapper(DepartmentMapper.class);
Department department = departmentMapper.selectByDeptId(1);
System.out.println("部门信息:"+department);//如果直接打印department,就不会有延迟加载效果
System.out.println("员工信息:"+department.getEmps());
}finally {
sqlSession.close();
}
}
结果:
DEBUG 02-19 21:35:15,789 ==> Preparing: SELECT id,department_name departmentName FROM t_dept WHERE id = ? (BaseJdbcLogger.java:145)
DEBUG 02-19 21:35:15,877 ==> Parameters: 1(Integer) (BaseJdbcLogger.java:145)
DEBUG 02-19 21:35:15,903 ====> Preparing: SELECT * FROM t_employee WHERE d_id=? (BaseJdbcLogger.java:145)
DEBUG 02-19 21:35:15,904 ====> Parameters: 1(Integer) (BaseJdbcLogger.java:145)
DEBUG 02-19 21:35:15,911 <==== Total: 3 (BaseJdbcLogger.java:145)
DEBUG 02-19 21:35:15,911 <== Total: 1 (BaseJdbcLogger.java:145)
部门信息:Department{id=1, departmentName='开发部'}
员工信息:[Employee{id='2', lastName='jack', age=33, addr='长沙'}, Employee{id='4', lastName='jerry', age=36, addr='上海'}, Employee{id='5', lastName='bob', age=31, addr='深圳'}]
2.2.3 collection分步查询实现延迟加载
原理和association的延迟加载是一样的,只要设置了那两个值就可以了。
2.2.4 collection分步查询传递多列值
上面的分步查询,我们都是只传了一个值作为查询条件,那么怎么传递多个值呢?
其实很简单,只要将多个值封装成map传递就好了:{key1=column1,key2=column2....}
在2.2.3的示例中,在EmployeeMapper.xml中,根据部门id查询员工信息的sql是:
SELECT * FROM t_employee WHERE d_id=#{deptId}
那么说明,map传值的时候是:{deptId=id}
所以把DepartmentMapper.xml的sql修改如下:
<collection property="emps" select="com.mybatis.mapper.EmployeeMapper.selectByDeptId"
column="{deptId=id}">
</collection>
column="{deptId=id}":主要修改的是这一块,其他都是不变的。
3、discriminator鉴别器
鉴别器:mybatis可以使用discriminator判断某列的值,然后根据某列的值改变封装行为
需求:封装Employee:
如果查出的是女生(gender=0):就把部门信息查询出来,否则不查询;
如果是男生(gender=0),把last_name这一列的值赋值给email;
resultMap的编写
<resultMap type="com.atguigu.mybatis.bean.Employee" id="MyEmpDis">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
<!--
column:指定判定的列名
javaType:列值对应的java类型 -->
<discriminator javaType="string" column="gender">
<!--女生 resultType:指定封装的结果类型;不能缺少。/resultMap-->
<case value="0" resultType="com.atguigu.mybatis.bean.Employee">
<association property="dept"
select="com.atguigu.mybatis.dao.DepartmentMapper.getDeptById"
column="d_id">
</association>
</case>
<!--男生 ;如果是男生,把last_name这一列的值赋值给email; -->
<case value="1" resultType="com.atguigu.mybatis.bean.Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="last_name" property="email"/>
<result column="gender" property="gender"/>
</case>
</discriminator>
</resultMap>