关于java:面试官说说-MyBatis-二级缓存关联刷新实现我懵B了

37次阅读

共计 7083 个字符,预计需要花费 18 分钟才能阅读完成。

起源:blog.csdn.net/qq_38245668/article/details/105803298

1、MyBatis 缓存介绍

Mybatis 提供对缓存的反对,然而在没有配置的默认状况下,它只开启一级缓存,二级缓存须要手动开启。

一级缓存只是绝对于同一个 SqlSession 而言。 也就是针对于同一事务,屡次执行同一 Mapper 的雷同查询方法,第一查问后,MyBatis 会将查问后果放入缓存,在两头不波及相应 Mapper 的数据更新(Insert,Update 和 Delete)操作的状况下,后续的查问将会从缓存中获取,而不会查询数据库。

二级缓存是针对于利用级别的缓存,也就是针对不同的 SqlSession 做到缓存。 当开启二级缓存时,MyBatis 会将首次查问后果存入对于 Mapper 的全局缓存,如果两头不执行该 Mapper 的数据更新操作,那么后续的雷同查问都将会从缓存中获取。

2、二级缓存问题

依据二级缓存的介绍发现,如果 Mapper 只是单表查问,并不会呈现问题,然而如果 Mapper 波及的查问呈现 联表 查问,如 UserMapper 在查问 user 信息时须要关联查问 组织信息,也就是须要 user 表和 organization 表关联,OrganizationMapper 在执行更新时并不会更新 UserMapper 的缓存,后果会导致在应用雷同条件 应用 UserMapper 查问 user 信息时,会等到未更新前的 organization 信息,造成数据不统一的状况。

2.1、数据不统一问题验证

查问 SQL

SELECT
 u.*, o.name org_name 
FROM
 user u
 LEFT JOIN organization o ON u.org_id = o.id 
WHERE
 u.id = #{userId}

UserMapper

UserInfo queryUserInfo(@Param("userId") String userId);

UserService

public UserEntity queryUser(String userId) {UserInfo userInfo = userMapper.queryUserInfo(userId);

    return userInfo;
}

调用查问,失去查问后果(屡次查问,失去缓存数据),这里 userId = 1,data 为 user 查问后果

{
 "code": "1",
 "message": null,
 "data": {
   "id": "1",
   "username": "admin",
   "password": "admin",
   "orgName": "组织 1"
 }
}

查问 对应 organization 信息,后果

{
 "code": "1",
 "message": null,
 "data": {
   "id": "1",
   "name": "组织 1"
 }
}

发现和 user 缓存数据统一。

执行更新 organization 操作,将 组织 1 改为 组织 2,再次查问组织信息

{
 "code": "1",
 "message": null,
 "data": {
   "id": "1",
   "name": "组织 2"
 }
}

再次查问 user 信息,发现仍旧从缓存中获取

{
 "code": "1",
 "message": null,
 "data": {
   "id": "1",
   "username": "admin",
   "password": "admin",
   "orgName": "组织 1"
 }
}

造成此问题起因为 organization 数据信息更新只会本人 Mapper 对应的缓存数据,而不会告诉到关联表 organization 的一些 Mapper 更新对应的缓存数据。

2.2、问题解决思路

  • 在 Mapper1 定义时,手动配置 相应的关联 Mapper2
  • 在 Mapper1 缓存 cache1 实例化时,读取 所关联的 Mapper2 的缓存 cache2 相干信息
  • 在 cache1 中存储 cache2 的援用信息
  • cache1 执行 clear 时,同步操作 cache2 执行 clear

3、关联缓存刷新实现

关上二级缓存,本地我的项目应用 MyBatis Plus

mybatis-plus.configuration.cache-enabled=true

次要用到自定义注解 CacheRelations,自定义缓存实现 RelativeCache 和缓存上下文 RelativeCacheContext。

注解 CacheRelations,应用时需标注在对应 mapper 上

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheRelations {
    // from 中 mapper class 对应的缓存更新时,须要更新以后注解标注 mapper 的缓存
    Class<?>[] from() default {};
    // 以后注解标注 mapper 的缓存更新时,须要更新 to 中 mapper class 对应的缓存
    Class<?>[] to() default {};}

