5/12day52_mybatis嵌套查询&缓存

MyBatis嵌套查询&缓存.png

回顾

1. 单表高级查询
    resultMap标签:手动映射封装
    多条件查询
        @param("xx")  #{xx}
        User          #{属性名}
    like模糊匹配:concat() 函数拼接
        
2. mapper映射文件深入
    返回主键: select late_insert_id 
    动态sql
        if 判断
        where 条件拼接(去掉 前置 and | or)
        set 更新拼接(去掉 后置 ,)
        foreach 循环
            普通list  collection="list"
            普通array collection="array"
            实体list属性 collection="属性名"
    sql片段:抽取公共sql,提高复用性
3. 多表回顾
    数据库表关系:主外键
    实体(模型)关系:属性

4. 多表查询
    一对一
    一对多
    多对多
        看老师发的画图

MyBatis嵌套查询&缓存

今日目标

1. 嵌套查询

2. 加载策略
    立即加载
    延迟加载【讲】
    
3. 缓存:提高查询效率
    一级缓存
    二级缓存
    
4. 回顾核心配置文件常用标签

一 MyBatis嵌套查询

1.1 什么是嵌套查询

嵌套查询就是将原来多表查询中的联合查询语句拆成==多个单表的查询==,再使用mybatis的语法嵌套在一起。

举个栗子

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

* 关联查询:
        select * from orders o inner join user u on o.uid = u.id where o.id = 1;
* 缺点:
        sql语句编写难度大
        数据量过大,笛卡尔积数量倍增,可能造成内存溢出
* 嵌套查询:
    1.根据订单id查询订单表
        select * from orders where id = 1;
    2.再根据订单表中uid(外键)查询用户表
        select * from user where id = 订单表uid;
    3.最后由mybatis框架进行嵌套组合
    
* 优点:
        sql语句编写简单
        没有多表关联,不会产生笛卡尔积

环境搭建

1589246911073.png

1.2 一对一==嵌套==查询

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

sql语句

-- 1.根据订单id查询订单表
    select * from orders where id = 1;
-- 2.再根据订单表中uid(外键)查询用户表
    select * from user where id = 41;

① OrderMapper接口

public interface OrderMapper {

    // 一对一嵌套查询
    public Order findByIdWithUser(Integer id);
}

② OrderMapper映射





    
        
        
        
        
    

    
    

③ UserMapper接口

public interface UserMapper {
    
    // 根据用户id查询user对象
    public User findById(Integer id);
}

④ UserMapper映射





    
    


⑤ 通过mybatis进行嵌套组合

1589247990774.png

⑥ 测试

public class OrderMapperTest extends BaseMapperTest { // 继承父类,就可以直接使用 父类的方法和成员变量了

    // 一对一嵌套测试
    @Test
    public void test01() throws Exception {
        // 获取代理对象
        OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class);

        // 根据id查询
        Order order = orderMapper.findByIdWithUser(1);
        System.out.println(order);
    }
}
1589247953911.png

⑦ 嵌套关系

1589248264800.png

1.3 一对多嵌套查询

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

sql语句

-- 1. 先根据用户id,查询用户表(一个)
SELECT * FROM USER WHERE id = 41;
-- 2. 再根据用户id,查询订单表(多个)
SELECT * FROM orders WHERE uid = 41;

① UserMapper接口

public interface UserMapper {


    // 一对多嵌套查询
    public User findByIdWithOrders(Integer id);

}

② UserMapper映射


    
    
    
    
    
    




③ OrderMapper接口

public interface OrderMapper {

    // 根据用户id,查询订单列表
    public List findByUid(Integer uid);
}

④ OrderMapper映射


⑤ 通过mybatis进行嵌套组合

1589249903827.png

⑥ 测试

public class UserMapperTest extends BaseMapperTest {

    // 一对多嵌套查询测试
    @Test
    public void test01() throws Exception {
        // 获取代理
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

        User user = userMapper.findByIdWithOrders(41);

        System.out.println(user);
    }

}
1589249880000.png

