原创

Spring Boot 2.缓存框架(一):进程内缓存的使用

随着时间的积累,应用的使用用户不断增加,数据规模也越来越大,往往数据库查询操作会成为影响用户使用体验的瓶颈,此时使用缓存往往是解决这一问题非常好的手段之一。Spring 3+ 开始提供了强大的基于注解的缓存支持,可以通过注解配置方式低侵入的给原有Spring应用增加缓存功能,提高数据访问性能。

在Spring Boot中对于缓存的支持,提供了一系列的自动化配置,使我们可以非常方便的使用缓存。下面我们通过一个简单的例子来展示,我们是如何给一个既有应用增加缓存功能的。

快速入门

下面我们将使用使用Spring5 + MybatisPlus + Mysql , 访问MySQL数据库为基础。早操作数据库后,我们为其添加缓存,来减少对数据库的IO,以达到访问加速的作用。

数据实体的定义

import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Getter;
import lombok.Setter;
import org.springframework.format.annotation.DateTimeFormat;

import java.util.Date;

/**
 * 用户表
 */
@TableName(value = "agent_account", autoResultMap = true)
@Getter
@Setter
public class AgentAccountEntry {
    /** 自然主键 */
    @TableId(type = IdType.AUTO)
    private Long id;

    /**
     * 创建时间。格式 "yyyy-MM-dd HH:mm:ss"
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
    private Date createTime;

    /**
     * 修改时间。格式 "yyyy-MM-dd HH:mm:ss"
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
    private Date modifyTime;

    /**
     * 基础账户唯一标识, 32位
     */
    private String accountCode;

    /**
     * 用户登陆名。
     */
    private String loginName;

    /**
     * 用户的全名。
     */
    private String displayName;

    /**
     * 用户电话号码。
     */
    private String phone;

    /**
     * 用户电子邮件地址。
     *
     * 新建成功后会发送邮件,邮件中包含呼叫中心登录地址,以及用户名密码。
     */
    private String email;


    // 内部企业 OA
    /**
     * 内部企业 OA 工号
     */
    private String jobNumber;
}

数据实体的数据访问实现

import com.baomidou.mybatisplus.core.mapper.BaseMapper;

public interface AgentAccountMapper extends BaseMapper<AgentAccountEntry> {
    //.
}

业务逻辑服务

接口 IAgentAccountDemoService

/**
 * @author <a href="mailto:yueny09@126.com"> 袁洋
 * @date 2021-02-0410:19
 * @inc
 * @category
 */
public interface IAgentAccountDemoService extends IService<AgentAccountEntry> {

    /**
     * 根据 accountCode 查询
     *
     * @param accountCode accountCode
     */
    AgentAccountEntry findByCode(String accountCode);

}

接口实现 AgentAccountDemoServiceImpl

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang3.StringUtils;

/**
 * @author <a href="mailto:yueny09@126.com"> 袁洋
 * @date 2021-02-0410:19
 * @inc
 * @category
 */
public class AgentAccountDemoServiceImpl extends ServiceImpl<AgentAccountMapper, AgentAccountEntry>
        implements IAgentAccountDemoService {

    @Override
    public AgentAccountEntry findByCode(String accountCode) {
        if (StringUtils.isNotEmpty(accountCode)) {
            LambdaQueryWrapper<AgentAccountEntry> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(AgentAccountEntry::getAccountCode, accountCode);

            return super.getOne(queryWrapper);
        }

        return null;
    }
}

测试用例

@RunWith(SpringRunner.class)
@SpringBootTest
public class AgentAccountDemoServiceTest {
    @Autowired
    private IAgentAccountDemoService demoService;

    @Test
    public void test() throws Exception {
        AgentAccountEntry entry = new AgentAccountEntry();
        entry.setAccountCode("setAccountCode");
        entry.setDisplayName("setDisplayName");
        entry.setEmail("123@126.com");
        entry.setJobNumber("66");
        entry.setLoginName("setLoginName");
        entry.setPhone("11111111111");

        // 创建1条记录
        boolean rs = demoService.save(entry);
        Assert.assertTrue(rs);

        AgentAccountEntry u1 = demoService.findByCode(entry.getAccountCode());
        System.out.println("第一次查询:" + u1.getAccountCode());

        AgentAccountEntry u2 = demoService.findByCode(entry.getAccountCode());
        System.out.println("第二次查询:" + u2.getAccountCode());
    }

}

第一次运行

在没有加入缓存之前,我们可以先执行一下这个案例 AgentAccountDemoServiceTest,可以看到如下的日志:

==>  Preparing: SELECT * FROM agent_account WHERE (account_code = ?) 
==> Parameters: setAccountCode(String)
<==    Columns: id, account_code, login_name, display_name, phone, email, job_number, create_time, modify_time
<==        Row: 76, setAccountCode, setLoginName, setDisplayName, 11111111111, 123@126.com, 66, 2021-02-03 20:32:08, 2021-02-03 20:32:08
<==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@41bd8ec9]
第一次查询:setAccountCode

==>  Preparing: SELECT * FROM agent_account WHERE (account_code = ?) 
==> Parameters: setAccountCode(String)
<==    Columns: id, account_code, login_name, display_name, phone, email, job_number, create_time, modify_time
<==        Row: 76, setAccountCode, setLoginName, setDisplayName, 11111111111, 123@126.com, 66, 2021-02-03 20:32:08, 2021-02-03 20:32:08
<==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6ea0dfaa]
第二次查询:setAccountCode

两次 findByCode 查询都执行了两次SQL,都是对MySQL数据库的查询。

引入缓存

第一步:在pom.xml中引入cache依赖

添加如下内容

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

第二步:在Spring Boot主类中增加@EnableCaching注解开启缓存功能

如下:

@EnableCaching
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

第三步:在service访问接口中,增加缓存配置注解

如:

@CacheConfig(cacheNames = "agentAccount")
public interface IAgentAccountDemoService extends IService<AgentAccountEntry> {

    /**
     * 根据 accountCode 查询
     *
     * @param accountCode accountCode
     */
    @Cacheable
    AgentAccountEntry findByCode(String accountCode);
}

第四步:再次执行单元测试用例

==>  Preparing: SELECT * FROM agent_account WHERE (account_code = ?) 
==> Parameters: setAccountCode(String)
<==    Columns: id, account_code, login_name, display_name, phone, email, job_number, create_time, modify_time
<==        Row: 81, setAccountCode, setLoginName, setDisplayName, 11111111111, 123@126.com, 66, 2021-02-03 21:11:29, 2021-02-03 21:11:29
<==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3f473daf]
第一次查询:setAccountCode
第二次查询:setAccountCode

到这里,我们可以看到,在调用第二次 findByCode 时,没有再执行select语句,也就直接减少了一次数据库的读取操作。

我们可以在单元测试中注入CacheManager, 进一步查看其中存在的元素,在debug模式下运行单元测试,观察CacheManager中的缓存集以及其中对象的缓存存储。

~ end

正文到此结束
广告是为了更好的提供数据服务
本文目录