> 文档中心 > javaEE(SSM)-5:关联映射

javaEE(SSM)-5:关联映射

[概念1]关联映射: 从多个关联表查询所需要的数据。

(1)关联,是指表与表之间的联系;关联关系有:1:1 ;1:n ;n:n 。

(2)映射是指将关联表之间的查询结果,自动进行类型转换和并存到java对象的各个对应的属性中。也就是表字段值映射到对象的属性值;

(3)关联映射通常是指关联表的查询操作,将查询结果保存到对象的基础属性及复合属性中。

通常一个表会对应定义一个java对象(pojo)来保存数据,当相关的其他表的数据,也要保存在这个对象中时,可以在该对象中定义一个包含其他对象的属性(这里称之为:复合属性/关联属性)。

例如:当查询用户时,将其对应的身份证信息(其他表)同时查询出来并保存,可以定义两个java对象:User和IDCard ,对应两表的数据;查询出User信息后,根据关联关系,再查询出IDCard信息,将IDCard的查询结果,作为User的一个属性,进行保存。

如果此时在User对象中也定义IDCard的每一个属性,那么这是冗余且不规范的、不合理的。

[概念2]关联表的查询: 分为嵌套查询(可懒加载)和嵌套结果(常用)。

(1)嵌套查询:先根据条件在一个表中查询,将查询结果映射到对象的基础属性后,再根据查询结果中的外键,在另外的关联表中继续查询,将结果映射到复合属性中。

(2)嵌套结果:使用SQL关联查询,一次性查询出结果,然后将结果分别映射到基础属性和复合属性中。

(一)1:1关联映射

【描述】

  • 一对一查询,如一个用户只有一个身份证。

【要求】

  • (1)用户表保存的是用户基本信息,如id,姓名,其他(如:密码、手机号码等);
  • (2)身份证信息保存id、真实姓名、身份证号码、性别、地址、发证机关、有效期等。

【功能】

  • 查询用户信息,同时查询身份证信息。

【思路】

  • (1)创建数据库mybatis,创建用户表user和身份证信息表idcard为简单起见,user表只包含id、username字段,使用cardid与身份证信息表相的id关联;而身份证信息表仅包含id和code。

  • (2)创建项目,在pom.xml中仅需要包含mybatis和mysql连接器的pom信息。

  • (3)创建mybatis配置文件.

  • (4)创建mapper,在mapper实现关联映射。

【实现】

1. 创建数据库mybatis

user用户表结构

id int primary  key ;auto_increnmentusername varchar 20cardid int 

身份证表结构

id int primary key ; auto_incrementcode int 

这里自行输入必要的测试数据。

2. 创建项目mybatis

file=>new =>maven project=>勾选“create a simple project ” =>输入:>groupId:com.my

artifactId:mybatis =>完成finish。

2.1 配置pom.xml文件

4.0.0com.mymybatis0.0.1-SNAPSHOT    org.mybatismybatis3.5.9mysqlmysql-connetcor-java8.0.27log4jlog4j1.2.17   org.apache.maven.plugins    maven-war-plugin    3.3.2

必须添加mybatis和connectorJ依赖 (war依赖也可加进去) ;为了调试时清楚看出SQL执行情况,增加log4j的依赖

在src/main/java根目录下创建log4j.properties文件(固定文件名),将以下内容赋值到该文件。

# Global logging configuration log4j.rootLogger=ERROR, stdout # MyBatis logging configuration...#################################### 根据需要,只需要修改这里,其余地方不修改log4j.logger.mapper=TRACE #####################################log4j.logger 后面只跟包名# Console output... log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

log4j.properties是固定文件名,而且在src/main/java根目录下

log4j.logger.[这里是mapper包名]=TRACE ,表示对指定包下的mapper文件进行跟踪输出

2.2 mybatis的配置文件

在src/main/resource根目录下,创建配置文件:mybatis.xml和配置的属性文件db.properties

db.properties

driver=com.mysql.cj.jdbc.Driverurl=jdbc:mysql://localhost:3306/mybatis?serverTimezone=Asia/Shanghaiuname=rootpwd=1234

