Mybatis多表查询
概念
在SQL语法中,是可以进行多表查询的.
而多表查询的语法分为很多种形式,有隐式内连接,显示内连接,左外连接,右外连接,子查询等等语法来实现多表查询.
多表查询就是将多个表,依照条件进行过滤,最终把符合过滤条件的内容合并在一起,形成一个’结果表’.
多表查询分为一对一,一对多,多对多关系.
在Mybatis中,多表查询分为两种形式,一种是传统的形式.一种是优化形式.
传统形式
:
优点:只需要在dao层创建一个select查询方法即可实现.
缺点:需要通过复杂的sql多表查询语法(如左外连接)来实现,费脑子
原理:将结果表中本表字段数据封装到到本表的实体类成员变量中;再将结果表中另外一张表的字段数据封装到本表实体类中的另一个实体类对象中.
优化形式
:
优点:只需要简简单单的sql查询语法(如select * from 表名 以及 WHERE条件)就能实现和多表查询同等的效果
缺点:因为sql查询语法过于简单,所以需要在dao层创建多个select查询方法才能实现多表查询的效果.
原理:先将结果表中的本表字段封装到本表的实体类成员变量中.然后再执行Dao接口中的其它查询方法,然后将其它查询方法的结果封装到本表实体类中的零一个实体类对象中.
注:在日常开发中,多数使用优化形式来完成多表查询和多表查询的结果数据封装.
XML配置-传统形式多表查询
一对一查询
格式:
<mapper namespace="接口包路径和接口名">
<!-- 普通式多表查询 -->
<!-- 一对一 -->
<resultMap id="自定义封装规则id,要同select标签处的resultMap属性值一致" type="指定接口方法处所返回的实体类路径和实体类名">
<result property="自定义实体类成员变量名" column="自定义其A表中与之对应的字段名"/>
<!-- 在一对一多表查询下,如果要将表字段的数据封装到实体类内置的成员变量对象中,则需要使用association围堵标签 -->
<association property="自定义实体类中的内置成员变量对象名" javaType="自定义其内置成员对象变量的实体类包路径和实体类名">
<result property="自定义其内置成员对象变量的成员变量名" column="自定义其B表中与之对应的字段名"/>
</association>
</resultMap>
<select id="自定义接口中用于一对一查询的方法名" resultMap="自定义封装规则id,要同resultMap标签处的id属性值一致">
<!-- 通过左外连接实现一对一的多表查询 -->
SELECT 字段列表 FROM 表A LEFT JOIN 表B ON 条件表达式;
</select>
</mapper>
列如:
<mapper namespace="cn.mybatis.dao.OrdersUserDao">
<!-- 普通式多表查询 -->
<!-- 一对一 -->
<resultMap id="duobiaochaxun" type="cn.mybatis.domin.Orders">
<result property="id" column="id"/>
<result property="ordertime" column="ordertime"/>
<result property="total" column="total"/>
<association property="userObj" javaType="cn.mybatis.domin.User">
<result property="id" column="uid"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<result property="birthday" column="birthday"/>
</association>
</resultMap>
<select id="findOrdersUser" resultMap="duobiaochaxun">
SELECT o.*,us.username,us.password,us.birthday FROM orders o LEFT JOIN USER us ON o.uid=us.id;
</select>
</mapper>
一对多查询
格式:
<mapper namespace="接口包路径和接口名">
<!-- 普通式多表查询 -->
<!-- 一对多 -->
<resultMap id="自定义封装规则id,要同select标签处的resultMap属性值一致" type="指定接口方法处所返回的实体类路径和实体类名">
<result property="自定义实体类成员变量名" column="自定义其A表中与之对应的字段名"/>
<!-- 在一对多的多表查询下,如果要将表字段的数据封装到实体类内置的成员变量对象中,则需要使用collection围堵标签 -->
<collection property="自定义实体类中的内置成员变量对象名" ofType="自定义其内置成员对象变量的实体类包路径和实体类名">
<result property="自定义其内置成员对象变量的成员变量名" column="自定义其B表中与之对应的字段名"/>
</collection>
</resultMap>
<select id="自定义接口中用于一对多查询的方法名" resultMap="自定义封装规则id,要同resultMap标签处的id属性值一致">
<!-- 通过左外连接实现一对多的多表查询 -->
SELECT 字段列表 FROM 表A LEFT JOIN 表B ON 条件表达式;
</select>
</mapper>
列如:
<!-- 一对多 -->
<resultMap id="yiduiduo" type="cn.mybatis.domintwo.User">
<result property="id" column="id"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<result property="birthday" column="birthday"/>
<collection property="ordersListObj" ofType="cn.mybatis.domintwo.Orders">
<result property="id" column="oid"/>
<result property="ordertime" column="ordertime"/>
<result property="total" column="total"/>
</collection>
</resultMap>
<select id="findUserOrders" resultMap="yiduiduo">
SELECT us.*,o.id oid,o.ordertime,o.total FROM USER us LEFT JOIN orders o ON us.id=o.uid;
</select>
XML配置-优化形式多表查询
优化形式的多表查询在日常开发中更为常用.
它的优点是对SQL语法要求不高,但是需要通过多个方法才能达到多表查询的效果
缺点是因为sql查询语法过于简单,所以需要在dao层创建多个select查询方法才能实现多表查询的效果.
一对一查询
格式:
<mapper namespace="接口包路径和接口名">
<!-- 优化形式的一对一查询 -->
<resultMap id="自定义封装规则id,要同select标签处的resultMap属性值一致" type="指定接口方法处所返回的实体类路径和实体类名">
<result property="自定义实体类成员变量名" column="自定义其主表中与之对应的字段名"/>
<!-- 一对一查询下,要使用association标签来将另一个方法查询出来的结果封装到实体类内置的成员变量对象中 -->
<association property="自定义实体类中的内置成员变量对象名" javaType="自定义其内置成员对象变量的实体类包路径和实体类名" select="耦合另一个查询方法所在的包路径+Dao接口类名+方法名" column="指定一个主表查询结果中的字段名,将字段数据当作参数传递给另一个方法">
</association>
</resultMap>
<!-- 绑定用于查询主表的方法,并为之定义SQL语句 -->
<select id="耦合接口中用于查询主表的方法名" resultMap="自定义封装规则id,要同resultMap标签处的id属性值一致">
<!-- 以下SQL语句语法为非固定格式,需要根据实际需求来更改 -->
SELECT * FROM 主表名
</select>
<!-- 绑定用于查询副表的方法,该方法所查询到的结果将被封装到其对应的副表实体类中,在将该副表实体类中的数据封装到主表所对应的实体类中 -->
<select id="耦合接口中用于查询副表的方法名" resultType="此处为设置该方法的返回值类型,你需要指定一个用于封装副表查询结果的实体类名和其所在路径" parameterType="int">
<!-- 以下SQL语句语法为非固定格式,需要根据实际需求来更改 -->
SELECT * FROM 副表名 WHERE 用于条件查询的字段名=#{用于条件查询的数据名}
</select>
</mapper>
列如:
<mapper namespace="cn.mybatis.dao.OrdersDao">
<!-- 延迟加载式多个查询步骤来实现多表查询 -->
<!-- 一对一 一个订单携带一个用户 -->
<resultMap id="ordersAndUser" type="cn.mybatis.domin.Orders">
<result property="id" column="id"/>
<result property="ordertime" column="ordertime"/>
<result property="total" column="total"/>
<association property="userObj" javaType="cn.mybatis.domin.User" select="cn.mybatis.dao.OrdersDao.findUserByUid" column="uid">
</association>
</resultMap>
<!-- SQL语句:查询订单表(orders)所有数据 -->
<select id="findAllOrders" resultMap="ordersAndUser">
SELECT * FROM orders
</select>
<!-- SQL语句:根据订单表的uid来查询对应的用户 -->
<select id="findUserByUid" resultType="cn.mybatis.domin.User" parameterType="int">
SELECT * FROM user WHERE id=#{id}
</select>
一对多查询
格式:
<mapper namespace="接口包路径和接口名">
<!-- 优化形式的一对多查询 -->
<resultMap id="自定义封装规则id,要同select标签处的resultMap属性值一致" type="指定接口方法处所返回的实体类路径和实体类名">
<result property="自定义实体类成员变量名" column="自定义其主表中与之对应的字段名"/>
<!-- 当为一对多或对多对多时,要使用collection标签来将另一个方法查询出来的结果封装到实体类内置的成员变量对象中 -->
<collection property="自定义实体类中的内置成员变量对象名" ofType="自定义其内置成员对象变量的实体类包路径和实体类名" select="耦合另一个查询方法所在的包路径+Dao接口类名+方法名" column="指定一个主表查询结果中的字段名,将字段数据当作参数传递给另一个方法">
</collection>
</resultMap>
<!-- 绑定用于查询主表的方法,并为之定义SQL语句 -->
<select id="耦合接口中用于查询主表的方法名" resultMap="自定义封装规则id,要同resultMap标签处的id属性值一致">
<!-- 以下SQL语句语法为非固定格式,需要根据实际需求来更改 -->
SELECT * FROM 主表名
</select>
<!-- 绑定用于查询副表的方法,该方法所查询到的结果将被封装到其对应的副表实体类中,在将该副表实体类中的数据封装到主表所对应的实体类中 -->
<select id="耦合接口中用于查询副表的方法名" resultType="此处为设置该方法的返回值类型,你需要指定一个用于封装副表查询结果的实体类名和其所在路径" parameterType="int">
<!-- 以下SQL语句语法为非固定格式,需要根据实际需求来更改 -->
SELECT * FROM 副表名 WHERE 用于条件查询的字段名=#{用于条件查询的数据名}
</select>
</mapper>
列如:
<mapper namespace="cn.mybatis.dao.OrdersDao">
<!-- 一对多 一个用户携带多个订单 -->
<resultMap id="userAndOredrs" type="cn.mybatis.dominthree.UserThree">
<result property="username" column="username"/>
<result property="password" column="password"/>
<result property="birthday" column="birthday"/>
<!-- 当为一对多或对多对多时,如果要将字段数据封装至内部对象的成员变量或者作为参数传递时,需要用collection标签 -->
<collection property="ordersObj" ofType="cn.mybatis.dominthree.OrdersThree" select="cn.mybatis.dao.OrdersDao.findOrdersById" column="id">
</collection>
</resultMap>
<!-- 查询user表的所有信息 -->
<select id="findAllUser" resultMap="userAndOredrs">
SELECT * FROM user
</select>
<!-- SQL语句:根据用户表的id来查询对应的订单 -->
<select id="findOrdersById" resultType="cn.mybatis.dominthree.OrdersThree" parameterType="int">
SELECT * FROM orders WHERE uid=#{uid}
</select>
</mapper>
注解形式用于SQL操作
任何插件或者框架都有xml和注解这两种应用方式,Mybatis也不例外.
我们可以通过Mybaits的注解来实现增删改查和多表查询.
准备-加载sql配置文件
在核心配置文件的sqlMapConfig.xml的configuration围堵标签的末尾(当然,你还得先在上面配置数据库源环境)进行如下配置
以耦合Dao层接口类
格式:
<!-- 加载sql配置文件 -->
<mappers>
<!-- 指定一个Dao层的包路径,当使用注解形式时必须要这样 -->
<package name="dao层的包路径"></package>
</mappers>
注意:注解定义在Dao层接口类里头,方法的上方.
注解:表数据增加之应用
格式:
@Insert(" INSERT INTO 表名 VALUES(#{实体类成员变量名})")
自定义方法返回值类型 自定义接口中的方法名(形参);
列如:
@Insert("INSERT INTO user VALUES(#{id},#{username},#{password},#{birthday})")
void insertUser(UserOne userObj);
注解:表数据删除之应用
格式:
@Delete("DELETE FROM 表名 WHERE 列名=#{实体类成员变量名}")
自定义方法返回值类型 自定义接口中的方法名(形参);
列如:
@Delete("DELETE FROM user WHERE id=#{id}")
void deleteUser(int id);
注解:表数据修改之应用
格式:
@Update("UPDATE 表名 SET 列名=#{实体类成员变量名} WHERE 列名=#{实体类成员变量名}")
自定义方法返回值类型 自定义接口中的方法名(形参);
列如:
@Update("UPDATE user SET username = #{username} WHERE id = #{id}")
void updateUser(UserOne userObj);
注解:表数据查询之应用
格式:
@Select("自定义SQL查询语句")
自定义方法返回值类型 自定义接口中的方法名(形参);
列如:
@Select("SELECT * FROM user")
List<UserOne> findUser();
注解:应用于多表查询
注解同样可以用于多表查询(废话)
但为了省力气,下面我只介绍优化形式的多表查询.
传统形式的多表查询可以自己对照XML配置的格式来实现
注解:优化形式 一对一查询
格式:
@Select("SELECT * FROM 主表名")//先定义用于查询主表的SQL语句(仅用于优化形式)
@Results({
@Result(property = "自定义实体类成员变量名",column = "自定义其主表中与之对应的字段名"),
@Result(property = "自定义实体类中的内置成员变量对象名",//指定一个实体类中的成员对象,将查出的数据封装到这里头
column = "自定义其主表中与之对应的字段名",//指定一个查询出来的字段名称,将其数据用于在调用查询方法时所传递的标识符形参.
javaType = 封装副表数据的实体类名.class,//指定一个副表方法查询结果的返回值类型
one = @One(select = "指定副表查询方法所在的包路径+实体类名+方法名"))//指定一个执行SQL语句的接口方法
//定义用于查询副表的方法,以便将其查询出来的结果封装到主表的实体类中.
//注:以下SQL查询语句语法为非固定格式,需要根据实际需求来更改
@Select("SELECT * FROM 副表名 WHERE 用于条件查询的字段名=#{用于条件查询的数据名}")
封装副表数据的实体类名 自定义接口中的方法名(形参);
})
列如:
//一对一 (优化方式)
//查询所有订单信息
@Select("SELECT * FROM orders")
//将查询出来的数据封装到orders实体类中.
//再将查询出来的uid字段当作参数,传递给'userById'方法的形参,并调用该方法,接着自动将'userById'查询出来的数据封装到orders实体类的'userObj'对象里头.
@Results({
@Result(property = "id", column = "id"),
@Result(property = "ordertime", column = "ordertime"),
@Result(property = "total", column = "total"),
@Result(property = "userObj",//指定一个实体类中的成员对象,将查出的数据封装到这里头
column = "uid",//指定一个查询出来的字段名称,将其数据用于在调用查询方法时当作形参.
javaType = UserOne.class,//指定一个查询结果的返回值类型,
one = @One(select = "cn.mybatisanno.dao.AnnoDao.userById"))//指定一个执行SQL语句的接口方法
})
public List<OrdersOne> findAllOrders();
//根据订单的uid查询其对应的用户
@Select("SELECT * FROM user WHERE id=#{id}")
public UserOne userById(int id);
注解:优化形式 一对多查询
格式:
@Select("SELECT * FROM 主表名") //先定义用于查询主表的SQL语句(仅用于优化形式)
@Results({
@Result(property = "自定义实体类成员变量名",column = "自定义其主表中与之对应的字段名"),
//下面开始调用查询副表的方法,并将其副表的结果进行封装
@Result(property = "自定义实体类中的内置成员变量对象名",//封装目标
column = "自定义其主表中与之对应的字段名",//用于当作其附表查询方法至形参来传递的字段
javaType = List.class,//查询方法的返回值类型 注意:当为一对多查询时,那么其被调用的查询方法返回值必定是通过List集合来封装主表实体类对象的.
// 因此,jayaType所设置的返回值类型必须为List.class.而非是实体类名.class
many = @Many(select = "指定副表查询方法所在的包路径+实体类名+方法名")
)
})
List<封装主表数据的实体类名> 自定义接口中的方法名(形参);
//定义用于查询副表的方法,以便将其查询出来的结果封装到主表的实体类中.
//注:以下SQL查询语句语法为非固定格式,需要根据实际需求来更改
@Select("SELECT * FROM 副表名 WHERE 用于条件查询的字段名=#{用于条件查询的数据名}")
List<封装副表数据的实体类名> 自定义接口中的方法名(形参);
列如:
//一对多 (优化方式)
@Select("SELECT * FROM user")
@Results({
@Result(property = "id",column = "id"),
@Result(property = "username",column = "username"),
@Result(property = "password",column = "password"),
@Result(property = "birthday",column = "birthday"),
@Result(property = "ordersObj",//封装目标
column = "id",//用于当作形参来传递的字段
javaType = List.class,//查询方法的返回值类型 注意:当为一对多查询时,那么其被调用的查询方法返回值必定是通过List集合来封装实体类对象的.
// 因此,jayaType所设置的返回值类型必须为List.class.而非是实体类名.class
many = @Many(select = "cn.mybatisanno.dao.AnnoDao.ordersById")
)
})
public List<UserTwo> findAllUser();
//根据用户的id来查询其对应的多个订单
@Select("SELECT * FROM orders WHERE uid=#{uid}")
public List<OrdersTwo> ordersById(int id);