Skip to content

Commit

Permalink
update spring boot bean cache utils.
Browse files Browse the repository at this point in the history
  • Loading branch information
vnobo committed Feb 18, 2024
1 parent 6ddfc31 commit 88f70f1
Show file tree
Hide file tree
Showing 17 changed files with 89 additions and 102 deletions.
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package com.platform.boot.commons.base;

import com.platform.boot.commons.utils.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.r2dbc.convert.R2dbcConverter;
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
import org.springframework.data.relational.core.query.Query;
import org.springframework.r2dbc.core.DatabaseClient;
import org.springframework.util.ObjectUtils;
import org.springframework.util.unit.DataSize;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

Expand All @@ -21,8 +20,6 @@
*/
public abstract class AbstractDatabase extends AbstractService {

@Value("${spring.codec.max-in-memory-size:256kb}")
private DataSize maxInMemorySize;
protected R2dbcEntityTemplate entityTemplate;
protected DatabaseClient databaseClient;
protected R2dbcConverter r2dbcConverter;
Expand All @@ -42,30 +39,30 @@ protected <T> Flux<T> queryWithCache(Object key, Query query, Class<T> entityCla

/**
* 查询带有缓存的对象
* @param key 缓存键
* @param query 查询语句
* @param bindParams 绑定参数
*
* @param key 缓存键
* @param sql 查询语句
* @param bindParams 绑定参数
* @param entityClass 实体类
* @return 带有缓存的查询结果
*/
protected <T> Flux<T> queryWithCache(Object key, String query,
protected <T> Flux<T> queryWithCache(Object key, String sql,
Map<String, Object> bindParams, Class<T> entityClass) {
// 构建数据库执行规范
DatabaseClient.GenericExecuteSpec executeSpec = this.databaseClient.sql(() -> query);
for (Map.Entry<String, Object> e : bindParams.entrySet()) {
executeSpec = executeSpec.bind(e.getKey(), e.getValue());
}
var executeSpec = this.databaseClient.sql(() -> sql);
executeSpec = executeSpec.bindValues(bindParams);
// 执行查询并映射结果
Flux<T> source = executeSpec
.map((row, rowMetadata) -> this.r2dbcConverter.read(entityClass, row, rowMetadata)).all();
.map((row, rowMetadata) -> this.r2dbcConverter.read(entityClass, row, rowMetadata))
.all();
// 调用带有缓存的查询方法
return queryWithCache(key, source);
}

/**
* 使用缓存和源Flux查询数据。
*
* @param key 用于标识数据的键
* @param key 用于标识数据的键
* @param sourceFlux 数据源Flux
* @return 查询到的数据流
*/
Expand All @@ -79,7 +76,7 @@ protected <T> Flux<T> queryWithCache(Object key, Flux<T> sourceFlux) {
// 将源Flux的数据添加到缓存数据集合,并在其完成时将更新后的数据放入缓存
Flux<T> source = sourceFlux
.doOnNext(cacheData::add)
.doAfterTerminate(() -> this.cachePut(cacheKey, cacheData));
.doAfterTerminate(() -> BeanUtils.cachePut(cacheKey, cacheData, this.cache));

// 如果缓存数据不为空,则直接返回缓存数据流;否则,当数据流为空时切换为从源Flux获取数据
return Flux.fromIterable(ObjectUtils.isEmpty(cacheData) ? Collections.emptyList() : cacheData)
Expand All @@ -92,35 +89,20 @@ protected <T> Mono<Long> countWithCache(Object key, Query query, Class<T> entity
return countWithCache(key, source);
}

protected Mono<Long> countWithCache(Object key, String query, Map<String, Object> bindParams) {
DatabaseClient.GenericExecuteSpec executeSpec = this.databaseClient.sql(() -> query);
for (Map.Entry<String, Object> e : bindParams.entrySet()) {
executeSpec = executeSpec.bind(e.getKey(), e.getValue());
}
Mono<Long> source = executeSpec
.map(readable -> readable.get(0, Long.class)).one();
protected Mono<Long> countWithCache(Object key, String sql, Map<String, Object> bindParams) {
var executeSpec = this.databaseClient.sql(() -> sql);
executeSpec = executeSpec.bindValues(bindParams);
Mono<Long> source = executeSpec.mapValue(Long.class).first();
return countWithCache(key, source);
}

protected Mono<Long> countWithCache(Object key, Mono<Long> sourceMono) {
String cacheKey = key + ":count";
Long cacheCount = this.cache.get(cacheKey, () -> null);
Mono<Long> source = sourceMono.doOnNext(count -> this.cachePut(cacheKey, count));
Mono<Long> source = sourceMono.doOnNext(count -> BeanUtils.cachePut(cacheKey, count, this.cache));
return Mono.justOrEmpty(cacheCount).switchIfEmpty(Mono.defer(() -> source));
}

private void cachePut(String cacheKey, Object obj) {
if (ObjectUtils.isEmpty(obj)) {
return;
}
DataSize objectSize = com.platform.boot.commons.utils.BeanUtils.getBeanSize(obj);
if (objectSize.toBytes() > this.maxInMemorySize.toBytes()) {
log.warn("Object size is too large,Max memory size is " + this.maxInMemorySize + "," +
" Object size is " + objectSize + ".");
}
this.cache.put(cacheKey, obj);
}

@Autowired
public void setEntityTemplate(R2dbcEntityTemplate entityTemplate) {
this.entityTemplate = entityTemplate;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.platform.boot.converters;
package com.platform.boot.commons.converters;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.platform.boot.converters;
package com.platform.boot.commons.converters;

import com.fasterxml.jackson.databind.JsonNode;
import com.platform.boot.commons.exception.JsonException;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.platform.boot.converters;
package com.platform.boot.commons.converters;

import com.platform.boot.security.core.UserAuditor;
import lombok.NonNull;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,26 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.google.common.collect.Maps;
import com.platform.boot.commons.exception.JsonException;
import com.platform.boot.commons.exception.RestServerException;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.Cache;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.unit.DataSize;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Map;
import java.util.Objects;

/**
* @author <a href="https://github.com/vnobo">Alex bob</a>
*/
@Log4j2
@Component
public class BeanUtils implements InitializingBean {
private final static ByteArrayOutputStream BYTE_ARRAY_OUTPUT_STREAM = new ByteArrayOutputStream();
private final static ObjectOutputStream OBJECT_OUTPUT_STREAM;
Expand All @@ -31,6 +37,38 @@ public class BeanUtils implements InitializingBean {
}
}

public static DataSize MAX_IN_MEMORY_SIZE;

/**
* 将对象缓存到指定的缓存中
*
* @param cacheKey 缓存的key
* @param obj 缓存的对象
*/
public static void cachePut(String cacheKey, Object obj, Cache cache) {
// 如果对象为空,则直接返回
if (ObjectUtils.isEmpty(obj)) {
return;
}

// 获取对象的大小
DataSize objectSize = getBeanSize(obj);

// 如果对象的大小超过了最大内存大小,则输出警告信息
if (objectSize.toBytes() > MAX_IN_MEMORY_SIZE.toBytes()) {
log.warn("Object size is too large, Max memory size is " + MAX_IN_MEMORY_SIZE
+ ", Object size is " + objectSize + ".");
}

// 将对象缓存到指定的缓存中
cache.put(cacheKey, obj);
}

public static String cacheKey(Object... objects) {
int hashCode = Objects.hash(objects);
return String.valueOf(hashCode);
}

public static DataSize getBeanSize(Object obj) {
if (ObjectUtils.isEmpty(obj)) {
throw RestServerException.withMsg("Object is empty!", "This object not null.");
Expand All @@ -41,10 +79,15 @@ public static DataSize getBeanSize(Object obj) {
OBJECT_OUTPUT_STREAM.flush();
return DataSize.ofBytes(BYTE_ARRAY_OUTPUT_STREAM.size());
} catch (IOException e) {
throw JsonException.withError(e);
throw RestServerException.withMsg("Bean Size IO exception!", e);
}
}

@Value("${spring.codec.max-in-memory-size:256kb}")
public void setMaxInMemorySize(DataSize dataSize) {
MAX_IN_MEMORY_SIZE = dataSize;
}

public static <T> T copyProperties(Object source, Class<T> clazz) {
T target = org.springframework.beans.BeanUtils.instantiateClass(clazz);
BeanUtils.copyProperties(source, target, true);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.platform.boot.commons.utils;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.platform.boot.commons.exception.JsonException;
Expand All @@ -10,8 +9,6 @@
import com.platform.boot.security.core.UserAuditor;
import com.platform.boot.security.core.user.UsersService;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
Expand All @@ -25,16 +22,14 @@
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Base64;
import java.util.Objects;
import java.util.StringJoiner;

/**
* @author Alex bob(<a href="https://github.com/vnobo">Alex Bob</a>)
*/
@Component
public final class ContextUtils implements Serializable {
private static final String[] IP_HEADER_CANDIDATES = {
private final static String[] IP_HEADER_CANDIDATES = {
"X-Forwarded-For",
"X-Real-IP",
"Proxy-Client-IP",
Expand All @@ -48,6 +43,7 @@ public final class ContextUtils implements Serializable {
"HTTP_VIA",
"REMOTE_ADDR"
};

public final static String RULE_ADMINISTRATORS = "ROLE_ADMINISTRATORS";
public final static String CSRF_TOKEN_CONTEXT = "CSRF_TOKEN_CONTEXT";

Expand Down Expand Up @@ -79,42 +75,6 @@ public static String getClientIpAddress(ServerHttpRequest httpRequest) {
return Objects.requireNonNull(httpRequest.getRemoteAddress()).getAddress().getHostAddress();
}

public static String cacheKey(Object... objects) {
ObjectMapper objectMapper = ContextUtils.OBJECT_MAPPER.copy();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);

StringJoiner keyBuilder = new StringJoiner("&");
for (Object object : objects) {
if (object instanceof Pageable pageable) {
keyBuilder.merge(applySort(pageable.getSort()));
keyBuilder.add("page=" + pageable.getPageNumber());
keyBuilder.add("size=" + pageable.getPageSize());
keyBuilder.add("offset=" + pageable.getOffset());
continue;
}
try {
String objectJson = objectMapper.writeValueAsString(object);
keyBuilder.add(objectJson);
} catch (JsonProcessingException e) {
throw JsonException.withError(e);
}
}
return Base64.getEncoder().encodeToString(keyBuilder.toString().getBytes());
}

private static StringJoiner applySort(Sort sort) {
StringJoiner sortKey = new StringJoiner("&");
if (sort == null || sort.isUnsorted()) {
return sortKey;
}
for (Sort.Order order : sort) {
String sortedPropertyName = order.getProperty();
String sortedProperty = order.isIgnoreCase() ? "lower(" + sortedPropertyName + ")" : sortedPropertyName;
sortKey.add(sortedProperty + "=" + (order.isAscending() ? "asc" : "desc"));
}
return sortKey;
}

public static Mono<SecurityDetails> securityDetails() {
return ReactiveSecurityContextHolder.getContext()
.map(securityContext -> securityContext.getAuthentication().getPrincipal())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.platform.boot.relational.logger;

import com.platform.boot.commons.base.AbstractDatabase;
import com.platform.boot.commons.utils.ContextUtils;
import com.platform.boot.commons.utils.BeanUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
Expand All @@ -21,13 +21,13 @@ public class LoggersService extends AbstractDatabase {
private final LoggersRepository loggersRepository;

public Flux<Logger> search(LoggerRequest request, Pageable pageable) {
var cacheKey = ContextUtils.cacheKey(request, pageable);
var cacheKey = BeanUtils.cacheKey(request, pageable);
Query query = Query.query(request.toCriteria()).with(pageable);
return this.queryWithCache(cacheKey, query, Logger.class);
}

public Mono<Page<Logger>> page(LoggerRequest request, Pageable pageable) {
var cacheKey = ContextUtils.cacheKey(request);
var cacheKey = BeanUtils.cacheKey(request);
Query query = Query.query(request.toCriteria());
var searchMono = this.search(request, pageable).collectList();
var countMono = this.countWithCache(cacheKey, query, Logger.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
@Table("se_menus")
public class Menu implements BaseEntity<Integer> {


@Id
private Integer id;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import com.platform.boot.commons.base.AbstractDatabase;
import com.platform.boot.commons.exception.RestServerException;
import com.platform.boot.commons.utils.BeanUtils;
import com.platform.boot.commons.utils.ContextUtils;
import com.platform.boot.security.core.group.authority.GroupAuthoritiesRepository;
import com.platform.boot.security.core.user.authority.UserAuthoritiesRepository;
Expand Down Expand Up @@ -33,7 +34,7 @@ public class MenusService extends AbstractDatabase {
private final UserAuthoritiesRepository userAuthoritiesRepository;

public Flux<Menu> search(MenuRequest request) {
var cacheKey = ContextUtils.cacheKey(request);
var cacheKey = BeanUtils.cacheKey(request);
Query query = Query.query(request.toCriteria()).sort(Sort.by("sort"));
return this.queryWithCache(cacheKey, query, Menu.class)
.flatMap(ContextUtils::serializeUserAuditor);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public class GroupsService extends AbstractDatabase {
private final GroupsRepository groupsRepository;

public Flux<Group> search(GroupRequest request, Pageable pageable) {
var cacheKey = ContextUtils.cacheKey(request, pageable);
var cacheKey = BeanUtils.cacheKey(request, pageable);
ParamSql paramSql = request.bindParamSql();
String query = "select * from se_groups" + paramSql.whereSql() + CriteriaUtils.applyPage(pageable);
return super.queryWithCache(cacheKey, query, paramSql.params(), Group.class)
Expand All @@ -33,7 +33,7 @@ public Flux<Group> search(GroupRequest request, Pageable pageable) {
public Mono<Page<Group>> page(GroupRequest request, Pageable pageable) {
var searchMono = this.search(request, pageable).collectList();

var cacheKey = ContextUtils.cacheKey(request);
var cacheKey = BeanUtils.cacheKey(request);
ParamSql paramSql = request.bindParamSql();
String query = "select count(*) from se_groups" + paramSql.whereSql();
var countMono = this.countWithCache(cacheKey, query, paramSql.params());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@


import com.platform.boot.commons.base.AbstractDatabase;
import com.platform.boot.commons.utils.BeanUtils;
import com.platform.boot.commons.utils.ContextUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
Expand Down Expand Up @@ -29,7 +30,7 @@ public class GroupAuthoritiesService extends AbstractDatabase {

public Flux<GroupAuthority> search(GroupAuthorityRequest request, Pageable pageable) {

var cacheKey = ContextUtils.cacheKey(request, pageable);
var cacheKey = BeanUtils.cacheKey(request, pageable);
Query query = Query.query(request.toCriteria()).with(pageable);

return super.queryWithCache(cacheKey, query, GroupAuthority.class)
Expand Down
Loading

0 comments on commit 88f70f1

Please sign in to comment.