Redis缓存系列--(四)Redis基础数据类型在Java中的使用

Redis在Java中的基础使用

Redis作为缓存主要使用在Java应用或者服务中读多写少的场景,从而来提高用户请求服务器数据的速度。而且Redis服务器面对Java的高并发请求时,不会出现并发问题,因为Redis服务器在执行命令的时候,是原子性的操作。

Redis在Java中的使用方式

以下示例项目采用SpringMvc+JdbcTemplate的框架,同时使用Druid作为数据库连接池,示例代码只展示了核心的代码,有关SpringMvc配置文件以及相关实体类、控制器类以及日志配置在这里不做过多赘述,完整项目代码请参考springmvc-redis-demo项目源码。下边实例代码模拟了一个查找用户信息的应用场景来实现查询出数据库的数据时同时使用Redis来缓存当前查询的用户信息的场景。

  1. 使用JedisPool来构造Redis连接池,然后通过jedisPool.getResource()来获取Redis客户端对象。
  • 优点:简单易操作
  • 缺点:冗余代码较多,如果有多个服务请求需要进行Redis操作,那么每个请求都需要来获取和释放Redis客户端对象,同时还要来对Java对象进行字符串的序列化转换。

核心的代码操作
步骤1:添加相关依赖包

<properties>
        <java-version>1.8</java-version>
        <org.springframework-version>5.1.5.RELEASE</org.springframework-version>
        <org.slf4j-version>1.7.12</org.slf4j-version>
        <!-- json -->
        <jackson.codehaus.version>1.9.13</jackson.codehaus.version>
        <aspect-version>1.8.0</aspect-version>
      <mysql.connector.java.version>8.0.11</mysql.connector.java.version>
        <druid.version>1.1.20</druid.version>
        <sdr.version>2.1.5.RELEASE</sdr.version> 
        <lettuce.version>5.1.4.RELEASE</lettuce.version> 
        <jedis.version>2.9.2</jedis.version>
</properties>
    