注意:确保拼写正确;特别是url中的大小写!!!

mybatis.xml

<configuration><properties resource="db.properties" /><typeAliases><package name="pojo"/></typeAliases><environments default="mysql"><environment id="mysql"><transactionManager type="JDBC"/><dataSource type="POOLED" ><property name="driver" value="${driver}" /><property name="url" value="${url}"/><property name="username" value="${uname}" /><property name="password" value="${pwd}" /></dataSource></environment></environments><mappers><mapper resource="mapper/UserMapper.xml" /><!--  !--></mappers></configuration>

mybatis.xml配置文件主要有四个部分:
(1)装载属性文件;
(2)配置pojo 别名。在后面mapper使用到pojo时,不需要使用全限定名
(3)配置数据源;;
(4)装载mapper文件!(添加mapper文件在下一步)
注意,$与#的区别

2.3 添加mapper文件

在src/main/java中,添加包:mapper ,在该包名下添加UserMapper.xml文件。

为了先测试项目各个配置是否正确(建议配置完一个测试一个,最好不要全部写好才测试!否则很难查找错误,确保每一步正确才进行下一步的实现)

<mapper namespace="mapper.UserMapper"><select id="getCount"   resultType="int">select count(*) from user </select></mapper>

获取user表的数量,不需要输入参数,输出为整数,这样就暂时不需要POJO支持。

2.4 添加测试类

在src/test/java添加一个MyTest.java类,测试项目是否运行正确。

package mybatis;import java.io.IOException;import java.io.Reader;import org.apache.ibatis.io.Resources;import org.apache.ibatis.session.SqlSession;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;import org.junit.Test;public class MyTest {@Testpublic void testOne() throws IOException {Reader reader=Resources.getResourceAsReader("mybatis.xml");SqlSessionFactory ssf=new SqlSessionFactoryBuilder().build(reader);SqlSession sqlSession=ssf.openSession();int count =sqlSession.selectOne("getCount");sqlSession.close();}}

2.5 测试结果

运行单元测试,如果一切无误,那么控制台将输出执行SQL的日志信息,如:

DEBUG [main] - ==>  Preparing: select count(*) from userDEBUG [main] - ==> Parameters: TRACE [main] - <==    Columns: count(*)TRACE [main] - <== Row: 3 【输出结果】DEBUG [main] - <==      Total: 1 【执行次数】

3. 关联映射的实现-嵌套查询

3.1 添加POJO

在src/main/java添加包:pojo

添加idcard表对应的POJO(IDCard.java)

