系列十、SpringBoot + MyBatis + Redis实现分布式缓存(基于注解方式)

一、概述

        上篇文章 系列九、SpringBoot + MyBatis + Redis实现分布式缓存 介绍了基于xml方式实现分布式缓存的效果,当前大家使用的技术栈基本是springboot+各种框架的组合,而springboot显著的一个特点就是去xml配置,那么在无xml配置的情形下,又该如何实现分布式缓存呢?请看下面的代码实战

二、代码实战

2.1、分布式缓存相关的注解

        基于注解方式的分布式缓存,主要涉及到如下几个注解:

        (1)@EnableCaching:一般标注在配置类上,表示开启Spring的缓存,如果不加此注解的话Spring自带的缓存将不生效;

        (2)@CacheConfig(cacheNames = "xxx"):一般标注在service类上,用于配置cache的名字,建议以当前service类的全路径名作为cache的名字;

        (3)@Cacheable:一般标识在service层的查询方法上,表示将一个方法的返回值缓存起来,  默认情况下,缓存的key就是方法的参数,缓存的value就是方法的返回值,如果查询 方法无参数,则会使用默认的key,即SimpleKey [];

        (4)@CachePut(key = "#department.id"):一般加在service层的更新方法上(update),当数据库中的数据更新后,缓存中的数据也要跟着更新,使用此注解,可以将方法的返回值 自动更新到已经存在的key上

        (5)@CacheEvict:一般加在service层的删除方法上,当数据库中的数据删除后,相关的缓存也会被删除,使用该注解的时候,也可以配置按照某种条件删除(某种条件:@CacheEvict注解中的条件,例如:value、cacheNames、key、keyGenerator...)

2.2、项目概览

2.3、pom

<dependencies>
	<!-- springboot -->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-devtools</artifactId>
	</dependency>

	<!-- 数据源 -->
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>8.0.26</version>
	</dependency>
	<dependency>
		<groupId>org.mybatis.spring.boot</groupId>
		<artifactId>mybatis-spring-boot-starter</artifactId>
		<version>2.3.1</version>
	</dependency>
	<dependency>
		<groupId>com.alibaba</groupId>
		<artifactId>druid-spring-boot-starter</artifactId>
		<version>1.1.10</version>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-data-redis</artifactId>
	</dependency>

	<!-- 工具 -->
	<dependency>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
		<version>1.18.30</version>
	</dependency>
	<dependency>
		<groupId>cn.hutool</groupId>
		<artifactId>hutool-all</artifactId>
		<version>5.8.21</version>
	</dependency>
	<dependency>
		<groupId>org.apache.commons</groupId>
		<artifactId>commons-lang3</artifactId>
	</dependency>
	<dependency>
		<groupId>org.apache.commons</groupId>
		<artifactId>commons-collections4</artifactId>
		<version>4.4</version>
	</dependency>
	<dependency>
		<groupId>com.alibaba.fastjson2</groupId>
		<artifactId>fastjson2</artifactId>
		<version>2.0.25</version>
	</dependency>

</dependencies>

2.4、yml

server:
  port: 9999

spring:
  redis:
    host: 
    port: 6379
    database: 0
    password: 123456

  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/20231018_redis?useSSL=false&useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT
    username: root
    password: 123456

mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: org.stat.entity.model
  configuration:
    map-underscore-to-camel-case: true

logging:
  level:
    org:
      star:
        mapper: debug

2.5、主启动

/**
 * @Author : 一叶浮萍归大海
 * @Date: 2023/12/10 12:44
 * @Description:
 *
 */
@MapperScan(basePackages = "org.star.mapper")
@SpringBootApplication
public class SpringbootRedisDistributeCacheAnnotationApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootRedisDistributeCacheAnnotationApplication.class, args);
    }


}

2.6、MyRedisConfig

/**
 * @Author : 一叶浮萍归大海
 * @Date: 2023/12/10 15:28
 * @Description:
 * @EnableCaching的作用:开启Spring的缓存,如果不加此注解的话Spring自带的缓存将不生效
 *
 */
@EnableCaching
@Configuration
public class MyRedisConfig {

    /**
     * RedisTemplate k v 序列化
     *
     * @param connectionFactory
     * @return
     */
    @Bean
    public RedisTemplate<Object, Object> redisTemplate(LettuceConnectionFactory connectionFactory) {
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);

