Mybatis中的多表查询和动态sql

spring中的mybatis

mybatis配置相关问题-仅在用xml文件配置映射有以下类似问题

  • 将对应mapper.xml文件放在java文件下,需要在pom.xml中配置过滤器,保证xml文件被扫描到,类似格式如下
<!-- 此处是为了方面在java目录中读取xml配置文件  如果不加 默认的只能在resources目录中读取-->
 <resource>
      <directory>src/main/java</directory>
      <includes>
        <include>**/*.xml</include>
        <include>**/*.properties</include>
      </includes>
      <filtering>false</filtering>
    </resource>
    <resource>
      <directory>src/main/resources</directory>
      <includes>
        <include>**/*.xml</include>
        <include>**/*.properties</include>
      </includes>
      <filtering>false</filtering>
    </resource>
  • 而将mapper对应的xml文件放在resource文件夹时,需要在properties或者是 yml 中配置对应的映射路径
//此为在yml或者yaml配置文件中写法,其中mapper是recource目录下存放xml文件的文件夹
mapper-locations: classpath:mapper/*.xml

//此为properties配置文件中的写法
mybatis.mapperLocations=classpath:mapper/*.xml  
  • 而在resource下需要创建mapper文件夹,将xml文件放入其下(对应的路径一定要相同,防止扫描不到对应的xml文件导致映射失败)

  • 如果是映射失败,报错如下:Invalid bound statement (not found):

mybatis查询(重要)

多表查询(xml实现-主要)

多表查询思路以及注意事项

多表连接设计实现的过程以及思路:

1.查询sql分析(select语句需要写连接关系- 直接内联/子查询实现,这里只考虑内联不考虑子查询, 与注解开发区别出来 ,在xml中实现内联主要是依靠resultMap中的select语句中,实现方法与注解开发类似,放在下面解释)

2.在对应pojo对象中添加关联关系

3.编写接口方法

4.编写映射文件(或者是直接写注解)

- resultMap 下 只需要写对应的<id> 对应 :一对一 <association> 下的<id>,

一对多 <collection> 下的<id>

<id> 中有两个常用书写 column 是给当前表的主键(为了防止id注入相同常常在sql语句中指定别名,这样也需要将所有字段列出来) ,property是当前pojo对象与表对应的主键属性名

<collection> 中的注意点 property 中的是指pojo中映射对象的属性名,javaType是指当前类型,ofType是指内部类型。

注意:

collection和association 标签对中,在表与表之间的字段名没有重复时,

不写对应的映射(<id/> <result/>)也可以的,直接让mybatis在生成时自动注入映射(需要写 autoMapping="true")。

这种只适用于多表连接,不适用于子查询。

<collection property="orders" javaType="List" ofType="Order" autoMapping="true">

//property对应的是 属性名
private List<Order> orders;

5.测试(在test中测试每个方法)

图解

一对一

一对多

数据准备:

一对一:

xml实现多表时: 需要在对应的pojo内 加入对应的对象 来映射

这里的例子为 order - user (1-1)

public class Order {

    private Integer id;
    private String orderNumber;
    //关联User对象
    private User user;
}

xml中一对一映射

<resultMap id="orderUserMap" type="Order" autoMapping="true">
				<!--resultMap的id为 下面sql语句调用的唯一标识  type为主表pojo对象 
  					autoMapping(true)指除了当前指定的映射集,其他字段的映射自动进行
            resultMap中id 标签对 指当前pojo对象以及对应数据库表中的主键字段
  			-->
        
        <id column="oid" property="id"/>
        <!--主表主键 column中 表中需要指定订单别名为oid property中为pojo属性名为id-->


        <association property="user" javaType="User" autoMapping="true">
				<!--association 中的property是指当前order 中的 pojo对象名字,
          	javaType指该类型的名称 
            -->
            <id column="uid" property="id"/>
           <!--从表主键  column用户id别名为uid property为当前user属性名为id-->
        </association>
</resultMap>


<select id="queryOrderAndUserByOrderNumber" resultMap="orderUserMap" >
        select o.id as oid,     //这里指定的别名 与 resultMap中的id 的column属性进行对应
               o.order_number ,

               u.id as uid,     //与column属性进行对应
               u.user_name,
               u.password,
               u.name,
               u.age,
               u.sex
        from tb_order as o
        inner join tb_user as u on o.user_id = u.id
        where o.order_number = #{orderNumber}
    </select>

<association> 标识两个表的一对一关系

一对多:

主表pojo中加入List<?> 子表对象 来映射

public class User implements Serializable{
    private Long id;
    // 用户名
    private String userName;
    // 密码
    private String password;
    // 姓名
    private String name;
    // 年龄
    private Integer age;
    //0-女 1-男
    private Integer sex;
    //用户1对多订单
    private List<Order> orders;
}

xml中一对多映射

<resultMap id="userOrder" type="User" autoMapping="true">
			<!--resultMap的id为 下面sql语句调用的唯一标识  type为主表pojo对象(这里为User) 
  					autoMapping(true)指除了当前指定的映射集,其他字段的映射自动进行
            resultMap中id 标签对 指当前pojo对象以及对应数据库表中的主键字段
  			-->
      <id column = "uid" property ="id">
        <!--user 的主键为id,这里为了防止id注入相同为id取了别名  
        		property中对应pojo的对象-->

        <!--Order集合关联映射-->
        <collection property="orders" javaType="List" ofType="Order" autoMapping="true">
					<!--property指当前类中的对应映射的成员变量 javaType指当前类型(一对多中的一般为List)
              ofType为其中包含的类型Order, autoMapping    
          -->
          <!--Order的主键 表中的主键,property中对应Order的主键-->
            <id column="oid" property="id"/>
          
        </collection>
</resultMap>


<!--根据用户id查询用户及其订单数据-->
    <select id="getUserOrders" resultType="User" resultMap="userOrders">
        select
            o.id as oid,    -- 别称定义为oid 为了防止出现结果集输出时id注入相同出错
            o.order_number,
            u.id as uid,       -- 别称定义为uid
            u.user_name,
            u.password,
            u.name,
            u.age,
            u.sex
        from tb_user u
        inner join tb_order o on o.user_id = u.id
        where u.id = #{uid}
    </select>

<collection> 标识两个表 一对多的关系

多对多:

多对多 本质上是一对一 - 一对多 -的转换 ,通过一张中间表来进行转换

图解 : 用视图(虚表的概念来帮助理解)

在对应pojo对象中加入对应的对象,还要根据需求来定义

如下需求为 : 根据商品编号查询 详细订单信息 (商品1-n 商品订单详细 商品订单详细1-1订单详细)

如果需求为根据订单编号查询 ,方向是相反的 (订单1-n 商品订单详细 商品订单详细1-1商品详细)

public class Item { //主表
    private Integer id;
    private String itemName;
    private Float itemPrice;
    private String itemDetail;

    //多对多查询
    // 一个订单信息包含多个订单详细信息
    private List<Orderdetail> detailList;
}

//中间表
public class Orderdetail{
    private Integer id;
    private Double totalPrice;
    private Integer status;

    //多对多查询 一条订单详细中包含一条订单记录
    private Order order;
}

//Item 1-n Orderdetail  
//Orderdetail 1-1 Order
//最后被关联表无需修改 

xml实现:

思路:

1.主表 Order (1-n) 映射 Orderdetail (用colloection 标签对)

<!--多对多查询 通过商品编号查询多个商品细节 1个商品细节对应一个订单信息-->
<!--    结果集映射-->
<resultMap id="getDetailByItemId" type="Item" autoMapping="true">
      <!--item的主键(id)以及别名(column)-->
      <id column="itemId" property="id"/>
      <!--一个商品对应多个商品详细-->
      <collection property="detailList" javaType="java.util.List" ofType="Orderdetail" autoMapping="true">
        <!--property为pojo中的对象的名称 -->
        <!--商品详细的主键以及别名 column-主键别名  property-pojo对象中的主键 主键相对应 -->
        <id column="orderDetailId" property="id"/>
        
        <!--一个商品详细表对应一个订单-->
        	<association property="order" javaType="Order" autoMapping="true">
          		<!--property为pojo中的对象的名称 -->
            	<!--订单表的主键以及别名-->
            	<id column="orderId" property="id"/>
        	</association>
      </collection>
</resultMap>

<select id="getDetailByItemId" resultMap="getDetailByItemId" resultType="item">
        select
               tb_item.id as itemId,     -- 取别名 与上面的id内的column对应
               tb_item.item_name,
               tb_item.item_price,
               tb_item.item_detail,
               tb_orderdetail.id as orderDetailId,  -- 取别名 与上面的id内的column对应
               tb_orderdetail.total_price,
               tb_orderdetail.status,
               tb_order.id as orderId,     -- 取别名 与上面的id内的column对应
               tb_order.user_id,
               tb_order.order_number

        from tb_item
                 inner join tb_orderdetail on tb_item.id = tb_orderdetail.item_id
                 inner join tb_order on tb_orderdetail.order_id = tb_order.id
        where tb_item.id = #{itemId};
    </select>

多表查询(注解实现-主要是子查询嵌套的理念)

多表查询注解开发思路

多表连接设计实现的过程:

1.查询sql分析(注解实现,最好的理解是用子查询来理解)

2.在对应pojo对象中添加关联关系(这一步与xml文件内理解一样)

3.编写接口方法(需要额外写 one和many字段的子查询的 对应的方法 ,相当于子表)

4.编写结果映射集(或者是直接写注解)

@Results 下,用@Result来指定映射

@Result内常用字段如下: column 指定数据库的列名,property指定pojo对应的属性字段名,

一对一中(等效于<assocation/>标签对): one = @One(select="" )

一对多中(等效于<colloection/>标签对):many = @Many(select="" )

@One和@Many中传递的参数都是被关联pojo对象对应Mapper中的方法。

我的理解是,one中传递的是查询出一个对象的值,many是查询出多个对象(集合)的值。

在装配时,将对应pojo对象中的值装配进去,与xml不同的是,纯注解开发在select字段时,是不需要将全部字段列出的,包括多对多,xml需要标明字段的不同(比如id会重名,导致结果集映射时,找不到对应的id)

在实现时,用子查询的方式来理解更为清晰 以及好理解,与xml中直接映射不同

数据准备

一对一:

1.需求:根据订单编号查询订单和用户信息

这里从tb_order找tb_user是一对一的

select * from tb_order
where user_id in (select id from tb_user)
and order_number = #{order_number};

2.在pojo对象中写相应的映射

这里在 order中加入user对应的映射对象

public class Order {

    private Integer id;
    private String orderNumber;
    //1-1 关联用户对象
    private User user;
}

3.在接口中写对应的方法

这里因为有子查询的存在,需要在UserMapper中写一个对应的查询,也就是上述查询语句中小括号内的内容

在UserMapper中相当于需要查询id字段的内容,进行以下转换

因为在进行映射时,该从表user的主键id与order的user_id 相对应,是通过user的id来关联order相应字段的

(select id from tb_user) => select * from tb_user where id =#{id}

在UserMapper中

public interface UserMapper {
    //一对一中的字段
    //根据 userId 查询字段 在 order中为user_id
    @Select("select * from tb_user where id = #{id}")
    User selectById(Integer id);
}

在OrderMapper中

public interface OrderMapper {
    //注解开发多表查询
    //1.一对一
    //显示所有订单信息和用户信息

    //定义映射集 - 本质上是子查询的嵌套
    @Select("select * from tb_order where order_number=#{order_number}")
    @Results({
            //与resultMap中类似 column 代表sql表中字段, property代表pojo中字段
            @Result(column = "id" ,property = "id"),
            @Result(
                    property = "user",         //被包含对象的变量名
                    javaType = User.class,     //被包含对象的实际类型
                    column = "user_id",        //根据查询出的Order表中的user_id来查询User表
                    //相当于指定从表的主键 是select语句中的 where后的字段名
                    /**
                     * one, @One 一对一的固定写法
                     * select 属性 : 指定调用 哪个接口 中的哪个方法
                    */
                    one = @One(select ="com.itheima.dao.db6_dao.UserMapper.selectById")
                    //子查询中 另外一张表对主键的查询
                    //select 语句为
                    //select * from tb_order
                    //where user_id in (select id from tb_user);
            )
    })
    List<Order> selectAll(Integer order_number);