package pojo;public class IDCard {int id;String code;    //public int getId() {return id;}public void setId(int id) {this.id = id;}public String getCode() {return code;}public void setCode(String code) {this.code = code;}}

添加user表对应的POJO—User.java

package pojo;public class User {int id;String username;IDCard card;//准备保存idcard表数据//public int getId() {return id;}public void setId(int id) {this.id = id;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public IDCard getCard() {return card;}public void setCard(IDCard card) {this.card = card;}}

3.2 修改UserMapper.xml文件,实现1:1映射-嵌套查询

【文件位置】src/main/java/mapper/UserMapper.xml

当查询结果包含复合属性时,在select中不能再使用reslutType属性,而是需要resultMap标记进行结果映射;

【基本语法】

    。。。    

实际上这里是装配当前已查询出来的列值,保存到对象的属性中

关联查询部分,当1:1关联查询时,在resultMap标记中,使用association标记。

【association标记语法】

    。。。        

【代码实现】

<mapper namespace="mapper.UserMapper"><select id="getCount" resultType="int">select count(*) from user</select>    <select id="getById" parameterType="int" resultMap="userMap">select * fromuser where id=#{0} </select>    <resultMap id="userMap" type="User"> <id property="id" column="id" /><result property="username" column="username" />  </resultMap><select id="getIDCard" parameterType="int" resultType="IDCard">select * from idcard where id=#{id}</select></mapper>

(1) resultMap中的id标记指明这个是关键字段;也可以使用result标记。

(2)property代表对象的属性;column代表列表,即select语句中的查询字段名,两者建立映射关系

(3)association标记只能用于1:1关联映射。 javaType指明复合属性的类型,property指明复合属性的属性名(POJO定义的属性和类型);这里column字段是嵌套查询的依据,外键。

(4)getIDCard查询结果将装配association中指定的属性card。

(5) association理解:根据外键,将查询结果赋值给POJO中哪个类型的哪个复合属性。

(6) resultMap理解:指定POJO类型,给它哪个属性赋值

3.3 测试运行

【测试方法】

@Testpublic void testTwo() throws IOException {Reader reader=Resources.getResourceAsReader("mybatis.xml");SqlSessionFactory ssf=new SqlSessionFactoryBuilder().build(reader);SqlSession sqlSession=ssf.openSession();User user =sqlSession.selectOne("getById",1);sqlSession.close();}

【测试结果】

主查询DEBUG [main] - ==>  Preparing: select * from user where id=?DEBUG [main] - ==> Parameters: 1(Integer)TRACE [main] - <==    Columns: id, username, cardidTRACE [main] -   Preparing: select * from idcard where id=?DEBUG [main] - ====> Parameters: 1(Integer)TRACE [main] - <====    Columns: id, codeTRACE [main] - <==== Row: 1, 1111111DEBUG [main] - <====      Total: 1DEBUG [main] - <==      Total: 1

4. 嵌套结果的实现

使用连接查询语句,一次性查询出结果,然后使用resultMap依次赋值,不同的是,association仅指定POJO类型和属性名,然后使用reslut逐个匹配赋值。

4.1 UserMapper.xml添加以下内容

select user.*,idcard.id as cardid,idcard.codefrom user,idcardwhere user.id=#{id} and user.cardid=idcard.id

(1)这种方式灵活,常用;但是不能应用懒加载。

(2)懒加载是指当需要获取复合属性时,才会执行嵌套的查询,否则不执行。

(3)实际上,可以有多个association。(多表)

4.2 测试

public void test3() throws IOException {Reader reader=Resources.getResourceAsReader("mybatis.xml");SqlSessionFactory ssf=new SqlSessionFactoryBuilder().build(reader);SqlSession sqlSession=ssf.openSession();User user =sqlSession.selectOne("byId",1);sqlSession.close();}
DEBUG [main] - ==>  Preparing: select user.*,idcard.id as cardid,idcard.code from user,idcard where user.id=? and user.cardid=idcard.idDEBUG [main] - ==> Parameters: 1(Integer)TRACE [main] - <==    Columns: id, username, cardid, cardid, codeTRACE [main] - <== Row: 1, zs1, 1, 1, 1111111DEBUG [main] - <==      Total: 1

(二)1:n关联映射

【描述】

  • 一个用户有N张订单,一张订单只属于一个用户

【功能】

  • 根据用户ID,查询该用户信息的同时查询其所有订单

【要求】

  • 每张订单包含的信息:id 、用户id、购物数量count,(简单起见,如:当前单价、购买日期,支付状态等。)

【思路】

  • 创建订单表orders,添加订单POJO—Orders.java

  • 创建Mapper实现1对多查询。

1. 订单表信息

id int primary key ;auto_increment ;//订单编号userid int ;//外键number int ;//商品数量

这里不需添加商品id,因为订单和商品之间组恰当的方式是使用N多对多实现。

2. 订单POJO—Orders.java

src/main/java/pojo/Orders.java

package pojo;public class Orders {int id;//订单idint userid;//客户IDint number;//购物数量//public int getId() {return id;}public void setId(int id) {this.id = id;}public int getUserid() {return userid;}public void setUserid(int userid) {this.userid = userid;}public int getNumber() {return number;}public void setNumber(int number) {this.number = number;}}

3. 修改User.java

为了将订单查询结果保存到User对象中,修改User.java内容为(添加了一个List属性):

package pojo;import java.util.List;public class User {int id;//客户idString username;//客户名称IDCard card;//保存idcard表数据//多了一个orderList复合属性List<Orders> orderList;//查询订单信息用    //省略了getter/setter }

4. 添加UserOrdersMapper.xml

在src/main/java/mapper包中添加映射文件:UserOrdersMapper.xml

select * from user where id=#{id}  select * from orders where userid=#{id}

这里不再使用association标记,而代替的是collection标记,并且使用ofType代替javaType了,其他含义与1:1查询一致的。

5. 修改配置文件,加入新增加的mapper资源

src/main/resource/mybatis.xml配置文件,注册新创建的mapper文件

不要忘记要注册,经常会忘记写

6. 测试执行

@Testpublic void test4() throws IOException {Reader reader=Resources.getResourceAsReader("mybatis.xml");SqlSessionFactory ssf=new SqlSessionFactoryBuilder().build(reader);SqlSession sqlSession=ssf.openSession();User user =sqlSession.selectOne("getUserById",1);sqlSession.close();}
DEBUG [main] - ==>  Preparing: select * from user where id=?DEBUG [main] - ==> Parameters: 1(Integer)TRACE [main] - <==    Columns: id, username, cardidTRACE [main] -   Preparing: select * from orders where userid=?DEBUG [main] - ====> Parameters: 1(Integer)TRACE [main] - <====    Columns: id, userid, numberTRACE [main] - <==== Row: 1, 1, 2TRACE [main] - <==== Row: 3, 1, 13TRACE [main] - <==== Row: 4, 1, 23DEBUG [main] - <====      Total: 3DEBUG [main] - <==      Total: 1

7. 嵌套结果

实际上,理解了一对一,一对多的实现基本差不多,不同的是关联属性使用了collection,而且使用ofType属性指定复合属性的类型。嵌套结果依然使用连接查询,然后给各个属性赋值。

一对多查询,一般在一对一方,包含映射其他表的复合属性。

7.1 修改UserOrdersMapper.xml

在src/main/java/mapper/UserOrdersMapper.xml,添加如下内容

select user.*,orders.id as orderid ,orders.numberfrom user LEFT JOIN orders ON user.id=orders.useridwhere user.id=#{id}

这里使用了left join语法,不使用也是可以的

注意,要在配置文件注册!!!

7.2 执行结果

DEBUG [main] - ==>  Preparing: select user.*,orders.id as orderid ,orders.number from user left join orders on user.id=orders.userid where user.id=?DEBUG [main] - ==> Parameters: 1(Integer)TRACE [main] - <==    Columns: id, username, cardid, orderid, numberTRACE [main] - <== Row: 1, zs1, 1, 1, 2TRACE [main] - <== Row: 1, zs1, 1, 3, 13TRACE [main] - <== Row: 1, zs1, 1, 4, 23DEBUG [main] - <==      Total: 3

(三)n:n关联查询

【描述】

  • 一张订单有n个商品;一个商品可以存在不同的多张订单;根据订单id,找到所有商品的信息。

【功能】

  • 根据订单ID,查询订单信息,以及订单中所有的商品。

【要求】

  • 对于多对多的一般做法是在两者之间建立一个关联的中间表:
  • 订单作为一个独立的表,商品也作为一个独立的表,两者可以使用一个中间表建立联系,中间表包含两者的关键字id,这样实际是转化为1:N关系:一个订单包含多个商品,一个商品存在于多个订单,而商品和订单都有为一个id。

【思路】

  • 关键理解:根据订单id,查询订单信息;再根据订单id,在中间表找到所有商品的id(一组,1:n),商品信息包含在这一组id中(select in (一组商品id ))。

(1)创建订单表orders,及商品表,这里假定商品为书籍表book,添加订单POJO—Books.java

(2)创建中间表,book_orders,根据订单id与书籍id建立关联。

(3)创建Mapper实现1对多查询。

1. 商品表信息(book)

id int primary key ; auto_increment;//商品idbookname varchar 255 //书名price decimal  //价格

2. 中间表 (book_orders)

id int primary key ; auto_increment;//本身idorderid int ;//订单idbookid int ;//书籍id

3. 添加POJO—Books.java

package pojo;public class Books {int id;//商品IDString bookname;//书名float price;//价格    //省略getter/setter}

4. 修改POJO(Orders.java),添加书籍列表信息

在查询订单的同时,查询订单中的商品

package pojo;import java.util.List;public class Orders {int id;int userid;int number;//新增加:一张订单包含哪些商品:这里的商品是书籍List<Books> books ; //省略getter/setter}

5. 新增加BooksOrdersMapper.xml,实现n:n

在src/main/java/mapper包中添加映射文件:BooksOrdersMapper.xml实现多对多查询

<mapper namespace="mapper.BooksOrdersMapper"><select id="getOrder" parameterType="int" resultMap="map">select * from orders where id=#{id}</select><resultMap id="map" type="Orders"><id property="id" column="id"/><result property="number" column="number" /></resultMap><select id="getBooks" parameterType="int" resultType="Books">  select book.*   from book inner join book_orders on book.id=book_orders.bookid  where book_orders.orderid =#{id}</select></mapper>

(1)有些地方使用wher id in (…),如不能查重复购买的商品这里修改为INNER JOIN.(实际上同一个订单不会出现两个同一个商品,会使用计数避免重复。)

(2)在collection标记中,传入的是订单id,但book表并没有订单的信息,是通过中间表的订单idbook表建立联系的

(3)注意:要在配置文件注册!

6. 测试结果

@Testpublic void test6() throws IOException {Reader reader=Resources.getResourceAsReader("mybatis.xml");SqlSessionFactory ssf=new SqlSessionFactoryBuilder().build(reader);SqlSession sqlSession=ssf.openSession();Orders order =sqlSession.selectOne("getOrder",1);sqlSession.close();}
DEBUG [main] - ==>  Preparing: select * from orders where id=?DEBUG [main] - ==> Parameters: 1(Integer)TRACE [main] - <==    Columns: id, userid, numberTRACE [main] -   Preparing: select book.*,BO.id as boid,BO.bookid,BO.orderid from book INNER JOIN book_orders BO on BO.orderid=? and book.id=BO.bookidDEBUG [main] - ====> Parameters: 1(Integer)TRACE [main] - <====    Columns: id, bookname, price, boid, bookid, orderidTRACE [main] - <==== Row: 1, 三国演义, 12.60, 1, 1, 1TRACE [main] - <==== Row: 2, 水浒传, 22.00, 2, 2, 1TRACE [main] - <==== Row: 3, 红楼梦, 32.00, 3, 3, 1TRACE [main] - <==== Row: 1, 三国演义, 12.60, 5, 1, 1DEBUG [main] - <====      Total: 4DEBUG [main] - <==      Total: 1

7. 更好的实现方式-嵌套结果

修改src/main/java/mapper包中添加映射文件:BooksOrdersMapper.xml

【添加查询】

select orders.*,book.id as bid,book.bookname,book.pricefrom orders left join book_orders on orders.id=book_orders.orderidleft join book on book.id=book_orders.bookidwhere orders.id =#{id}  

内连接:逐个找表之间的对应关系,最后是where条件

【测试代码】

Orders order =sqlSession.selectOne("findOrder",1);

【测试结果】

DEBUG [main] - ==>  Preparing: select orders.*,BO.orderid,BO.bookid,book.id as bid,book.bookname,book.price from orders INNER JOIN book_orders BO ON orders.id=BO.orderid INNER JOIN book ON BO.bookid =book.id where orders.id=?DEBUG [main] - ==> Parameters: 1(Integer)TRACE [main] - <==    Columns: id, userid, number, orderid, bookid, bid, bookname, priceTRACE [main] - <== Row: 1, 1, 2, 1, 1, 1, 三国演义, 12.60TRACE [main] - <== Row: 1, 1, 2, 1, 2, 2, 水浒传, 22.00TRACE [main] - <== Row: 1, 1, 2, 1, 3, 3, 红楼梦, 32.00TRACE [main] - <== Row: 1, 1, 2, 1, 1, 1, 三国演义, 12.60DEBUG [main] - <==      Total: 4