回顾
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语句编写简单
没有多表关联,不会产生笛卡尔积
环境搭建
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进行嵌套组合
⑥ 测试
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);
}
}
⑦ 嵌套关系
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进行嵌套组合
⑥ 测试
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);
}
}
⑦ 嵌套关系
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进行嵌套组合
⑥ 测试
// 多对多测试(根据用户查询角色)
@Test
public void test02()throws Exception{
// 获取代理
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.findByIdWithRoles(41);
System.out.println(user);
}
⑦ 嵌套关系
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也提供了缓存策略,分为一级缓存,二级缓存
3.1 一级缓存
3.1.1 介绍
MyBatis一级缓存是:SqlSession级别的缓存,默认开启,不需要手动配置
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()方法,都会清空缓存
一级缓存源码
3.2 二级缓存【了解】
3.2.1 介绍
MyBatis的二级缓存虽然是默认开启的,但需要在映射文件中配置
标签才能使用,而且要求实体类的必须实现序列化接口
3.2.2 验证
mybatis全局配置,默认值就是开启了二级缓存
指定需要开启二级缓存的映射配置文件
指定User实现序列化接口
// 二级缓存
@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来划分)
3.3 知识小结
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
3.分包分层
4. 导入静态资源文件
==注意: 2019&2020版拷贝静态资源的时候经常没有拷贝到out目录中,程序运行的时候是执行out目录的内容==
5. 导入工具类(在今天素材里面)
6.导入字符过滤器
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.流程分析
2.修改login.jsp页面,修改其提交的地址的信息
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.流程分析
2.修改index.jsp页面修改连接地址
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表达式
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
用户信息管理系统
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);
}
}