缓存与分页
一、动态SQL
修改
Mapper中的修改,主要基于set标签;
功能:1.可根据上下文添加或去掉set指令; 2.根据上下文添加或去掉SQL语句后面的’,'
<update id="updateUser" parameterType="user">
update user
<set>
<if test="name!=null">
name=#{name},
</if>
<if test="password!=null">
password=#{password},
</if>
<if test="sex!=null">
sex=#{sex}
</if>
</set>
where id=#{id}
</update>
批量删除
使用foreach标签来完成批处理操作;批处理操作包括,批量删除和批量添加
<!--
foreach:进行批处理 collection:批处理传参类型-容器 array-数组
open:开头 close:结尾 separator:分隔 item:循环遍历的名字
<foreach collection="array" open="(" close=")" separator="," item="id">
-->
<delete id="deleteByIds">
delete from user where id in
<foreach collection="array" open="(" close=")" separator="," item="id">
#{id}
</foreach>
</delete>
批量添加
使用foreach标签,批量添加的SQL语句如下:
insert into user(name,password,sex) values(?,?,?),(?,?,?),(?,?,?)…
<!--
foreach:循环标签 collection:传的参数是集合 item:集合元素是user对象
separator:按,分隔
<foreach collection="list" item="u" separator=",">
(#{u.name},#{u.password},#{u.sex})
</foreach> -->
<insert id="addBatch">
insert into user(name,password,sex) values
<foreach collection="list" item="u" separator=",">
(#{u.name},#{u.password},#{u.sex})
</foreach>
</insert>
二、缓存
概述
内存中的一块存储空间,服务于某个应用程序,旨在将频繁读取的数据临时保存在内存中,便于二次快速访问。
注意:缓存就是查询缓存,和DML没有关系;且DML会影响缓存,会清除缓存
场景:当频繁需要查询,且DML操作较少时,使用查询缓存,可大大提升性能,因为有了缓存,则减少和数据库交互次数。
一级缓存
SQLSession级别的缓存,也就是同一个SqlSession对象的同构查询,会产生缓存,直接使用该缓存即可;当然,应用场景很少,只需了解即可。
一级缓存不用配置,直接就有,测试即可。
//测试一级缓存---同一个session(了解)
SqlSession session = MyBatisUtils.getSession();
UserDao mapper = session.getMapper(UserDao.class);
System.out.println(mapper.selectById(1));
System.out.println("----------一级缓存---------");
//session = MyBatisUtils.getSession(); //不同session对象,则重新到数据库中查询
mapper = session.getMapper(UserDao.class); //同一个session对象,则从缓存中取资源
System.out.println(mapper.selectById(1));
二级缓存
SqlSessionFactory级别的缓存,也就是同一个SqlSessionFactory对象的同构查询,会产生缓存,直接从缓存中获取资源即可。
二级缓存需要手动配置开启。且需要关闭该查询,缓存才起作用(且实体类需要序列化)
//二级缓存测试:
SqlSession session = MyBatisUtils.getSession();
UserDao mapper = session.getMapper(UserDao.class);
System.out.println(mapper.selectById(1));
session.close(); //关闭当前session,二级缓存才起作用
System.out.println("进行了修改操作...");
session = MyBatisUtils.getSession(); //不同session对象,也可从缓存中获取资源
mapper = session.getMapper(UserDao.class);
User user = new User(5,"小赵","999","男",null);
mapper.updateUser(user);
session.commit(); //修改后,进行提交和关闭; 清空了与上次操作表相关的缓存
session.close();
System.out.println("----------二级缓存---------");
session = MyBatisUtils.getSession(); //不同session对象,也可从缓存中获取资源
mapper = session.getMapper(UserDao.class); //同一个session对象,则从缓存中取资源
System.out.println(mapper.selectById(1));
session.close(); //关闭资源才会产生缓存
System.out.println("----------继续二级缓存---------");
session = MyBatisUtils.getSession(); //不同session对象,也可从缓存中获取资源
mapper = session.getMapper(UserDao.class); //同一个session对象,则从缓存中取资源
System.out.println(mapper.selectById(1));
查询缓存中的提示:
----------继续二级缓存---------
DEBUG [main] - Cache Hit Ratio [com.qf.dao.UserDao]: 0.3333333333333333 #清了缓存,三次命中一次
User(id=1, name=zs, password=123, sex=男, birthday=Wed Sep 27 17:20:30 CST 2023)
三、Druid连接池
概述
Druid连接池,是阿里巴巴提供的性能最好的一款连接池产品。mybatis中自带了连接池产品,如果需要提升性能,则可选择Druid连接池,替换原有的连接池产品。
应用
需要将原有连接池改为Druid的连接池,只需变更mybatis配置文件中的数据源即可。
引入Druid的依赖:
<!-- 阿里巴巴提供的druid驱动 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.18</version>
</dependency>
创建Druid连接池类,继承之前的类,更改数据源即可
//Druid连接池类,变更数据源
public class MyDruidPoolFactory extends PooledDataSourceFactory {
public MyDruidPoolFactory(){
//使用Druid的数据源替换原有的
this.dataSource = new DruidDataSource();
}
}
mybatis配置中,需要将druid数据源引入
<!-- 从数据源工厂中取出数据源对象 -->
<dataSource type="com.qf.utils.MyDruidPoolFactory">
<!-- 注意:属性name需要查找DruidDataSource提供的属性名:set/get-->
<property name="driverClassName" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
四、分页
概述
在之前的内容中进行了分页的操作,麻烦之处在于,需要手工创建Page实体,在业务层封装回传到前端的数据,非常麻烦。
现在有了分页插件后,即可直接在业务层完成分页的封装。
测试案例
在业务层封装,然后在测试类中直接进行分页查询即可
导入分页插件的依赖包:
<!-- 分页依赖包 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.10</version>
</dependency>
在mybatis配置中,需要导入插件的配置:
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
编写业务层,进行分页封装;最终返回PageInfo分页实体
public class UserServiceImpl implements UserService {
@Override //参数1:当前页 参数2:页大小
public PageInfo<User> selectByPage(int pageNum, int pageSize) {
UserDao mapper = MyBatisUtils.getMapper(UserDao.class);
//limit 0,4 拼接到了下面的sql语句中
PageHelper.startPage(pageNum,pageSize);
//select * from user
List<User> list = mapper.selectByPage();
//PageInfo相当于之前jdbc中自定义的Page实体; 注入list内容
PageInfo<User> pageInfo = new PageInfo<>(list);
return pageInfo;
}
}
在测试类中即可打印分页信息:
//查询所有
UserService userService = new UserServiceImpl();
PageInfo<User> page = userService.selectByPage(2, 4);
System.out.println(page);
web分页
将上述测试案例,在web中完成数据的展示。
技术点:Servlet+mybatis+JSP+分页插件
步骤:
- 转web项目,将web环境搭建好
- 创建Servlet控制层
- 创建JSP进行展示
控制层的处理:
@WebServlet("/user")
public class UserController extends HttpServlet {
private UserService userService = new UserServiceImpl();
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//从前端获取到参数
int pageNum = Integer.parseInt(req.getParameter("pageNum"));
int pageSize = Integer.parseInt(req.getParameter("pageSize"));
//交给业务层处理,并返回分页实体
PageInfo<User> pageInfo = userService.selectByPage(pageNum, pageSize);
req.setAttribute("p",pageInfo); //存值+转发
req.getRequestDispatcher("page.jsp").forward(req,resp);
}
}
jsp分页展示:
<table border="2">
<tr>
<td>ID号</td>
<td>姓名</td>
<td>密码</td>
<td>性别</td>
<td>生日</td>
<td>操作</td>
</tr>
<c:forEach items="${p.list}" var="u">
<tr>
<td>${u.id}</td>
<td>${u.name}</td>
<td>${u.password}</td>
<td>${u.sex}</td>
<td>${u.birthday}</td>
<td><a href="#">删除</a>|<a href="#">修改</a></td>
</tr>
</c:forEach>
</table>
<a href="user?pageNum=1&pageSize=4">首页</a>
<c:if test="${p.hasPreviousPage}">
<a href="user?pageNum=${p.pageNum-1}&pageSize=4">上一页</a>
</c:if>
<c:if test="${p.hasNextPage}">
<a href="user?pageNum=${p.pageNum+1}&pageSize=4">下一页</a>
</c:if>
<a href="user?pageNum=${p.pages}&pageSize=4">尾页</a>
共${p.pageNum}/${p.pages}页
注意事项:
- 只有在PageHelper.startPage()方法之后的第一个查询会有执行分页。
- 分页插件不支持带有“for update”的查询语句。(行级锁)
- 分页插件不支持“嵌套查询”(了解-从一张表,根据规则查另一张;与关联查询对立),由于嵌套结果方式会导致结果集被折叠,所以无法保证分页结果数量正确。
五、mybatis注解
注解操作
mybatis中基本都是使用mapper配置方式完成SQL操作;除了该方式,也可以通过注解方式完成SQL;注解方式会简单很多,但只使用一些简单的SQL操作(关联查询与动态SQL不适合)
应用:
在mybatis配置中注册注解方式:
<mappers>
<!-- 注册注解方式(接口全限定名) -->
<mapper class="com.qf.dao.UserDao"></mapper>
</mappers>
在指定的UserDao接口中编写注解的SQL语句:
public interface UserDao {
@Select("select * from user where id=#{id}")
public User selectById(Integer id); //根据id获取对象
@Select("select * from user where id=#{id} and password=#{pwd}")
public List<User> selectByIdAndPwd(@Param("id")Integer id, @Param("pwd") String pwd);
}
后续的DML操作都和Mapper文件的方式类似,注解中更简便,只需关注SQL语句
#{}与$
在mybatis的SQL语句的传参中,都是使用#{},如果改为${},可以吗,先进行测试。
通过观察可知:
- 传一个参数时,会将参数当成实体对象;必须加@Param(“id”),去确定匹配参数
- ${}是使用普通的执行对象进行操作的,会有SQL注入的隐患
- ${}是字符串内容的直接替换(不带‘ ’, #{}则根据类型规则带‘ ’)
- ${}会优先取数据库连接中的数据,所以尽量避免和数据库中重名,例如,${username},${password}
@Select("select * from user where id=${id}")
public User selectById(@Param("id")Integer id); //根据id获取对象
//注意:使用${},尽量避免 数据库驱动中的名字,例如${username},${password},因为优先会取数据库中的名字
@Select("select * from user where id=${id} and password='${pwd}'")
public List<User> selectByIdAndPwd(@Param("id")Integer id, @Param("pwd") String pwd);
应用场景:只适合用在SQL中的字符串直接替换中,例如排序规则查询
//${}的场景:排序规则
@Select("select * from user order by id ${rule}")
public List<User> selectOrder(@Param("rule") String rule);
六、总结与作业
总结
1.动态SQL(重点)
修改、批量删除、批量添加
2.缓存(重点)
概述、一级缓存(了解),二级缓存(SqlSessionFactory级别)
3.Druid连接池(重点)
性能最好的连接池,应用
4.分页
按之前方式如何操作;转mybatis方式(分页插件)
测试案例,web分页
5.mybatis注解
注解操作-mybatis配置注解注册;在DAO接口中编写注解SQL
#{}与${}区别(重点-面试)
作业
完成分页中的删除和修改