        redisTemplate.setKeySerializer(RedisSerializer.string());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());

        redisTemplate.setHashKeySerializer(RedisSerializer.string());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    @Bean
    public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisTemplate.getConnectionFactory());
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                // 设置默认的超时时间为2小时
                .entryTtl(Duration.ofHours(2))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()))
                // 设置默认的缓存前缀
                .prefixCacheNameWith("REDIS_CACHE_");
        return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
    }

}

2.7、DepartmentDO

/**
 * @Author : 一叶浮萍归大海
 * @Date: 2023/12/10 12:48
 * @Description:
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@ToString(callSuper = true)
public class DepartmentDO implements Serializable {
    /**
     * 编号
     */
    private Integer id;

    /**
     * 部门名称
     */
    private String departmentName;

}

2.8、DepartmentMapper.java

/**
 * @Author : 一叶浮萍归大海
 * @Date: 2023/12/10 12:50
 * @Description:
 */
public interface DepartmentMapper {

    /**
     * 查询所有部门
     * @return
     */
    List<DepartmentDO> listAllDepartment();

    /**
     * 根据id查询部门信息
     * @param id
     * @return
     */
    DepartmentDO getDepartmentById(Integer id);

    /**
     * 根据id和departmentName查询部门
     * @param id
     * @param departmentName
     * @return
     */
    DepartmentDO getDepartment(Integer id,String departmentName);

    /**
     * 更新Department
     * @param department
     * @return
     */
    int updateDepartment(DepartmentDO department);

    /**
     * 删除部门
     * @param id
     */
    void deleteDepartment(Integer id);
}

2.9、DepartmentMapper.xml

<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="org.star.mapper.DepartmentMapper">

    <select id="listAllDepartment" resultType="org.star.entity.model.DepartmentDO">
        select id,department_name from department
    </select>

    <select id="getDepartmentById" resultType="org.star.entity.model.DepartmentDO">
        select id,department_name from department where id = #{id}
    </select>

    <select id="getDepartment" resultType="org.star.entity.model.DepartmentDO">
        select id,department_name from department where id = #{id} and department_name = #{departmentName}
    </select>

    <update id="updateDepartment" useGeneratedKeys="true" keyProperty="id">
        update department set department_name = #{departmentName} where id = #{id}
        <selectKey resultType="org.star.entity.model.DepartmentDO" order="AFTER" keyProperty="id">
            select id,department_name from department where id = #{id}
        </selectKey>
    </update>

    <delete id="deleteDepartment">
        delete from department where id = #{id}
    </delete>

</mapper>

2.10、DepartmentService

/**
 * @Author : 一叶浮萍归大海
 * @Date: 2023/12/10 20:00
 * @Description:
 * 基于注解的分布式缓存,redis中key的生成规则:${prefixCacheNameWith} + "_" + ${cacheNames} + "_" + ${key}
 *      说明:prefixCacheNameWith为RedisCacheManager中配置的前缀
 *      举例:
 *          (1)listAllDepartment ===> REDIS_CACHE_org.star.service.DepartmentService::SimpleKey []
 *          (2)getDepartmentById ===> REDIS_CACHE_org.star.service.DepartmentService::1
 *          (3)getDepartment ===> REDIS_CACHE_org.star.service.DepartmentService::SimpleKey [1,研发部]
 *
 */
@Service
@CacheConfig(cacheNames = "org.star.service.DepartmentService")
public class DepartmentService {

    @Resource
    private DepartmentMapper departmentMapper;

    /**
     * @return
     * @Cacheable的作用:
     *      @Cacheable注解一般加在查询方法上,表示将一个方法的返回值缓存起来,
     * 默认情况下,缓存的key就是方法的参数,缓存的value就是方法的返回值,如果查询
     * 方法无参数,则会使用默认的key,即SimpleKey []
     */
    @Cacheable
    public List<DepartmentDO> listAllDepartment() {
        List<DepartmentDO> departments = departmentMapper.listAllDepartment();

        return departments;
    }

    /**
     * 对于只有一个参数的查询方法,其key位id对应的值
     * @param id
     * @return
     */
    @Cacheable
    public DepartmentDO getDepartmentById(Integer id) {
        return departmentMapper.getDepartmentById(id);
    }

    /**
     *
     * 对于有多个参数的查询方法,其key为所有的参数,如果想修改,可以单独指定,例如:@Cacheable(key = "#id")
     * @param id
     * @param departmentName
     * @return
     */
    @Cacheable
    public DepartmentDO getDepartment(Integer id,String departmentName) {
        return departmentMapper.getDepartment(id,departmentName);
    }

