mybatis进阶学习

mybatis进阶学习

内容介绍

  1. 动态sql
  2. mybatis中多表查询
  3. mybatis中嵌套查询
  4. 懒加载(延迟加载)
  5. 缓存

一 映射文件-动态sql

当我们要根据不同的条件,来执行不同的sql语句的时候,需要用到动态sql。

例如:多条件查询,修改密码和修改用户基本信息

1 if标签

需求:查询用户,若usename不为空添加username条件,若sex不为空添加sex条件

public interface UserDao {
    List<User> find4If(User user);
}
    <select id="find4If" resultType="user">
        select * from user where 1=1
        <if test="username != null and username != ''">
            and username = #{username}
        </if>
        <if test="sex != null and sex != ''">
            and sex = #{sex}
        </if>
    </select>
@Test
public void testIf() {
    //0. 模拟条件
    User user = new User();
    //user.setUsername("王小二");
    user.setSex("女");

    //1.获取sqlsession
    SqlSession sqlSession = MybatisUtils.openSession();
    //2.获取dao
    UserDao userDao = sqlSession.getMapper(UserDao.class);
    //3.调用dao中方法
    List<User> list = userDao.find4If(user);
    for (User user1 : list) {
        System.out.println(user1);
    }
    //4.提交事务 释放资源
    MybatisUtils.commitAndClose(sqlSession);
}

​ 注意: sql中判断并且的时候不要使用 “&&” , 应该使用 “and”

2 where标签

我们使用where之后就不需要再写1=1(处理第一个条件and或者or),可以判断是否所有的条件都不成立,若都不成立就不加where ;若有一个成立 就添加where 且还会把第一个条件前面的and或者or处理掉

需求:根据用户名性别查询用户,有哪些条件就添加那些条件,没有就查询所有

 List<User> find4IfAndWhere(User user);
  <select id="find4IfAndWhere" resultType="user">
        select * from user
        <where>
            <if test="username != null and username != ''">
                and username = #{username}
            </if>
            <if test="sex != null and sex != ''">
                and sex = #{sex}
            </if>
        </where>
    </select>
    @Test
    public void testIfAndWhere() {
        //0. 模拟条件
        User user = new User();
        user.setUsername("王小二");
        user.setSex("女");

        //1.获取sqlsession
        SqlSession sqlSession = MybatisUtils.openSession();
        //2.获取dao
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        //3.调用dao中方法
        List<User> list = userDao.find4IfAndWhere(user);
        for (User user1 : list) {
            System.out.println(user1);
        }
        //4.提交事务 释放资源
        MybatisUtils.commitAndClose(sqlSession);
    }

3 set标签

需求:根据id更新用户信息,那些属性有值就更新那些属性

set标签可以将set中最后一个字段的","去掉

	int update4Set(User user);
   <update id="update4Set">
        update user
        <set>
            <if test="username != null and username != ''">
                username = #{username},
            </if>
            <if test="sex != null and sex != ''">
                sex = #{sex},
            </if>
            <if test="birthday != null">
                birthday = #{birthday},
            </if>
            <if test="address != null and address != ''">
                address = #{address},
            </if>
        </set>
        where id = #{id}
    </update>
@Test
public void testSet() {
    //0. 模拟条件
    User user = new User();
    user.setId(50);
    user.setUsername("睡觉1");
    user.setSex("男");
    user.setBirthday(new Date());

    //1.获取sqlsession
    SqlSession sqlSession = MybatisUtils.openSession();
    //2.获取dao
    UserDao userDao = sqlSession.getMapper(UserDao.class);
    //3.调用dao中方法
    userDao.update4Set(user);
    //4.提交事务 释放资源
    MybatisUtils.commitAndClose(sqlSession);
}

## 4 foreach标签

foreach主要是用来做数据的循环遍历

例如:`select * from user where id in (1,2,3) `在这样的语句中,传入的参数部分必须依靠 foreach遍历才能实现。

> <foreach>标签主要用于遍历集合,它的属性:
>     • collection:代表要遍历的集合元素
>     	若遍历集合,属性值为colletion或者list
>     	若遍历数组,属性值为array
>     	若遍历对象中的list或者array,属性值为对象中属性名
>     • open:代表语句的开始部分
>     • close:代表结束部分
>     • item:代表遍历集合的每个元素,生成的变量名
>     • separator:代表分隔符

