The single database transaction of my IoT project also needs to be cautious

The database under the monolithic architecture mode is basically a single database, so the essence of the application layer through the spring transaction control is actually the database's support for transactions. Without the database's transaction support, spring cannot provide transaction functions. There are also two ways to implement transactions through spring: declarative transactions and programmatic transactions , no matter which one is easier to implement. Like a general business, type programming in the following way:

1. Configuration file

    <!-- 事务控制 -->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource" />
	</bean>

    <!--  配置事务传播特性 -->
	<tx:advice id="txAdvice" transaction-manager="transactionManager">
	    <tx:attributes>
	      <tx:method name="save*" propagation="REQUIRED"/>
	      <tx:method name="update*" propagation="REQUIRED"/>
	      <tx:method name="delete*" propagation="REQUIRED"/>
	      <tx:method name="*" read-only="true" />
	    </tx:attributes>
	</tx:advice>

	<!--  配置参与事务的类 -->
	<aop:config>
		<aop:pointcut id="interceptorPointCuts" expression="execution(* com.orange.adminweb.service.*.*(..))"/>
		<aop:advisor pointcut-ref="interceptorPointCuts" advice-ref="txAdvice" />
	</aop:config>

 

2. The java code under the com.orange.adminweb.service package

public void updateBinding(String userId,String merchantId,Order order) throws Exception {
     //删除用户
     userService.delete(userId);
     //删除商家
     merchantService.delete(merchantId);
     //更新订单
     orderService.update(order);
		
}

Just pay attention to two things like simple transaction similar to the above programming:

1. The save*, update*, and delete* in the above xml configuration indicate that the wildcards starting with save, update, and delete methods (under the com.orange.adminweb.service package) are all transaction-enabled.

2. The methods beginning with save, update, delete (under the com.orange.adminweb.service package) must continue to throw exceptions out.

Therefore, many colleagues who have just started to write code basically follow this method, and there is no problem, but some people in the test recently reported that there is a business (similar to the above) data inconsistency. Simply put, the data of several tables are inconsistent. Find the method class, open it, and the scene is indeed slightly different from the above. The following is the simulation code.

private void saveSubscribe(){
		StringBuilder clientBuilder = new StringBuilder();
		clientBuilder.append(GlobalConstant.GROUPID);
		clientBuilder.append("@@@");
		clientBuilder.append("ClientID_");
		clientBuilder.append(UuidUtil.get32UUID());
		String clientId=clientBuilder.toString();
		MemoryPersistence persistence = new MemoryPersistence();
		try {
			final MqttClient sampleClient = new MqttClient(GlobalConstant.BROKER, clientId, persistence);
            final MqttConnectOptions connOpts = new MqttConnectOptions();
            System.out.println("Connecting to broker: " + GlobalConstant.BROKER);
            String sign=MacSignature.macSignature(clientId.split("@@@")[0], GlobalConstant.SECRETKEY);
            final String[] topicFilters=new String[]{GlobalConstant.TOPIC + "/#"};
            final int[]qos={1};
            connOpts.setUserName(GlobalConstant.ACESSKEY);
            connOpts.setServerURIs(new String[] { GlobalConstant.BROKER });
            connOpts.setPassword(sign.toCharArray());
            connOpts.setCleanSession(false);
            connOpts.setKeepAliveInterval(100);
            sampleClient.setCallback(new MqttCallback() {
                public void connectionLost(Throwable throwable) {
                    log.info("mqtt connection lost");
                    throwable.printStackTrace();
                    while(!sampleClient.isConnected()){
                        try {
                            sampleClient.connect(connOpts);
                            sampleClient.subscribe(topicFilters,qos);
                        } catch (MqttException e) {
                            e.printStackTrace();
                        }
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
                public void messageArrived(String topic, MqttMessage mqttMessage){
                	try {
                		saveOrder(new String(mqttMessage.getPayload()));
				 } catch (Exception e) {
						e.printStackTrace();
				 }
                }
                public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
                    log.ifo("deliveryComplete:" + iMqttDeliveryToken.getMessageId());
                }
            });
            sampleClient.connect(connOpts);
            sampleClient.subscribe(topicFilters,qos);
		}catch(Exception ex){
			ex.printStackTrace();
		}
	}

The saveOrder method for operating three tables is as follows:

private void saveOrder(String message) throws Exception{
     //修改用户
     userService.updateUser(......);

     //修改商家
     merchantService.updateMerchant(......);
     
     //下订单
     orderService.saveOrder(.......);
}

Because of the business itself, when any of the methods in the saveOrder method modify users, modify merchants, or place orders is abnormal, the saveSubscribe method does not roll back the data.

Focus on the code snippets in the saveSubscribe method:

sampleClient.setCallback(new MqttCallback() {
                public void connectionLost(Throwable throwable) {
                    log.info("mqtt connection lost");
                    throwable.printStackTrace();
                    while(!sampleClient.isConnected()){
                        try {
                            sampleClient.connect(connOpts);
                            sampleClient.subscribe(topicFilters,qos);
                        } catch (MqttException e) {
                            e.printStackTrace();
                        }
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
                public void messageArrived(String topic, MqttMessage mqttMessage){
                	try {
                		saveOrder(new String(mqttMessage.getPayload()));
				 } catch (Exception e) {
						e.printStackTrace();
				 }
                }
                public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
                    log.ifo("deliveryComplete:" + iMqttDeliveryToken.getMessageId());
                }
            });

There is a setCallback callback in this, and the exception in it cannot be thrown out, so the data will not be rolled back. Simply put, the saveSubscribe method is a method without transaction control.

In fact, this business scenario is somewhat similar to our previous business needs:

Both AService and BService are configured with transactions. AService calls BService. BService needs to record logs, but when BService is abnormal, it is found that no logs are recorded. The reason is that there is a parameter propagation when AService and BService configure transactions. Both are configured by default. REQUIRED

 <tx:method name="save*" propagation="REQUIRED"/>

When using this strategy, BService will use Aservice's transactions, so AService rollback will roll back everything in the entire method body. Therefore, to solve this business scenario, BService needs to configure independent transactions. Regardless of whether there is an abnormality in the Aservice of the business logic, the BService log should be able to record success.

So to solve the problem that the setCallback callback does not throw exceptions, the configuration can be modified to saveOrder to configure independent transactions.

<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="saveOrder" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"/>

 

Through the review of the solution of this problem, in the final analysis, Spring transaction types are not paid attention to. Specific business scenarios should use different transaction types instead of blindly using REQUIRED. Finally, we post seven types of transaction propagation behaviors of Spring. :

PROPAGATION_REQUIRED-support the current transaction, if there is no current transaction, create a new transaction. This is the most common choice.

PROPAGATION_SUPPORTS-supports the current transaction, if there is no transaction currently, it will be executed in a non-transactional manner.

PROPAGATION_MANDATORY-supports the current transaction, if there is no current transaction, throw an exception.

PROPAGATION_REQUIRES_NEW--Create a new transaction, if there is a transaction currently, suspend the current transaction.

PROPAGATION_NOT_SUPPORTED--The operation is performed in a non-transactional manner. If there is a transaction currently, the current transaction is suspended.

PROPAGATION_NEVER--execute in a non-transactional manner. If there is a transaction currently, an exception is thrown.

PROPAGATION_NESTED--If a transaction currently exists, it will be executed within a nested transaction. If there is no transaction currently, proceed similar to PROPAGATION_REQUIRED.

Guess you like

Origin blog.csdn.net/u010460625/article/details/108893953