之前我们做的都是单表查询,那么问题来了,如果我们想要做多表关联查询该怎么处理呢,本篇Blog来探索关联查询(多对一关系)和集合查询(一对多关系)的实现方式。同样还是在之前的Person表基础上做一些扩展。
数据及代码准备
首先我们预设一个场景:一条人员Person数据对应多个账户BankAccount数据,然后分别进行账户数据的查询时获取到关联的人员数据【多对一关联查询】和通过人员获取账户数据【一对多关联查询】。
1 数据表准备
首先我们需要准备两张数据表,首先Person表是之前准备好的,我们还是用之前预设的三条数据:
然后我们配置一个账户表并且在账户表中关联好人员id以及初始化一些数据:
2 数据Model准备
我们沿用之前的数据Model,Person,并且新增一个BankAccount的Model:
Person
package com.example.MyBatis.dao.model; import lombok.Data; /* * person表对应对象 * */ @Data public class Person { private int id; private String username; private String password; private int age; private int phone; private String email; private String interests; }
BankAccount
package com.example.MyBatis.dao.model; import lombok.Data; /* * bank_account表对应对象 * */ @Data public class BankAccount { private int id; private String bankName; private String accountName; private Person person; private int personId; }
3 DAO接口类准备
我们提前准备好PersonDao以及BankAccountDao,其中PersonDao中的接口维持之前的定义,BankAccountDao新定义暂无接口
PersonDao
package com.example.MyBatis.dao.mapper; import com.example.MyBatis.dao.model.Person; import org.apache.ibatis.annotations.*; import java.util.List; import java.util.Map; public interface PersonDao { List<Person> getPersonList(); Person getPersonByUsername(@Param("username")String username, @Param("password")String password); Person getPersonByMap(Map<String,Object> map); int addPerson(Person person); int updatePerson(Person person); int deletePerson(@Param("id")int id); List<Person> getPersonListLike(@Param("username")String username); List<Person> getPersonListPage(Map<String,Integer> map); }
PersonDao
package com.example.MyBatis.dao.mapper; import com.example.MyBatis.dao.model.BankAccount; import java.util.List; public interface BankAccountDao { }
4 Mapper文件编写及集成
我们需要配置personMapper以及bankAccountMapper,其中personMapper沿用之前的,bankAccountMapper为新增的
personMapper
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!--namespace=绑定一个指定的Dao/Mapper接口--> <mapper namespace="com.example.MyBatis.dao.mapper.PersonDao"> <!-- id确定返回map, type为模型全类名 --> <resultMap id="PersonMap" type="com.example.MyBatis.dao.model.Person"> <!-- id为主键 --> <id column="id" property="id"/> <result column="username" property="username"/> <result column="password" property="password"/> <result column="age" property="age"/> <result column="phone" property="phone"/> <!-- column写错会因为查到数据匹配不上,给默认值, type写错会报错,找不到属性--> <result column="email" property="email"/> <!-- column是数据库表的列名 , property是对应实体类的属性名 --> <result column="hobby" property="interests"/> </resultMap> <select id="getPersonList" resultMap="PersonMap"> select * from person </select> <select id="getPersonByUsername" resultMap="PersonMap" > select * from person where password=#{password} and username = #{username} </select> <select id="getPersonByMap" resultMap="PersonMap" parameterType="map"> select * from person where password=#{password} and username = #{username} </select> <insert id="addPerson" parameterType="com.example.MyBatis.dao.model.Person" > insert into person (id,username,password,age,phone,email,hobby) values (#{id},#{username},#{password},#{age},#{phone},#{email},#{interests}) </insert> <update id="updatePerson" parameterType="com.example.MyBatis.dao.model.Person" > update person set username=#{username},phone=#{phone} where id=#{id} </update> <delete id="deletePerson" parameterType="int" > delete from person where id = #{id} </delete> <select id="getPersonListLike" resultMap="PersonMap" > select * from person where username like "%"#{username}"%" </select> <select id="getPersonListPage" parameterType="map" resultMap="PersonMap"> select * from person limit #{startIndex},#{pageSize} </select> </mapper>
bankAccountMapper
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> </mapper>
并且我们将两个mapper文件都添加到核心配置文件中:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <!--configuration核心配置文件--> <configuration> <!--导入properties文件--> <properties resource="properties/db.properties"/> <settings> <setting name="logImpl" value="log4j"/> </settings> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <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> <mappers> <mapper resource="mappers/personMapper.xml"/> <mapper resource="mappers/bankAccountMapper.xml"/> <!-- <mapper class="com.example.MyBatis.dao.mapper.PersonDao"/>--> </mappers> </configuration>
多对一关联查询
多对一的关联查询方式有两种,一种是子查询的方式,一种是联表查询的方式。
子查询实现方式
所谓子查询的方式就是在将要获取BankAccount对象中的Person对象时通过绑定的参数关系查一次,是一种查询嵌套方式。
1 BankAccountDao增加接口方法
首先我们在BankAccountDao中增加接口方法getBankAccountListBySelect
List<BankAccount> getBankAccountListBySelect();
2 bankAccountMapper增加实现配置
然后我们在bankAccountMapper中增加配置实现,配置内容如下:
<select id="getBankAccountListBySelect" resultMap="BankAccountPersonBySelectMap"> select * from bank_account </select> <!-- id确定返回map, type为模型全类名 --> <resultMap id="BankAccountPersonBySelectMap" type="com.example.MyBatis.dao.model.BankAccount"> <!-- id为主键 --> <id column="id" property="id"/> <result column="bank_name" property="bankName"/> <result column="account_name" property="accountName"/> <!--association关联属性 property属性名 javaType属性类型 column在多的一方的表中的列名--> <association column="person_id" property="person" javaType="com.example.MyBatis.dao.model.Person" select="getPerson"/> </resultMap> <select id="getPerson" resultMap="PersonMap"> <!--这里的参数就是上一个sql的column,--> select * from person where id = #{person_id} </select> <resultMap id="PersonMap" type="com.example.MyBatis.dao.model.Person"> <!-- id为主键 --> <id column="id" property="id"/> <result column="username" property="username"/> <result column="password" property="password"/> <result column="age" property="age"/> <result column="phone" property="phone"/> <!-- column写错会因为查到数据匹配不上,给默认值, type写错会报错,找不到属性--> <result column="email" property="email"/> <!-- column是数据库表的列名 , property是对应实体类的属性名 --> <result column="hobby" property="interests"/> </resultMap>
其实如果阅读过源码不难理解,当返回结果执行person映射的时候通过动态代理生成了javaType中对应的Person代理,然后执行方法getPerson获取到查询结果,返回时因为hobby字段不对应,所以我们还做了一次结果集映射。
3 测试实现效果
最后我们测试下实现效果,测试代码如下:
@Test public void testGetBankAccountListBySelect(){ //1.获取SqlSession对象 SqlSession sqlSession = MybatisUtils.getSqlSession(); //2.执行SQL BankAccountDao bankAccountDao = sqlSession.getMapper(BankAccountDao.class); List<BankAccount> bankAccounts = bankAccountDao.getBankAccountListBySelect(); for (BankAccount bankAccount : bankAccounts) { System.out.println(bankAccount); } //关闭sqlSession sqlSession.close(); }
测试结果如下:
联表查询实现方式
所谓联表查询的方式就是在一次性查完所有结果,将结果嵌套映射到结果集上。
1 BankAccountDao增加接口方法
首先我们在BankAccountDao中增加接口方法getBankAccountListByResult
List<BankAccount> getBankAccountListByResult();
2 bankAccountMapper增加实现配置
然后我们在bankAccountMapper中增加配置实现:
<select id="getBankAccountListByResult" resultMap="BankAccountPersonByResultMap"> select ba.id id, ba.bank_name bank_name, ba.account_name account_name,ba.person_id person_id,p.id pid,p.username username,p.password password,p.age age,p.phone phone,p.email email,p.hobby hobby from bank_account ba,person p where ba.person_id = p.id </select> <!-- id确定返回map, type为模型全类名 --> <resultMap id="BankAccountPersonByResultMap" type="com.example.MyBatis.dao.model.BankAccount"> <!-- id为主键 --> <id column="id" property="id"/> <result column="bank_name" property="bankName"/> <result column="account_name" property="accountName"/> <association column="person_id" property="person" javaType="com.example.MyBatis.dao.model.Person"> <!-- id为主键 --> <id column="pid" property="id"/> <result column="username" property="username"/> <result column="password" property="password"/> <result column="age" property="age"/> <result column="phone" property="phone"/> <result column="email" property="email"/> <result column="hobby" property="interests"/> </association> </resultMap>
其实就是通过联表查询一次性把所有结果都查了出来,然后进行结果集的嵌套映射。
3 测试实现效果
最后我们测试下实现效果,测试代码编写如下:
@Test public void testGetBankAccountListByResult(){ //1.获取SqlSession对象 SqlSession sqlSession = MybatisUtils.getSqlSession(); //2.执行SQL BankAccountDao bankAccountDao = sqlSession.getMapper(BankAccountDao.class); List<BankAccount> bankAccounts = bankAccountDao.getBankAccountListByResult(); for (BankAccount bankAccount : bankAccounts) { System.out.println(bankAccount); } //关闭sqlSession sqlSession.close(); }
实现效果如下,可以看到打印日志如下:
一对多关联查询
一个人员Person拥有多个账户信息,是个一对多现象,也分为两种情况,按照子查询方式和按照结果查询方式。
子查询实现方式
所谓子查询的方式就是在将要获取BankAccount对象中的Person对象时通过绑定的参数关系查一次,是一种查询嵌套方式。
1 PersonDao增加接口方法
首先我们在PersonDao中增加接口方法getPersonByIdSelect
Person getPersonByIdSelect(@Param("id")int id);
2 personMapper增加实现配置
然后我们在personMapper中增加配置实现:
<select id="getPersonByIdSelect" resultMap="PersonBankAccountBySelectMap"> select * from person where id = #{id} </select> <resultMap id="PersonBankAccountBySelectMap" type="com.example.MyBatis.dao.model.Person"> <!-- id为主键 --> <id column="id" property="id"/> <result column="username" property="username"/> <result column="password" property="password"/> <result column="age" property="age"/> <result column="phone" property="phone"/> <!-- column写错会因为查到数据匹配不上,给默认值, type写错会报错,找不到属性--> <result column="email" property="email"/> <!-- column是数据库表的列名 , property是对应实体类的属性名 --> <result column="hobby" property="interests"/> <!--column是一对多的外键 , 写的是一的主键的列名--> <collection column="id" property="bankAccounts" javaType="ArrayList" ofType="com.example.MyBatis.dao.model.BankAccount" select="getBankAccountByPersonId"/> </resultMap> <select id="getBankAccountByPersonId" resultMap="BankAccountResultMap"> select * from bank_account where person_id = #{id} </select> <resultMap id="BankAccountResultMap" type="com.example.MyBatis.dao.model.BankAccount"> <!-- id为主键 --> <id column="id" property="id"/> <result column="bank_name" property="bankName"/> <result column="account_name" property="accountName"/> <result column="person_id" property="personId"/> </resultMap>
JavaType和ofType都是用来指定对象类型的,JavaType是用来指定pojo中属性的类型,ofType指定的是映射到list集合属性中pojo的类型。
3 测试实现效果
最后我们测试下实现效果,测试代码编写如下:
@Test public void testGetPersonByIdSelect() { //1.获取SqlSession对象 SqlSession sqlSession = MybatisUtils.getSqlSession(); //2.执行SQL PersonDao personDao = sqlSession.getMapper(PersonDao.class); Person person = personDao.getPersonByIdSelect(1); System.out.println(person); //关闭sqlSession sqlSession.close(); }
实现效果如下,可以看到打印日志如下:
联表查询实现方式
所谓联表查询的方式就是在一次性查完所有结果,将结果嵌套映射到结果集上。
1 PersonDao增加接口方法
首先我们在PersonDao中增加接口方法getPersonByIdResult
Person getPersonByIdResult(@Param("id")int id);
2 personMapper增加实现配置
然后我们在personMapper中增加配置实现:
<select id="getPersonByIdResult" resultMap="PersonBankAccountByResultMap"> select p.id id,p.username,username,p.password password,p.age age,p.phone phone,p.email email,p.hobby hobby,ba.id bid,ba.bank_name bank_name,ba.account_name account_name,ba.person_id person_id from person p,bank_Account ba where p.id=ba.person_id and p.id=#{id} </select> <resultMap id="PersonBankAccountByResultMap" type="com.example.MyBatis.dao.model.Person"> <!-- id为主键 --> <id column="id" property="id"/> <result column="username" property="username"/> <result column="password" property="password"/> <result column="age" property="age"/> <result column="phone" property="phone"/> <!-- column写错会因为查到数据匹配不上,给默认值, type写错会报错,找不到属性--> <result column="email" property="email"/> <!-- column是数据库表的列名 , property是对应实体类的属性名 --> <result column="hobby" property="interests"/> <collection property="bankAccounts" javaType="ArrayList" ofType="com.example.MyBatis.dao.model.BankAccount"> <result column="bid" property="id" /> <result column="bank_name" property="bankName"/> <result column="account_name" property="accountName"/> <result column="person_id" property="personId"/> </collection> </resultMap>
3 测试实现效果
最后我们测试下实现效果,测试代码编写如下:
@Test public void testGetPersonByIdResult() { //1.获取SqlSession对象 SqlSession sqlSession = MybatisUtils.getSqlSession(); //2.执行SQL PersonDao personDao = sqlSession.getMapper(PersonDao.class); Person person = personDao.getPersonByIdResult(1); System.out.println(person); //关闭sqlSession sqlSession.close(); }
实现效果如下,可以看到打印日志如下:
总结一下
多对一关联查询使用association,并且把配置写到多的一方;一对多集合查询使用collection,并且把配置写到一的一方;每种关联查询类型都有两种查询方式,一种是类似子查询的查询嵌套,另一种是类似联表查询的结果集映射。附加的属性类似于外键配置在多的一方。自我感觉,用子查询嵌套+一对多关联查询+在多的一方配置一的一方的主键id应该是我们常用的使用方式,但更多场景我觉得还是不要引入复合关联查询,让数据库的数据获取更纯粹,业务逻辑在上层处理更好理解也更好修改,基础查询只包含简单增删查改即可,除非这个复合查询使用场景很频繁可以定义一下。