MyBatis中的复杂映射

news/2024/5/18 22:06:29/文章来源:https://blog.csdn.net/qq_55917018/article/details/127055509

上一章中实现的MyBatis对象映射较为简单,对象中的属性和数据库中的表字段是一一对应的(无论数量和名称都完全一样),如果对象中的属性名和表中的字段名不一致怎么办?又或者Java对象中存在复杂类型属性(即类似Hibernate中多对一、一对多关系对象时)怎么完成数据库表和对象的映射?本章来解决这样的问题。

1 MyBatis的数据映射规则

        MyBatis可以自动把查询到的表数据填充到对象中,这一过程是通过Java反射技术实现的,默认情况下,MyBatis会照查询出来的结果集字段名去填充对象的属性,因此数据库表中的字段名应该与对象的属性名相符合。但这个要求并不总是能保证。

1.1 使用查询别名映射对象属性

1)对象属性名和表字段名不一致时

        如果仅仅是数据库表中的字段名和Java对象的属性名不一致时,可以在select语句中指定查询字段的别名,别名与对象的属性名相同,MyBatis就可以对属性正确赋值了。

        例如有以下Java对象和数据库表Street:

  

 

        对象中的外键属性名为“districtId”而数据表中的外键字段名为“district_id”这时,可以编写以下SQL完成映射:

<mapper namespace="mycinema.dao.StreetDao"><select id="getAll" parameterType="int" resultType="demo.entity.Street">select s.*, s.district_id as districtId from Street s</select>
</mapper>

2)需要跨表查询并填充对象时

        比如我们希望在查询Movie的对象的同时,获取到Movie对应的外键表Category的Name值,因此我们在Movie对象中添加CategoryName属性。为了获取该值,我们可以使用表连接Join语句,并把Category的Name字段在查询中起别名为CategoryName以符合对象填充要求。

 

Movie表中只有CategoryId外键

 

 

Category表中有Name字段

 

对象中需要跨表获取数据

        针对上述需求,我们可以把MyBatis中的Movie查询按如下方式实现。

<mapper namespace="mycinema.dao.MovieMapper"><select id="fetchById" parameterType="int" resultType="Movie">select 	m.*,c.Name as CategoryName from Movie m left join Category c on m.CategoryId=c.Id where m.Id=#{id}</select>
</mapper>

1.2 使用hashmap作为查询结果的返回类型

        如果从多个数据表中查询一些字段,无法填充到某一个实体中,我们还可以把resultType(返回结果类型)声明为hashmap,这时,查询到的每一行数据都会封装到一个HashMap<String, Object>集合中,键就是字段名,值就是字段值。

<select id="getMoviesMap" resultType="hashmap">select m.title, c.name as CategoryName from Movie m inner join Category c on m.CategoryId=c.Id 
</select>

上述查询的执行代码如下:

public static void main(String[] args) throws IOException {SqlSession sess = MyBatisUtil.openSession();MovieMapper dao = sess.getMapper(MovieMapper.class);for(Map<String,Object> map : dao.getMoviesMap()){for(String key : map.keySet()){System.out.print(key+":"+map.get(key)+"\t");}System.out.println();}sess.close();}

执行结果如下所示:

2 SQL的重用

        映射配置文件中还有一个<sql>元素,用于声明可以被重用的sql语句块。例如上述Movie信息的连接查询语句,可能需要在多个<select>中被重用,就可以通过<sql>元素声明,然后使用<include>元素引用。

<sql id="movieJoinCategory">select m.*, c.name CategoryNamefrom Movie m inner join Category c on m.categoryid=c.id 
</sql>	
<select id="fetchById" parameterType="int" resultMap="movieResultMap"><include refid="movieJoinCategory"/> where m.id=#{id}
</select>

 

在<sql>元素中用id声明该SQL语句块的名称,然后在<include>元素中通过refid属性来应用它,这样就可以大大提高SQL语句的可维护性。

3SQL语句中传入多个参数

        实际应用中,SQL语句所需的参数往往不止一个。这时,我们可以把<select>元素的parameterType设置为“hashmap”,即通过键值对集合(HashMap)的方式为SQL语句传入多个命名参数。

        例如下面这个分页查询,其中包含三个命名参数categoryId(分类ID)、skips(跳过的行数)和takes(取出的最大行数)。