理解上是从子表的查询开始理解,比较合理,子查询也可以很好理解。

但是mybatis在执行时,是从正向执行的。

这里测试的时候没有加入额外的约束条件,是查询所有的订单和用户信息,

从订单出发的查询所有用户信息,相当于一个左外连接(订单在左,用户在右)。

select * from tb_order
where user_id in (select id from tb_user);

以下是mybatis的执行过程:

mybaits首先准备 select * from tb_order这个预编译语句,然后发现当前没有参数后,直接执行,查询出结果;

通过结果集映射发现当前还有一个属性需要映射,通过one得到这个映射调用的子查询方法,如下

然后通过column指定的user_id来获得当前参数的值,从数据库中查出对应的数据,再将数据装到当前对象中,这里子查询对象为映射集指定的javaType=User.class,以及在主表中的属性名为user,这些都是@Result中指定的,这里因为结果不是一条,所以同样的操作执行了多次。

一对多:

1.需求:通过用户名查询用户名和订单信息

tb_user(1-n)tb_order

select * from tb_user
where id in (select user_id from tb_order)
and user_name =#{user_name};

2.pojo对象中加入相应映射

public class User implements Serializable{
    private Long id;
    // 用户名
    private String userName;
    // 密码
    private String password;
    // 姓名
    private String name;
    // 年龄
    private Integer age;
    //0-女 1-男
    private Integer sex;
    //1-n
    //关联一对多的对象
    private List<Order> orders;
}

