在之前写的SpringBoot项目中
SpringBoot笔记 —— 使用MVC和三层架构模拟网站用户登录检查
SpringBoot笔记 —— 数据库连接池
使用数据库连接池,解决了多次创建连接的问题。现在又要开始思考一个新的问题,那就是如何提高查询效率。
在上面的博客里面,我都是通过查询mysql数据库来获取相应的数据,但是当要查询的表里面的数据非常多的时候,比如数十万的数据,查询效率就会很低,往往要消耗两三秒的时间才能查询到一条数据。

而在学习redis数据库之后,我们可以通过这个基于内存的数据库来提升查询效率。redis数据库的查询效率很高,往往在几毫秒的时间就可以完成一条数据的查询,因此选择redis作为SpringBoot项目中的缓存。将一部分的数据存储在缓存,也就是redis数据库中。每次查询数据的时候,先到缓存中查询,如果查询成功则直接返回数据,如果查询失败再通过到mysql数据库中查询的方式获取数据。

初次之外,对于redis数据库的连接方式,我们也像连接mysql数据库一样,使用连接池。

最后还要思考一件事情,应该把什么样的数据放入缓存呢?
我们将数据放入缓存的思路是这样的:
一开始缓存为空,数据都在MySQL中。第一次访问某一条数据时,先到缓存查询,缓存为空,必然查询失败,因此到mysql中查询,返回查询的结果,并且将这条数据放入缓存中。这样缓存中有了第一条数据,之后再访问这条数据时就会直接从缓存中获取,而不需要查询数据库了。

但是这样下去,缓冲中的数据量会一直增加,如果缓存中存储的数据量与mysql数据库的一致,那么就无法体现出缓存的优势了。所以应该只是把一部分经常被使用到的数据放入缓存中。
我们可以通过redis数据库的设置过期时间的方式,给缓存中的每一条数据设置一个过期时间,比如30秒,如果30秒后这条数据没有被第二次访问,就会在缓存中被删除。而如果30秒内,这条数据被再次访问了,那么就重新给这条数据设置一个过期时间。也可以理解为刷新这条数据的过期时间,重新变回30秒。

关于如何给redis数据库的数据设置过期时间,我在之前的博客也写过了
Redis笔记 ——String (用位图统计一段时间内用户登录天数,与网站指定时间段内活跃用户数)

整体的思路都梳理完毕,下面就是具体实现的代码

Students类

用来存放mysql数据库中查询到的数据,每一个Students类对象都是一条数据库中的数据

package com.shujia.spring.mvc1.bean;

import lombok.*;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Students {
    private String id;
    private String name;
    private String age;
    private String sex;
    private String clazz;
}

表现层
package com.shujia.spring.mvc1.controller;

import com.shujia.spring.mvc1.service.UserServiceImpl;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {
    @RequestMapping("/selectById")
    public String selectById(String id){
        UserServiceImpl userService = new UserServiceImpl();
        String s = userService.selectById(id);
        return s;
    }
}

业务逻辑层

接口

package com.shujia.spring.mvc1.service;

public interface UserSeriver {
    public String selectById(String id);
}

package com.shujia.spring.mvc1.service;

import com.shujia.spring.mvc1.bean.Students;
import com.shujia.spring.mvc1.bean.User;
import com.shujia.spring.mvc1.dao.UserDao;
import com.shujia.spring.mvc1.dao.UserDaoImpl;
import com.shujia.spring.util.RedisUtil;
import redis.clients.jedis.Jedis;

import java.util.HashMap;
import java.util.Map;

//业务逻辑层 验证
public class UserServiceImpl implements UserSeriver {
    //利用Jedis连接池来获取连接,连接到redis
    Jedis jedis = RedisUtil.getJedis();
    static UserDao userdao=new UserDaoImpl();