自定义缓存 RelativeCache 实现 MyBatis Cache 接口

public class RelativeCache implements Cache {private Map<Object, Object> CACHE_MAP = new ConcurrentHashMap<>();

    private List<RelativeCache> relations = new ArrayList<>();

    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);

    private String id;
    private Class<?> mapperClass;
    private boolean clearing;

    public RelativeCache(String id) throws Exception {
        this.id = id;
        this.mapperClass = Class.forName(id);
        RelativeCacheContext.putCache(mapperClass, this);
        loadRelations();}

    @Override
    public String getId() {return id;}

    @Override
    public void putObject(Object key, Object value) {CACHE_MAP.put(key, value);
    }

    @Override
    public Object getObject(Object key) {return CACHE_MAP.get(key);
    }

    @Override
    public Object removeObject(Object key) {return CACHE_MAP.remove(key);
    }

    @Override
    public void clear() {ReadWriteLock readWriteLock = getReadWriteLock();
        Lock lock = readWriteLock.writeLock();
        lock.lock();
        try {
            // 判断 以后缓存是否正在清空,如果正在清空,勾销本次操作
            // 防止缓存呈现 循环 relation,造成递归无终止,调用栈溢出
            if (clearing) {return;}
            clearing = true;
            try {CACHE_MAP.clear();
                relations.forEach(RelativeCache::clear);
            } finally {clearing = false;}
        } finally {lock.unlock();
        }


    }

    @Override
    public int getSize() {return CACHE_MAP.size();
    }

    @Override
    public ReadWriteLock getReadWriteLock() {return readWriteLock;}

    public void addRelation(RelativeCache relation) {if (relations.contains(relation)){return;}
        relations.add(relation);
    }

    void loadRelations() {
        // 加载 其余缓存更新时 须要更新此缓存的 caches
        // 将 此缓存 退出至这些 caches 的 relations 中
        List<RelativeCache> to = UN_LOAD_TO_RELATIVE_CACHES_MAP.get(mapperClass);
        if (to != null) {to.forEach(relativeCache -> this.addRelation(relativeCache));
        }
        // 加载 此缓存更新时 须要更新的一些缓存 caches
        // 将这些缓存 caches 退出 至 此缓存 relations 中
        List<RelativeCache> from = UN_LOAD_FROM_RELATIVE_CACHES_MAP.get(mapperClass);
        if (from != null) {from.forEach(relativeCache -> relativeCache.addRelation(this));
        }

        CacheRelations annotation = AnnotationUtils.findAnnotation(mapperClass, CacheRelations.class);
        if (annotation == null) {return;}

        Class<?>[] toMappers = annotation.to();
        Class<?>[] fromMappers = annotation.from();

        if (toMappers != null && toMappers.length > 0) {for (Class c : toMappers) {RelativeCache relativeCache = MAPPER_CACHE_MAP.get(c);
                if (relativeCache != null) {
                    // 将找到的缓存增加到以后缓存的 relations 中
                    this.addRelation(relativeCache);
                } else {
                    // 如果找不到 to cache,证实 to cache 还未加载,这时需将对应关系寄存到 UN_LOAD_FROM_RELATIVE_CACHES_MAP
                    // 也就是说 c 对应的 cache 须要 在 以后缓存更新时 进行更新
                    List<RelativeCache> relativeCaches = UN_LOAD_FROM_RELATIVE_CACHES_MAP.putIfAbsent(c, new ArrayList<RelativeCache>());
                    relativeCaches.add(this);
                }
            }
        }

        if (fromMappers != null && fromMappers.length > 0) {for (Class c : fromMappers) {RelativeCache relativeCache = MAPPER_CACHE_MAP.get(c);
                if (relativeCache != null) {
                    // 将找到的缓存增加到以后缓存的 relations 中
                    relativeCache.addRelation(this);
                } else {
                    // 如果找不到 from cache,证实 from cache 还未加载,这时需将对应关系寄存到 UN_LOAD_TO_RELATIVE_CACHES_MAP
                    // 也就是说 c 对应的 cache 更新时须要更新以后缓存
                    List<RelativeCache> relativeCaches = UN_LOAD_TO_RELATIVE_CACHES_MAP.putIfAbsent(c, new ArrayList<RelativeCache>());
                    relativeCaches.add(this);
                }
            }
        }
    }

}