<select id="getMoviesPaging" parameterType="hashmap" resultMap="movieResultMap"><include refid="movieJoinCategory"/> where m.CategoryId=#{categoryId}limit #{skips},#{takes}</select>

(1)使用命名查询方式时的参数传递

在执行的时侯,通过定义一个Map集合作为SQL参数,即可完成参数传递。

public List<Movie> getMoviesPaging(int cid, int pageNum, int pageSize) {SqlSession session = MyBatisUtil.openSessionn();Map<String,Object> parameters = new HashMap<String,Object>();parameters.put("categoryId", cid);parameters.put("skips", (pageNum-1)*pageSize);parameters.put("takes", pageSize);try {return session.selectList(
"mycinema.dao.MovieDao.getMoviesPaging", parameters);} finally {session.close();}	}

(2)使用Mapper方式时的参数传递

        使用Mapper方式时,只需声明接口就可以去调用<select>了,因此没有办法通过代码传入HashMap参数,这是,需要通过MyBatis提供的@Param注解在接口参数中声明SQL参数的名称。

public interface MovieMapper {
public List<Movie> getMoviesPaging(
@Param("categoryId")  int cid, 
@Param("skips") 		int skips, 
@Param("takes") 		int takes
);…
}

4 使用resultMap描述复杂映射

        如果对象和表之间有更复杂的差异,比如Java对象中内嵌其它对象属性(多对一或一对多),就需要在MyBatis的实体配置文件中使用resultMap元素描述映射细节。

例如在MyCinema中,电影(Movie)对象中如果内嵌一个电影分类(Category)对象作为属性描述数据库外键,结构如下图所示:

实体对象Movie中包含另一个实体对象Category

        具体代码参考如下:

public class Movie {private int id;private String title;private String movieCode;private Category category;private String director;private Date dateReleased;……
}
public class Category {private int id;private String name;……
}

 对于上述情况,若仅仅配置返回resultType为Movie类型的查询,MyBatis就无法完成外键属性Category 的数据填充了,因此Category属性的值为null。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mycinema.dao.MovieDao"><select id="fetchById" parameterType="int" resultType="Movie">select * from Movie where id=#{id}</select>
</mapper>

使用resultType填充实体对象

Category属性为null

4.1 resultMap的使用

        如果希望一口气填充对象及其子对象,可以使用<select>元素中resultMap属性替代resultType属性,配置对象的映射。<select>中resultMap属性指定的是一个名为<resultMap>的元素定义,<resultMap>是MyBatis中非常重要的元素,它完成了类似JDBC中从ResultSet往Java对象填充数据的过程。通过配置resultMap,可以实现任意复杂的Java对象的数据映射问题。

4.1.1 外键对象映射

(1)通过join关联

        下面的示例中:select语句使用了join把外键表相关数据一并查询了出来;通过resultMap元素,定义了查询结果字段与Java对象之间的映射填充关系。注意的是,resultMap中的<association>子元素,声明了外键对象Category的填充细节。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mycinema.dao.MovieMapper"><!-- 定义填充数据用的resultMap --><resultMap type="Movie" id="movieResultMap"><id column="movie_id" property="id"/><result column="movie_title" property="title"/><result column="movie_director" property="director"/><result column="movie_movieCode" property="movieCode"/><result column="movie_dateReleased" property="dateReleased"/><association property="category" javaType="Category"><id column="category_id" property="id"/><result column="category_name" property="name" /></association></resultMap>
<!-- 定义查询fetchById 使用resultMap填充返回的数据对象 --><select id="fetchById" parameterType="int" resultMap="movieResultMap">select  m.id as movie_id,m.title 			as movie_title,m.moviecode 	as movie_moviecode,m.director 		as movie_director,m.dateReleased 	as movie_dateReleased,c.id 				as category_id,c.name 			as category_namefrom Movie m inner join Category c on m.categoryid=c.id where m.id=#{id}</select>
</mapper>

以下是resultMap配置的重要子元素的解析:

子元素

作用

id

一个 ID 结果;标记结果作为 ID 可以帮助提高整体效能。

result