3.在接口中写对应的方法

这里再OrderMapper中写:根据user_id查询到多个Order的方法

(select user_id from tb_order) => select * from tb_order where user_id = #{id}

OrderMapper中的方法

public interface OrderMapper{
    //2.一对多     user (1-n) orders
    //select 语句
    //select * from tb_user
    //where id in (select user_id from tb_order)
    //这里实现的是:select user_id from tb_order
    //所以对应子查询查询出来的应该是对应的user_id的列 ,用于在result中映射
    @Select("select * from tb_order where user_id = #{id}")
    //预编译的id只是一个传入的参数 不是order的主键值
    List<Order> selectByUserId(Integer id);
}

UserMapper中的方法

//2.一对多
    @Select("Select * from tb_user where user_name=#{user_name}")
    //映射集
    @Results({
            //column指定表中的字段名  property 指定pojo对象中的对应的字段名
            @Result(column = "id", property = "id"),
            @Result(
                    column = "id",                    //根据查询出的user表的id字段去查询order表
                    //select * from tb_user           //这里指的是where后的id
                    //where id in (select user_id from tb_order)
                    javaType = List.class,            //java中的对象的类型
                    property = "orders",              //pojo关联对象中的对象名
                    /**
                     * many = @Many,一对多的固定写法,
                     * select属性,指定调用哪一个接口中的哪个查询方法
                    */
                    many = @Many(select = "com.itheima.dao.db6_dao.OrderMapper.selectByUserId")
                    //select语句
                    //select * from tb_user
                    //where id in (select user_id from tb_order)
            )
    })
    List<User> selectAll(String user_name);

