文章目录
十一、关联映射
11.1 实体关系
实体——数据实体,实体关系指的就是数据与数据之间的关系
例如:用户和角色、房屋和楼栋、订单和商品
实体关系分为以下四种:
一对一关联
- 人和身份证、学生和学生证、用户基本信息和详情
一个场景:
用户信息(30个字段,用户数量也很多)
用户登录:根据用户名查询用户信息,当用户表中数据量很大且字段很多时会严重影响查询速度。
那么我可以分为两张表。
①用户基本信息表(5个字段):
用户id,账号,密码,姓名,最后登陆时间
②用户详情表(20+个字段):
详情id,…
用户登录的时候我只需要查询用户基本信息表就可以了,效率会大大提升。
那我在查看用户详情的时候是不是需要获取到用户基本信息表中的用户id,所以这两张表要有一个关联,这也属于一对一关联。
- 主键关联(用户表主键和详情表主键相同时,表示是匹配的数据)
①用户基本信息表(5个字段):
用户id
,账号,密码,姓名,最后登陆时间 1 zhangsan …
2 lisi …
3 wangwu …
②用户详情表(20+个字段):
详情id
,手机,… 1 1303030303…
3 1313131313…
2 1323232323…
此时,当用户id
与详情id
相等时,即表示这两个记录是匹配的。
- 唯一外键关联
①用户基本信息表(5个字段):
用户id
,账号,密码,姓名,最后登陆时间
1 zhangsan …
2 lisi …
3 wangwu …
②用户详情表(20+个字段):
详情id
,手机,… uid(外键,唯一)
1 1303030303… 2
2 1313131313… 3
3 1323232323… 1
此时,用户id
与详情id
各是各的,其相等与否没有任何关系。
我在用户详情表中再加一个外键叫做uid
(用户id的意思)。通过uid
与用户id
是否相等,来匹配数据。
但是这样会有一个问题,如果两个用户详情记录的uid都为2,就表示‘lisi’这个用户有两个详情信息了,这样不合理。所以,我在把uid设置为外键的同时,还要把它设置为唯一键。
这就叫唯一外键关联。
一对多关联、多对一关联
实例:
- 一对多:班级和学生、类别和商品、楼栋和房屋
- 多对一:学生和班级、商品和类别
(一对多和多对一只是一个角度的区别)
数据表关系:
- 在”多“的一端添加外键和”一“的一端进行关联
多对多关联
实例:用户和角色、角色和权限、房屋和业主、学生和社团、订单和商品
数据表关系:建立第三张关系表,添加两个外键分别与两张表主键进行关联
用户(user_id) 用户角色表(uid,rid) 角色(role_id)
11.2 创建项目,部署MyBatis框架
11.2.1 WEB项目的搭建
创建web项目(基于maven)
-
新建一个Maven项目
-
如果你是一个web项目,设置打包方式为
war
<groupId>com.wyl</groupId> <artifactId>mybatis-demo2</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> //添加这一句
-
在src.main目录下新建一个
webapp
文件夹。 -
在webapp目录下新建一个
WEB-INF
文件夹。- 在WEB-INF目录下新建一个
web.xml
配置文件,根据本笔记2.3的方法可建立此模板,模板内容如下
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> </web-app>
- 在WEB-INF目录下新建一个
-
然后刷新一下Maven
-
在pom.xml中添加web的依赖支持
<!-- servlet依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<!-- jsp依赖 -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
</dependency>
-
作为一个WEB项目,要放到服务器运行。
- 点击右上角Add Configuration
-
找到Tomcat,点击local,创建本地Tomcat
-
选择一个你已经配置好的Tomcat
-
然后点一下右下角的Fix -> war exploded,把项目部署进来
-
自己可以改一下项目的名字
- 这样一来,整个WEB项目的搭建,就结束了。
11.2.2 部署MyBatis框架
上面的笔记已经讲过了,此处大致做一遍,具体的原理理论翻看上面
- 添加依赖(mybatis和mysql )
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<!-- mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
-
配置文件
在src->main->resources目录中新建一个xml文件
mybatis-config.xml
(利用自定义模板创建)-
在里面要进行数据库连接信息的配置
-
在同级目录下新建一个配置文件
jdbc.properties
driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/mybatis_study?characterEncoding=utf-8 username=root password=root
-
在主配置文件
mybatis-config.xml
中引入<properties resource="jdbc.properties"></properties> <environments default="mysql"> <environment id="mysql"> <transactionManager type="JDBC"></transactionManager> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments>
到此,数据库的连接配置已经结束了。
-
-
-
帮助类
-
在src->main->java目录下新建一个包
com.wyl.utils
,在包中新建一个类MyBatisUtil.java
public class MyBatisUtil { private static SqlSessionFactory factory; private static final ThreadLocal<SqlSession> local = new ThreadLocal<SqlSession>(); static { try { InputStream is = Resources.getResourceAsStream("mybatis-config.xml"); factory = new SqlSessionFactoryBuilder().build(is); } catch (IOException e) { e.printStackTrace(); } } //由于factory跟缓存相关,有可能会被人使用,所以这里给factory提供一个get方法(不写也可以) public static SqlSessionFactory getSqlSessionFactory(){ return factory; } //获取SqlSession这个是比较重要的 //给个true,表示自动事务提交;不给参数或者给个false,表示手动事务提交 public static SqlSession getSqlSession(boolean isAutoCommit){ SqlSession sqlSession = local.get(); if (sqlSession == null){ sqlSession = factory.openSession(isAutoCommit); local.set(sqlSession); } return sqlSession; } //对上面的方法进行一个封装,不传参默认是手动提交 public static SqlSession getSqlSession(){ return getSqlSession(false); } //对于查询操作,不需要事务管理,只需要获取DAO public static <T extends Object>T getMapper(Class<T> c){ SqlSession sqlSession = getSqlSession(true); return sqlSession.getMapper(c); } }
对于工具类的封装,最好要熟练,要知道每一个步骤为什么这样写。
-
11.3 一对一关联
实例:用户和详情
11.3.1 创建数据表
-- 用户信息表
create table users(
user_id int primary key auto_increment,
user_name varchar(20) not null unique,
user_pwd varchar(20) not null,
user_realname varchar(20) not null,
user_img varchar(100) not null --用户头像,用varchar保存图片路径
);
-- 用户详情表
create table details(
detail_id int primary key auto_increment,
user_addr varchar(50) not null,
user_tel char(11),
user_desc varchar(200), -- 用户的个性签名
uid int not null unique, -- unique表示唯一键
--constraint FK_USER foreign key(uid) references users(user_id)
-- 表示建立物理关联,你在删除的时候,比如你在删除用户的时候,详情信息没删,你是删不掉的
--现在在企业中,即使uid要与user_id关联起来,我们也不会给它创建外键。这个uid的字段是有的,但是我们不 会去建立关联。现在的物理关联都是由业务逻辑去做,都是逻辑关联。
);
11.3.2 创建实体类
- 为了便于创建实体类,引入lombok依赖
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
-
在src->main->java目录下新建包
com.wyl.pojo
-
新建一个User类
User.java
- 实体类中属性的个数与类型尽量要与表中的保持一致。
@Data @NoArgsConstructor @AllArgsConstructor @ToString public class User { private int userId; private String userName; private String userPwd; private String userRealName; private String userImg; }
-
使用lombok提供的方法
lombok是一个工具包,可以帮助我们去简化实体类中的例如get、set,ToString这些方法,一律使用注解就可以完成。
@Data: 声明它为一个实体类,之后,它的get、set方法你就不用手动去写了。
@NoArgsConstructor:这样写之后,这个类就有无参构造器了。
@AllArgsConstructor:这个类就有全参构造器了。
@ToString:这个类就重写了ToSTring方法了。
-
新建一个Detail类
Detail.java
@Data @NoArgsConstructor @AllArgsConstructor @ToString public class Detail { private int detailId; private String userAddr; private String userTel; private String userDesc; private int userId; }
11.3.3 添加操作
-
在src->main->java目录下新建包
com.wyl.dao
-
在dao目录下新建UserDAO接口
- 实现添加用户操作
public int insertUser(User user);
-
在src->main->
resources
目录下新建mappers
目录- 在mappers目录下新建
UserMapper.xml
而UserMapper是要实现UserDAO的,所以不要忘记将包名放到namespace
<mapper namespace="com.wyl.dao.UserDAO"> </mapper>
- 配置insert操作
<mapper namespace="com.wyl.dao.UserDAO"> <insert id="insertUser"> insert into users(user_name,user_pwd,user_realname,user_img) values(#{userName},#{userPwd},#{userRealName},#{userImg}) </insert> </mapper>
insert into时,是不需要插入user_id的,因为它是自增的
values括号中的内容,是通过#{ }来取的
-
但是还有个问题,在添加用户之后,在对这个用户的相应的详情信息进行操作时,要获取到对应的user_id,所以这个insert操作还需要设置对user_id的主键回填,以便于实现详情相关操作时能够获取到对应的userId。
<mapper namespace="com.wyl.dao.UserDAO"> <insert id="insertUser" useGeneratedKeys="true" keyProperty="userId"> insert into users(user_name,user_pwd,user_realname,user_img) values(#{userName},#{userPwd},#{userRealName},#{userImg}) </insert> </mapper>
到此,
添加用户
就做好了。
- 在mappers目录下新建
-
单元测试
想要进行单元测试,首先要导入junit依赖
<!-- junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency>
怎么做单元测试,把你的光标放在被测试类的上面,
alt+insert
或者鼠标右键->generate
,然后点Test
我们使用JUnit4,然后把下面想要做测试的方法勾选上,OK
单元测试类就创建好了。它被放在src->test->java->com.wyl.dao下,类名叫UserDAOTest。
11.3.4 插入操作
插入操作,或者说是用户注册的实现。要实现向用户基本信息User表中插入记录的同时,还要向用户详情信息Detail表中相对应地插入记录。
-
在测试类中,你想要实现testInsertUser()方法,或者说是
用户注册
-
首先你要获取到UserDAO对象
通过MyBatisUtil.getMapper(UserDAO.class)(这个是我们刚才自己写好的帮助类中的方法)
然后执行userDao.insertUser(user)方法(先new一个User对象user)
@Test public void testInsertUser() { User user = new User(0,"zhangsan","123123","张三","1.jpg"); UserDAO userDAO = MyBatisUtil.getMapper(UserDAO.class); int i = userDAO.insertUser(user); System.out.println(i); }
-
运行测试方法,发现报出异常
org.apache.ibatis.binding.BindingException: Type interface com.wyl.dao.UserDAO is not known to the MapperRegistry.
说明我的mapper没找到,UserDAO没找到。
为什么呢?我的UserMapper.xml中定义好了namespace=“com.wyl.dao.UserDAO”
但是不要忘了,还要在主配置文件里添加
<mappers> <mapper resource="mappers/UserMapper.xml"></mapper> </mappers>
运行成功,返回值1。向user表中插入了一条记录。
-
想实现
用户注册
,还需要实现对详情表
信息的插入,如何处理- 首先new一个Detail对象(先将userId设为0)
User user = new User(0,"lisi","456456","李四","2.jpg"); Detail detail = new Detail(0,"河南省新乡市","13030303300","我的个性签名",0);
-
由于在mapper文件中标签设置了主键回填,所以可以通过user.getUserId()获取到插入的用户信息的userId。再通过detail.setUserId去设置Detail对象的userId。
于是,Detail就获取到了与User所关联的userId了
int i = userDAO.insertUser(user); //插入了一条user detail.setUserId(user.getUserId()); //此时我的detail中就获得了userId了
-
接下来,要实现对Detail的记录的插入操作,与User的插入操作类似
-
第一步:创建DetaiDAO接口,声明插入方法
public interface DetaiDAO { public int insertDetail(Detail detail); }
-
第二步:新建mapper文件
DetailMapper.xml
<mapper namespace="com.wyl.dao.DetaiDAO"> <insert id="insertDetail"> insert into details(user_addr,user_tel,user_desc,user_img,uid) values (#{userAddr},#{userTel},#{userDesc},{userImg},#{userId}) </insert> </mapper>
这里就没必要主键回填了,因为要它的主键没啥用
-
第三步:在主配置文件中加入
<mappers> <mapper resource="mappers/UserMapper.xml"></mapper> <mapper resource="mappers/DetailMapper.xml"></mapper> </mappers>
-
-
处理好这些配置后,就可以在测试类里面实现了
@Test public void testInsertUser() { User user = new User(0,"lisi","456456","李四","2.jpg"); Detail detail = new Detail(0,"河南省新乡市","13030303300","我的个性签名",0); UserDAO userDAO = MyBatisUtil.getMapper(UserDAO.class); int i = userDAO.insertUser(user); //插入了一条user System.out.println(i); detail.setUserId(user.getUserId()); //此时我的detail中就获得了userId了 DetaiDAO detailDAO = MyBatisUtil.getMapper(DetaiDAO.class); int j = detailDAO.insertDetail(detail); //插入了一条detail System.out.println(j); }
-
结果展示:
-
-
但是还有一个问题,上面的代码,如果 i 返回值为1,则表示user插入成功;如果 j 返回值为1,则表示detail插入成功。但是,如果user插入成功,但detail插入失败,就有问题了。我处理失败问题之后再次插入,它会告诉我重复插入记录了,因为刚才user已经插入成功进去了。
- 也就是说这两个插入操作应该属于同一个事务。这两个操作,有一个失败,整个注册操作就失败。
- 通过手动提交来实现
@Test public void testInsertUser() { //用户注册,提交了基本信息和详情到Servlet,Servlet接收注册信息封装到User和Detail对象中 User user = new User(0,"lisi","456456","李四","2.jpg"); Detail detail = new Detail(0,"河南省新乡市","13030303300","我的个性签名",0); SqlSession sqlSession = MyBatisUtil.getSqlSession(); try{ UserDAO userDAO = sqlSession.getMapper(UserDAO.class); int i = userDAO.insertUser(user); //插入了一条user,OK。那么detail该怎么插入 System.out.println(i); detail.setUserId(user.getUserId()); DetaiDAO detailDAO = sqlSession.getMapper(DetaiDAO.class); int j = detailDAO.insertDetail(detail); System.out.println(j); sqlSession.commit(); //手动提交 } catch (Exception e){ e.printStackTrace(); sqlSession.rollback(); //失败回滚 } }
11.3.5 关联查询
关联操作最难的不是增删改,最难的是查询。当我查询用户信息的时候,请把详情信息一并查出来。查询是个重点。
-
我先完成对用户表user的单表查询(按照用户名username查询)
- 首先,在UserDAO接口中添加方法
public User queryUser(String username);
- 然后,到UserMapper中进行的配置
<resultMap id="userMap" type="User"> <id column="user_id" property="userId"/> <result column="user_name" property="userName"/> <result column="user_pwd" property="userPwd"/> <result column="user_realname" property="userRealName"/> <result column="user_img" property="userImg"/> </resultMap> <select id="queryUser" resultMap="userMap"> select user_id,user_name,user_pwd,user_realname,user_img from users where user_name=#{username} </select>
type可以直接写作User的原因是我在主配置文件mybatis-config.xml中进行了设置
<typeAliases> <typeAlias type="com.wyl.pojo.User" alias="User"></typeAlias> <typeAlias type="com.wyl.pojo.Detail" alias="Detail"></typeAlias> </typeAliases>
- 最后写测试类
@Test public void testQueryUser(){ UserDAO userDAO = MyBatisUtil.getMapper(UserDAO.class); User user = userDAO.queryUser("lisi"); System.out.println(user); }
- 成功执行
User(userId=6, userName=lisi, userPwd=456456, userRealName=李四, userImg=2.jpg) Process finished with exit code 0
-
关联查询(我不仅要查用户,在查用户的同时,我还要求把与之对应的详情也查出来)
现在,如果我做查询。首先,实体要有所变化。刚才的那个查询的类型是User,即使我把最终要求的结果给你全部查询出来,你User类型能装么?
所以,在查询一个对象的时候,如果想关联地查询出与之匹配的信息,我们有一种操作是这样的:
直接在实体类中private定义一个Detail对象。
这样一来,我们的User中有没有用户基本信息?有。
里面能不能放我们的Detail对象,也就是详情?可以。
实体
User.java
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class User {
private int userId;
private String userName;
private String userPwd;
private String userRealName;
private String userImg;
private Detail detail; //加入Detail
}
Detail.java
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Detail { //不用做修改
private int detailId;
private String userAddr;
private String userTel;
private String userDesc;
private int userId;
}
当然,这样修改之后,你的User再去insert添加操作的时候,要多添加一个detail属性,写成null就行了。
public void testInsertUser() {
User user = new User(0,"zhaoliu","111111","赵六","3.jpg",null);
...
}
映射文件
如果我映射文件不改,还是原来的
<select id="queryUser" resultMap="userMap"> select user_id,user_name,user_pwd,user_realname,user_img from users where user_name=#{username} </select>
那么你还是只能查询出用户的基本信息。
我们有两种实施方案。一种叫连接查询,一种叫子查询。
1.第一种方案(连接查询):
先学习一下SQL语句怎么写:
select * -- select * 不太好,一会儿把所有的字段写上去就行了 from users u INNER JOIN details d on u.user_id=d.uid where u.user_name="lisi"
运行结果:
只修改SQL语句,不修改其他的:
<resultMap id="userMap" type="User">
<id column="user_id" property="userId"/>
<result column="user_name" property="userName"/>
<result column="user_pwd" property="userPwd"/>
<result column="user_realname" property="userRealName"/>
<result column="user_img" property="userImg"/>
</resultMap>
<select id="queryUser" resultMap="userMap">
select user_id,user_name,user_pwd,user_realname,user_img,detail_id,user_addr,user_tel,user_desc
from users u
INNER JOIN details d
on u.user_id=d.uid
where u.user_name=#{username}
</select>
只修改SQL语句,其他的不作改动。这样去执行查询,也可以执行,只是查询的结果有点问题:
User(userId=6, userName=lisi, userPwd=456456, userRealName=李四, userImg=2.jpg, detail=null)
Process finished with exit code 0
为什么?
因为我的中,设置的字段还是原来的那几个字段,而后面的字段没有装进去。
直接在里添加配置即可
<result column="detail_id" property="detail.detailId"/>
//detail_id是User实体类中detail属性的一个属性,detailId是detail中的一个属性,所以是这样写
只添加这一个,看看效果:
User(userId=6, userName=lisi, userPwd=456456, userRealName=李四, userImg=2.jpg, detail=Detail(detailId=1, userAddr=null, userTel=null, userDesc=null, userId=0))
Process finished with exit code 0
我们会发现,不再是null了,而是真正放入了一个detail对象。只不过里面的字段除了detailId外都是null。
那么同理再把其他字段也都添加进去吧,完成。
<result column="detail_id" property="detail.detailId"/>
<result column="user_addr" property="detail.userAddr"/>
<result column="user_tel" property="detail.userTel"/>
<result column="user_desc" property="detail.userDesc"/>
2.第二种方案(子查询):
先学习一下SQL语句怎么写:
-- 第一次查询,查询用户信息 select * from users where user_name='lisi'; -- 第二次查询,根据第一次查询出的用户id=6,查询详情信息 select * from details where uid=6;
没有做连接,而只是执行了两个基本查询。
mapper中的SQL语句:
<resultMap id="userMap" type="User">
<id column="user_id" property="userId"/>
<result column="user_name" property="userName"/>
<result column="user_pwd" property="userPwd"/>
<result column="user_realname" property="userRealName"/>
<result column="user_img" property="userImg"/>
</resultMap>
<select id="queryUser" resultMap="userMap">
select user_id,user_name,user_pwd,user_realname,user_img
from users
where user_name=#{username}
</select>
只是一个最简单的单表查询。
但是,当我查询出user以后,我还要做第二次查询,第二次查询是去查询detail表。
所以,在detailDAO中应该有一个操作。
public Detail queryDetailByUid(int uid);
同时,在detail的映射文件DetailMapper中,做相应配置:
<resultMap id="detailMap" type="Detail">
<id column="detail_id" property="detailId"/>
<result column="user_addr" property="userAddr"/>
<result column="user_tel" property="userTel"/>
<result column="user_desc" property="userDesc"/>
</resultMap>
<select id="queryDetailByUid" resultMap="detailMap">
select detail_id,user_addr,user_tel,user_desc
from details
where uid=#{uid}
</select>
这样,detail的单表查询也完成了。
但是,我现在想要在我查User的时候,顺带把Detail查出来,查出来之后,放到我的User里面的Detail属性里。怎么放,通过resuletMap的去放,只不过detail和其他字段相比的不同是,detail是一个对象。想放对象怎么办,这里我们用到一个标签,通过这个标签,调用子查询,关联查询一个对象。
<association property="detail"/>
而这个“detail”是从哪来的?这里要用到一个属性叫select。
<association property="detail" select="com.wyl.dao.DetaiDAO.queryDetailByUid"/>
但是我这个queryDetailByUid需不需要传入一个uid进去?需要。所以我又写了一个属性column。
<association property="detail" select="com.wyl.dao.DetaiDAO.queryDetailByUid" column="user_id"/>
理解:我刚刚做了第一次查询。我第一次查询出来的字段user_id的结果,赋值给我的detail,我的detail再根据select的内容作第二次查询。
我的User里面就有detail了:
User(userId=6, userName=lisi, userPwd=456456, userRealName=李四, userImg=2.jpg, detail=Detail(detailId=1, userAddr=河南省新乡市, userTel=13030303300, userDesc=我的个性签名, userId=0))
Process finished with exit code 0
11.4 一对多关联
案例:班级(1)和学生(n)
增删改不是重点。重点是查。
11.4.1 创建数据表
一对多关联,就是要在“多”的一端添加外键。
-- 创建班级信息表
create table classes(
cid int primary key auto_increment,
cname varchar(30) not null unique,
cdesc varchar(100)
);
-- 创建学生信息表
create table students(
sid char(5) primary key,
sname varchar(20) not null,
sage int not null,
scid int not null -- 不加唯一键,意味着多个学生能属于同一个班级
);
11.4.2 创建实体类
Student.java
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Student {
private String stuId; //学号
private String stuName;
private int stuAge;
private int stuCid; //学生所在班级的id
}
Clazz.java
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Clazz {
private int classId;
private String className;
private String classDesc;
}
11.4.3 关联查询
当查询一个班级的时候,要关联查询出这个班级下的所有学生
所以班级的实体类,应当重新构造一下:
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Clazz {
private int classId;
private String className;
private String classDesc;
private List<Student> stus; //存储当前班级下所有的学生信息
}
由于增删改不是重点,所以我们先手动地在数据表中添加一些记录:
classes表:
students表:
连接查询
SQL语句写法:
-- 根据班级编号查询班级信息,同时查询这个班级下所有的学生信息 -- 连接查询 select * from classes c INNER JOIN students s ON c.cid=s.scid where c.cid=1
查询结果(查询出1班所有学生信息):
新建DAO,配置mapper:
Class.java
public interface ClassDAO {
//根据班级编号,查询班级信息
public Clazz queryClass(int classId);
}
ClassMapper.xml
<resultMap id="classMap" type="Clazz">
<id column="cid" property="classId"/>
<result column="cname" property="className"/>
<result column="cdesc" property="classDesc"/>
<!--CLazz对象的stus属性是一个List集合,需要使用collection标签-->
<!--collection标签的ofType属性声明集合中元素的类型-->
<collection property="stus" ofType="Student">
<result column="sid" property="stuId"/>
<result column="sname" property="stuName"/>
<result column="sage" property="stuAge"/>
</collection>
</resultMap>
<select id="queryClass" resultMap="classMap">
select cid,cname,cdesc,sid,sname,sage
from classes c
INNER JOIN students s
ON c.cid = s.scid
where c.cid = #{classId}
</select>
一定不要忘了把mapper文件进行注册,注册到主配置文件mybatis-config中!!
创建测试类:
@Test
public void testQueryClass() {
ClassDAO classDAO = MyBatisUtil.getMapper(ClassDAO.class);
Clazz clazz = classDAO.queryClass(1);
System.out.println(clazz);
}
运行结果:
Clazz(classId=1, className=Java1班, classDesc=666, stus=[Student(stuId=10001, stuName=Tom, stuAge=21, stuCid=0), Student(stuId=10003, stuName=HanMeimei, stuAge=19, stuCid=0), Student(stuId=10004, stuName=Lily, stuAge=18, stuCid=0)])
Process finished with exit code 0
子查询
再多新建一个StudentDAO
public interface StudentDAO {
public List<Student> listStudentsByCid(int cid);
}
新建一个StudentMapper
<resultMap id="studentMap" type="Student">
<id column="sid" property="stuId"/>
<result column="sname" property="stuName"/>
<result column="sage" property="stuAge"/>
<result column="scid" property="stuCid"/>
</resultMap>
<select id="listStudentsByCid" resultMap="studentMap">
select sid,sname,sage,scid from students where scid=#{cid}
</select>
对ClassMapper进行修改:
<resultMap id="classMap" type="Clazz">
<id column="cid" property="classId"/>
<result column="cname" property="className"/>
<result column="cdesc" property="classDesc"/>
<collection property="stus" select="com.wyl.dao.StudentDAO.listStudentsByCid" column="cid"/>
</resultMap>
<select id="queryClass" resultMap="classMap">
select cid,cname,cdesc
from classes
where cid = #{classId}
</select>
运行成功。
11.5 多对一关联
案例:学生(n)和班级(1)
当查询一个学生的时候,关联查询这个学生所在的班级信息
11.5.1 创建实体类
(与一对多的实体类略有不同)
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Student {
private String stuId; //学号
private String stuName;
private int stuAge;
//private int stuCid; //学生所在班级的id
private Clazz clazz; //学生所在班级
}
要在学生实体类下面添加一个Clazz类的对象属性,存放学生所在的班级对象。此处,由于班级对象里面包含的就有班级id,所以原来的stuCid可以删去。
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Clazz {
private int classId;
private String className;
private String classDesc;
//private List<Student> stus;
}
此时,班级实体类中的“学生列表”对象属性就可以删去了,留着没什么意义了。
11.5.2 关联查询
连接查询
-
此时,StudentDAO接口中实现的方法就不再是根据班级id查出班级下所有学生了,而是根据学生id查询出一条关联其班级信息的一条记录
public interface StudentDAO { //public List<Student> listStudentsByCid(int cid); public Student queryStudentBySid(String sid); }
-
StudentMapper中对SQL语句作对应修改,映射配置
-- 根据学号查询学生信息,同时关联查询到学生所属的班级信息 -- 1.连接查询 select * from students s INNER JOIN classes c ON s.scid=c.cid where s.sid='10001'
<resultMap id="studentMap" type="Student"> <id column="sid" property="stuId"/> <result column="sname" property="stuName"/> <result column="sage" property="stuAge"/> <result column="cid" property="clazz.classId"/> <result column="cname" property="clazz.className"/> <result column="cdesc" property="clazz.classDesc"/> </resultMap> <select id="queryStudentBySid" resultMap="studentMap"> select sid,sname,sage,scid,cid,cname,cdesc from students s INNER JOIN classes c ON s.scid=c.cid where s.sid=#{sid} </select>
-
测试类
@Test public void testQueryStudentBySid() { StudentDAO studentDAO = MyBatisUtil.getMapper(StudentDAO.class); Student student = studentDAO.queryStudentBySid("10001"); System.out.println(student); }
-
运行结果
Student(stuId=10001, stuName=Tom, stuAge=21, clazz=Clazz(classId=1, className=Java1班, classDesc=666)) Process finished with exit code 0
子查询
-
映射文件
- StudentMapper
<resultMap id="studentMap" type="Student"> <id column="sid" property="stuId"/> <result column="sname" property="stuName"/> <result column="sage" property="stuAge"/> <association property="clazz" select="com.wyl.dao.ClassDAO.queryClass" column="scid"/> </resultMap> <select id="queryStudentBySid" resultMap="studentMap"> select sid,sname,sage,scid from students where sid=#{sid} </select>
- ClassMapper
<resultMap id="classMap" type="Clazz"> <id column="cid" property="classId"/> <result column="cname" property="className"/> <result column="cdesc" property="classDesc"/> </resultMap> <select id="queryClass" resultMap="classMap"> select cid,cname,cdesc from classes where cid=#{cid} </select>
-
测试类运行结果
Student(stuId=10001, stuName=Tom, stuAge=21, clazz=Clazz(classId=1, className=Java1班, classDesc=666)) Process finished with exit code 0
子查询的思路就是,先做一个简单的单表查询,把学生信息查出来,但是我这个学生信息里面想包含一个Clazz类型的clazz属性,怎么获取它?再对classes表做一个单表查询,把clazz查出来。最后想办法把这两次查询联系起来就可以。
联系起来的方法就是resultMap里面的设置,如果你的这个对象属性是一个对象(单个),则使用标签,如果这个对象属性是一个集合(多个),则使用标签。
11.6 多对多关联
案例:学生(m)和课程(n)
11.6.1 创建数据表
应该有三张表。两张表分别存放两者的基本信息,还有一张表专门存放两表间记录的关联信息。(此处叫做选课表)
-- 学生信息表(如上)
-- 课程信息表
create table courses(
course_id int primary key auto_increment,
course_name varchar(50) not null
);
-- 选课信息表/成绩表(学号、课程号、成绩)
create table grades(
sid char(5) not null,
cid int not null,
score int not null
);
先向新表中插入几条记录:
score为-1,表示该学生选了课但是还没有考试。不能记为0,因为有可能考试成绩为0分。
11.6.2 关联查询
查询学生时,同时查询出学生选择的课程
- 创建实体类
Student实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Student {
private String stuId; //学号
private String stuName;
private int stuAge;
private List<Course> courses; //学生选择的课程
}
Course实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Course {
private int courseId;
private String courseName;
}
- 这一角度的具体操作不讲了,参考下面的另一个角度的实现过程即可
根据课程编号,查询课程时,同时查询选择了这门课程的学生
- 创建实体类
Student实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Student {
private String stuId; //学号
private String stuName;
private int stuAge;
}
Course实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Course {
private int courseId;
private String courseName;
private List<Student> students;
}
两种不同的业务需求,其实体类的实现略有不同。
如果两个操作都想完成,那么两边同时写List<>就可以了。但是不建议这样做,因为你包含我、我又包含你、你又包含我,很容易造成错误。所以一般根据业务需求,二选一去写就行了。
- 创建DAO接口
public interface CourseDAO {
public Course queryCourseById(int courseId);
}
- 映射文件
连接查询
-- 连接查询
select *
from courses c INNER JOIN grades g INNER JOIN students s
ON c.course_id=g.cid and g.sid=s.sid
where c.course_id=1;
CourseMapper.xml
<mapper namespace="com.wyl.dao.CourseDAO">
<resultMap id="courseMap" type="Course">
<id column="course_id" property="courseId"/>
<result column="course_name" property="courseName"/>
<collection property="students" ofType="Student">
<result column="sid" property="stuId"/>
<result column="sname" property="stuName"/>
<result column="sage" property="stuAge"/>
</collection>
</resultMap>
<select id="queryCourseById" resultMap="courseMap">
select course_id,course_name,s.sid,sname,sage
from courses c INNER JOIN grades g INNER JOIN students s
ON c.course_id=g.cid and g.sid=s.sid
where c.course_id=#{courseId}
</select>
</mapper>
子查询
CourseMapper.xml
<mapper namespace="com.wyl.dao.CourseDAO">
<resultMap id="courseMap" type="Course">
<id column="course_id" property="courseId"/>
<result column="course_name" property="courseName"/>
</resultMap>
<select id="queryCourseById" resultMap="courseMap">
select course_id,course_name
from courses
where course_id=#{courseId}
</select>
</mapper>
这样写之后,能查是能查,只不过course中的students属性内容为null:
Course(courseId=1, courseName=Java, students=null)
Process finished with exit code 0
那我怎样获取到students的内容并将它放到resultMap里面呢。
执行第二次查询。
首先在StudentDAO中添加一个“根据课程id查询学生”的方法:
public interface StudentDAO {
//public List<Student> listStudentsByCid(int cid);
public Student queryStudentBySid(String sid);
public List<Student> queryStudentsByCourseId(int courseId); //添加这个
}
然后在StudentMapper中实现这个操作
在实现这个操作的时候,它不是一个简单的单表查询,而是也需要进行一个关联查询:
select s.sid,sname,sage from students s INNER JOIN grades g
ON s.sid=g.sid
where g.cid=1
StudentMapper.xml
<resultMap id="studentMap2" type="Student">
<id column="sid" property="stuId"/>
<result column="sname" property="stuName"/>
<result column="sage" property="stuAge"/>
<association property="clazz" select="com.wyl.dao.ClassDAO.queryClass" column="scid"/>
</resultMap>
<select id="queryStudentsByCourseId" resultMap="studentMap2">
select s.sid,sname,sage from students s INNER JOIN grades g
ON s.sid=g.sid
where g.cid=#{courseId}
</select>
CourseMapper.xml
<mapper namespace="com.wyl.dao.CourseDAO">
<resultMap id="courseMap" type="Course">
<id column="course_id" property="courseId"/>
<result column="course_name" property="courseName"/>
<collection property="students" select="com.wyl.dao.StudentDAO.queryStudentsByCourseId" column="course_id"/>
</resultMap>
<select id="queryCourseById" resultMap="courseMap">
select course_id,course_name
from courses
where course_id=#{courseId}
</select>
</mapper>
- 测试类
@Test
public void testQueryClass() {
ClassDAO classDAO = MyBatisUtil.getMapper(ClassDAO.class);
Clazz clazz = classDAO.queryClass(1);
System.out.println(clazz);
}