Commit bb643df296650cfbe420512490339f39b6661668

Authored by Igor Kulikov
Committed by GitHub
2 parents 09ed69a5 f50dec4f

Merge pull request #3888 from YevhenBondarenko/fix-device-profile-service

fix ConstraintViolationException on findOrCreateDeviceProfile
@@ -64,8 +64,10 @@ import java.util.Arrays; @@ -64,8 +64,10 @@ import java.util.Arrays;
64 import java.util.Collections; 64 import java.util.Collections;
65 import java.util.HashSet; 65 import java.util.HashSet;
66 import java.util.List; 66 import java.util.List;
67 -import java.util.stream.Collectors;  
68 import java.util.Set; 67 import java.util.Set;
  68 +import java.util.concurrent.locks.Lock;
  69 +import java.util.concurrent.locks.ReentrantLock;
  70 +import java.util.stream.Collectors;
69 71
70 import static org.thingsboard.server.common.data.CacheConstants.DEVICE_PROFILE_CACHE; 72 import static org.thingsboard.server.common.data.CacheConstants.DEVICE_PROFILE_CACHE;
71 import static org.thingsboard.server.dao.service.Validator.validateId; 73 import static org.thingsboard.server.dao.service.Validator.validateId;
@@ -101,6 +103,8 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D @@ -101,6 +103,8 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D
101 @Autowired 103 @Autowired
102 private CacheManager cacheManager; 104 private CacheManager cacheManager;
103 105
  106 + private final Lock findOrCreateLock = new ReentrantLock();
  107 +
104 @Cacheable(cacheNames = DEVICE_PROFILE_CACHE, key = "{#deviceProfileId.id}") 108 @Cacheable(cacheNames = DEVICE_PROFILE_CACHE, key = "{#deviceProfileId.id}")
105 @Override 109 @Override
106 public DeviceProfile findDeviceProfileById(TenantId tenantId, DeviceProfileId deviceProfileId) { 110 public DeviceProfile findDeviceProfileById(TenantId tenantId, DeviceProfileId deviceProfileId) {
@@ -220,7 +224,15 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D @@ -220,7 +224,15 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D
220 log.trace("Executing findOrCreateDefaultDeviceProfile"); 224 log.trace("Executing findOrCreateDefaultDeviceProfile");
221 DeviceProfile deviceProfile = findDeviceProfileByName(tenantId, name); 225 DeviceProfile deviceProfile = findDeviceProfileByName(tenantId, name);
222 if (deviceProfile == null) { 226 if (deviceProfile == null) {
223 - deviceProfile = this.doCreateDefaultDeviceProfile(tenantId, name, name.equals("default")); 227 + try {
  228 + findOrCreateLock.lock();
  229 + deviceProfile = findDeviceProfileByName(tenantId, name);
  230 + if (deviceProfile == null) {
  231 + deviceProfile = this.doCreateDefaultDeviceProfile(tenantId, name, name.equals("default"));
  232 + }
  233 + } finally {
  234 + findOrCreateLock.unlock();
  235 + }
224 } 236 }
225 return deviceProfile; 237 return deviceProfile;
226 } 238 }
@@ -15,6 +15,10 @@ @@ -15,6 +15,10 @@
15 */ 15 */
16 package org.thingsboard.server.dao.service; 16 package org.thingsboard.server.dao.service;
17 17
  18 +import com.google.common.util.concurrent.Futures;
  19 +import com.google.common.util.concurrent.ListenableFuture;
  20 +import com.google.common.util.concurrent.ListeningExecutorService;
  21 +import com.google.common.util.concurrent.MoreExecutors;
18 import org.junit.After; 22 import org.junit.After;
19 import org.junit.Assert; 23 import org.junit.Assert;
20 import org.junit.Before; 24 import org.junit.Before;
@@ -34,6 +38,9 @@ import org.thingsboard.server.dao.exception.DataValidationException; @@ -34,6 +38,9 @@ import org.thingsboard.server.dao.exception.DataValidationException;
34 import java.util.ArrayList; 38 import java.util.ArrayList;
35 import java.util.Collections; 39 import java.util.Collections;
36 import java.util.List; 40 import java.util.List;
  41 +import java.util.concurrent.ExecutionException;
  42 +import java.util.concurrent.ExecutorService;
  43 +import java.util.concurrent.Executors;
37 import java.util.stream.Collectors; 44 import java.util.stream.Collectors;
38 45
39 public class BaseDeviceProfileServiceTest extends AbstractServiceTest { 46 public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
@@ -113,6 +120,23 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { @@ -113,6 +120,23 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
113 } 120 }
114 121
115 @Test 122 @Test
  123 + public void testFindOrCreateDeviceProfile() throws ExecutionException, InterruptedException {
  124 + ListeningExecutorService testExecutor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(100));
  125 + try {
  126 + List<ListenableFuture<DeviceProfile>> futures = new ArrayList<>();
  127 + for (int i = 0; i < 50; i ++) {
  128 + futures.add(testExecutor.submit(() -> deviceProfileService.findOrCreateDeviceProfile(tenantId, "Device Profile 1")));
  129 + futures.add(testExecutor.submit(() -> deviceProfileService.findOrCreateDeviceProfile(tenantId, "Device Profile 2")));
  130 + }
  131 +
  132 + List<DeviceProfile> deviceProfiles = Futures.allAsList(futures).get();
  133 + deviceProfiles.forEach(Assert::assertNotNull);
  134 + } finally {
  135 + testExecutor.shutdownNow();
  136 + }
  137 + }
  138 +
  139 + @Test
116 public void testSetDefaultDeviceProfile() { 140 public void testSetDefaultDeviceProfile() {
117 DeviceProfile deviceProfile1 = this.createDeviceProfile(tenantId,"Device Profile 1"); 141 DeviceProfile deviceProfile1 = this.createDeviceProfile(tenantId,"Device Profile 1");
118 DeviceProfile deviceProfile2 = this.createDeviceProfile(tenantId,"Device Profile 2"); 142 DeviceProfile deviceProfile2 = this.createDeviceProfile(tenantId,"Device Profile 2");