注入到字段或 JavaBean 普通属性的普通结果

association

一个复杂的类型关联;许多结果将包成这种类型嵌入结果映射

collection

复杂类型的集嵌入结果映射

        上述示例中,association元素实现了Movie和Category对象的多对一关系。

4.1.2 外键集合(一对多)映射

(1)通过join关联

        与上述类似,如果现在的情况是Category对象中包含一个Movie对象的集合(如下代码所示),就需要使用<resultMap>中的<collection>子元素来描述集合属性映射。

Category对象中包含Movie对象的集合

public class Category {……public List<Movie> movies;public List<Movie> getMovies() {	return movies;		}public void setMovies(List<Movie> movies) {		this.movies = movies; 	}
}

在<collection>元素中,property是对象中集合属性的属性名,ofType是集合元素类型(也就是一对多中多一方对象的类型)。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mycinema.dao.CategoryDao"><resultMap type="Category" id="categoryResultMap"><id property="id" column="category_id" /><result property="name" column="category_name"/><collection property="movies" ofType="Movie"><id column="movie_id" property="id"/><result column="movie_title" property="title"/><result column="movie_director" property="director"/><result column="movie_movieCode" property="movieCode"/><result column="movie_dateReleased" property="dateReleased"/>	<association property="category" javaType="Category"><id column="category_id" property="id"/><result column="category_name" property="name" /></association></collection></resultMap><select id="fetchById" parameterType="int" resultMap="categoryResultMap">select  	c.id 			as category_id,c.name 			as category_name,m.id 			as movie_id,m.title 		as movie_title,m.moviecode 	as movie_moviecode,m.director 		as movie_director,m.dateReleased 	as movie_dateReleasedfrom Category c inner join Movie m on c.id=m.categoryid where c.id=#{id}</select>    ……
</mapper>

 

4.2 另一种外键映射方式:通过二次查询实现外键加载

1)外键对象的二次查询映射

        除了通过join的方式关联外键对象,还可通过二次查询的方式关联。也就是说,把主对象和外键对象的查询,分成两个独立查询来执行,通过主对象,找到外键ID,再根据外键ID查询外键对象,其具体配置如下所示。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mycinema.dao.MovieDao"><resultMap type="Movie" id="movieResultMap"><association property="category" javaType="Category"column="CategoryId" select="mycinema.dao.CategoryDao.fetchById" /></resultMap><select id="fetchById" parameterType="int" resultMap="movieResultMap">select * from Movie where id=#{id}</select>
</mapper>

 注意上述的配置,对于Movie的查询,只是单表查询,而resultMap的association元素,多提供了column(外键字段名)和select(使用外键值做关联二次查询)两个属性。其中,select的值“mycinema.dao.CategoryDao.fetchById”指的是CategoryMapper.xml配置文件中,根据id查询Category对象的select元素,如下所示:

<mapper namespace="mycinema.dao.CategoryDao"><select id="fetchById" parameterType="int" resultType="Category">select * from Category where id=#{id}</select>……
</mapper>

上述这种方式,看起来配置方便一些,但是会造成1+N次查询的问题,实际使用应慎重考虑。下图是该查询的执行日志(log4j)输出的执行过程,从中可以看出,一个Movie对象的查询,使用了两条SQL语句。

2)外键集合的二次查询映射

        外键集合映射同样可以使用二次加载的方式。

<resultMap type="Category" id="categoryResultMap"><id property="id" column="category_id" /><result property="name" column="category_name"/><collection property="movies" ofType="Movie" column="id" select="mycinema.dao.MovieDao.getMoviesByCate" /></resultMap><select id="fetchById" parameterType="int" resultMap="categoryResultMap">select * from Category where id=#{id}</select>

 在上述的<collection>元素中,column属性是一对多关系中一方被外键引用的字段名(通常是主键字段名),select属性则是根据外键获取多方集合的查询名称,在上述列子中,这个查询应预先配置在MovieMapper.xml中,例如:

<select id="getMoviesByCate" parameterType="int" resultType="Movie">select * from Movie where CategoryId=#{categoryId}</select>

