利用MyBatis插件实现统一的用户输入长度校验

本文描述了如何统一的增加用户输入长度校验以及自己的思考过程

1、需求

在理想情况下,无论是前端还是后台都应该对用户输入的长度做检验。在每个页面和后台的接口都加上检验,比较繁琐,而且编码人员经常忽视检验。造成用户输入的长度大于数据库设定的长度就会直接报错。因此希望在框架上做统一校验。

2、分析

系统采用的是spring+spring mvc+mybatis+mysql。

分析前,先整理下新增或修改请求的大致流程:

1.客户端提交数据,如:/rest/user/add?name=张三&age=18。如果字段比较多通常用json格式post,格式:{name:"张三",age:18}

2.无论何种形式提交过来,后台先由spring的拦截器拦截,主要做登录验证,确保请求身份的合法性。

3.如果是合法请求,会根据请求地址跳到对应的controller里面。

4.controller调用对应的业务类。在该类里,会根据name,age等参数,创建一个user类型的po对象。

在我们的项目里,数据库里的每一张表都会有一个po实体对象与之对应,这些对象都由mybatis generator 统一生成。与po对象同时生成的还有mapper和sql配置文件。

5.调用mapper的insert 方法,代码类似于:

User u = new User();
u.setName(user);
u.setAge(age);
u.setUserId(GUID);
userMapper.insert(user);//保存到数据库

6.在执行sql之前会先经过mybatis的插件/拦截器

7.最后调用mybatis里的 Executor对象的update方法,执行sql语句。


大致这7步,一些细致末节已略去。接下来便是分析思路(嫌烦的朋友可以直接看第三部分的实现):

首先既然是统一的校验,肯定是放在公共的地方,每个接口都会调用。最合适的地方便是第2或6步骤,通过拦截器统一处理。2和6里面,2其实是最合适的,因为如果检验不通过,直接返回给客户端,效率最高。

检验前需要获取用户输入的长度和数据库里设定的长度。获取这两个长度应该都没问题。但步骤2却有个问题,提交过来的参数各式各样,将参数和数据库字段对应起来有难度。因此先放弃该方案。

再来分析步骤6。在项目里所有的更新或新增方法数据都是用po对象传递过去(个别直接写sql语句的除外),如:userMapper.insert(user);这里的参数user就是一个po对象。而po对象有个好处就是它里面的字段都可以和数据库的字段对应起来。

找到这个突破口似乎看到了一丝曙光。在执行sql之前的拦截方法里面我们应该可以拿到po对象。进而我们就得到了用户的输入长度。如果用户输入有多项,我们只要通过反射遍历po对象的每个属性就可以了。

到这里我们还缺数据库字段的长度。因为上面提到需要反射遍历po对象的所有属性,所以第一个想法就是把长度用注解的方式加在字段上,这样就可以直接比较了,如:

public class User implements Serializable {
    /**
     * 用户登录名
     *
     * @mbggenerated
     */
    private String username;

    /**
     * 登录手机号
     *
     * @mbggenerated
     */
    private String phone;

     /**
     * 获取 用户登录名
     *
     * @return 用户登录名
     *
     * @mbggenerated
     */
    @org.beanopen.fw.ws.annotation.CodeFieldNotes("用户登录名")
    @org.beanopen.tools.mybatis.annotation.DBColumnLength(45)
    public String getUsername() {
        return username;
    }

    /**
     * 获取 登录手机号
     *
     * @return 登录手机号
     *
     * @mbggenerated
     */
    @org.beanopen.fw.ws.annotation.CodeFieldNotes("登录手机号")
    @org.beanopen.tools.mybatis.annotation.DBColumnLength(11)
    public String getPhone() {
        return phone;
    }
}

这里每个字段都有两个注解,字段名称和长度。

到这一步感觉胜利在望了。但每个字段的注解都手工配置,那也不可行。好在上文已经提到,po对象都是mybatis generator里生成,mybatis generator也提供了插件机制,允许生成时,添加用户自己的逻辑。那么生成实体对象时添加字段程度的注解即可。

以上思路总结为:

1.mybatis generator 生成实体对象时,为每个字符串属性添加关于字段长度的自定义注解。

2.mybatis 执行sql方法之前进行拦截,遍历po对象的每个字符串属性,比较用户输入长度和注解长度。

3.如果过长,则抛出异常。后台进行统一的异常处理返回友好的错误提示。

3.代码实现

首先是mybatis generator的插件部分:



package org.beanopen.tools.mybatis.generator;

import java.util.List;

import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.Plugin;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.dom.java.Method;
import org.mybatis.generator.api.dom.java.TopLevelClass;

public class DBColumnLengthPlugin extends PluginAdapter {

    public DBColumnLengthPlugin() {
    }

