一个注解实现校验主键id是否存在

201 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第4天,点击查看活动详情

前言:

作为一个CRUD的'资深'coder,我们修改、删除、查看详情接口我们需要做的第一件事就是先校验前端传来的主键id是否有效,我们一般会这样操作,获取到入参的id,然后查询数据库此id对应的数据是否存在,不存在抛出异常,id有效继续执行下面的业务操作,其实我们可以发现这些接口中的这个操作都是一样的业务逻辑,只是查询的数据库表和入参id对应的属性名不同,这个时候我们就可以使用切面来处理这个统一的业务逻辑。下面我们用Spring的aop实现这个切面。

二丶分析

我们分析一下难点

  • 如何获取到入参?
  • 如何知道这个业务id应该查询哪个数据库表?

1. 如何获取到入参?

这个我们可以通过spel表达式来实现,通过运行期间执行的表达式将值装配到我们的属性或构造函数当中,也可以调用JDK中提供的静态常量,获取外部Properties文件中的的配置。

2. 如何知道这个业务id应该查询哪个数据库表?

我们可以在注解中定义一个参数通过参数指定是哪个表,但是由于我们项目中用到了mybatis-plus我们每个实体类上都会使用一个@TableName注解,会指定当前实体类对应的数据库表,所以我这里是在注解中定义一个类属性,然后去拿到类上的@TableName注解中填写的实体类名。

一丶代码

定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface CheckId {

    String id();

    String idAlias() default "id";

    boolean isDeletedField() default true;

    Class<?> clazz();
    
    String errorMessage() default "Id is invaild.";


}

定义aspect

@Aspect
@Component
public class CheckIdAspect {

    private static final String SQL = "select count(*) from %s where %s = %s";
    private static final String SQL_DELETED = "select count(*) from %s where %s = %s and deleted = 0";
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Pointcut("@annotation(com.zzzwww.aspect.annotation.CheckId)")
    public void pointcut(){}

    @Before("pointcut()")
    @SuppressWarnings("all")
    public void checkIdAspect(JoinPoint jp) {
        MethodSignature signature = (MethodSignature) jp.getSignature();
        Method method = signature.getMethod();
        CheckId checkId = method.getAnnotation(CheckId.class);
        String id = this.buildExternalIdId(checkId, jp, method);
        String idAlias = checkId.idAlias();
        Class<?> clzss = checkId.clazz();
        TableName annotation = clzss.getAnnotation(TableName.class);
        String tableName = annotation.value();
        long count;
        String sql;
        if (BooleanUtil.isTrue(checkId.isDeletedField())) {
            sql = String.format(SQL_DELETED, tableName ,idAlias, id);
        } else {
            sql = String.format(SQL, tableName ,idAlias, id);
        }
        count = jdbcTemplate.queryForObject(sql, Long.class);
        Assert.isTrue(count > 0, checkId.errorMessage());
    }

    private String buildExternalIdId(CheckId anno, JoinPoint pjp, Method method) {
        ExpressionParser parser = new SpelExpressionParser();
        LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
        String[] params = discoverer.getParameterNames(method);
        Object[] args = pjp.getArgs();
        EvaluationContext context = new StandardEvaluationContext();
        for (int len = 0; len < params.length; len++) {
            context.setVariable(params[len], args[len]);
        }
        Expression expression = parser.parseExpression(anno.id());
        return expression.getValue(context, String.class);
    }
}

总结

Aop其实还有很多使用场景,这只是其中一个使用场景,我会在后续文章中分享更多的aop对于其他场景的具体实现案例。