关于数据懒加载的应用

233 阅读3分钟

背景

在业务开发过程中,我们会有一些配置数据需要初始化数据,例如企业配置数据。我们可以在创建企业的同时初始化企业配置数据,对于已经存在的企业就需要特殊处理,这样会有多个初始化的企业配置数据初始化逻辑,不利于之后的维护,那么我们可以选择懒加载的方式初始化这种数据。

什么是懒加载

懒加载其实就是延时加载。刚开始了解到这个概念是在项目启用优化相关知识中了解到的,就是当对象需要用到的时候再去加载对象。

在对象初始化的时候是需要消耗性能的,在项目启动的时候可能需要初始化成千上万个对象,会导致启动时间较长,而且在项目使用过程中可能很长时间不会用到一些对象,这样在一开始就初始化这些没有用到的对象就太浪费了。因此,为了节省性能和启动时间,就有了懒加载机制。

懒加载的应用

对于一些配置数据而言,也会存在初始化后很长时间不需要用到的情况,那么我们也可以使用懒加载的方式来初始化这种数据。

那么我们需要一个基础方法获取配置信息,先查询数据库,当数据库不存在时就初始化数据。这样既能避免在多个地方初始化数据,也能在减少在创建企业时减少不必要的操作,代码如下所示:

// 获取配置
public ConfigDto getConfig() {
	// 查询数据库
    ConfigDto dto = repository.getConfig();
    if (dto != null) {
        return dto;
    }

    return init();
}

// 初始化配置
public ConfigDto init() {
    ConfigDto dto = new ConfitDto();
    // init

    return dto;
}

懒加载缺点

事情都有两面性,懒加载也带来了一些额外的问题,例如重复初始化配置。

双重检查分布式锁实现

配置数据大多数情况下有唯一性的,且入口会被很多地方调用可能存在并发问题,因此可能会重复初始化配置。

实现要点:

  • 使用分布式锁初始化方法的并发执行
  • 初始化的时候,二次检查数据库是否存在数据(参考单例模式实现)
// 获取配置
public ConfigDto getConfig() {
    ConfigDto dto = repository.getConfig();
    if (dto != null) {
        return dto;
    }
    // 分布式锁避免重复初始化数据
    return LockHelper.lock("initConfig", () -> init());
}

// 初始化配置
public ConfigDto init() {
    // 第二次检查数据库是否存在数据
    ConfigDto dto = repository.getConfig();
    if (dto != null) {
        return dto;
    }
    dto = new ConfitDto();
    // init

    return dto;
}

事务问题

如果业务方法A比较复杂,在方法A中调用获取配置的方法,且方法A加上了事务,那么在方法A获取配置(初始化配置)后,事务还未提交,初始化的分布式锁也解开了,这时候方法B也去获取配置(初始化配置),且方法B保存了数据,那么方法A在事务提交的时候就会出现异常(数据重复)。

那么我们需要在初始化方法上再配置事务级别,生成新的子事务去执行初始化方法。

// 初始化配置
@Transactional(propagation = Propagation.REQUIRES_NEW)
public ConfigDto init() {
    // 第二次检查数据库是否存在数据
    ConfigDto dto = repository.getConfig();
    if (dto != null) {
        return dto;
    }
    dto = new ConfitDto();
    // init

    return dto;
}

THE END.