### 遍历list
```java
    List<User> find4ForeachByList(List ids);
    <select id="find4ForeachByList" resultType="user">
        select * from user where id in
        <foreach collection="list" open="(" close=")" separator="," item="n">
            #{n}
        </foreach>
    </select>

遍历数组

    List<User> find4ForeachByArray(int[] ids);
    <select id="find4ForeachByArray" resultType="user">
        select * from user where id in
        <foreach collection="array" open="(" close=")" separator="," item="n">
            #{n}
        </foreach>
    </select>

遍历bean中的list或者数组属性

    List<User> find4ForeachByQueryVo(QueryVo vo);
<select id="find4ForeachByQueryVo" resultType="user">
    select * from user where id in
    <foreach collection="ids" open="(" close=")" separator="," item="n">
        #{n}
    </foreach>
</select>

QueryVo类

/*
    vo:value object 值对象(封装数据的对象)
 */
public class QueryVo {
    private int[] ids;

    public int[] getIds() {
        return ids;
    }

    public void setIds(int[] ids) {
        this.ids = ids;
    }
}

5 sql片段

映射文件中可将重复的 sql 提取出来,使用时用 include 引用即可,最终达到 sql 重用的目的

例如:查询的时候,开发中一般不使用select * 而使用的 select 字段名,这样话效率比较高效.

<sql id="BaseSelectSQL">
    select id,username,birthday,sex,address from user
</sql>

<select id="find4ForeachByQueryVo" resultType="user">
    <include refid="BaseSelectSQL"/>
    where id in
    <foreach collection="ids" open="(" close=")" separator="," item="n">
        #{n}
    </foreach>
</select>

二 多表关系映射及查询

1 实体关系

表关系:

  • 一对多(多对一) : 部门和员工 分类和线路 线路和线路图片 商家和线路 用户和订单
  • 多对多 : 用户收藏线路 用户和角色 学生和课程 订单和商品
  • 一对一 : 公民和身份证号 丈夫和妻子

表设计:

  • 一对多:一般在多表上添加一个外键字段,指向一表的主键
  • 多对多:添加一个中间表,表中至少保留另外俩表的主键,可以将多对多拆分成俩一对多
  • 一对一:
    • 俩表合二为一
    • 唯一外键对应
    • 主键对应

java实体关系和设计:

从实际业务角度出发的话,只存在一对多和一对一这两种情况

一对多关系来说:在当前类中存放对方的集合

一对一关系来说:在当前类中存放对方的对象

2 一对多

需求 : 查询id=41的用户的信息及其订单信息

sql分析:

SELECT u.*,o.id oid,o.ordertime,o.money
FROM USER u
LEFT JOIN orders o
	ON u.id = o.uid
WHERE u.id = 41;

分析:

返回的应该是一个用户对象.对象中包含ta的订单的信息

步骤分析:

  1. 把orders表创建出来,把Order实体类创建出来
  2. 在User类中添加订单的集合(Order),提供get和set方法 表示用户有那些订单
  3. 在UserDao中添加方法 User findUserByUidWithOrders(int uid)
  4. 在UserDao的映射文件中提供statement标签
    • 处理一对多

代码实现:

1.创建orders表

CREATE TABLE `orders` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `uid` int(11) DEFAULT NULL,
  `ordertime` datetime DEFAULT NULL,
  `money` double DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `uid` (`uid`),
  CONSTRAINT `orders_ibfk_1` FOREIGN KEY (`uid`) REFERENCES `user` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

/*Data for the table `orders` */

insert  into `orders`(`id`,`uid`,`ordertime`,`money`) values (1,41,'2019-05-20 02:58:02',999.5),(2,45,'2019-02-14 07:58:00',1399),(3,41,'2019-06-01 21:00:02',1666);

2.编写Order实体

public class Order {
    private Integer id;
    private Date orderTime;
    private Double money;
    //get和set自己生成
}

3.在OrderDao中编写方法

    User findUserByUidWithOrders(int uid);

4.在OrderDao.xml中编写一对多的映射

<resultMap id="WithOrdersMap" type="user">
        <id property="id" column="id"/>
        <result property="username" column="username"/>
        <result property="birthday" column="birthday"/>
        <result property="sex" column="sex"/>
        <result property="address" column="address"/>

        <!--
            一对多的映射
                将查询的行记录封装成订单对象,然后加入user的orderList属性中
                property:对方集合在当前类中的属性名称
                ofType:对方的全限定类名-支持别名
        -->
        <collection property="orderList" ofType="order">
            <!--查询结果和订单对象的映射关系-->
            <id property="id" column="oid"/>
            <result property="orderTime" column="ordertime"/>
            <result property="money" column="money"/>
        </collection>

    </resultMap>
    <select id="findUserByUidWithOrders" resultMap="WithOrdersMap">
        SELECT u.*,o.id oid,o.ordertime,o.money
        FROM USER u
        LEFT JOIN orders o
            ON u.id = o.uid
        WHERE u.id = #{uid};
    </select>
public class B_TestManyTable {
    @Test
    public void testOne2Many() {
        //获取session
        SqlSession sqlSession = MybatisUtils.openSession();

        //获取dao
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        //调用dao方法
        User user = userDao.findUserByUidWithOrders(41);
        System.out.println(user);
        List<Order> orderList = user.getOrderList();
        if (orderList != null) {
            for (Order order : orderList) {
                System.out.println(order);
            }
        }

        //提交事务
        MybatisUtils.commitAndClose(sqlSession);
    }
}

3 一对一

需求 : 查询id=1的订单信息及其所属用户信息

sql分析:

SELECT o.id oid,o.ordertime,o.money,u.*
FROM orders o
JOIN USER u
	ON o.uid = u.id
WHERE o.id = 1;

返回一个订单对象,订单的信息封装给订单对象,用户的信息封装给订单中的用户对象

步骤分析:

  1. 在Order类中添加一个用户对象,用来表示当前订单属于那个用户
  2. 编写OrderDao接口,提供对应的映射文件
  3. 在OrderDao中编写方法 findOrderByOidWithUser,及其在映射文件中提供相应的statement标签即可

方式1:使用resultMap进行封装结果集

  • 在OrderDao提供方法 Order findOrderByOidWithUser_1(int oid);
  • 在OrderDao.xml提供相应的statement标签

方式2:使用resultMap+association标签(一对一映射配置)完成封装

  • 在OrderDao提供方法 Order findOrderByOidWithUser_2(int oid);
  • 在OrderDao.xml提供相应的statement标签

方式1:使用resultMap进行封装结果集

public interface OrderDao {
    Order findOrderByOidWithUser_1(int oid);
}
<resultMap id="WithUser_1" type="order">
    <id property="id" column="oid"/>
    <result property="orderTime" column="ordertime"/>
    <result property="money" column="money"/>

    <result property="user.id" column="id"/>
    <result property="user.username" column="username"/>
    <result property="user.birthday" column="birthday"/>
    <result property="user.sex" column="sex"/>
    <result property="user.address" column="address"/>
</resultMap>

<select id="findOrderByOidWithUser_1" resultMap="WithUser_1">
    SELECT o.id oid,o.ordertime,o.money,u.*
    FROM orders o
    JOIN USER u
    ON o.uid = u.id
    WHERE o.id = #{oid};
</select>

方式2:使用resultMap+association标签(一对一映射配置)完成封装

public interface OrderDao {
    Order findOrderByOidWithUser_1(int oid);
    Order findOrderByOidWithUser_2(int oid);
}
<resultMap id="WithUser_2" type="order">
    <id property="id" column="oid"/>
    <result property="orderTime" column="ordertime"/>
    <result property="money" column="money"/>

    <!--
            一对一的映射:将查询出来的对象的信息封装指定的对象
                property:对方在当前类中的属性名
                javaType:对象的全限定类名
        -->
    <association property="user" javaType="user">
        <id property="id" column="id"/>
        <result property="username" column="username"/>
        <result property="birthday" column="birthday"/>
        <result property="sex" column="sex"/>
        <result property="address" column="address"/>
    </association>

</resultMap>
<select id="findOrderByOidWithUser_2" resultMap="WithUser_2">
    SELECT o.id oid,o.ordertime,o.money,u.*
    FROM orders o
    JOIN USER u
    ON o.uid = u.id
    WHERE o.id = #{oid};
</select>

三 嵌套查询

将多表查询的复杂sql语句拆分成多个简单的查询语句,再由mybatis框架进行嵌套

举个栗子

* 需求:查询一个订单,同时查询该订单的用户

* 联合查询:
select *
from orders o
join user u
	on o.uid = u.id
where o.id = 1;
		
* 嵌套查询
1.先查询订单信息
select * from orders where id = 1;-- 可以查询到uid = 41;
2.在查询用户信息
select * from user where id = 41;-- 根据上面查询的结果来查询用户

嵌套查询是延迟加载的基础.

1 一对一

需求:查询一个订单,与此同时查询出该订单所属的用户

1.先查询订单信息
select * from orders where id = 1;-- 可以查询到uid = 41;
2.在查询用户信息
select * from user where id = 41;-- 根据上面查询的结果来查询用户

步骤分析

  1. 在UserDao中提供一个方法: User findUserByUid(int uid);
  2. 在UserDao.xml中提供上述方法的statement即可
  3. 在OrderDao中提供一个方法: Order findOrderByOidWithUser_QianTao(int oid)
  4. 在OrderDao.xml中提供上述方法的statement
    • 要配置嵌套查询,将查询订单信息的结果中uid作为UserDao中查询用户信息条件,最后将用户信息封装给订单中的用户

代码实现

UserDao

User findUserByUid(int uid);

UserDao.xml配置如下

<select id="findUserByUid" resultType="user">
    select * from user where id = #{uid}
</select>

OrderDao

Order findOrderByOidWithUser_QianTao(int oid);

OrderDao.xml中代码

<resultMap id="WithUser_QianTao" type="order">
    <id property="id" column="id"/>
    <result property="orderTime" column="ordertime"/>
    <result property="money" column="money"/>
    <!--
            一对一的嵌套查询
            将查询结果中的uid获取到,作为userDao中某个statement的条件查询数据,结果类型为user,赋值给order中的user对象
        -->
    <association property="user" column="uid" select="com.itheima.dao.UserDao.findUserByUid" javaType="user"/>
</resultMap>
<select id="findOrderByOidWithUser_QianTao" resultMap="WithUser_QianTao">
    select * from orders where id = #{oid}
</select>

2 一对多

需求:查询一个用户,与此同时查询出该用户具有的订单

1.先根据用户的id 查询出用户的信息
	select * from user where id = 41;
2.再根据用户的id 查询订单表中当前用户的所有订单
	select * from orders where uid = 41;

步骤分析

  1. 在OrderDao中提供方法: List findAllOrdersByUid(int uid);
  2. 在OrderDao.xml中提供上述方法对应的statement即可
  3. 在UserDao中提供方法: User findUserByUidWithOrders_QianTao(int uid);
  4. 在UserDao.xml中配置上述方法的statement
    • 配置嵌套查询,将用户的id作为查询订单的条件,查询所有的订单

代码实现

OrderDao

List<Order> findAllOrdersByUid(int uid);

OrderDao.xml

<select id="findAllOrdersByUid" resultType="order">
    select * from orders where uid = #{uid}
</select>

UserDao

User findUserByUidWithOrders_QianTao(int uid);

UserDao.xml

<resultMap id="WithOrdersMap_QianTao" type="user">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <result property="birthday" column="birthday"/>
    <result property="sex" column="sex"/>
    <result property="address" column="address"/>

    <!--
            一对多的嵌套查询
            在查询的结果中获取column对应的值(id)作为查询条件,去执行orderDao中某个statement,将查询的结果赋值给用户中订单集合
        -->
    <collection property="orderList" ofType="order" column="id"
                select="com.itheima.dao.OrderDao.findAllOrdersByUid"/>
</resultMap>
<select id="findUserByUidWithOrders_QianTao" resultMap="WithOrdersMap_QianTao">
    select * from user where id = #{uid}
</select>

3 知识小结

一对一(多对一)配置:使用<resultMap>+<association>做配置,通过column条件,执行select查询

一对多(多对多)配置:使用<resultMap>+<collection>做配置,通过column条件,执行select查询

优点:简化多表查询操作,是懒加载的基础

缺点:执行多次sql语句,浪费数据库性能

四 加载策略

在企业开发时,不是在查询所有的数据的时候都会去查询关联数据,这个时候就要用到延迟加载机制

* 一个用户,该用户下有1000个订单
	我们在查询用户的时候,要不要把关联的订单一起查询? 不用
	我们在查询订单详情的时候,要不要把关联的用户一起查询?用

	
* 延迟加载策略
	就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载。
	
* 应用场景
	一对多或者多对多来说,一般采用懒加载去查询关联数据
	一对一或者多对一来说,一般采用立即加载去查询关联数据
* 注意
	懒加载是基于嵌套查询实现的

分类:

  • 全局懒加载
  • 局部懒加载
  • 注意:
    • 都需要手动去开启
    • 局部懒加载优先级高于全局懒加载

1 全局延迟加载

在核心配置文件中,设置全局延迟加载开启

    <settings>
        <!--开启全局懒加载-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!--设置任何方法都不触发懒加载,除属性的get方法-->
        <setting name="lazyLoadTriggerMethods" value=""/>
    </settings>

在debug的时候或者直接打印改对象的时候,会执行对象的toString方法,这时候会触发延迟加载,所有我们还需要在核心配置文件中配置toString方法不触发懒加载

注意:局部加载策略优先于全局加载策略,通常可以将一对一设置为立即加载

2 局部延迟加载

<association> 和 <collection>标签,有一个共同的属性
	fetchType="lazy | eager"
		lazy 懒加载
		eager 立即加载
<association property="user" javaType="cn.itcast.domain.User" column="uid"
                     select="cn.itcast.dao.UserDao.findUserByUid" fetchType="eager"/>
<collection property="orderList" ofType="cn.itcast.domain.Order" column="id"
                    select="cn.itcast.dao.OrderDao.findAllOrdersByUid" fetchType="lazy"/>

五 缓存

什么是缓存?

  • 存储在内存中的数据

为什么使用缓存?

  • 因为查询内存中的数据效率高于查询磁盘中的数据

什么样的数据适合使用缓存?

  • 经常访问 不会经常修改的数据且不太敏感使用缓存

什么样的数据不适合使用缓存?

  • 敏感度比较高的数据 : 银行账户、商品数量、股票牌价

MyBatis是一款优秀的ORM框架,内置缓存机制

  • 一级缓存:默认开启,且不能关闭,SqlSession级别的缓存,和SqlSession的生命周期一样.
  • 二级缓存:默认开启,要想使用还需要进行一些配置,映射器级别(SqlSessionFactory级别)的缓存

1 一级缓存

一级缓存是SqlSession级别的缓存,是默认开启且无法关闭的。

  • 查询数据的时候优先去缓存中查询数据,若缓存中直接返回;若缓存中没有数据,去磁盘中查询数据.查询到的数据根据一定的规则放入缓存中,方便下次查询使用;
  • 当session关闭的时候或者清空的时候clearCache()就是把session的一级缓存清空
  • 清空缓存的操作:保存,更新,删除操作(为了保证数据的一致性)
  • 一级缓存中存放的是查询出来的对象

演示一级缓存:在一个sqlSession查询一个对象两次即可

    @Test
    //测试一级缓存的存在:在一个sqlSession中查询某个对象两次
    public void testFirstCache() {
        SqlSession sqlSession = MybatisUtils.openSession();

        UserDao userDao = sqlSession.getMapper(UserDao.class);
        User user1 = userDao.findUserByUid(42);
        System.out.println(user1);

        System.out.println("------再次查询-------");

        User user2 = userDao.findUserByUid(42);
        System.out.println(user2);

        System.out.println(user1 == user2);

        MybatisUtils.commitAndClose(sqlSession);
    }

演示清空一级缓存:

	@Test
    //测试清除一级缓存
    public void testClearFirstCache() {
        SqlSession sqlSession = MybatisUtils.openSession();

        UserDao userDao = sqlSession.getMapper(UserDao.class);
        User user1 = userDao.findUserByUid(42);
        System.out.println(user1);

        System.out.println("------清空缓存-------");
        sqlSession.clearCache();


        System.out.println("------再次查询-------");

        User user2 = userDao.findUserByUid(42);
        System.out.println(user2);

        System.out.println(user1 == user2);

        MybatisUtils.commitAndClose(sqlSession);
    }

2 二级缓存

二级缓存是属于Mapper映射器级别,需要手动开启<cache>,还需要让实体类实现序列化serializable接口

二级缓存是跨sqlSession的.使用同一个sqlSession,不会去二级缓存中查询.

当sqlsession关闭的时候,mybatis会把数据放到二级缓存中去.

二级缓存只保存数据,不保存对象.

使用步骤

1. 在核心配置文件开启二级缓存的支持【默认开启】
	    <!--开启二级缓存支持-->
        <setting name="cacheEnabled" value="true"/>
2. 在指定Mapper映射文件中开启二级缓存 <cache/>
		<mapper namespace="cn.itcast.dao.UserDao">
             <!--当前映射开启二级缓存-->
             <cache/>
3. 修改实体类实现serializable序列化接口

4. 测试二级缓存
@Test
//测试二级缓存的存在:使用两个sqlSession中查询某个对象两次,切记,第一个sqlsession查询完毕之后需要先关闭
public void testSecondCache() {
    SqlSession sqlSession1 = MybatisUtils.openSession();

    UserDao userDao1 = sqlSession1.getMapper(UserDao.class);
    User user1 = userDao1.findUserByUid(42);
    System.out.println(user1);

    //关闭sqlSession
    MybatisUtils.commitAndClose(sqlSession1);

    System.out.println("------再次查询-------");

    SqlSession sqlSession2 = MybatisUtils.openSession();

    UserDao userDao2 = sqlSession2.getMapper(UserDao.class);
    User user2 = userDao2.findUserByUid(42);
    System.out.println(user2);

    System.out.println(user1 == user2);

    //关闭sqlSession
    MybatisUtils.commitAndClose(sqlSession2);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值