Cache of the dashboard titles

This commit is contained in:
Andrii Shvaika 2023-03-31 17:43:49 +03:00
parent 95e9f5b715
commit b0d177ee51
12 changed files with 175 additions and 5 deletions

View File

@ -185,9 +185,9 @@ public class DefaultTbUserSettingsService implements TbUserSettingsService {
Map<UUID, String> dashboardTitles = new HashMap<>();
uniqueIds.forEach(id -> {
var dashboardInfo = dashboardService.findDashboardInfoById(tenantId, new DashboardId(id));
if (dashboardInfo != null && StringUtils.isNotEmpty(dashboardInfo.getTitle())) {
dashboardTitles.put(id, dashboardInfo.getTitle());
var title = dashboardService.findDashboardTitleById(tenantId, new DashboardId(id));
if (StringUtils.isNotEmpty(title)) {
dashboardTitles.put(id, title);
}
}
);

View File

@ -474,6 +474,10 @@ cache:
userSettings:
timeToLiveInMinutes: "${CACHE_SPECS_USER_SETTINGS_TTL:1440}"
maxSize: "${CACHE_SPECS_USER_SETTINGS_MAX_SIZE:100000}"
dashboardTitles:
timeToLiveInMinutes: "${CACHE_SPECS_DASHBOARD_TITLES_TTL:1440}"
maxSize: "${CACHE_SPECS_DASHBOARD_TITLES_MAX_SIZE:100000}"
#Disable this because it is not required.
spring.data.redis.repositories.enabled: false

View File

@ -18,6 +18,7 @@ package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.After;
import org.junit.Assert;
import org.junit.Ignore;
@ -1104,6 +1105,30 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
Assert.assertEquals(savedDashboard2.getId().getId(), starred.getId());
Assert.assertEquals(savedDashboard2.getTitle(), starred.getTitle());
//TEST renaming in the cache.
savedDashboard1.setTitle(RandomStringUtils.randomAlphanumeric(10));
savedDashboard1 = doPost("/api/dashboard", savedDashboard1, Dashboard.class);
savedDashboard2.setTitle(RandomStringUtils.randomAlphanumeric(10));
savedDashboard2 = doPost("/api/dashboard", savedDashboard2, Dashboard.class);
newSettings = doGet("/api/user/dashboards/" + savedDashboard1.getId().getId() + "/unstar", UserDashboardsInfo.class);
Assert.assertNotNull(newSettings);
Assert.assertNotNull(newSettings.getLast());
Assert.assertEquals(2, newSettings.getLast().size());
lastVisited = newSettings.getLast().get(0);
Assert.assertEquals(savedDashboard2.getId().getId(), lastVisited.getId());
Assert.assertEquals(savedDashboard2.getTitle(), lastVisited.getTitle());
Assert.assertTrue(lastVisited.isStarred());
lastVisited = newSettings.getLast().get(1);
Assert.assertEquals(savedDashboard1.getId().getId(), lastVisited.getId());
Assert.assertEquals(savedDashboard1.getTitle(), lastVisited.getTitle());
Assert.assertFalse(lastVisited.isStarred());
Assert.assertNotNull(retrievedSettings.getStarred());
Assert.assertEquals(1, newSettings.getStarred().size());
starred = newSettings.getStarred().get(0);
Assert.assertEquals(savedDashboard2.getId().getId(), starred.getId());
Assert.assertEquals(savedDashboard2.getTitle(), starred.getTitle());
doDelete("/api/dashboard/" + savedDashboard1.getId().getId().toString()).andExpect(status().isOk());
newSettings = doGet("/api/user/dashboards", UserDashboardsInfo.class);

View File

@ -36,7 +36,7 @@ public interface DashboardService extends EntityDaoService {
DashboardInfo findDashboardInfoById(TenantId tenantId, DashboardId dashboardId);
// String findDashboardTitleById(TenantId tenantId, DashboardId dashboardId);
String findDashboardTitleById(TenantId tenantId, DashboardId dashboardId);
ListenableFuture<DashboardInfo> findDashboardInfoByIdAsync(TenantId tenantId, DashboardId dashboardId);

View File

@ -42,4 +42,5 @@ public class CacheConstants {
public static final String TWO_FA_VERIFICATION_CODES_CACHE = "twoFaVerificationCodes";
public static final String VERSION_CONTROL_TASK_CACHE = "versionControlTask";
public static final String USER_SETTINGS_CACHE = "userSettings";
public static final String DASHBOARD_TITLES_CACHE = "dashboardTitles";
}

View File

@ -16,6 +16,8 @@
package org.thingsboard.server.dao.dashboard;
import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.Dao;
@ -77,4 +79,5 @@ public interface DashboardInfoDao extends Dao<DashboardInfo> {
DashboardInfo findFirstByTenantIdAndName(UUID tenantId, String name);
String findTitleById(UUID tenantId, UUID dashboardId);
}

View File

@ -19,8 +19,12 @@ import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.event.TransactionalEventListener;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.thingsboard.server.cache.TbTransactionalCache;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DashboardInfo;
@ -36,6 +40,7 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.settings.UserSettingsCompositeKey;
import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.edge.EdgeDao;
import org.thingsboard.server.dao.entity.AbstractEntityService;
@ -43,6 +48,7 @@ import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
import org.thingsboard.server.dao.user.UserSettingsEvictEvent;
import java.util.List;
import java.util.Optional;
@ -70,6 +76,25 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
@Autowired
private DataValidator<Dashboard> dashboardValidator;
@Autowired
protected TbTransactionalCache<DashboardId, String> cache;
@Autowired
private ApplicationEventPublisher eventPublisher;
protected void publishEvictEvent(DashboardTitleEvictEvent event) {
if (TransactionSynchronizationManager.isActualTransactionActive()) {
eventPublisher.publishEvent(event);
} else {
handleEvictEvent(event);
}
}
@TransactionalEventListener(classes = DashboardTitleEvictEvent.class)
public void handleEvictEvent(DashboardTitleEvictEvent event) {
cache.evict(event.getKey());
}
@Override
public Dashboard findDashboardById(TenantId tenantId, DashboardId dashboardId) {
log.trace("Executing findDashboardById [{}]", dashboardId);
@ -91,6 +116,12 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
return dashboardInfoDao.findById(tenantId, dashboardId.getId());
}
@Override
public String findDashboardTitleById(TenantId tenantId, DashboardId dashboardId) {
return cache.getAndPutInTransaction(dashboardId,
() -> dashboardInfoDao.findTitleById(tenantId.getId(), dashboardId.getId()), true);
}
@Override
public ListenableFuture<DashboardInfo> findDashboardInfoByIdAsync(TenantId tenantId, DashboardId dashboardId) {
log.trace("Executing findDashboardInfoByIdAsync [{}]", dashboardId);
@ -103,8 +134,13 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
log.trace("Executing saveDashboard [{}]", dashboard);
dashboardValidator.validate(dashboard, DashboardInfo::getTenantId);
try {
return dashboardDao.save(dashboard.getTenantId(), dashboard);
var saved = dashboardDao.save(dashboard.getTenantId(), dashboard);
publishEvictEvent(new DashboardTitleEvictEvent(saved.getId()));
return saved;
} catch (Exception e) {
if (dashboard.getId() != null) {
publishEvictEvent(new DashboardTitleEvictEvent(dashboard.getId()));
}
checkConstraintViolation(e, "dashboard_external_id_unq_key", "Dashboard with such external id already exists!");
throw e;
}
@ -170,6 +206,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
deleteEntityRelations(tenantId, dashboardId);
try {
dashboardDao.removeById(tenantId, dashboardId.getId());
publishEvictEvent(new DashboardTitleEvictEvent(dashboardId));
} catch (Exception t) {
ConstraintViolationException e = extractConstraintViolationException(t).orElse(null);
if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("fk_default_dashboard_device_profile")) {

View File

@ -0,0 +1,25 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.dashboard;
import lombok.Data;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.settings.UserSettingsCompositeKey;
@Data
public class DashboardTitleEvictEvent {
private final DashboardId key;
}

View File

@ -0,0 +1,33 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.dashboard;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.CaffeineTbTransactionalCache;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.id.DashboardId;
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true)
@Service("DashboardTitlesCache")
public class DashboardTitlesCaffeineCache extends CaffeineTbTransactionalCache<DashboardId, String> {
public DashboardTitlesCaffeineCache(CacheManager cacheManager) {
super(cacheManager, CacheConstants.DASHBOARD_TITLES_CACHE);
}
}

View File

@ -0,0 +1,35 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.dashboard;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.CacheSpecsMap;
import org.thingsboard.server.cache.RedisTbTransactionalCache;
import org.thingsboard.server.cache.TBRedisCacheConfiguration;
import org.thingsboard.server.cache.TbFSTRedisSerializer;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.id.DashboardId;
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis")
@Service("DashboardTitlesCache")
public class DashboardTitlesRedisCache extends RedisTbTransactionalCache<DashboardId, String> {
public DashboardTitlesRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) {
super(CacheConstants.DASHBOARD_TITLES_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>());
}
}

View File

@ -72,4 +72,6 @@ public interface DashboardInfoRepository extends JpaRepository<DashboardInfoEnti
@Param("searchText") String searchText,
Pageable pageable);
@Query("SELECT di.title FROM DashboardInfoEntity di WHERE di.tenantId = :tenantId AND di.id = :dashboardId")
String findTitleByTenantIdAndId(@Param("tenantId") UUID tenantId, @Param("dashboardId") UUID dashboardId);
}

View File

@ -118,4 +118,9 @@ public class JpaDashboardInfoDao extends JpaAbstractSearchTextDao<DashboardInfoE
public DashboardInfo findFirstByTenantIdAndName(UUID tenantId, String name) {
return DaoUtil.getData(dashboardInfoRepository.findFirstByTenantIdAndTitle(tenantId, name));
}
@Override
public String findTitleById(UUID tenantId, UUID dashboardId) {
return dashboardInfoRepository.findTitleByTenantIdAndId(tenantId, dashboardId);
}
}