    public String selectById(String id){
        try {
        /*
            连接redis
            使用hash作为缓存数据结构,即value值的类型为hash散列
            通过key值id,查询得到value值,也就是一个map,map的值有三种情况
            情况一:如果map中有数据,即查询成功
            情况二:如果map的内容为{},说明map中没有数据,为空,但是有一对大括号,
            所以不能用null判断,应该用isEmpty判断
            情况三:连接中断异常,导致map为null
        */
            Map<String, String> map = jedis.hgetAll("student:"+id);
            //查询到数据则返回数据内容,map不为null且不为空,才算查询数据成功
            if(map!=null&&!map.isEmpty()){
                //缓存中的这一条数据被访问,则刷新过期时间为30秒
                jedis.expire("student:"+id,30);

                return "redis---"+map.toString();
            }
            jedis.close();
        }catch (Exception e){
            System.out.println("redis查询失败");
            e.printStackTrace();
        }finally {
            jedis.close();
        }

        //如果在缓冲中没有查询到数据,那么就到mysql中查询数据
        //然后将查询到的数据存储在缓存中
        Students students = userdao.loginStudent(id);
        //mysql的查询结果stu,也有两种结果,查询成功则有值,查询失败则为null
        if(students==null){
            return "用户不存在";
        }

        //查询到数据,则将数据存储到缓存中
        //因为选择redis的hash数据结构存储数据,所以key值为String,value值为HashMap
        //这里创建一个HashMap对象用来存储数据,然后插入redis数据库中
       try {
           Map<String, String> stu = new HashMap<>();
           stu.put("id",students.getId());
           stu.put("name",students.getName());
           stu.put("age",students.getAge());
           stu.put("sex",students.getSex());
           stu.put("clazz",students.getClazz());
           System.out.println(stu);

           //注意这里要用 hmet 批量插入,因为HashMap对象stu中有多个键值对
           //如果只有一个键值对使用hset,多个键值对批量插入用hmet
           jedis.hmset("student:"+id,stu);

           //给放入缓存中的每一条数据设置一个过期时间30秒
           jedis.expire("student:"+id,30);
       }catch (Exception e){
           System.out.println("redis存储失败");
           e.printStackTrace();
       }finally {
           jedis.close();
       }
        return "mysql---"+students.toString();
    }
}

持久化层(Dao层)

接口

package com.shujia.spring.mvc1.dao;
import com.shujia.spring.mvc1.bean.Students;

public interface UserDao {
    public Students loginStudent(String id);
}

package com.shujia.spring.mvc1.dao;

import com.shujia.spring.mvc1.bean.Students;
import com.shujia.spring.mvc1.bean.User;
import com.shujia.spring.util.JDBCUtil;

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

public class UserDaoImpl implements UserDao {
    public Students loginStudent(String id){
        Students students=null;

        try {
            Connection conn = JDBCUtil.getConn();
            PreparedStatement ps = conn.prepareStatement("select * from studentTest where id=?");
            ps.setString(1,id);
            ResultSet rs = ps.executeQuery();
            if (rs.next()){
                students=new Students();
                students.setId(rs.getString("id"));
                students.setName(rs.getString("name"));
                students.setAge(rs.getString("age"));
                students.setSex(rs.getString("sex"));
                students.setClazz(rs.getString("clazz"));
            }
            rs.close();
            ps.close();
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return students;
    }
}

mysql数据库的连接池
package com.shujia.spring.util;

import org.apache.commons.dbcp2.BasicDataSource;

import java.sql.Connection;
import java.sql.SQLException;

public class JDBCUtil {
    private static BasicDataSource bds;
    static {
        System.out.println("创建连接池");
        bds = new BasicDataSource();
        bds.setInitialSize(5);
        bds.setUrl("jdbc:mysql://master:3306/show1?useUnicode=true&characterEncoding=utf-8");
        bds.setDriverClassName("com.mysql.jdbc.Driver");
        bds.setUsername("root");
        bds.setPassword("123456");
    }
    public static Connection getConn(){
        System.out.println("获取连接");
        try {
            return bds.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }
}

Redis数据库的连接池

Redis数据库的连接池需要Jedis工具,利用maven导入相关jar包

<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
		<dependency>
			<groupId>redis.clients</groupId>
			<artifactId>jedis</artifactId>
			<version>3.0.0</version>
		</dependency>
package com.shujia.spring.util;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class RedisUtil {
    private static JedisPool jedisPool;
    static {
        //创建连接池对象
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        //设置连接池的最大连接数
        jedisPoolConfig.setMaxTotal(5);
        //设置连接失败时的最大等待时间
        jedisPoolConfig.setMaxWaitMillis(100);
        //配置连接到redis数据库的连接信息
        jedisPool= new JedisPool(jedisPoolConfig,"master",6379);
    }
    public static Jedis getJedis(){
        return jedisPool.getResource();
    }
}

接下来就可以运行程序,尝试查询一条id为15001000086的数据
右小角有第一条查询这条数据时的用时,用时为477毫秒,这里是从mysql数据库中查询数据
在这里插入图片描述
然后第二次查询这条数据,可以发现用时降低到了13毫秒,这里是从缓存中查询得到的
在这里插入图片描述
过了30秒后,再次查询这条数据
在这里插入图片描述
会发现结果的最前面的信息,又从redis变成了mysql,说明这条数据超过了设置的过期时间,已经被缓存删除,所以又从mysql数据库中查询。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