使用上述这种方式获取外键集合,同样会造成N+1次查询的问题,该查询的执行日志(log4j)如下:

        这种做法查询语句简单了,但可能换来了性能损耗。实践中往往难以两全其美,需要根据情况选择不同的方案。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_203640.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

百分点数据科学产教融合计划继续扩大招募

从全球发展来看&#xff0c;数字经济已经成为重组全球要素资源、重塑全球经济结构、改变全球竞争格局的关键力量&#xff0c;是全球共同的发展战略。作为新经济中的数据科学&#xff0c;伴随社会各领域对数字人才需求的与日俱增&#xff0c;也成为了这一波科技革命中的人才竞争…

中国新出海故事:人、疫情与纽带

【潮汐商业评论/原创】 《枪炮、病菌与钢铁》中对人类社会发展的洞察与新千禧年发生了奇妙的呼应&#xff0c;战争、疫情成为了这十年的历史注脚&#xff0c;但时代的车轮总是滚滚向前的&#xff0c;新的世界版图里&#xff0c;全球化、数字化的浪潮构成了新世界跳动的脉搏&am…

第2章 ROS 通信机制 4 —— 常用命令

文章目录1 应用场景2 rosnode 功能包 plumbing_pub_sub编译执行3 rostopic 功能包 plumbing_pub_sub编译执行4 rosservice (服务通信) 功能包 plumbing_server_client编译执行5 rosmsg (话题通信) 功能包 plumbing_pub_sub编译执行6 rossrv (服务通信) 功能包 plumbing_server_…

常见网络知识面试题总结

&#x1f353;个人主页&#xff1a;个人主页 &#x1f352;系列专栏&#xff1a;C/C基础与进阶 &#x1f4ac;推荐一款模拟面试、刷题神器&#xff0c;从基础到大厂面试题&#x1f449;点击跳转刷题网站进行注册学习 目录 1、OSI七层模型与TCPIP四层模型是什么&#xff1f; 2…

核爆!字节跳动算法大佬手写1000页数据算法笔记:Github已标星79k

数据结构是什么 数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。通常情况下&#xff0c;精心选择的数据结构可以带来更高的运行或者存储效率。数据结构往往同高效的检索算法和索引技术有关。 算法是什么 算法是对解题方案…

PC 端网页特效

一、元素偏移量 offset 系列 (一)offset 概述 1、offset 翻译过来就是偏移量,我们使用 offset 系列相关属性可以动态的得到该元素的位置(偏移)、大小等。 (1)获得元素距离带有定位父元素的位置; (2)获得元素自身的大小(宽度高度); (3)注意:返回的数值都不带单位…

使用polkadot.js在substrate frontier上安装ERC20token合约

使用polkadot.js在substrate frontier上安装ERC20token合约参考资料Substrate Frontier Node Template github.com/substrate-developer-hub/frontier-node-template frontier-node-template/examples/contract-erc20/truffle/contracts/MyToken.json安装ERC20 token合约 sourc…

SwiftUI AR教程之应用程序中使用 RealityKit 生成 3D 文本(教程含完整源码)

项目文件 基本设置 我们将从 Xcode 上的“增强现实应用程序”模板开始: 我使用 SwiftUI 作为界面,使用 Swift 作为语言,使用 RealityKit 作为内容技术: 您现在应该拥有基本的增强现实应用程序模板代码。 使用 RealityKit 生成 3D 文本网格 我们将向我们的项目添加一个函…

快速入门SSMS的使用

快速入门SSMS的使用1.Install SSMS2.Demo2.1 Export DDL2.2 XXXXX3.XXXX4.Waken1.Install SSMS 官方地址 SSMS Website: https://learn.microsoft.com/zh-tw/docs/. 也可以直接下载 Download Website: https://aka.ms/ssmsfullsetup.双击安装 要有电脑的管理员权限 快速启动…

springboot基于JAVA游戏周边商城设计与实现毕业设计源码261622

Springboot游戏周边商城的开发 摘 要 现今人们的生活方式逐渐丰富&#xff0c;电脑和网络已经融入了人们生活中的滴滴点点&#xff0c;无时不刻的影响着我们的日常生活&#xff0c;网络游戏已经进入到了大多数人的生活之中。在游戏的世界中人们会得到很多游戏商品&#xff0c;然…

