Application of AOP in actual development

AOP application in actual development

1. Background

The company’s business belongs to the B-side, and the existing back-end management and Android client are implemented by separating the front and back ends of the Web. The client supports offline mode. Since it supports offline mode, the problem of data synchronization arises. The Android side uses SQLite to save local data, and users can synchronize data after being connected to the Internet. Synchronized data uses incremental synchronization to save network overhead. Since each module of the system has been developed, this requirement is a new requirement. If the original code is changed, there will be many changes involved, and many identical codes are likely to appear, so AOP implementation is considered.

2. Table structure design

Since the backend and client adopt incremental synchronization to synchronize data, after analyzing the existing tables and combining with new requirements, we considered using the modification time in the entity table as the version number and adding a VersionControl table to store the information of each entity table. The latest version number.

CREATE TABLE `version_control` (
  `id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
  `table_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'data table name',
  `version` datetime DEFAULT NULL COMMENT 'version (table latest modification time)',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

3. Coding design

Since this project has been running for many years and there are constantly new requirements, AOP was considered for implementation during the design phase. Taking into account the scalability requirements, AOP + annotations were used for implementation at the specific implementation level. After adopting this solution, you only need to add this annotation to the method that modifies certain tables to maintain the version number of a certain table.

3.1, Annotation implementation
@Target({<!-- -->ElementType.METHOD}) // Declare the location of annotation usage
@Retention(RetentionPolicy.RUNTIME) // Specify the annotation’s retention policy as runtime
public @interface VersionControl {<!-- -->
    @AliasFor("tableName")
    String value() default ""; // Table name that needs to maintain version number

    @AliasFor("value")
    String tableNames() default "";
}
3.2, AOP implementation
3.2.1, Entity Class
@TableName(value ="version_control")
@Data
public class VersionControl implements Serializable {<!-- -->
    /**
     *
     */
    @TableId
    private String id;

    /**
     * Data table name
     */
    private String tableName;

    /**
     * version (the latest modification time of the table)
     */
    private LocalDateTime version;

    @TableField(exist = false)
    private static final long serialVersionUID = 1L;
}
3.2.2, mapper
@Mapper
public interface VersionControlMapper extends BaseMapper<VersionControl> {<!-- -->

    //Update version number
    boolean updateVersion(String tableName, String newVersion);

    // Check the maximum version number
    LocalDateTime checkMaxVersion(String tableName);
}
3.2.3, mapper.xml
<update id="updateVersion">
    UPDATE version_control
    SET version = #{newVersion}
    WHERE table_name = #{tableName}
</update>

<select id="checkMaxVersion" resultType="java.time.LocalDateTime">
    SELECT MAX(modify_time)
    FROM ${tableName} <!--Note that precompiled placeholders are not used here. If precompiled placeholders are used, it will not be possible. The table name here is passed internally in the program, and there is no SQL injection problem -->
</select>
3.2.4, Expansion

When using the MAX() function to query, will there be any performance problems if the data volume of the table is too large? What is the efficiency difference between Mysql Max function and Order By Desc?

After testing, in the case of millions of data, the performance is as follows:

SELECT MAX(modify_time) from tableName # 2.6s without index, 0.2s with index
SELECT modify_time from tableName ORDER BY modify_time limit 1 # 2.6s when there is no index, 0s when there is an index

You can see that both take 2.6s when there is no index. Order By Desc is slightly faster when there is an index, but there is almost no big difference.

3.2.5、Service
@Service
public class VersionControlServiceImpl extends ServiceImpl<VersionControlMapper, VersionControl>
        implements VersionControlService {<!-- -->

    /**
     * Update version number
     *
     * @param tableName
     * @param newVersion
     * @return
     */
    @Override
    public boolean updateVersion(String tableName, LocalDateTime newVersion) {<!-- -->
        VersionControl versionControl = getBaseMapper().selectOne(Wrappers.<VersionControl>lambdaQuery()
                .eq(VersionControl::getTableName, tableName));
        // If there is no version field that needs to be updated in the versionControl table, add it
        if (Objects.isNull(versionControl)) {<!-- -->
            versionControl = new VersionControl();
            versionControl.setId(IdGenUtils.genId());
            versionControl.setTableName(tableName);
            versionControl.setVersion(newVersion);
            return save(versionControl);
        }

        //Update version number
        versionControl.setVersion(newVersion);
        return updateById(versionControl);
    }

    /**
     * Check the maximum version number
     *
     * @param tableName
     * @return
     */
    @Override
    public LocalDateTime checkMaxVersion(String tableName) {<!-- -->
        return getBaseMapper().checkMaxVersion(tableName);
    }
3.2.6、Aspect
@Slf4j
@Aspect
@Component
public class VersionControlAspect {<!-- -->

    @Autowired
    private VersionControlService versionControlService;

    // Valid where the VersionControl annotation is marked
    @Pointcut("@annotation(top.szpu.aop.annotation.VersionControl)")
    public void updateVersionByAnnotation() {<!-- -->

    }

    //Execute after return
    @AfterReturning("updateVersionByAnnotation()")
    public void doAfterReturning(JoinPoint joinPoint) {<!-- -->
        // Get method signature
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // Get VersionControl annotation
        VersionControl annotationsByMethod = signature.getMethod().getAnnotation(VersionControl.class);

        if (StrUtil.isBlank(annotationsByMethod.tableName()) & amp; & amp; StrUtil.isBlank(annotationsByMethod.value())) {<!-- -->
            return;
        }

        //Get tableName
        String tableNameByMethod = "";
        if (StrUtil.isNotBlank(annotationsByMethod.tableName())) {<!-- -->
            tableNameByMethod = annotationsByMethod.tableName();
        } else {<!-- -->
            tableNameByMethod = annotationsByMethod.value();
        }

        // Check the maximum version number of the target table
        LocalDateTime newVersion = versionControlService.checkMaxVersion(tableNameByMethod);
        //Update the maximum version number of the VersionControl table
        versionControlService.updateVersion(tableNameByMethod, newVersion);

        log.info("tableName=[{}],newVersion=[{}]", tableNameByMethod, newVersion);
    }
}

4. Application

@VersionControl(tableName = "my_table")
public void someMethod() {<!-- -->
    //Method implementation
}

5. Conclusion

At this point, the use of annotations to control entity table version numbers is completed. This article only describes the implementation process of AOP + annotations, but the project also uses cache to store the maximum version number of each table to reduce the pressure on database queries. In addition, since the version number uses the modify_time field, indexes can be selectively added to the existing table according to business needs.