缓存上下文 RelativeCacheContext

public class RelativeCacheContext {

    // 存储全量缓存的映射关系
    public static final Map<Class<?>, RelativeCache> MAPPER_CACHE_MAP = new ConcurrentHashMap<>();
    // 存储 Mapper 对应缓存 须要 to 更新缓存,然而此时 Mapper 对应缓存还未加载
    // 也就是 Class<?> 对应的缓存更新时,须要更新 List<RelativeCache> 中的缓存
    public static final Map<Class<?>, List<RelativeCache>> UN_LOAD_TO_RELATIVE_CACHES_MAP = new ConcurrentHashMap<>();
    // 存储 Mapper 对应缓存 须要 from 更新缓存,然而在 加载 Mapper 缓存时,这些缓存还未加载
    // 也就是 List<RelativeCache> 中的缓存更新时,须要更新 Class<?> 对应的缓存
    public static final Map<Class<?>, List<RelativeCache>> UN_LOAD_FROM_RELATIVE_CACHES_MAP = new ConcurrentHashMap<>();

    public static void putCache(Class<?> clazz, RelativeCache cache) {MAPPER_CACHE_MAP.put(clazz, cache);
    }

    public static void getCache(Class<?> clazz) {MAPPER_CACHE_MAP.get(clazz);
    }

}

应用形式

UserMapper.java

@Repository
@CacheNamespace(implementation = RelativeCache.class, eviction = RelativeCache.class, flushInterval = 30 * 60 * 1000)
@CacheRelations(from = OrganizationMapper.class)
public interface UserMapper extends BaseMapper<UserEntity> {UserInfo queryUserInfo(@Param("userId") String userId);
}

queryUserInfo 是 xml 实现的接口,所以须要在对应 xml 中配置 <cache-ref namespace=“com.mars.system.dao.UserMapper”/>,不然查问后果不会被缓存化。如果接口为 BaseMapper 实现,查问后果会主动缓存化。

UserMapper.xml

<mapper namespace="com.mars.system.dao.UserMapper">
    <cache-ref namespace="com.mars.system.dao.UserMapper"/>
    <select id="queryUserInfo" resultType="com.mars.system.model.UserInfo">
        select u.*, o.name org_name from user u left join organization o on u.org_id = o.id
        where u.id = #{userId}
    </select>
</mapper>

OrganizationMapper.java

@Repository
@CacheNamespace(implementation = RelativeCache.class, eviction = RelativeCache.class, flushInterval = 30 * 60 * 1000)
public interface OrganizationMapper extends BaseMapper<OrganizationEntity> {}

CacheNamespace 中 flushInterval 在默认状况下是有效的,也就是说缓存并不会定时清理。ScheduledCache 是对 flushInterval 性能的实现,MyBatis 的缓存体系是用装璜器进行性能扩大的,所以,如果须要定时刷新,须要应用 ScheduledCache 给到 RelativeCache 增加装璜。

至此,配置和编码实现。

开始验证:

查问 userId= 1 的用户信息

{
    "code":"1",
    "message":null,
    "data":{
        "id":"1",
        "username":"admin",
        "password":"admin",
        "orgName":"组织 1"
    }
}

更新组织信息,将 组织 1 改为 组织 2

{
    "code":"1",
    "message":null,
    "data":{
        "id":"1",
        "name":"组织 2"
    }
}

再次查问用户信息

{
    "code":"1",
    "message":null,
    "data":{
        "id":"1",
        "username":"admin",
        "password":"admin",
        "orgName":"组织 2"
    }
}

合乎预期。

近期热文举荐:

1.1,000+ 道 Java 面试题及答案整顿 (2022 最新版)

2. 劲爆!Java 协程要来了。。。

3.Spring Boot 2.x 教程,太全了!

4. 别再写满屏的爆爆爆炸类了,试试装璜器模式,这才是优雅的形式!!

5.《Java 开发手册(嵩山版)》最新公布,速速下载!

感觉不错,别忘了顺手点赞 + 转发哦!

正文完
 0