⑦ 嵌套关系

1589250191685.png

1.4 多对多嵌套查询(由二个一对多组成)

需求:查询用户同时查询出该用户的所有角色

mybatis的实现方案就是(一对多),区别在于sql语句不同

sql语句

-- 1. 先根据用户id,查询用户表(一个)
SELECT * FROM USER WHERE id = 41;
-- 2. 再根据用户id,查询角色表(多个)
SELECT * FROM role r INNER JOIN user_role ur ON ur.`rid` = r.`id` WHERE ur.`uid` = 41;

① UserMapper接口

public interface UserMapper {

    // 多对多嵌套查询
    public User findByIdWithRoles(Integer id);
}

② UserMapper映射


    
    
    
    
    
    




③ RoleMapper接口

public interface RoleMapper {

    // 根据用户id,查询角色列表
    public List findByUid(Integer id);
}

④ RoleMapper映射






    
        
        
        

    

    
    

⑤ 通过mybatis进行嵌套组合

1589251531415.png

⑥ 测试

// 多对多测试(根据用户查询角色)
@Test
public void test02()throws Exception{
    // 获取代理
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    User user = userMapper.findByIdWithRoles(41);

    System.out.println(user);
}
1589251469136.png

⑦ 嵌套关系

1589251724851.png

1.5 知识小结

* 步骤:一对多举例
    1)先查询(一方)单表
    2)在查询(多方)单表
    3)最后由mybatis嵌套组合


一对一配置:使用+做配置,通过column条件,执行select查询

一对多配置:使用+做配置,通过column条件,执行select查询

多对多配置:使用+做配置,通过column条件,执行select查询

优点:1.简化sql语句编写、2.不会产生笛卡尔积

缺点:麻烦...


开发中到底使用哪一种?
    传统开发,数据量小:使用关联查询
    互联网开发,数据量大:使用嵌套查询
        当前也有人这么玩
            在java中先查用户,在查角色,不在使用嵌套....

二 MyBatis加载策略

2.1 什么是加载策略

​ 当多个模型(表)之间存在关联关系时, 加载一个模型(表)的同时, 是否要立即加载其关联的模型, 我们把这种决策成为==加载策略==

​ 如果加载一个模型(表)的时候, 需要立即加载出其关联的所有模型(表), 这种策略称为==立即加载==

​ 如果加载一个模型的时候, 不需要立即加载出其关联的所有模型, 等到真正需要的时候再加载, 这种策略称为==延迟加载(懒加载)==

Mybatis中的加载策略有两种:立即加载和延迟加载, 默认是立即加载

注意:延迟加载是在嵌套查询基础上实现的

* 什么样的场景使用立即加载
    一对一
    
* 什么样的场景使用延迟加载(什么时候用,什么时候查询,提高数据库性能)
    一对多、多对多

2.2 配置延迟加载

2.2.1 全局

SqlMapConfig.xml,设置开启全局延迟加载



    
    

2.2.3 局部

mapper映射文件,指定某一个select标签配置

 标签
 标签
    fetchType=""属性
        eager 立即加载
        lazy  延迟加载

注意:局部优先级高于全局的...

2.3 触发(立即)加载

有这样一个全局配置lazyLoadTriggerMethods,它定义的方法会触发立即加载

也就说当你调用它定义的方法时, 会执行数据加载, 它的默认值是equals,clone,hashCode,toString



    
    
    
    

三 MyBatis缓存

什么是缓存?

服务器内存(硬盘)中的一块区域

为什么使用缓存?

提高查询效率的

什么样的数据适合做缓存?

经常访问但又不经常修改的数据...

缓存是用来提高查询效率的,所有的持久层框架基本上都有缓存机制
Mybatis也提供了缓存策略,分为一级缓存,二级缓存

1589254870618.png

3.1 一级缓存

3.1.1 介绍

MyBatis一级缓存是:SqlSession级别的缓存,默认开启,不需要手动配置


1589201319071.png

3.1.2 验证

需求:根据id查询用户