<dependencies>
        <!--spring相关依赖包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${org.springframework-version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${org.springframework-version}</version>
        </dependency>
        
        <!--jdbctemplate依赖包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${org.springframework-version}</version>
        </dependency>

        <!-- 数据库驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.connector.java.version}</version>
        </dependency>

        <!-- 加入druid数据源依赖包 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>${druid.version}</version>
        </dependency>

        <!-- Jackson JSON Processor -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.9.8</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.8</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.9.8</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.28</version>
        </dependency>

        <!--redis-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>${jedis.version}</version>
        </dependency>

        <!-- 使用RedisTemplate所需要的依赖包-->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
            <version>${sdr.version}</version>
        </dependency>

        <!--添加lombok依赖 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${org.slf4j-version}</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>${org.slf4j-version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${org.slf4j-version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.16</version>
            <scope>runtime</scope>
        </dependency>

        <!-- 加入spring测试依赖包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${org.springframework-version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

        <!-- Servlet -->
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-servlet-api</artifactId>
            <version>7.0.30</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

步骤2:加载JedisPool以Javabean的形式注入到spring容器中

/**
 * 通过注解的形式来配置spring中的bean
 * @author young
 */
@Configuration
public class AppConfig {

    /**
     * 使用Redis方式1:通过JedisPool来获取Redis client
     * @return
     */
    @Bean
    public JedisPool jedisPool(){
        JedisPool jedisPool = new JedisPool("127.0.0.1", 6379);
        return jedisPool;
    }
}

步骤3:编写服务层代码

/**
* 用户服务层代码
*/
@Service
public class UserService {
		private Logger logger = LoggerFactory.getLogger(UserService.class);
		
    @Autowired
    JdbcTemplate jdbcTemplate;

    @Autowired
    JedisPool jedisPool;

    /**
     * 方式1:根据ID查询用户信息 (redis缓存,用户信息以json字符串格式存在(序列化))
     * 好处:查询出所有的用户信息
     * 坏处:如果只需要其中的部分信息,则会增加网络传输信息量大的压力
     * @param userId
     */
    public User findUserById(String userId) {
        User user = null;
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            // 1. 查询Redis是否有数据 -- string方式
            String result = jedis.get(userId);
            if(result != null && !"".equals(result) ) {
                //json字符串转换为User,把缓存中的值,返回给你的用户
                user = JSONObject.parseObject(result, User.class);
                // 命中 缓存
                return user;
            }

            // 2. 查询数据库
            String sql = "select id,user_name,password,name,age from tb_user where id=?";
            user = jdbcTemplate.queryForObject(sql, new String[]{userId}, new BeanPropertyRowMapper<>(User.class));

            // 3. 数据塞到redis中 // 方式1:json格式字符串放入value为String类型的缓存中
             String userJsonStr = JSONObject.toJSONString(user);
             jedis.set(userId,userJsonStr);
        }catch (Exception e){
            System.out.println(e.getMessage());
            logger.error(e.getMessage());
            logger.error("userservice error:",e);
        }
        finally {
            if (jedis != null) {
                jedis.close();
            }
        }
        return user;
    }

    /**
     * 方式1:根据ID查询用户信息
     * (redis缓存,用户信息以Redis中hash的数据结构来存储))
     * @param userId
     */
    public User findUserById1(String userId) {
        User user = null;
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            // 1. 查询Redis是否有数据 -- 通过hash的方式获取
            Map<String, String> userMap = jedis.hgetAll(userId);

            //将获取的map对象转换为User对象
            if(userMap != null && userMap.size() >0 ) {
                logger.info("缓存命中");
                user = new User();
                user.setId(Integer.valueOf(userId));
                user.setUserName(userMap.get("username"));
                user.setPassword(userMap.get("password"));
                user.setName(userMap.get("name"));
                user.setAge(userMap.get("age"));
                //命中缓存
                return user;
            }

            // 2. 查询数据库
            String sql = "select id,user_name,password,name,age from tb_user where id=?";
            logger.info("执行的sql语句为:" + sql);
            user = jdbcTemplate.queryForObject(sql, new String[]{userId}, new BeanPropertyRowMapper<>(User.class));

            // 3. 使用value为hash的数据结构,将用户数据放入Redis缓存中
            HashMap<String, String> userInfo = new HashMap<>();
            userInfo.put("username", String.valueOf(user.getUserName()));
            userInfo.put("password", String.valueOf(user.getPassword()));
            userInfo.put("name", String.valueOf(user.getName()));
            userInfo.put("age", String.valueOf(user.getAge()));
            jedis.hmset(userId, userInfo);
        }catch (Exception e){
            System.out.println(e.getMessage());
            logger.error(e.getMessage());
            logger.error("userservice error:",e);
        }
        finally {
            if (jedis != null) {
                jedis.close();
            }
        }
        return user;
    }
    
    /**
     * 根据ID查询用户名称(在缓存用户信息时使用hash的方式对用户信息进行存储,那么在获取用户的部分信息时就可以使用hget命令来获取用户具体某个字段的信息了)
     */
    public String findUserNameById(String userId) {
        String uname = null;
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            // 1. 查询Redis是否有数据
            uname = jedis.hget(userId, "username"); // 向远程的Redis发起 查询 请求
            if (uname != null && !"".equals(uname)) {
                return uname; // 命中 缓存
            }

            return null;

        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }
}

如果使用string对用户的信息进行存储,那么如果查询的用户的信息量过大就会导致如果Redis缓存了之后,假如再有服务请求用户的部分信息时,如果使用该用户信息的缓存,就需要对获取
用户的缓存所有内容进行解析,这样就会增加网络的负担;所以可以使用hash的数据结构来缓存用户信息,在获取部分信息时,可以直接使用hget的方式来获取某一字段信息。

  1. 使用RedisTemplate来进行Redis缓存
  • 优点:省去了创建客户端和关闭客户端的冗余代码,不需要对从Redis查询出来的对象或者从数据库查询出来的对象进行包装
  • 缺点:同样对业务代码有侵入,有一定的耦合性,不利于对已开发系统的改造

核心代码操作
步骤一:添加RedisTemplate的maven依赖包,请参考上边示例中的maven
步骤二:在全局配置类中添加RedisTemplate相关的java bean

@Configuration
public class AppConfig {
/**
     * 使用Redis方式2:通过RedisConnectionFactory来获取RedisTemplate,从而进行Redis的相关操作(注解方式同样需要)
     * @return
     */
    @Bean
    public RedisConnectionFactory redisConnectionFactory(){
        //配置Redis的主机和端口
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName(host);
        redisStandaloneConfiguration.setPort(port);

        //
        RedisConnectionFactory redisConnectionFactory = new JedisConnectionFactory(redisStandaloneConfiguration);
        return redisConnectionFactory;
    }

    /**
     * 方式2:加载RedisTemplate bean
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(StringRedisSerializer.UTF_8);
        return redisTemplate;
    }
}

步骤三:编写服务层核心代码,注入RedisTemplate类

@Service
public class UserService {

    private Logger logger = LoggerFactory.getLogger(UserService.class);

    @Autowired
    JdbcTemplate jdbcTemplate;

    /**
     * 方式2:使用模板方法来操作Redis客户端
     */
    @Autowired
    RedisTemplate redisTemplate;

    /**
     * 方式2:使用RedisTemplate实现Redis缓存
     * 优点:省略了创建和关闭Redis客户单以及对查询对象的序列化解析和包装
     * @param userId
     * @return
     */
    public User findUserById2(String userId){
        User user = null;

        user =(User) redisTemplate.opsForValue().get(userId);
        // 1. 查询Redis是否有数据 -- 通过hash的方式获取

        //判断缓存数据是否存在
        if(user != null) {
            logger.info("缓存命中");
            //命中缓存
            return user;
        }

        // 2. 查询数据库
        String sql = "select id,user_name,password,name,age from tb_user where id=?";
        logger.info("执行的sql语句为:" + sql);
        user = jdbcTemplate.queryForObject(sql, new String[]{userId}, new BeanPropertyRowMapper<>(User.class));

        // 3. 使用value为string的数据结构,将用户数据放入Redis缓存中
        redisTemplate.opsForValue().set(userId,user);
        return user;
    }
}

这种方式同样的,如果仍然使用String作为用户信息的存储形式,同样在获取部分用户信息的数据上会不太方便,同样也可以使用hash的方式对用户信息进行存储。所以要根据项目的具体需求,对用户信息采用Redis适当的存储方式进行存储。

  1. 使用Spring提供的缓存注解@EnableCache和@Cacheable进行缓存
  • 优点:使用Spring提供的注解可以实现无侵入的功能改造,降低代码的耦合度,同时减少了对Redis客户端的连接和释放等冗余操作。
  • 缺点:可读性相对较差,需要了解注解里每个参数的具体含义以及注解的使用。

核心代码操作如下(maven依赖同上)
步骤一:加载全局配置的Bean,通过注解@EnableCaching开启缓存

@Configuration
@EnableCaching
public class AppConfig {

    /**
     * 通过注释来获取properties配置文件中的自定义值
     */
    @Value("${spring.redis.host}")
    String host;

    @Value("${spring.redis.port}")
    int port;

    /**
     * 配置RedisConnectionFactory
     * @return
     */
    @Bean
    public RedisConnectionFactory redisConnectionFactory(){
        //配置Redis的主机和端口
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName(host);
        redisStandaloneConfiguration.setPort(port);

        RedisConnectionFactory redisConnectionFactory = new JedisConnectionFactory(redisStandaloneConfiguration);
        return redisConnectionFactory;
    }


    /**
     * 使用方式3:使用注解方式来对业务代码的结果进行缓存时需要加载CacheManager对象
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory){
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
        //指定Redis的key和value序列化方式
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()  .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.string())) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json()));

        CacheManager cacheManager = new RedisCacheManager(redisCacheWriter,redisCacheConfiguration);
        ((RedisCacheManager) cacheManager).isTransactionAware();
        return cacheManager;
    }

}

步骤二:在服务类向对应的方法上添加@Cacheable注解并配置缓存key的相关参数

/**
     * 方式3:使用注解方式实现Redis缓存,redis的key为注解的value+key;
     *      其中key是一个EL表达式,根据方法传递的参数而自动变化
     * 优点:减少缓存程序对已有业务代码的侵入,使它与业务代码解耦
     * @param userId
     * @return
     */
    @Cacheable(value = "tb_user",key = "#userId")
    public User findUserById3(String userId){
        //1.在查询数据库前会查询缓存是否存在
        User user;
        // 2. 查询数据库
        String sql = "select id,user_name,password,name,age from tb_user where id=?";
        logger.info("执行的sql语句为:" + sql);
        user = jdbcTemplate.queryForObject(sql, new String[]{userId}, new BeanPropertyRowMapper<>(User.class));

        //3.查询数据库后会将数据写入Redis缓存
        return user;
    }

注解方式主要运用了Java的反射机制和Spring的AOP特性,我们同样的也可以通过自定义注解以及AOP来实现自己的缓存注解,从而满足项目的各种需求。

这里还需要注意一个问题:当用户查询数据的时候,将数据放入Redis缓存;同时在用户更新数据的时候,用户数据更新到数据库之后,也要有Redis的相关操作,比如删除原来的缓存。这样在用户下次进行查询的时候,会读取数据库的最新数据,然后将最新数据再次缓存到Redis中。
使用Redis注解的方式进行缓存删除的代码如下:

   /**
     * 方式3:当有数据更新时,先更新数据库数据然后清除缓存,等待下一次有查询的时候再将数据库数据存入Redis缓存
     * @param user 用户的更新信息
     * @return 最新用户的信息
     */
    @CacheEvict(value = "tb_user",key = "#user.id")
    public User updateUser(User user) { // 同步关键字 --- 应该能够解决 --
        //修改数据数据
        String sql = "update tb_user set user_name = ? where id=?";
        jdbcTemplate.update(sql, new String[]{user.getUserName(), String.valueOf(user.getId())});
        return user;
    }

总结

Redis在Java中的使用方式分为以下几种:

  1. 使用原始的Jedis或者JedisPool来获取Redis的客户端对象,然后对其进行相应的Redis操作。
  2. 使用RedisTemplate对象来对Redis客户端进行操作。
  3. 使用Spring提供的@EnableCaching和@Cacheable注解对相关方法进行Redis的缓存操作。
  4. 使用自己定义的缓存注解来对相关方法进行Redis缓存操作,具体的使用请参考Redis缓存系列--(五)自定义Redis缓存注解的使用

各种方式各有优缺点,一般采用2或者3的方式相对来说较为简洁和方便,使用4方式更具有灵活性,具体的项目需要来决定采用哪种方式进行实现。

posted @ 2020-11-08 09:52  爪哇洋  阅读(745)  评论(0编辑  收藏  举报