    public boolean modelGetterMethodGenerated(Method method, TopLevelClass topLevelClass,
                                              final IntrospectedColumn introspectedColumn, IntrospectedTable introspectedTable,
                                              Plugin.ModelClassType modelClassType) {

        List<IntrospectedColumn> pkcs = introspectedTable.getPrimaryKeyColumns();

        if("VARCHAR".equals(introspectedColumn.getJdbcTypeName())
                &&pkcs.indexOf(introspectedColumn)<0){
            //数据库字段为varchar类型的,且不是主键的属性上添加注解
            method.addAnnotation("@org.beanopen.tools.mybatis.annotation.DBColumnLength("
                    +introspectedColumn.getLength()
                    + ")");
        }

        return true;
    }

    /**
     * This plugin is always valid - no properties are required
     */
    @Override
    public boolean validate(List<String> warnings) {
        return true;
    }
}

自定义的插件类继承于org.mybatis.generator.api.PluginAdapter,主要在modelGetterMethodGenerated方法里,getter方法上添加字段长度的注解

插件写好之后,还需要配置进去。在mybatis generator的配置文件generatorConfig.xml里添加插件<plugin type="org.beanopen.tools.mybatis.generator.DBColumnLengthPlugin" />:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
		"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd" >
<generatorConfiguration>
	<classPathEntry location="conf/lib/mysql-connector-java-5.1.15.jar" />
	<context id="context1">
		
		<property name="javaFileEncoding" value="UTF-8"/>
		
		<plugin type="org.beanopen.tools.mybatis.generator.MySQLPaginationPlugin2" />
        <!-- 添加刚才写好的插件 -->
		<plugin type="org.beanopen.tools.mybatis.generator.DBColumnLengthPlugin" />
		<plugin type="org.mybatis.generator.plugins.SerializablePlugin" />
		
		………………



	</context>

</generatorConfiguration>

到这里生成实体的时候就会自动添加注解了。接下来便是mybatis的拦截代码:

package org.beanopen.tools.mybatis;

import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.beanopen.fw.ws.annotation.CodeFieldNotes;
import org.beanopen.tools.mybatis.annotation.DBColumnLength;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Properties;

@Intercepts(@Signature(type = Executor.class, method = "update", args = {
        MappedStatement.class, Object.class}))
public class DBColumnLengthCheck implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object arg = invocation.getArgs()[1];
        Field[] fields = arg.getClass().getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {//遍历每个属性
            Field field = fields[i];

            if(field.getType() == String.class) {
                PropertyDescriptor pd = new PropertyDescriptor(field.getName(), arg.getClass());
                Method getMethod = pd.getReadMethod();
                if (getMethod != null) {
                    DBColumnLength length = getMethod.getAnnotation(DBColumnLength.class);

                    if(length!=null) {//如果有DBColumnLength注解,则需要判断
                        Object value = getMethod.invoke(arg);
                        if(value!=null){
                            if(length.value()<value.toString().length()){//输入内容过长时抛出异常
                                CodeFieldNotes fieldNotes = getMethod.getAnnotation(CodeFieldNotes.class);//CodeFieldNotes 注解是字段名称,为了显示友好而已,这里可忽略
                                String fieldName = fieldNotes!=null&& StringUtils.isNotBlank(fieldNotes.value())?fieldNotes.value():field.getName();
                                throw new RuntimeException(String.format("\"%s\"输入项不能多于%s个字符,您已输入%s个",fieldName,length.value(),value.toString().length()));
                            }
                        }
                    }
                }
            }
        }

        return invocation.proceed();
    }

    @Override
    public Object plugin(Object o) {
        return Plugin.wrap(o, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }

}

这里类注解@Intercepts @Signature指定了要拦截Executor类的update方法。mybatis貌似有4个类可以拦截,Executor类是其中一个

我们重写了intercept方法,添加了一段检验代码,如果检验通过则调用invocation.proceed(),继续原有逻辑;反之抛出异常。

将插件配置进去<bean class="org.beanopen.tools.mybatis.DBColumnLengthCheck"> </bean>:

<!-- 数据库会话工厂 -->
	<bean name="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="configLocation" value="classpath:config/mybatis-configuration.xml"></property>
		<property name="dataSource" ref="dataSource" />
		<property name="mapperLocations" value="classpath*:cn/bodtec/zzb/mappers/**/*.xml" />
		<property name="plugins">
			<list>
				<bean
					class="org.beanopen.tools.mybatis.pagination.PaginationInterceptor">
					<property name="dialect">
						<bean class="org.beanopen.tools.mybatis.pagination.dialect.MySQLDialect" />
					</property>
				</bean>
                <!-- 添加插件 -->
				<bean class="org.beanopen.tools.mybatis.DBColumnLengthCheck"> </bean>
			</list>
		</property>
	</bean>

主要代码算是完成了,为了让程序更友好,可以再对异常做全局处理。代码就不放了。

4.总结

此方案比较简单,日常开发中,只需建表的时候将字段名称和长度定好就可以了,其他什么都不用做。后台将会自动进行校验。缺点是性能稍差,在执行sql前才做校验。期待能有更好的方案。

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值