mybaits内的执行过程,与一对一的例子一样,在测试时,用的直接查询所有数据,

这里不难看出,多表映射集中result 中的 column指的是当前where 后的字段名,根据查询出的id去查询order表中user_id= 当前id的值

select * from tb_user
where id in (select user_id from tb_order)

mybatis的大致执行流程如下

一对多与一对一关系的区别就在于,在子查询那个Mapper对应方法时,返回的是多个数据,用List接收,pojo中也是;而一对一中直接用对应对象, 和 pojo中一样映射。

多对多:

1.需求:通过订单编号 查询订单详细以及商品详细

order (1-n) orderdetail (1-1) item

sql语句如下

select * from tb_order
where id in (select order_id from tb_orderdetail 
             where item_id in(select id from tb_item))
where order_number = #{order_number};

2.pojo对象中加入相应映射

public class Order {
    private Integer id;
    private String orderNumber;
    //n-n 关联订单信息
    //order(1-n) orderdetail 
    //一个订单有多个订单详细
    private List<Orderdetail> detailList;
}

public class Orderdetail { 
    private Integer id;
    private Double totalPrice;
    private Integer status;
    
    //多对多中的一对一
    //一个订单详细对应一个Item对象
    private Item item;
}

3.在接口中写对应的方法

建议书写顺序,从最内层的子查询写,这里一共两个子查询

select * from tb_order
where id in (select order_id from tb_orderdetail 
             where item_id in(select id from tb_item))

-- 第一个子查询
(select id from tb_item)

先写第一个子查询方法对应的接口方法ItemMapper的方法

//内部的 item和orderdetail是一对一的
public interface ItemMapper{
	@Select("select * from tb_item where id = #{id}")
	Item selectByItemId(Integer id);
}

第二个子查询

select * from tb_order
where id in (select order_id from tb_orderdetail 
             where item_id in(select id from tb_item))

-- 第二个子查询 这里不考虑内部的子查询 
(select order_id from tb_orderdetail 
             where item_id in(select id from tb_item))

第二个子查询对应的接口方法OrderDetailMapper

public interface OrderDetailMapper{
    @Select("select * from tb_orderdetail where order_id= #{order_id}")
	@Results({
        @Result(
            column = "item_id", //column字段是表中的字段 这里多为where后的字段
            //查询完表后的 item_id对应的字段
            javaType = Item.class,
            property = "item", //orderdetail 这个pojo中的对应的字段名
            one = @One(select="com.itheima.dao.db6_dao.ItemMapper.selectByItemId")
        )
    })
    List<Orderdetail> OrderdetailToItem(Integer order_id);
}

最后一个查询对应的接口方法

public interface OrderMapper{
    //3.多对多      order(1-n) orderdetail (1-1) item
    //select * from tb_order
    //where id in (select order_id from tb_orderdetail
    //                             where item_id in (select id from tb_item));
    //select写出来后, pojo 对象是正向映射, resultMap中的方法是逆向书写
    //从最后一个子查询往前写,层层调用

    //最后一步 多对多


    //结果如下:
    //Order{id=1, orderNumber='20140921001', user=null,
    //          detailList=[Orderdetail [id=1, totalPrice=5288.0, status=1],
    //                      Orderdetail [id=2, totalPrice=6288.0, status=1]]}
    //Order{id=2, orderNumber='20140921002', user=null,
    //          detailList=[Orderdetail [id=3, totalPrice=6288.0, status=1]]}
    //Order{id=3, orderNumber='20140921003', user=null,
    //          detailList=[Orderdetail [id=4, totalPrice=5288.0, status=1]]}
    @Select("select * from tb_order")
    @Results({
            //column中指的是表中的字段   property指的是pojo对象中的对应字段名
            @Result(
                    column = "id",
                    //根据查询出来的order表中的id 与 子查询中的结果进行对比
                    javaType = List.class,
                    property = "detailList",
                    //pojo中的对应的名称
                    many = @Many(select = "com.itheima.dao.db6_dao.OrderDetailMapper.OrderdetailToItem")
                    //对应的方法
            )
    })
    List<Order> selectAllOrderAndItem();
}

多表查询(对比以及混用思路)

注解开发

1.简单的增删改查

2.在xml中写映射resultMap结果集,然后用@ResultMap来映射结果集感觉也是很好用的办法

3.需要实现子查询效果时

xml开发

1.多表查询,映射方便,清晰明了

2.可以实现动态sql,如果用注解开发的话,要多写一个ProviderUtils类调用方法。

动态sql(xml实现)

where

where常与if一起连用,防止所有条件为空出现多余的where和and