// 一级缓存测试
@Test
public void test03() throws Exception {
    // 获取sqlSession会话对象
    SqlSession sqlSession = MyBatisUtils.openSession();

    // 获取第一个代理对象
    UserMapper userMapper1 = sqlSession.getMapper(UserMapper.class);
    User user1 = userMapper1.findById(41);// 走数据库
    System.out.println(user1);

    // 清除缓存(自己测试增、删、改)
    sqlSession.clearCache();

    // 获取第二个代理对象
    UserMapper userMapper2 = sqlSession.getMapper(UserMapper.class);
    User user2 = userMapper2.findById(41);// 走缓存(如果上面清除缓存,还是走数据库)
    System.out.println(user2);

    // sqlSession关闭(清除缓存...)
    MyBatisUtils.close(sqlSession);
}

3.1.3 分析

​ 一级缓存是SqlSession范围的缓存,不同的SqlSession之间的缓存区域是互相不影响的,执行SqlSession的C(增加)U(更新)D(删除)操作,或者调用clearCache()、commit()、close()方法,都会清空缓存

1589255593980.png

一级缓存源码

1589256171662.png

3.2 二级缓存【了解】

3.2.1 介绍

​ MyBatis的二级缓存虽然是默认开启的,但需要在映射文件中配置标签才能使用,而且要求实体类的必须实现序列化接口

1589202614175.png

3.2.2 验证

mybatis全局配置,默认值就是开启了二级缓存

1589263893048.png

指定需要开启二级缓存的映射配置文件

1589263857841.png

指定User实现序列化接口

1589263995869.png
// 二级缓存
@Test
public void test04() throws Exception {
    // 模拟第一个用户
    SqlSession sqlSession1 = MyBatisUtils.openSession();
    UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
    User user1 = userMapper1.findById(41);
    System.out.println(user1);
    sqlSession1.close();

    // 模拟第二个用户
    SqlSession sqlSession2 = MyBatisUtils.openSession();
    UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
    User user2 = userMapper2.findById(41);
    System.out.println(user2);
    sqlSession2.close();

}

3.1.3 分析

​ 二级缓存是mapper映射级别的缓存,多个SqlSession去操作同一个Mapper映射的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。

二级缓存相比一级缓存的范围更大(按namespace来划分)

1589264629385.png

3.3 知识小结

1564842702588.png
1. mybatis的缓存,都不需要我们手动存储和获取数据。mybatis自动维护的。

2. 使用mybatis,如果是中小型项目,使用自带缓存的机制是可以满足需求的。如果是大型(分布式)项目,mybatis的缓存灵活性不足,需要使用第三方的缓存技术解决问题。

四 核心配置文件回顾

4.1 properties标签

加载外部的properties文件


4.2 settings标签

全局参数配置


    
    
    
    
    
    

4.3 typeAliases标签

为 Java 类型设置一个别名

1. 单个定义别名

    
        
    

1. 使用包的形式批量定义别名

    
        
    

4.4 mappers标签

加载映射配置

1. 加载指定的src目录下的映射文件,例如:

    

1. 加载并扫描指定包下所有的映射文件(接口),例如:

    

4.5 environments标签

数据源环境配置


    
        
        
        
        
            
            
            
            
        
    

老师下午总结

回顾(登陆展示用户列表)

1.环境搭建

1.创建数据库

