MyBatis从前世到今生一网打尽(全网最全,建议收藏)3️⃣
五、MyBatis 映射文件
MyBatis 的真正强大在于它的映射语句,也是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 就是针对 SQL 构建的,并且比普通的方法做的更好。
SQL 映射文件有很少的几个顶级元素(按照它们应该被定义的顺序):
cache – 给定命名空间的缓存配置。
cache-ref – 其他命名空间缓存配置的引用。
resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
parameterMap – 已废弃!
sql – 可被其他语句引用的可重用语句块。
insert – 映射插入语句
update – 映射更新语句
delete – 映射删除语句
select – 映射查询语
5.1 、Mybatis两种开发方式的比较
5.1.1、传统dao开发的弊端
我们在前面入门的例子就是传统的dao操作,我们可以看到这个操作极为繁琐,不仅繁琐,而且dao实现类也没有干什么实质性的工作,它仅仅就是通过 SqlSession 的相关 API 定位到映射文件 mapper 中相应 id 的 SQL 语句,真正对 DB 进行操作的工作其实是由框架通过 mapper 中的 SQL 完成的。
5.1.2、现代dao开发好处
MyBatis 框架就抛开了 Dao 的实现类,直接定位到映射文件 mapper 中的相应 SQL 语句,对
DB 进行操作。这种对 Dao 的实现方式称为 Mapper 的动态代理方式。
Mapper 动态代理方式无需程序员实现 Dao 接口。接口是由 MyBatis 结合映射文件自动生成的动态代
理实现的。
5.2、Mybatis的动态代理
5.2.1、获取代理对象
我们只需调用 SqlSession 的 getMapper()
方法,即可获取指定接口的实现类对象。该方法的参数为指定 Dao接口类的 class 值。
SqlSession session = factory.openSession();
UserDao dao = session.getMapper(UserDao.class);
//或者可以使用工具类
UserDao userDao =
MyBatisUtil.getSqlSession().getMapper(UserDao.class);
5.2.2、使用代理对象执行sql语句
@Test
public void TestUpdate(){
User user = new User(16,"赵六","110");
userDao.update(user);
}
5.2.3、动态代理原理
这种手段我们称为动态代理,我们debug一下可以看到
点进MapperProxy 类定义:
invoke()方法:
点进去execute方法,重点方法:
5.3、主键生成方式和获取主键值
5.3.1、主键生成方式
- 支持主键自增,例如 MySQL 数据库
- 不支持主键自增,例如 Oracle 数据库
5.3.2、 获取主键值
若数据库支持自动生成主键的字段(比如 MySQL 和 SQL Server ),则可以设置`useGeneratedKeys=”true”`,然后再把 keyProperty 设置到目标属性上。
<insert id="insertEmployee"
insert id="insertEmployee"parameterType="com.atguigu.mybatis.beans.Employee" databaseId="mysql" useGeneratedKeys="true" keyProperty="id">
insert into tbl_employee(last_name,email,gender) values(#{lastName},#{email},#{gender})
</insert>
而对于不支持自增型主键的数据库(例如 Oracle),则可以使用 selectKey 子元素:selectKey 元素将会首先运行,id 会被设置,然后插入语句会被调用
<insert id="insertEmployee" parameterType="com.atguigu.mybatis.beans.Employee" databaseId="oracle">
<selectKey order="BEFORE" keyProperty="id" resultType="integer">
select employee_seq.nextval from dual
</selectKey>
insert into orcl_employee(id,last_name,email,gender) values(#{id},#{lastName},#{email},#{gender})
</insert>
或者这样写
<insert id="insertEmployee" parameterType="com.atguigu.mybatis.beans.Employee" databaseId="oracle">
<selectKey order="AFTER" keyProperty="id" resultType="integer">
select employee_seq.currval from dual
</selectKey>
insert into orcl_employee(id,last_name,email,gender) values(employee_seq.nextval,#{lastName},#{email},#{gender})
</insert>
5.4、参数传递
5.4.1、参数传递的方式
5.4.1.1、单个简单参数
可以接受基本类型,对象类型。这种情况 MyBatis 可直接使用这个参数,不需要经过任何处理。
在mapper.xml中用占位符#{ 任意字符 }
来表示这个参数,和方法的参数名无关。但是一般我们都会用方法的参数名来命名。
//接口中的方法:
User selectById(int id);
<!--mapper文件-->
<select id="selectById" resultType="com.bjpowernode.domain.Student">
select id,username,pwd from user where id=#{abcd}
</select>
<!--
#{abcd} , abcd是自定义的变量名称,可以不和方法参数名相同,但是实际开发中一般是相同的。
-->
5.4.1.2、多个参数(贴注解)
任意多个参数,都会被 MyBatis 重新包装成一个 Map 传入。Map 的 key 是 param1,param2,或者 0,1…,值就是参数的值。
我们一般需要在方法形参前面加入**@Param(“自定义参数名”), mapper 文件使用#{自定义参数名}**来表示传入多个参数。
//接口方法
List<User> selectUserByCondition(@Param("username") String username, @Param("pwd") int pwd);
<!--mapper文件-->
<select id="selectUserByCondition" resultType="com.domain.User">
select id,username,pwd from student where username = #{username} or pwd = #{pwd}
</select>
5.4.1.3、多个参数(封装成对象)
当我们需要传递多个参数的时候,我们可以将这些对象直接封装成一个对象,我们就直接传入JavaBean对象即可,在占位符内写对象的属性。
5.4.1.4、Map
Map 集合可以存储多个值,使用 Map 向 mapper 文件一次传入多个参数。Map 集合使用 String 的 key,Object 类型的值存储参数。 mapper 文件使用 # { key } 引用参数值。
//接口方法
List<User> selectMultiMap(Map<String,Object> map);
<!--mapper文件-->
<select id="selectMultiMap" resultType="com.domain.User">
select id,username,pwd from user where username=#{username} or pwd =#{pwd}
</select>
5.4.1.5、Collection/Array
会被MyBatis 封装成一个map 传入, Collection 对应的key 是collection,Array 对应的key是 array. 如果确定是 List 集合,key 还可以是 list.
5.4.2、参数传递的源码分析
以命名参数为例:
public User getUserByIdAndUsername
(@Param("id")Integer id, @Param("username")String username);
源码分析
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
return args[names.firstKey()];
} else {
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
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;
}
}
5.4.3、参数处理
参数位置支持的属性
javaType、jdbcType、mode、numericScale、resultMap、typeHandler、jdbcTypeName、expression
使用实例
**实际上通常被设置的是:为可能为空的列名指定 jdbcType **
<select id="selectMultiObject" resultType="com.domain.User">
select id,username pwd from user
where username=#{username,javaType=string,jdbcType=VARCHAR}
or pwd =#{pwd,javaType=int,jdbcType=INTEGER}
</select>
5.4.4、参数的获取方式
5.4.4.1、#
#
占位符:告诉 mybatis 使用实际的参数值代替。并使用 PrepareStatement 对象执行 sql 语句, #{…}
代替sql 语句的?
。这样做更安全,更迅速,通常也是首选做法.
<!--mapper文件-->
<select id="selectById" resultType="com.domain.User">
select id,username,pwd from user where id=#{id}
</select>
//转为 MyBatis 的执行是:
String sql=” select id,username,pwd from user where id=?”;
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1,1005);
//解释:
where id=? 就是 where id=#{id}
ps.setInt(1,1005) , 1005 会替换掉 #{id}
5.4.4.2、$
$
字符串替换:告诉 mybatis 使用
{}的内容连接起来。主要用在替换表名,列名,不同列排序等操作。
//需求:使用不同列作为查询条件
//接口方法
User findByDiffField(@Param("col") String colunName,@Param("cval") Object value);
<!--mapper文件-->
<select id="findByDiffField" resultType="com.domain.User">
select * from user where ${col} = #{cval}
</select>
5.5、select 查询的几种情况
5.5.1、查询单行数据返回单个对象
public User getUserById(Integer id );
5.5.2、查询多行数据返回对象的集合
public List<User> getAllUser();
5.5.3、查询单行数据返回 Map 集合
public Map<String,Object> getUserByIdReturnMap(Integer id );
5.5.4、 查询多行数据返回 Map 集合
@MapKey("id") // 指定使用对象的哪个属性来充当 map 的 key
public Map<Integer,User> getAllUserReturnMap();
5.6、resultType 自动映射
执行 sql 得到 ResultSet 转换的类型,使用类型的完全限定名或别名。 注意如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身。resultType 和 resultMap,不能同时使用。
接口方法返回是集合类型,需要指定集合中的类型,不是集合本身。
5.6.1、简单类型
//接口方法
int countUser();
<!--mapper 文件-->
<select id="countUser" resultType="int">
select count(*) from user
</select>
5.6.2、对象类型
//接口方法
List<User> selectUsers();
<!--mapper文件-->
<select id="selectUsers" resultType="com.domain.User">
select id,username,pwd from user
</select>
5.6.3、resultType的原理
使用构造方法创建对象。调用 setXXX 给属性赋值。
sql 语句列 | java 对象方法 |
---|---|
id | setId(rs.setInt(“id”)) |
username | setUsername(rs.setString(“username”)) |
pwd | setPwd(rs.setString(“pwd”)) |
- autoMappingBehavior 默认是 PARTIAL,开启自动映射的功能。唯一的要求是列名和javaBean 属性名一致
- 如果 autoMappingBehavior 设置为 null 则会取消自动映射
- 数据库字段命名规范,POJO 属性符合驼峰命名法,如 A_COLUMN aColumn,我们可以开启自动驼峰命名规则映射功能,mapUnderscoreToCamelCase=true
5.7、resultMap 自定义映射
resultMap 可以自定义 sql 的结果和 java 对象属性的映射关系。更灵活的把列值赋值给指定属性。
常用在列名和 java 对象属性名不一样的情况。
- id :用于完成主键值的映射。
- result :用于完成普通列的映射。
- association :一个复杂的类型关联,许多结果将包成这种类型。
- collection : 复杂类型的集。
属性 | 含义 |
---|---|
property | 映射到列结果的字段或属性,例如:“username"或"address.stree.number” |
column | 数据表的列名,通常和resultSet.getString(columnName) 的返回值一致 |
<!--resultMap: resultMap 标签中的 id 属性值-->
<select id="getEmployeeById" resultMap="myEmp">
select id, last_name,email, gender from tbl_employee where id =#{id}
</select>
<!-- 创建 resultMap
id:自定义的唯一名称,在<select>使用
type:期望转为的 java 对象的全限定名称或别名
-->
<resultMap type="com.domain.Employee" id="myEmp">
<!-- 主键字段使用 id -->
<id column="id" property="id" />
<!--非主键字段使用 result-->
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
</resultMap>
5.7.1、association
5.7.1.1、association
POJO 中的属性可能会是一个对象,我们可以使用联合查询,并以级 联属性的方式封装对象.使用 association 标签定义对象的封装规则
@Data
public class Department {
private Integer id ;
private String departmentName ;
}
@Data
public class Employee {
private Integer id ;
private String lastName;
private String email ;
private String gender ;
private Department dept ;
}
<select id="getEmployeeAndDept" resultMap="myEmpAndDept" >
SELECT e.id eid, e.last_name, e.email,e.gender ,d.id did, d.dept_name
FROM tbl_employee e , tbl_dept d
WHERE e.d_id = d.id
AND e.id = #{id}
</select>
<resultMap type="com.domain.Employee" id="myEmpAndDept">
<id column="eid" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
<association property="dept" javaType="com.domain.Department">
<id column="did" property="id"/>
<result column="dept_name" property="departmentName"/>
</association>
</resultMap>
5.7.1.2、association 分步查询
实际的开发中,对于每个实体类都应该有具体的增删改查方法,也就是 DAO 层, 因此对于查询员工信息并且将对应的部门信息也查询出来的需求,就可以通过分步的方式完成查询。
- 先通过员工的 id 查询员工信息
- 再通过查询出来的员工信息中的外键(部门 id)查询对应的部门信息.
<select id="getEmployeeAndDeptStep" resultMap="myEmpAndDeptStep">
select id, last_name, email,gender,d_id from tbl_employee where id =#{id}
</select>
<resultMap type="com.atguigu.mybatis.beans.Employee" id="myEmpAndDeptStep">
<id column="id" property="id" />
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
<association property="dept"
select="com.atguigu.mybatis.dao.DepartmentMapper.getDeptById"
column="d_id"
fetchType="eager">
</association>
</resultMap>
5.7.1.3、association 分步查询使用延迟加载
在分步查询的基础上,可以使用延迟加载来提升查询的效率,只需要在全局的Settings 中进行如下的配置
<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 设置加载的数据是按需还是全部 -->
<setting name="aggressiveLazyLoading" value="false"/>
5.7.2、collection
5.7.2.1、collection
POJO 中的属性可能会是一个集合对象,我们可以使用联合查询,并以级联属性的方式封
装对象.使用 collection 标签定义对象的封装规则
@Data
public class Department {
private Integer id ;
private String departmentName ;
private List<Employee> emps ;
}
<select id="getDeptAndEmpsById" resultMap="myDeptAndEmps">
SELECT d.id did, d.dept_name ,e.id eid ,e.last_name ,e.email,e.gender
FROM tbl_dept d
LEFT OUTER JOIN tbl_employee e
ON d.id = e.d_id
WHERE d.id = #{id}
</select>
<resultMap type="com.atguigu.mybatis.beans.Department" id="myDeptAndEmps">
<id column="did" property="id"/>
<result column="dept_name" property="departmentName"/>
<!--
property: 关联的属性名
ofType: 集合中元素的类型
-->
<collection property="emps" ofType="com.atguigu.mybatis.beans.Employee">
<id column="eid" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
</collection>
</resultMap>
5.7.2.2、collection 分步查询
实际的开发中,对于每个实体类都应该有具体的增删改查方法,也就是 DAO 层, 因此对于查询部门信息并且将对应的所有的员工信息也查询出来的需求,就可以通过分步的方式完成查询。
- 先通过部门的 id 查询部门信息
- 再通过部门 id 作为员工的外键查询对应的部门信息.
<select id="getDeptAndEmpsByIdStep" resultMap="myDeptAndEmpsStep">
select id ,dept_name from tbl_dept where id = #{id}
</select>
<resultMap type="com.atguigu.mybatis.beans.Department" id="myDeptAndEmpsStep">
<id column="id" property="id"/>
<result column="dept_name" property="departmentName"/>
<collection property="emps" select="com.atguigu.mybatis.dao.EmployeeMapper.getEmpsByDid" column="id"></collection>
</resultMap>
5.7.3、 扩展: 分步查询多列值的传递
- 如果分步查询时,需要传递给调用的查询中多个参数,则需要将多个参数封装成Map 来进行传递,语法如下: {k1=v1, k2=v2…}。
- 在所调用的查询方,取值时就要参考 Map 的取值方式,需要严格的按照封装 map时所用的 key 来取值。
5.7.4、扩展: association 或 collection 的 fetchType 属性
- 在
<association>
和<collection>
标签中都可以设置 fetchType,指定本次查询是否要使用延迟加载。默认为 fetchType=”lazy” ,如果本次的查询不想使用延迟加载,则可设置为fetchType=”eager”。 - fetchType 可以灵活的设置查询是否需要使用延迟加载,而不需要因为某个查询不想使用延迟加载将全局的延迟加载设置关闭。
5.8、模糊查询like
模糊查询的实现有两种方式,:
- 在 java 代码中给查询数据加上
%
。 - mapper 文件中使用
like name "%" #{xxx} "%"
5.8.1、方式一
来查询姓名有“力”的
//接口方法
List<User> selectLikeFirst(String username);
<!--接口文件-->
<select id="selectLikeFirst" resultType="com.domain.User">
select id,username,pwd from user where username like #{username}
</select>
5.8.2、方式二
//接口方法
List<User> selectLikeSecond(String username);
<!--mapper文件-->
<select id="selectLikeSecond" resultType="com.domain.User">
select id,username,pwd from user where username like "%" #{studentName} "%"
</select>
- 点赞
- 收藏
- 关注作者
评论(0)