    /**
     * @CachePut作用:
     *      @CachePut注解一般加在更新方法上(update),当数据库中的数据更新后,缓存中的数据也要跟着更新,使用此注解,可以将方法的返回值
     *      自动更新到已经存在的key上,示例如下:
     * @param department
     * @return
     */
    @CachePut(key = "#department.id")
    public DepartmentDO updateDepartment(DepartmentDO department) {
        departmentMapper.updateDepartment(department);
        return department;
    }

    /**
     * @CacheEvict()作用:
     *      @CacheEvict()注解一般加在删除方法上,当数据库中的数据删除后,相关的缓存也会被删除,使用该注解的时候,也可以配置按照某种条件
     *      删除(某种条件:@CacheEvict注解中的条件,例如:value、cacheNames、key、keyGenerator...)
     * @param id
     */
    @CacheEvict
    public void deleteDepartment(Integer id) {
        departmentMapper.deleteDepartment(id);
    }

}

2.11、DepartmentServiceTest

/**
 * @Author : 一叶浮萍归大海
 * @Date: 2023/12/10 20:07
 * @Description:
 */
@SpringBootTest
public class DepartmentServiceTest {

    @Resource
    private DepartmentService departmentService;

    @Test
    public void listAllDepartmentTest() {
        List<DepartmentDO> departments1 = departmentService.listAllDepartment();
        System.out.println("departments1 = " + departments1);

        System.out.println("=============================");

        List<DepartmentDO> departments2 = departmentService.listAllDepartment();
        System.out.println("departments2 = " + departments2);
    }

    @Test
    public void getDepartmentByIdTest() {
        DepartmentDO department1 = departmentService.getDepartmentById(1);
        System.out.println("department1 = " + department1);

        System.out.println("========================");
        DepartmentDO department2 = departmentService.getDepartmentById(1);
        System.out.println("department2 = " + department2);
    }

    @Test
    public void getDepartmentTest() {
        DepartmentDO department1 = departmentService.getDepartment(1, "研发部");
        System.out.println("department1 = " + department1);

        System.out.println("============================");

        DepartmentDO department2 = departmentService.getDepartment(1, "研发部");
        System.out.println("department2 = " + department2);
    }

    @Test
    public void updateDepartmentTest() {
        DepartmentDO department = new DepartmentDO().setDepartmentName("研发部444").setId(1);
        DepartmentDO updatedDepartment = departmentService.updateDepartment(department);
        System.out.println("updatedDepartment = " + updatedDepartment);
    }

    @Test
    public void deleteDepartmentTest() {
        departmentService.deleteDepartment(1);
    }

}

2.12、测试

2.12.1、listAllDepartmentTest

2.12.2、 getDepartmentByIdTest

2.12.3、getDepartmentTest

2.12.4、 updateDepartmentTest

2.12.5、 deleteDepartmentTest

  • 12
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用SpringBoot框架结合MyBatis实现Session共享和单点登录可以借助SpringSession和Redis实现。 首先,需要配置SpringSession以使用Redis作为存储方式。可以在SpringBoot的配置文件中添加以下配置: ``` spring.session.store-type=redis spring.session.redis.namespace=spring:session spring.redis.host=127.0.0.1 spring.redis.port=6379 ``` 这样配置后,SpringSession会自动将session信息存储到Redis中。 接着,在登录验证成功后,将用户信息存储到Redis中,并将该用户的唯一标识存储到当前Session的属性中,以便后续验证是否登录。例如: ``` @RequestMapping("/login") public String login(@RequestParam("username") String username, @RequestParam("password") String password, HttpSession session) { // 验证用户名和密码 // ... // 验证通过后,将用户信息存储到Redis中,并设置Session属性 redisTemplate.opsForHash().put("user:" + username, "username", username); session.setAttribute("username", username); return "success"; } ``` 在后续的请求中,可以通过拦截器或过滤器来验证Session是否有效。例如: ``` @Component public class SessionInterceptor implements HandlerInterceptor { @Autowired private RedisTemplate<String, Object> redisTemplate; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); String username = (String) session.getAttribute("username"); if (StringUtils.isEmpty(username)) { response.sendRedirect("/login"); return false; } String storedUsername = (String) redisTemplate.opsForHash().get("user:" + username, "username"); if (!StringUtils.equals(storedUsername, username)) { response.sendRedirect("/login"); return false; } return true; } } ``` 以上代码片段展示了如何通过拦截器验证Session的有效性。首先从当前Session中获取用户名,如果为空则重定向到登录页面。然后从Redis中获取存储的用户名,如果与当前用户名不匹配,则重定向到登录页面。 这样就实现SpringBootMyBatisSpringSession和Redis共同完成Session共享和单点登录的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值