<!--查询用户名和地址 ,可以允许任意一个条件为空-->
    <select id="queryByUserNameAndAddress" resultType="User">
        select * from user
        <where>
            <if test="userName != null and userName.trim()!=''">
                username = #{userName}
            </if>
            <if test="address != null and address.trim()!=''">
                and address = #{address}
            </if>
        </where>
    </select>

if

<!--查询男性用户,如果输入了用户名,按用户名模糊查询,
    如果没有输入用户名,就查询所有男性用户。-->
  <select id="queryLikeUserName" resultType="user">
        select * from user where sex='男'
        <if test="userName!=null and userName.trim()!= ''">
            and username like concat('%',#{userName},'%')
        </if>
  </select>

chose-when-otherwise

chose-when-otherwise相当于java中的case 条件选择

    <select id="queryByUserNameOrAddress" resultType="User">
        select * from user where sex='男'
        <choose>
            <when test="userName!=null and userName.trim()!=''">
            and username like concat('%',#{userName},'%')
            </when>
            <when test="address!=null and address.trim()!=''">
            and address  = #{address}
            </when>
            <otherwise>
            and username = '孙悟空'
            </otherwise>
        </choose>
    </select>

set

在修改数据时,希望用户修改值时可以仅仅修改一个,其他的条件可以不修改。

如果不用动态sql会导致用户没有输入修改的字段为空,更新到数据库时这个字段也会为空

<update id="updateSelectiveUser">
        update user
        <set>
            <if test="username!=null and username.trim()!=''">
                username = #{username},
            </if>
            <if test="birthday!=null">
                birthday = #{birthday},
            </if>
            <if test="sex !=null and sex.trim()!=''">
                sex = #{sex},
            </if>
            <if test="address != null and address.trim()!= ''">
                address = #{address},
            </if>
        </set>
        where id = #{id}
    </update>

foreach

希望用户可以进行批量添加和批量删除的操作

<!--    案例:
        按照id值是1,2,3来查询用户数据;
        select * from user where id in (1,2,3);
        编译后为
        select * from user where id in ( ? , ? , ? )
        -->
    <select id="queryByIds" resultType="User">
        select * from user where id in
        <foreach
                collection="arrIds"
                item="ID"
                separator=","
                open="("
                close=")">
        	#{ID}
        </foreach>
    </select>

动态sql(Provider工具类实现-注解@SelectProvider)

注解

@SelectProvider :type = "调用的工具类的class对象",method = "工具类中的对象名"

注意 : 用该种方法开发时,注意Mapper接口中方法的参数要与工具类中的参数一致

Mapper接口中方法写法

@SelectProvider(type = ProviderUtils.class,method = "queryUserBySexOrUsernameSQL2")
List<User> queryUserBySexOrUsernameSQL2(@Param("sex") String sex,@Param("username") String username);

ProviderUtil中的工具类

public class ProviderUtils {
//方式3 使用mybatis提供的一个对象 SQL
    public String queryUserBySexOrUsernameSQL2(@Param("sex") String sex,@Param("username") String username){
        //创建SQL对象
        SQL sql = new SQL();
        //链式编程,每个方法返回值都是sql对象
        sql.SELECT("*").FROM("user").WHERE("sex = #{sex}");
        //判断用户是否为空 不为空就继续链式编程,即继续拼接
        if (username != null && !"".equals(username)){
            sql.WHERE("username like concat('%',#{username},'%')");
        }

        //select * from user
        //转换为字符串并返回
        return sql.toString();
    }

	//也可以实现批量删除
	//批量删除
    public String delete(@RequestBody List<Integer> ids){
        SQL sql = new SQL();
        sql.DELETE_FROM("emp");
        //预编译结果达成 delete from emp where id in (?,?,?)
        StringJoiner sj = new StringJoiner(",","(",")");
        if (ids.size() > 0 && ids !=null){
            for (Integer id : ids) {
                sj.add(id.toString());
            }
            sql.WHERE("id in"+sj.toString());
        }
        return sql.toString();
    }
    
}

总结:理论上生成的sql语句满足动态就可以,这种写法更接近于java中的语法思路,只要条件满足就可以直接添加,原理是返回一个预编译的sql语句

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值