死锁检测组件原理及代码实现

一、引言 所谓死锁&#xff0c;是指多个线程或进程各自持有某些资源&#xff0c;同时又等待着别的线程或进程释放它们现在所保持的资源&#xff0c;否则就不能向前推进。如下图&#xff1a;线程各自占有一把锁&#xff0c;还需要申请别的线程当前持有的锁&#xff0c;形成锁资…

Cisco简单配置(十四)—第一跳冗余协议—HSRP

为什么使用第一跳冗余 默认网关限制 如果路由器或路由器接口&#xff08;作为默认网关&#xff09;发生故障&#xff0c;配置该默认网关的主机将与外部网络隔离。在交换网络中&#xff0c;每个客户端仅收到一个默认网关。即使存在第二个路由可以从本地网段传输数据包&#xf…

微信小程序 java高校新生报到宿舍安排管理系统python php

将小程序权限按管理员和用户这两类涉及用户划分。 (a) 管理员&#xff1b;管理员使用本程序涉到的功能主要有&#xff1a;个人中心、宿舍管理、学生管理、宿舍安排管理、缴费信息管理、程序管理等功能 (b)用户进入程序前台可以实现首页、互助沟通、我的等功能 uni-app框架&…

基于java校园志愿者管理系统(java毕业设计)

基于java校园志愿者管理系统 校园志愿者系统是基于java编程语言&#xff0c;mysql数据库&#xff0c;springboot框架&#xff0c;idea开发工具进行开发&#xff0c;本系统主要分为志愿者和管理员两个角色&#xff0c;其中志愿者的主要功能是查看系统公告&#xff0c;活动信息&…

红队工具合集,安全er值得拥有

背景 圈内很多师傅一直在做红队安全工具箱,用于在hvv、渗透等工作中提升工作效率。依照ATT&CK威胁图谱的指导,我们很容易整理出常用的红队工具合集,在这里为大家展示。 工具介绍 信息搜集 信息搜集一直是渗透测试工作开展的重中之重,找到无人关注的老旧应用,先对…

leetcode 617. Merge Two Binary Trees 合并二叉树(简单)

直接用递归调用给定函数,先判断如果root1为空返回root2,如果root2为空返回root1,都存在的情况下建立新节点node,然后对root1和root2的左子节点调用递归并赋给node的左子节点,再对root1和root2的右子节点调用递归并赋给node的右子节点,返回node即可。一、题目大意 给你两棵…

虚拟机安装

ubuntu 虚拟机安装配置&#xff0c;以 18.04 为例 一、安装步骤 > 安装 vmware wmware 下载地址 &#xff1a; download 点击进入下载界面 点击并下载 windows 平台下的安装包 安装时直接一键下一步即可&#xff0c;也可根据自己需求勾选&#xff0c;最后的注册码可以自行…

吃个晚饭的时间,看明白三相交流感应电机驱动原理

&#x1f495;三相交流感应单机驱动方式 物理开关&#xff1a;&#xff08;接触器开关、正反向控制&#xff0c;星三角启动&#xff09; 变频驱动&#xff1a;&#xff08;软启动、变频器调速、一般无星三角启动&#xff09; &#x1f495;一、物理开关驱动 &#x1f91e;该电…

一般勒索要钱,医疗勒索“要命”!重保时期别让患者病无所依

最近&#xff0c;美宾夕法尼亚州医疗机构遭受勒索软件攻击&#xff0c;攻击者访问75628个人的健康信息&#xff0c;包含姓名、地址、电子邮件地址、出生日期、医疗诊断等信息。事实上&#xff0c;近年来全球医疗系统遭遇网络攻击的事件时有发生。2022年8月法国一家医院遭到勒索…

基于JAVA的TCP网络QQ聊天工具系统

目 录 1 功能设计 1 1.1功能概述 1 1.2功能模块图 1 2 逻辑设计 2 3 界面设计 4 3.1注册界面&#xff1a; 4 3.2登录界面 5 3.3好友列表页面 5 3.4好友聊天页面 6 3.5服务器界面 7 4 各模块详细设计 7 4.1登录模块 7 4.2注册模块 9 4.3聊天模块 10 4.4数据库工具类 12 4.5封装的…