MyBatis的多对多关联关系,你知道吗?

在实际项目开发中,多对多关系也是非常常见的关系,比如,一个购物系统中,一个用户可以有多个订单,这是一对多的关系;一个订单中可以购买多种商品,一种商品也可以属于多个不同的订单,订单和商品就是多对多的关系。对于数据库中多对多关系建议使用一个中间表来维护关系,中间表中的订单d作为外键参照订单表的id,商品id作为外键参照商品表的id。

下面我们就用一个简单示例来看看MyBatis怎么处理多对多关系。

首先,在数据库创建三个表:TB_USER、TB_ARTICLE和TB_ORDER,再创建一个中间表维护TB_ARTICLE和TB_ORDER的关系,并插入测试数据。SQL脚本如下:

-- 创建用户表

CREATE TABLE tb_user (

id INT PRIMARY KEY AUTO_ INCREMENT,

username VARCHAR(18),

loginname VARCHAR(18),

PASSWORD VARCHAR (18),

phone VARCHAR (18),

address VARCHAF (18)

);

-- 插入用户表测试数据

INSERT INTO tb_user (username,loginname,PASSWORD,phone,address)

VALUES('张云','jack','123456','13600000000','杭州');

-- 创建商品表

CREATE TABLE tb_article(

id INT PRIMARY KEY AUTO_INCREMENT,

NAME VARCHAR (18),

price DOUBLE,

remark VARCHAR (18)

);

-- 插入商品表测试数据

INSERT INTO tb_article(NAME,price,remark)

VALUIES('精通Python自然语言处理',108.9,'经典著作');

INSERT INTO tb_article(NAME,price,remark)

VALUES('自然语言处理原理与实践',99.9,'经典著作');

INSERT INTO tb_article(MAME,price,remark)