CREATE TABLE user (
  id INT(11) NOT NULL,
  username VARCHAR(32) DEFAULT NULL,
  password VARCHAR(32) DEFAULT NULL,
  sex VARCHAR(6) DEFAULT NULL,
  email VARCHAR(50) DEFAULT NULL,
  PRIMARY KEY (id),
  KEY email (email)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

INSERT INTO user VALUES(1,'admin','123','男','[email protected]'),(2,'guest','123','男','[email protected]'),(3,'gouwa','123','男','[email protected]'),(4,'gousheng','123','男','[email protected]');
       ```

2.创建模块然后导入对应的jar

导入jar我们需要分析我们要使用到的技术,这个过程应该是你们的项目组长负责

  • mysql的驱动

  • 数据库的连接池

  • jstl

  • Beanutils

    1589269607126.png

3.分包分层

1589269804767.png

4. 导入静态资源文件

1589269869357.png

==注意: 2019&2020版拷贝静态资源的时候经常没有拷贝到out目录中,程序运行的时候是执行out目录的内容==

1589270024992.png

5. 导入工具类(在今天素材里面)

1589270143510.png

6.导入字符过滤器

1589270590593.png
package com.itheima.web.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

//该字符串过滤器要解决全局乱码问题
/*
乱码的分类:
     1. 请求乱码( html页面提交表单数据---servlet --- servlet通过getparameter()方法获取参数的时候是否乱码)
           get请求: 没有
           post请求: 有乱码
     2. 响应乱码     response.getWrite().write("呵呵")  向浏览器输出数据乱码
          不管任何请求方式都会乱码的。
 */
//配置过滤路径
@WebFilter("/*")
public class CharacterEncondingFilter implements Filter {

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
       //1. 强制类型转换 (目的: 是为了使用HttpServletRequest的getmethod方法)
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;

        //2. 解决response响应乱码问题
        response.setContentType("text/html;charset=utf-8");

        //3. 获取客户请求方式,如果是post请求方式我们需要解决获取请求参数乱码问题
        String method = request.getMethod();
        if("post".equalsIgnoreCase(method)){
            request.setCharacterEncoding("utf-8");
        }

        //解决完毕乱码之后记得要放行
        chain.doFilter(request, response);
    }


    public void destroy() {
    }



    public void init(FilterConfig config) throws ServletException {

    }

}

2.登陆

1.流程分析

1589271149898.png

2.修改login.jsp页面,修改其提交的地址的信息

1589271432614.png

2.编写Userservlet的login方法

package com.itheima.web.servlet;

import com.itheima.domain.User;
import com.itheima.service.UserService;
import org.apache.commons.beanutils.BeanUtils;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;

@WebServlet("/userServlet")
public class UserServlet extends HttpServlet {

    private UserService userService = new UserService();

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取action,从而得知用户需要调用方法
        String methodName = request.getParameter("action");
        if("login".equalsIgnoreCase(methodName)){
            //登录方法
            login(request,response);
        }else if("list".equalsIgnoreCase(methodName)){
            //展示用户列表
            list(request,response);
        }
    }

    //用户登陆
    protected void login(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        try {
            //1. 获取请求参数
            Map parameterMap = request.getParameterMap();
            //2. 把请求参数封装到user对象中
            User user = new User();
            BeanUtils.populate(user,parameterMap); //把参数封装到user对象中了
            //3.调用userService的login方法,判断是否登陆成功
            boolean isLogin = userService.login(user);
            if(isLogin){
                //4. 登陆成功,设置登陆成功的标记,并且返回首页
                request.getSession().setAttribute("loginUser",user);
                //请求重定向到首页
                response.sendRedirect(request.getContextPath()+"/index.jsp");  // request.getContextPath() 获取模块的根路径
            }else {
                //5. 登陆失败,设置登陆错误的信息,回到login.jsp页面
                request.setAttribute("error","用户名或者密码错误");
                //请求转发到登录页面(这里使用请求转发的原因是因为request域中存储有数据)
                request.getRequestDispatcher("/login.jsp").forward(request,response);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

    }


    //展示用户列表
    protected void list(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {


    }

        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
}

3.编写UserService的login方法

package com.itheima.service;

import com.itheima.dao.UserDao;
import com.itheima.domain.User;

public class UserService {

    private UserDao userDao = new UserDao();


    //用户登陆
    public boolean login(User user){
        return userDao.login(user);
    }
}

4.编写UserDao的login方法

package com.itheima.dao;

import com.itheima.domain.User;
import com.itheima.utils.JdbcUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class UserDao {



    public boolean login(User user) {
        Connection connection = null;
        PreparedStatement pst =null;
        ResultSet rs = null;
        try {
            //1. 通过工具类得到连接
            connection = JdbcUtils.getConnection();
            //2. 准备sql语句得到PreparedStatement
            String sql = "SELECT * FROM USER WHERE username=? AND PASSWORD=?";
             pst = connection.prepareStatement(sql);
            //3. 设置参数
            pst.setObject(1,user.getUsername());
            pst.setObject(2,user.getPassword());

            //4. 执行sql语句,得到结果
             rs = pst.executeQuery();
            //5. 返回结果
            return rs.next();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //6. 关闭资源
            JdbcUtils.close(rs,pst,connection);
        }

        //该语句只是为了让代码不报错,让方法最终有返回值而已
        return false;
    }
}

3.用户展示列表

1.流程分析

1589273051193.png

2.修改index.jsp页面修改连接地址

1589273149885.png

3.编写Userservlet的login方法

@WebServlet("/userServlet")
public class UserServlet extends HttpServlet {

    private UserService userService = new UserService();

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取action,从而得知用户需要调用方法
        String methodName = request.getParameter("action");
        if("login".equalsIgnoreCase(methodName)){
            //登录方法
            login(request,response);
        }else if("list".equalsIgnoreCase(methodName)){
            //展示用户列表
            list(request,response);
        }
    }

  

    //展示用户列表
    protected void list(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1. 调用userService的findAll方法得到所有的用户信息
        List list =  userService.findAll();
       //2. 把用户信息存储到request域中
        request.setAttribute("list",list);
        //3. 请求转发到list页面
        request.getRequestDispatcher("/list.jsp").forward(request,response);
    }
}

3.编写UserService的login方法

package com.itheima.service;

import com.itheima.dao.UserDao;
import com.itheima.domain.User;

import java.util.List;

public class UserService {

    private UserDao userDao = new UserDao();



    public List findAll() {
        return userDao.findAll();
    }
}

4.编写UserDao的login方法

package com.itheima.dao;

import com.itheima.domain.User;
import com.itheima.utils.JdbcUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class UserDao {



    //查询用户列表
    public List findAll() {
        Connection connection = null;
        PreparedStatement pst =null;
        ResultSet rs = null;
        List list = new ArrayList<>();
        try {
            //1. 通过工具类得到连接
            connection = JdbcUtils.getConnection();
            //2. 准备sql语句得到PreparedStatement
            String sql="select * from user";
            pst = connection.prepareStatement(sql);
            //3. 执行sql语句,得到查询的结果
             rs = pst.executeQuery();
            //4. 然后遍历resutset,把遍历结果存储到集合中
            while(rs.next()){
                //每一行数据就是一个用户对象的数据
                User user = new User();
                user.setId(rs.getInt("id"));
                user.setUsername(rs.getString("username"));
                user.setPassword(rs.getString("password"));
                user.setSex(rs.getString("sex"));
                user.setEmail(rs.getString("email"));
                list.add(user);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //6. 关闭资源
            JdbcUtils.close(rs,pst,connection);
        }
        return list;
    }
}

5.修改list.jsp的el表达式

1589273912576.png
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>




    
    
    
    
    
    
    
    用户信息管理系统

    
    
    
    
    
    
    


用户信息列表

编号 姓名 性别 邮箱 操作
${user.id} ${user.username} ${user.sex} ${user.email} 修改 删除

4.ajax发送数据

4.1 html&jsp页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>


    Title
    <%--1.导入jquery的js--%>
    




<%--//2. 给按钮注册点击事件--%>

用户名:
密码:

4.2 servlet接收参数

package com.itheima.web.servlet;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.itheima.domain.User;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/testServlet")
public class TestServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        User user = new User();
        user.setUsername("狗娃");
        user.setPassword("123");
        //把对象转发为json
        ObjectMapper objectMapper = new ObjectMapper();
        String json = objectMapper.writeValueAsString(user);
        //把json字符串写出
        response.getWriter().write(json);

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
}

你可能感兴趣的:(5/12day52_mybatis嵌套查询&缓存)