1.养成习惯,最好是
- CRUD 标签声明返回值类型 ResultType,声明参数类型 ParamType,省的之后报错
- 实体类如果存在有参构造,最好补上无参构造
- 在配置 ResultMap 时,可以自动一一映射的不用配置
- 在配置 ResultMap 时,除了配置对应的列名和属性,最好同时声明 javaType 和 jdbcType,省的出错
2.研究一下 ResultMap 结果映射的细节
3.研究一下 TypeHandler 类型转换器的细节
一、TypeHandler 数据类型转换器
- 定义类型转换器 TypeHandler
- 配置自定义的 TypeHandler
1.定义类型转换器 TypeHandler
- 可以双向转换
- 在数据插入的时候,将 List 集合转为字符串
- 在数据读取的时候,将字符串自动转为 List
- implements TypeHandler 实现接口
@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes(List.class)
public class ListStringTypeHandler implements TypeHandler<List<String>> {
/**
* 插入数据时,手动赋值
*
* @param ps
* @param i
* @param favorites 这就是 user 对象的 favorites 属性的值
* @param jdbcType
* @throws SQLException
*/
@Override
public void setParameter(PreparedStatement ps, int i, List<String> favorites, JdbcType jdbcType) throws SQLException {
//将 List 集合转为字符串
String s = favorites.stream().map(f -> f + ",").collect(Collectors.joining());
//设置转换后的参数
ps.setString(i, s);
}
/**
* 查询的时候调用
*
* @param rs
* @param columnName
* @return 手动解析查询结果,返回 List 集合
* @throws SQLException
*/
@Override
public List<String> getResult(ResultSet rs, String columnName) throws SQLException {
String s = rs.getString(columnName);
if (s != null && !"".equals(s)) {
//查询到的 足球,篮球 按照 , 拆分成数组,再将数组转为集合
List<String> list = Arrays.asList(s.split(","));
return list;
}
return null;
}
@Override
public List<String> getResult(ResultSet rs, int columnIndex) throws SQLException {
String s = rs.getString(columnIndex);
if (s != null && !"".equals(s)) {
//查询到的 足球,篮球 按照 , 拆分成数组,再将数组转为集合
List<String> list = Arrays.asList(s.split(","));
return list;
}
return null;
}
/**
* 如果调用的是存储过程的话,会触发该方法
*
* @param cs
* @param columnIndex
* @return
* @throws SQLException
*/
@Override
public List<String> getResult(CallableStatement cs, int columnIndex) throws SQLException {
String s = cs.getString(columnIndex);
if (s != null && !"".equals(s)) {
//查询到的 足球,篮球 按照 , 拆分成数组,再将数组转为集合
List<String> list = Arrays.asList(s.split(","));
return list;
}
return null;
}
}
2.配置自定义的 TypeHandler
- 对 Mapper.xml 中的值指定 TypeHandler
- 在 mybatis-config.xml 全局配置文件中配置,应用全局
- 在 mybatis-config.xml 全局配置文件中配置,在 TypeHandler 上加两个注解指定两边的数据类型映射
- @MappedJdbcTypes(JdbcType.VARCHAR)
- @MappedTypes(List.class)
2.1. 对 Mapper.xml 中的值指定 TypeHandler
typeHandler = com.ze.demo.typehandler.ListStringTypeHandler
<insert id="addUser" parameterType="com.ze.demo.model.User">
insert into user (username,address,favorites) values (#{username},#{address},#{favorites,typeHandler=com.ze.demo.typehandler.ListStringTypeHandler});
</insert>
2.2在 mybatis-config.xml 全局配置文件中配置,应用全局
<typeHandlers>
<typeHandler handler="com.ze.demo.typehandler.ListStringTypeHandler" javaType="java.util.List" jdbcType="VARCHAR"/>
</typeHandlers>
3. 在 mybatis-config.xml 全局配置文件中配置,在 TypeHandler 上加两个注解指定两边的数据类型映射
<typeHandlers>
<!--
全局配置的时候,可以不加后面两个属性,通过注解来配置后面两个
-->
<typeHandler handler="com.ze.demo.typehandler.ListStringTypeHandler"/>
</typeHandlers>
@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes(List.class)
public class ListStringTypeHandler implements TypeHandler<List<String>> {
......
}
二、++Mybatis 的核心重点 ORM 对象关系映射++【重点
】
- Mybatis 自动 ORM 映射
- 通过起别名来达到自动映射
- 通过 ResultMap 指定映射关系,然后再引用
1. Mybatis 自动 ORM 映射
MyBatis只能自动维护库表”列名“与”属性名“相同时的一一对应关系,二者不同时,无法自动ORM。
自动ORM失效 |
---|
![]() |
2. 方案一:通过起别名来达到自动映射
在SQL中使用 as 为查询字段添加列别名,以匹配属性名。
<mapper namespace="com.qf.mybatis.part2.orm.ManagerDao">
<select id="selectManagerByIdAndPwd" resultType="com.qf.mybatis.part2.orm.Manager">
SELECT mgr_id AS id , mgr_name AS username , mgr_pwd AS password
FROM t_managers
WHERE mgr_id = #{id} AND mgr_pwd = #{pwd}
</select>
</mapper>
3 方案二:结果映射(ResultMap - 查询结果的封装规则)
通过< resultMap id="" type="" >映射,匹配列名与属性名。
当列名和属性名相同时,会自动匹配,不用写映射关系
<mapper namespace="com.qf.mybatis.part2.orm.ManagerDao">
<!--定义resultMap标签-->
<resultMap id="managerResultMap" type="com.qf.mybatis.part2.orm.Manager">
<!--关联主键与列名-->
<id property="id" column="mgr_id" />
<!--关联属性与列名-->
<result property="username" column="mgr_name" />
<result property="password" column="mgr_pwd" />
</resultMap>
<!--使用resultMap作为ORM映射依据-->
<select id="selectAllManagers" resultMap="managerResultMap">
SELECT mgr_id , mgr_name , mgr_pwd
FROM t_managers
</select>
</mapper>
三、++详细说说 ResultMap 结果映射++【超级重点
】
- 一次定义,可以多个地方引用
- 可以有继承关系,extends = “XXXResultMap”,然后再写具体的映射
1. 定义 ResultMap 结果映射
重点:
- id 标签是指定主键, result 标签是指定其他单个的属性,association 和 collection 标签分别指定一个对象、一个集合
- 列名和属性名如果相同,则 Mybatis 会自动映射,不用配置
- 列名和属性名如果不相同,则要手动配置,同时指定两边的数据类型
<resultMap id="GradeResultMap" type="com.ze.demo.model.Grade">
<!--定义查询的列和属性之间的一一对应关系
主键使用 id 标签来做映射;其他的字段统一使用 result 标签来做映射
property 是指属性的名字
column 是指查询出来的列的名字
jdbcType 数据库中的字段的类型
javaType 是属性的类型
-->
<id property="id" column="id" jdbcType="INTEGER" javaType="java.lang.Integer"/>
<result property="gradeName" column="name" javaType="java.lang.String" jdbcType="VARCHAR"/>
</resultMap>
2. 继承结果集 extends
<!--
可以继承自另外一个 ResultMap
-->
<resultMap id="GradeWithClazz" type="com.ze.demo.model.Grade" extends="GradeResultMap">
</resultMap>
3. association 和 collection 表示一个对象、一个集合
3.1 一对一时,获取的结果是一个对象,用 association 标签
- 注意:指定“一方”关系时(对象),使用< association javaType="" >
- association 标签用法和普通的 result 标签一样,不过有内部属性
<!-- 关系表中数据的封装规则 --> <!-- 指定关系表的实体类型 -->
<association property="passport" javaType="com.qf.mybatis.part2.one2one.Passport">
<id property="id" column="passport_id" />
<result property="nationality" column="nationality" />
<result property="expire" column="expire" />
<result property="passenger_id" column="passenger_id" />
</association>
3.2 collection 标签 + ofType 属性 --> 指定集合类型
<resultMap id="GradeWithClazz" type="com.ze.demo.model.Grade" extends="GradeResultMap">
<!--指定一对多中多的一方
通过 ofType 来指定集合中的类型
-->
<collection property="clazzes" ofType="com.ze.demo.model.Clazz">
<id column="cid" property="id"/>
<result column="cname" property="clazzName"/>
</collection>
</resultMap>
3.3 collection 标签 + ofType 属性 + select 属性 + column 属性 --> 指定集合通过调用 select 指定的方法,根据 column 指定的列名来查询出集合
- 具体的查询次数,一般是 1 + 1的结果对应的 column 的个数
- column 指定的列名是之前已经查询出来的列名,亦即准备据此列名查从表(多的一方)的数据
<!--
select 标签表示 调用 getClazzByGid 方法去查询班级数据,查询班级数据的时候,需要年级 id,column 则指定了年级 id 是当前查询出来的 id
fetchType="lazy" 表示延迟加载,只有当调用 getClazzes 方法的时候,才会去执行查询
-->
<resultMap id="GradeWithClazz2" type="com.ze.demo.model.Grade" extends="GradeResultMap">
<collection property="clazzes" ofType="com.ze.demo.model.Clazz" fetchType="lazy"
select="com.ze.demo.mapper.ClazzMapper.getClazzByGid" column="id"/>
</resultMap>
四、MyBatis处理关联关系-多表连接【重点
】
实体间的关系:关联关系(拥有 has、属于 belong)
OneToOne:一对一关系(Passenger— Passport)
OneToMany:一对多关系(Employee — Department)
ManyToMany:多对多关系(Student — Subject)
Table建立外键关系 |
---|
![]() |
Entity添加关系属性 |
---|
![]() |
Mapper中将属性与列名对应 |
---|
![]() |
1. OneToOne
SQL参考OneToOneExample.sql
<mapper namespace="com.qf.mybatis.part2.one2one.PassengerDao">
<!-- 结果映射(查询结果的封装规则) -->
<resultMap id="passengerResultMap" type="com.qf.mybatis.part2.one2one.Passenger">
<id property="id" column="id"/>
<result property="name" column="name" />
<result property="sex" column="sex" />
<result property="birthday" column="birthday" />
<!-- 关系表中数据的封装规则 --> <!-- 指定关系表的实体类型 -->
<association property="passport" javaType="com.qf.mybatis.part2.one2one.Passport">
<id property="id" column="passport_id" />
<result property="nationality" column="nationality" />
<result property="expire" column="expire" />
<result property="passenger_id" column="passenger_id" />
</association>
</resultMap>
<!-- 多表连接查询 --> <!-- 结果映射(查询结果的封装规则)-->
<select id="selectPassengerById" resultMap="passengerResultMap">
<!-- 别名(避免与p1.id冲突) -->
SELECT p1.id , p1.name , p1.sex , p1.birthday , p2.id as passport_id , p2.nationality , p2.expire , p2.passenger_id
FROM t_passengers p1 LEFT JOIN t_passports p2
ON p1.id = p2.passenger_id
WHERE p1.id = #{id}
</select>
</mapper>
2. OneToMany
SQL参考OneToManyExample.sql
<mapper namespace="com.qf.mybatis.part2.one2many.DepartmentDao">
<!-- 封装规则 -->
<resultMap id="departmentResultMap" type="com.qf.mybatis.part2.one2many.Department">
<id property="id" column="id" />
<result property="name" column="name" />
<result property="location" column="location" />
<!-- 关系表中数据的封装规则 --> <!-- 指定关系表的实体类型 -->
<collection property="emps" ofType="com.qf.mybatis.part2.one2many.Employee">
<id property="id" column="emp_id" />
<result property="name" column="emp_name" />
<result property="salary" column="salary" />
<result property="dept_id" column="dept_id" />
</collection>
</resultMap>
<!-- 多表连接查询 --> <!-- 封装规则 -->
<select id="selectDepartmentById" resultMap="departmentResultMap" >
<!-- 别名(避免与d.id、d.name冲突)-->
SELECT d.id , d.name , d.location , e.id AS emp_id , e.name emp_name , e.salary , e.dept_id
FROM t_departments d LEFT JOIN t_employees e
ON d.id = e.dept_id
WHERE d.id = #{id}
</select>
</mapper>
3. 关系总结
一方,添加集合;多方,添加对象。
双方均可建立关系属性,建立关系属性后,对应的Mapper文件中需使用< ResultMap >完成多表映射。
持有对象关系属性,使用< association property=“dept” javaType=“department” >
持有集合关系属性,使用< collection property=“emps” ofType=“employee” >