VALUES('大数据架构详解',89.9,'经典著作’);

INSBRT INTO tb_article(NAE,price,remark)

VAIUES('推荐系统实践',69.9,'经典著作');

-- 创建订单表

CREATE TABLE tb_order(

id INT PRIMARY KEY AUTO_INCREMENT,

CODE VARCHAR(32),

total DOUBLE,

user_id INT,

FOREIGN KEY (user_id) REFBRENCES tb_user(id)

-- 插入订单表测试数据

INSERT INTO tb_order (CODE,total,user_id)

VALUES('20180315ORDER1212,388.6,1);

INSERT INTO tb_ordor(CODE,total,user id)

VALUES('20180315ORDER1213,217.8,1);

-- 创建中间表

CREATB TABLE tb_item(

order_id INT,

article_id INT,

amount INT,

PRIMARY KEY (order_id,article_id),

FOREIGN KEY (order_id) REPERENCES tb_order(id),

FOREIGN KEY (acticle_id) REERENCES tb_article(id)

);

-- 创建插入中间表数据

INSERT INTO tb_item(order_id,article_id,amount)

VALUES (1,1,1);

INSERT INTO tb_item(order_id,article_id,amount)

VALUES (1,2,1);

INSERT INTO tb_item(orde_id, article_id,amount)

VALUES (1,3,2);

INSERT INTO tb_item(orde_id, article_id,amount)

VALUES (2,4,2);

INSERT INTO tb_item(orde_id, article_id,amount)

VALUES (2,1,1);

**提示:**

t_order表的user_id作为外键参照tb_user表的主键id。tb_item表作为中间表,用来维护tb_article和tb_order的多对多关系,tb_item表的order_id作为外键参照tb_order表的主键id,article_id作为外键参照tb_article 表的主键id

在数据库中执行SQL脚本,完成创建数据库和表的操作。接下来, 建一个User对象、一个Article对象和一个Order对象分别映射tb_user、tb_article和tb_order表。

public class User implements serializable {

private Integer id; //用户id,主键

private string username; //用户名

private string loginname; //登录名

private string password; //密码

private string phone; //联系电话

private String address; //收货地址

//用户和订单是一对多的关系,即一个用户可以有多个订单

private List<Order> orders;

// 省略构选器和set/get方法.....

}

用户和订单是一对多的关系,即一个用户可以有多个订单。在User类中定义了一个orders属性,该属性是一个List集合,用来映射一对多的关联关系,表示一个用户有多个订单。

public class Order implements Serializable {

private Integer id; //订单id,主键

private string code; //订单编号

private Double total; //订单总金额

// 订单和用户是多对一的关系,即一个订单只属于一个用户

private User user;

// 订单和商品是多对多的关系,即一个订单可以包含多种商品

private List<Article> articles;

//省路构造器和set/get方法.....

}

订单和用户是多对一的关系,一个订单只属于一个用户,在Order类中定义了一个user属性,用来映射多对一的关联关系,表示该订单的用户;订单和商品是多对多的关系,即一个订单中可以包含多种商品,在Order类中定义了一个aticles属性,该属性是一个List集合,用来映射多对多的关联关系,表示一个订单中包含多种商品。

public class Article implements serializable {

// 商品id,主键

private Integer id;

// 商品名称

private string name;

// 商品价格

private Double price;

// 商品描述

private String rematk;

// 商品和订单是多对多的关系,即一种商品可以出现在多个订单中

private List<Order> orders;

// 省路构造器和set/get方法.....

}

商品和订单是多对多的关系,即一种商品可以出现在多个订单中。在Article 类中定义了一个orders 属性,该属性是一个List集合,用来映射多对多的关联关系,表示该商品关联的多个订单。

再接下来是XML映射文件。

<mapper namespace="cn.mybatis.mapper.UserMapper">

<resultMap type="cn.mybatis.domain.User" id="userResultMap">

<id porperty="id" column="id"/ >

<result property="username" column="username"/>

<result property="loginname" column="loginname"/>

<result property="password" column="password"/>

<result property="phone" column="phone"/>

<result property="address" column="address"/>

<!--一对多关联映射:collection-->

<collection property="orders" javaType="ArrayList"

column="id" ofType="cn.mybatis.domain.User"

select="cn.mybatis.mapper.ordeMapper.selectOrderById" fetchType="lazy">

<id porperty="id" column="oid"/ >

<result property="code" column="code"/>

<result property="total" column="total"/>

</collection>

</resultMap>

<select id="selectUserById" parameterType="int" resultMap="userResultMap">

SEIECT 由FROM tb user WHERE id = #{id}

</select>

</mapper>

UserMapper.xml中定义了一一个<select.../>,其根据id查询用户信息。由于User 类除了简单的属性id、usemame、loginame、password和address之外,还有一个关联对象orders,所以返回的是一个名为userResultMap的resultMap。由于orders是一个List集合,因此userResultMap 中使用了<collection.../>元素映射一对多的关联关系,select属性表示会使用columm属性的id值作为参数执行OrderMapper中定义的selectOrderByUserId查询该用户所下的所有订单,查询出的数据将被封装到property表示的orders对象当中。注意,一对多使用的都是lazy(懒加载)。

<mapper namespace="cn.mybatis.mapper.OrderMapper">

<resultMap id="orderResultMapper" type="cn.mybatis.domain.Order">

<id property="id" column="id"/>

<result property="code" column= "code" />

<result property="total" column="total"/>

<!--多对一关联映射:association-->

<association property="user" javaType="cn.mybatis.domain.User"/>

<id property="id" column="id"/>

<result property="username" column="username" />

<result property="loginname" column="loginname"/>

<result property="password" column="password"/>

<result property="phone" column="phone"/>

<result property="address" column="address"/>

</association>

<!--多对多关联映射:collection -->

<collection property="articles" javaType="ArrayList"

column="id" ofType="cn.mybatis.domain.Article"

select="cn.mybatis.mapper.ArticleMapper.selectArticleByOrderId" fetchType="lazy">

<id property="id" column="id" />

<result property="name" column= "name" />

<reault property="price" column="price"/>

<result property="remark" column="remark"/>

</collection>

</resultMap>

<!-- 注意,如果查询出来的列同名,例如tb_user表的id和tb_order表的id都是id,同名。则需要使用别名区分-->

< select id="selectOrderById" parameterType="int" resultMap="orderResultMap">

SELECT u.*,o.id AS oid,code,total,user_ id

FROM tb_user u,tb_ order o

WHERE u.id = o.user_id

AND o.id = #{id}

</select>

<!-- 根据userid查询订单-->

<select id="selectOrderByUserId parameterType="int" resultType="cn.mybatis.domain.Order"

SELECT * FROM tb_order WHERE user_id = #{id}

</select>

</mapper>

OrderMapper.xml中定义了一个<select id="selectOrderByUserId.../>其根据用户id查询订单信息,返回的是简单的Order对象。还定义了一<select id="selectOrderById.../>,其根据订单id 查询订单信息,由于 Order类和用户是多对一关系,和商品是多对多关系,而多对一通常都是立即加载,因此SQL语句是一条关联了tb_user和tb_order的多表查询语句。查询结果返回一个名为orderResultMap的resultMap。orderResultMap中使用了< association>元素映射多对一的关联关系,其将查询到的用户信息装载到Order 对象的user属性当中;orderResutMap中还使用了<collection..>元素映射多对多的关联关系,select属性表示会使用column 属性的oid 值作为参数执行ArticleMapper 中定义的selectArticleByOrderd 查询该订单中的所有商品,查询出的数据将被封装到property表示的articles对象当中。注意,一对多使用的都是lazy(懒加载)。

**提示:**

因为多表查询返回的结果集中tb user有个id列,tb_order也有个id列,当列同名时,MyBatis使用的元素中的column属性如果是id,则MyBatis会默认使用查询出的第一个id列。为了区分同名的列,最好的方法是给列取一个别名。SQL 语句中的o.id AS o.oid,resultMap中的column="oid"就是指使用的是tb_order 表的id:

<mapper namespace="cn.mybatis.mapper.ArticleMapper">

<select id="selectArticleByOrderId" parameterType="int" resultType="cn.mybatis.domain.Article>

SELECT * FROM tb_article WHERE id IN (

SELECT article.id FROM tb_item WHERE order_id = #(id}

</select>

</mapper>

ArticleMape.xml 中定义了一个<select id="selectArticleByOrderId.../>其根据订单id查询订单关联的所有商品,由于订单和商品是多对多的关系,数据库使用了一一个中间表tb_item维护多对多的关系,故此处使用了一个子查询,首先根据订单id定位到中间表中查询出所有的商品,之后根据所有商品的id 查询出所有的商品信息,并将这些信息封装到Atrticle对象当中。

再接下来是mapper接口对象。

public interface UserMapper {

User selectUserById(int id);

}

public interface OrderMapper {

Order selectorderById(int id);

}

最后,完成测试类。

public class ManyToManyTest {

public static void main(string[] args) throws Exception {

// 读取mybatia-config.xml文件

Inputstream inputStream = Resources.getResourceAstream("mybatis-config.xm");

// 初始化mybatis,创建sqlSessionFactory类的实例

SqlsessionFactory sqlSessionFactory = new sqlSessionFactoryBuilder().build(inputstream);

// 创建Session实例

SqlSession session = sqlSessionFactory.openSession();

ManyToManyTest t = new ManyToManyTest();

// 根据用户id查询用户,测试一对多关系

t.testSelectUserById(session);

// 根据订单id查询订单,测试多对多关系

t.testSelectOrderById(session) ;

// 提交事务

session.commit();

// 关闭session

session.close();

}

// 测试一对多关系,查询用户User(一)的时候级联查询订单Order(多)

public void testSelectUserById(SqlSession session){

// 获得UserMapper 接口的代理对象

UserMapper um = session.getMapper(UserMapper.class)

// 调用selectUserById 方法

User user = um.selectUserById(1) ;

// 查看查询到的user对象信息

System.out.println(user.getId() + " "+ user.getUserName());

// 查看uer对象关联的订单信息

List<Order> orders = uset.getOrders();

for(Order order : orders)

{

System.out.println(order);

}

}

//测试多对多关系,查询订单Order(多)的时候级联查询订单的商品Article(多)

public void testselectorderById(SqlSession session) {

//获得OrderMappex接口的代理对象

OrderMapper om = session.getMapper(OrderMapper.class) ;

// 调用selectorderById方法

Order order = om. selectOrderById(2);

// 查看查询到的order对象信息

System.out.println(order.getId() + " "+ order.getCode() + " " + order.getTotal() );

// 查看order对象关联的用户信息

User user = order.getUser() ;

System.out.println(user) ;

// 查看order对象关联的商品信息

// List<Article> articles = order.getArticles();

for(Article article : articles)

{

System.out.println(article);

}

}

运行ManyToManyTest类的main方法,首先测试testSelectUserById方法,根据用户id查询用户。控制台显示如下:

DEBUG [main]--> preparing: SELBCT * FROM tb_user WHERE id = ?

DEBUG [main]==> Parameters: 1(Integer)

DEBUG [main]<-- Total : 1

1 张云

DEBUG [main]--> ereparing: SELBCT * FROM tb_order WHERE user_ id = ?

DEBUG [main]--> Parameters: 1(Integer)

DEBUG [mainj<== Total : 2

Order [id=1,code=20180315ORDER1212,tota1=388.6]

order [1d=2,code=20180315ORDER1213,total=217.81]

可以看到,MyBatis执行了根据用户id查询用户的SQL语句,查询出了用户信息; 由于测试方法中立即又获取了用户的订单集合,故MyBatis又执行了根据用户id查询订单的SQL语句,查询出了该用户的两个订单。

接下来测试testSelectOrderById方法,根据订单id查询订单信息。控制台显示如下:

DEBUG Emain]-->Preparing: SELECT u.*, o.id AS oid, CODE, total, user_id FROM tb.user u, tb.order o WHERE u.id = o.user_id AND o.id = ?

DEBUG (main]--> Parameters: 2(Integer)

DEBUG [main]<==

2 20180315ORDER1213 217.8

User [id=1,username=张云,loginame=jack,password=123456,phone=13600000000,address=杭州]

可以看到,MyBatis执行了一个多表连接查询,同时查询出了订单信息和用户信息,由于测试方法中注释了查询订单中的商品代码,故MyBatis采用了懒加载机制,没有立即查询商品信息。

取消testSelectOrderByld方法中查询订单中的商品的代码注释,再次执行。控制台显示如下:

DEBUG [main]--> Preparing: SBLECT u.*, o.id AS o.oid, CODE, total, user_id FROM tb user u, tb_order o WHERE u.id = o.user_id AND o_id = ?

DEBUG [main]--> Parameters: 2(Integer)

DEBUG [main]<== Total: 1

2 20180315ORDER1213 217.8

User [id=1,username=张云,loginname=jack,password=123456,phone-13600000000,address=杭州]

DEBUG [main]--> Preparing: SELBCT* FROM tb_article WHERE id IN (SEIBCT artiche_id FROM tb_item WHERB order_id = ?)

DEBUG [main]--> Parameters: 2(Integer)

DEBUG [main]<== Total: 2

Article [id=1,name=精通Python自然语言处理,price=108.9,remark=经典著作]

Article [id=4,name=推荐系统实践,price=69.9,remark=经典著作]

可以看到,MyBatis 执行了ArticleMapper.xm中定义的子查询,查询出了订单所关联的所有商品信息。

**提示:**

多对多查询因为关联到中间表查询,所以读者需要对数据库的SQL知识有一定的了解。

最后分享一个我自己的后端技术群,群里自己收集了一些Java架构资料,大家可以进群领取群号:680075317,也可以进群一起交流,比如遇到技术瓶颈、面试不过的,大家一些交流学习!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,001评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,210评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,874评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,001评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,022评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,005评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,929评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,742评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,193评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,427评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,583评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,305评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,911评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,564评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,731评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,581评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,478评论 2 352

推荐阅读更多精彩内容