Browse Source
The initial RPC create is now persisted synchronously before sending (persist-before-send); only status updates remain on the async batched queue. The batched-insert plumbing that was retained for reversibility is now dead code and is removed: - remove createAsync from RpcService/RpcDao/BaseRpcService/JpaRpcDao - delete RpcQueueEntry (insert/update discriminator no longer needed) - rename RpcInsertRepository -> RpcUpdateRepository; the queue now carries RpcEntity directly and batches UPDATEs only - re-aim JpaRpcDaoTest / TbRpcServiceTest off the removed insert API No behavior change: update batching, uuid partitioning/sort, and the response COALESCE preservation are all unchanged.pull/15853/head
10 changed files with 100 additions and 198 deletions
@ -1,99 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.sql.rpc; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import org.springframework.jdbc.core.BatchPreparedStatementSetter; |
|||
import org.springframework.stereotype.Repository; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.server.dao.model.sql.RpcEntity; |
|||
import org.thingsboard.server.dao.sqlts.insert.AbstractInsertRepository; |
|||
|
|||
import java.sql.PreparedStatement; |
|||
import java.sql.SQLException; |
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.stream.Collectors; |
|||
|
|||
@Repository |
|||
public class RpcInsertRepository extends AbstractInsertRepository { |
|||
|
|||
private static final String INSERT = |
|||
"INSERT INTO rpc (id, created_time, tenant_id, device_id, expiration_time, request, response, additional_info, status) " + |
|||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) " + |
|||
"ON CONFLICT (id) DO UPDATE SET status = EXCLUDED.status, response = COALESCE(EXCLUDED.response, rpc.response);"; |
|||
|
|||
private static final String UPDATE = |
|||
"UPDATE rpc SET status = ?, response = COALESCE(?, response) WHERE id = ?;"; |
|||
|
|||
@FunctionalInterface |
|||
private interface ColumnBinder { |
|||
void bind(PreparedStatement ps, RpcEntity rpc) throws SQLException; |
|||
} |
|||
|
|||
List<Boolean> saveOrUpdate(List<RpcQueueEntry> entries) { |
|||
Map<Boolean, List<RpcEntity>> byIntent = entries.stream().collect(Collectors.partitioningBy( |
|||
RpcQueueEntry::insert, Collectors.mapping(RpcQueueEntry::entity, Collectors.toList()))); |
|||
List<RpcEntity> inserts = byIntent.get(true); |
|||
List<RpcEntity> updates = byIntent.get(false); |
|||
return transactionTemplate.execute(status -> { |
|||
if (!inserts.isEmpty()) { |
|||
batch(INSERT, inserts, (ps, rpc) -> { |
|||
ps.setObject(1, rpc.getUuid()); |
|||
ps.setLong(2, rpc.getCreatedTime()); |
|||
ps.setObject(3, rpc.getTenantId()); |
|||
ps.setObject(4, rpc.getDeviceId()); |
|||
ps.setLong(5, rpc.getExpirationTime()); |
|||
ps.setString(6, toJsonStr(rpc.getRequest())); |
|||
ps.setString(7, toJsonStr(rpc.getResponse())); |
|||
ps.setString(8, toJsonStr(rpc.getAdditionalInfo())); |
|||
ps.setString(9, rpc.getStatus().name()); |
|||
}); |
|||
} |
|||
int[] updateCounts = updates.isEmpty() ? new int[0] : batch(UPDATE, updates, (ps, rpc) -> { |
|||
ps.setString(1, rpc.getStatus().name()); |
|||
ps.setString(2, toJsonStr(rpc.getResponse())); |
|||
ps.setObject(3, rpc.getUuid()); |
|||
}); |
|||
|
|||
List<Boolean> persisted = new ArrayList<>(entries.size()); |
|||
int updateIdx = 0; |
|||
for (RpcQueueEntry entry : entries) { |
|||
persisted.add(entry.insert() || updateCounts[updateIdx++] > 0); |
|||
} |
|||
return persisted; |
|||
}); |
|||
} |
|||
|
|||
private int[] batch(String sql, List<RpcEntity> entities, ColumnBinder binder) { |
|||
return jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() { |
|||
@Override |
|||
public void setValues(PreparedStatement ps, int i) throws SQLException { |
|||
binder.bind(ps, entities.get(i)); |
|||
} |
|||
|
|||
@Override |
|||
public int getBatchSize() { |
|||
return entities.size(); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private String toJsonStr(JsonNode node) { |
|||
return node == null ? null : replaceNullChars(JacksonUtil.toString(node)); |
|||
} |
|||
} |
|||
@ -1,29 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.sql.rpc; |
|||
|
|||
import org.thingsboard.server.dao.model.sql.RpcEntity; |
|||
|
|||
record RpcQueueEntry(RpcEntity entity, boolean insert) { |
|||
|
|||
static RpcQueueEntry forInsert(RpcEntity entity) { |
|||
return new RpcQueueEntry(entity, true); |
|||
} |
|||
|
|||
static RpcQueueEntry forUpdate(RpcEntity entity) { |
|||
return new RpcQueueEntry(entity, false); |
|||
} |
|||
} |
|||
@ -0,0 +1,64 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.sql.rpc; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import org.springframework.jdbc.core.BatchPreparedStatementSetter; |
|||
import org.springframework.stereotype.Repository; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.server.dao.model.sql.RpcEntity; |
|||
import org.thingsboard.server.dao.sqlts.insert.AbstractInsertRepository; |
|||
|
|||
import java.sql.PreparedStatement; |
|||
import java.sql.SQLException; |
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
|
|||
@Repository |
|||
public class RpcUpdateRepository extends AbstractInsertRepository { |
|||
|
|||
private static final String UPDATE = |
|||
"UPDATE rpc SET status = ?, response = COALESCE(?, response) WHERE id = ?;"; |
|||
|
|||
List<Boolean> update(List<RpcEntity> updates) { |
|||
return transactionTemplate.execute(status -> { |
|||
int[] updateCounts = jdbcTemplate.batchUpdate(UPDATE, new BatchPreparedStatementSetter() { |
|||
@Override |
|||
public void setValues(PreparedStatement ps, int i) throws SQLException { |
|||
RpcEntity rpc = updates.get(i); |
|||
ps.setString(1, rpc.getStatus().name()); |
|||
ps.setString(2, toJsonStr(rpc.getResponse())); |
|||
ps.setObject(3, rpc.getUuid()); |
|||
} |
|||
|
|||
@Override |
|||
public int getBatchSize() { |
|||
return updates.size(); |
|||
} |
|||
}); |
|||
|
|||
List<Boolean> persisted = new ArrayList<>(updateCounts.length); |
|||
for (int updateCount : updateCounts) { |
|||
persisted.add(updateCount > 0); |
|||
} |
|||
return persisted; |
|||
}); |
|||
} |
|||
|
|||
private String toJsonStr(JsonNode node) { |
|||
return node == null ? null : replaceNullChars(JacksonUtil.toString(node)); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue