精华内容
下载资源
问答
  • 场景: 项目中有一个供应商模块,包含供应商的基础信息、供应商资质等,字段比较多,其中...最开始项目中记录日志只针对三四个关键字段,比较简单,直接用待更新的base获取指定字段值与数据库中这个字段的原始值比...

    场景:
    项目中有一个供应商模块,包含供应商的基础信息、供应商资质等,字段比较多,其中有些信息比较重要,如果修改了需要记录修改日志(如哪个信息从什么改为什么),同时还需要重新提交,而有些信息修改了值需要记下日志,并不需要重新审核,还有一些无关紧要的信息修改了无需记录日志,也不需要审核

    最开始项目中记录日志只针对三四个关键字段,比较简单,直接用待更新的base获取指定字段值与数据库中这个字段的原始值比较,如:
    StringBuffer sb = new StringBuffer("");
    if(updateBean.getPropertiesA().equals(oldBean.getPropertiesA())){
    //拼接更改信息
    sb.append(“XXX 由 oldBean.getPropertiesA()---->updateBean.getPropertiesA()).append(”;");
    }

    if(updateBean.getPropertiesB().equals(oldBean.getPropertiesB())){
    //拼接更改信息
    sb.append(“XXX 由 oldBean.getPropertiesB()---->updateBean.getPropertiesB()).append(”;");
    }

    最后将拼接的更改信息作为日志的content保存数据库日志表中

    如果别的字段也需要记日志,当然也可以直接在后面继续加if判断,但是当需要记录日志的字段变多,比如二十个,三十个,继续加if感觉很烦,就想了下能不能通过反射去get值,后来找了下反射获取字段值的文章看了下

    我这个还需要区分哪些字段需要记日志,哪些不需要记日志,反射获取值的时候只获取需要记日志的,结合之前的经验,可以用注解,对自己关心的字段加上自定义的注解。

    部分代码如下:

    package com.lyc.util;
    
    import java.lang.annotation.*;
    
    /**
     * Created by liyanchun on 2019/11/15.
     * 自定义的注解
     */
    @Documented
    @Target(ElementType.FIELD)
    @Inherited
    @Retention(RetentionPolicy.RUNTIME )
    public @interface MyLogAnnotation {
    
        /**字段的中文名称*/
        String paramName();
    
        /**是否需要重新提交审核*/
        boolean isNeedExamine() default true;
    
        /**是否需要记日志*/
        boolean isNeedLog() default true;
    	
    	/**后面还可以加自己需要的配置*/
    
    }
    
    package com.lyc.bean;
    
    import com.lyc.util.MyLogAnnotation;
    
    import java.io.Serializable;
    import java.util.Date;
    
    /**
     * Created by liyanchun on 2019/11/15.
     *
     * 用户实体测试
     */
    public class UserBase implements Serializable {
        private static final long serialVersionUID = -4831337074413975246L;
    
        @MyLogAnnotation(paramName = "姓名")
        private String userName;
    
        @MyLogAnnotation(paramName = "年龄")
        private Integer age;
    
        @MyLogAnnotation(paramName = "生日")
        private Date birthDay;
    
        @MyLogAnnotation(paramName = "身高",isNeedExamine = false)
        private Double height;
    
        /**体重 这个保密变化就不做记录也不通知任何人了^_^ */
        private Double weight;
    
        public String getUserName() {
            return userName;
        }
    
        public void setUserName(String userName) {
            this.userName = userName;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        public Date getBirthDay() {
            return birthDay;
        }
    
        public void setBirthDay(Date birthDay) {
            this.birthDay = birthDay;
        }
    
        public Double getHeight() {
            return height;
        }
    
        public void setHeight(Double height) {
            this.height = height;
        }
    
        public Double getWeight() {
            return weight;
        }
    
        public void setWeight(Double weight) {
            this.weight = weight;
        }
    }
    
    
    
    
    package com.lyc.bean;
    
    import com.lyc.util.MyLogAnnotation;
    
    import java.io.Serializable;
    
    /**
     * Created by liyanchun on 2019/11/15.
     *
     * 班级实体测试
     */
    public class ClassBase implements Serializable {
        private static final long serialVersionUID = 874547538509067967L;
    
    
        @MyLogAnnotation(paramName = "班级编号")
        private String classNo;
    
        @MyLogAnnotation(paramName = "班级学生总数",isNeedExamine = false)
        private Integer studentNum;
    
        @MyLogAnnotation(paramName = "班主任")
        private String classTeacher;
    
    
        public String getClassNo() {
            return classNo;
        }
    
        public void setClassNo(String classNo) {
            this.classNo = classNo;
        }
    
        public Integer getStudentNum() {
            return studentNum;
        }
    
        public void setStudentNum(Integer studentNum) {
            this.studentNum = studentNum;
        }
    
        public String getClassTeacher() {
            return classTeacher;
        }
    
        public void setClassTeacher(String classTeacher) {
            this.classTeacher = classTeacher;
        }
    }
    
    package com.lyc.util;
    
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    
    /**
     * Created by liyanchun on 2019/11/15.
     * 反射工具类
     */
    public class ReflectUtil {
    
        /**
         * 获取对象属性,返回一个字符串数组
         *
         * @param  o 对象
         * @return String[] 字符串数组
         */
        private String[] getFiledName(Object o){
            try{
                Field[] fields = o.getClass().getDeclaredFields();
                String[] fieldNames = new String[fields.length];
                for (int i=0; i < fields.length; i++)
                {
                    fieldNames[i] = fields[i].getName();
                }
                return fieldNames;
            } catch (SecurityException e){
                e.printStackTrace();
                System.out.println(e.toString());
            }
            return null;
        }
    
    
        /**
         * 使用反射根据属性名称获取属性值
         *
         * @param  fieldName 属性名称
         * @param  o 操作对象
         * @return Object 属性值
         */
        public static Object getFieldValueByName(String fieldName, Object o){
            try{
                String firstLetter = fieldName.substring(0, 1).toUpperCase();
                String getter = "get" + firstLetter + fieldName.substring(1);
                Method method = o.getClass().getMethod(getter, new Class[] {});
                Object value = method.invoke(o, new Object[] {});
                return value;
            } catch (Exception e){
                System.out.println("属性不存在");
                return null;
            }
        }
    }
    
    package com.lyc.bean;
    
    import java.io.Serializable;
    
    /**
     * Created by liyanchun on 2019/11/15.
     * 日志信息实体
     */
    public class TestLogVo implements Serializable{
        private static final long serialVersionUID = -4378093647208529734L;
    	
    	/**更改的字段名*/
        private String paramName;
        /**更改前的值*/
        private String fmParam;
    	/**更改后的值*/
        private String toParam;
    
        public String getParamName() {
            return paramName;
        }
    
        public void setParamName(String paramName) {
            this.paramName = paramName;
        }
    
        public String getFmParam() {
            return fmParam;
        }
    
        public void setFmParam(String fmParam) {
            this.fmParam = fmParam;
        }
    
        public String getToParam() {
            return toParam;
        }
    
        public void setToParam(String toParam) {
            this.toParam = toParam;
        }
    }
    

    上面都准备好了,开始写实现逻辑:

    package com.lyc.util;
    
    import com.lyc.bean.TestLogVo;
    
    import java.lang.annotation.Annotation;
    import java.lang.reflect.Field;
    import java.util.Arrays;
    import java.util.Date;
    import java.util.List;
    
    /**
     * Created by liyanchun on 2019/11/15.
     * 构建日志信息的工具类
     */
    public class LogUtil {
    
        public static Boolean buildLogByReflect(Object oldBase,Object newBase,List<TestLogVo> updateParamLogList){
            Boolean isNeedReSubmitExamine = Boolean.FALSE;
            List<Field> list = Arrays.asList(oldBase.getClass().getDeclaredFields());
            if(ListUtil.isNullOrEmpty(list)){
                return isNeedReSubmitExamine;
            }
            for(Field field : list){
                if(!field.isAnnotationPresent(MyLogAnnotation.class)){//是否使用了自定义的MyLogAnnotation注解
                    continue;
                }
                for (Annotation anno : field.getDeclaredAnnotations()) {//获得所有的注解
                    if(!anno.annotationType().equals(MyLogAnnotation.class) ){//找到自己的注解
                        continue;
                    }
                    if (((MyLogAnnotation)anno).isNeedLog()){//注解的值 是否需要记录日志
    
                        Object oldValue = ReflectUtil.getFieldValueByName(field.getName(), oldBase);
                        Object newValue = ReflectUtil.getFieldValueByName(field.getName(), newBase);
                        String paramName = ((MyLogAnnotation)anno).paramName();
    
                        //判断是否更改了值  如果更改了则记录日志
                        TestLogVo logVo = compareValue(oldValue,newValue,field,paramName);
                        if (null == logVo){//没有值更改
                            continue;
                        }
                        updateParamLogList.add(logVo);
                        if(((MyLogAnnotation)anno).isNeedExamine()){//注解的值 是否需要重新提交审核
                            isNeedReSubmitExamine = Boolean.TRUE;
                        }
                    }
                }
            }
            return isNeedReSubmitExamine;
        }
    
    
        public static TestLogVo compareValue(Object oldValue,Object newValue,Field field,String paramName){
            // 如果类型是String
            if (field.getGenericType().toString().equals("class java.lang.String")) { // 如果type是类类型,则前面包含"class ",后面跟类名
                String val1 = (String) oldValue;
                String val2 = (String) newValue;
                if (StringUtil.isNullOrBlank(val1) && !StringUtil.isNullOrBlank(val2)){//空改成有值
                    return buildLogVo(paramName,"空",val2);
                } else if (!StringUtil.isNullOrBlank(val1) && StringUtil.isNullOrBlank(val2)){//有值改成空
                    return buildLogVo(paramName,val1,"空");
                } else if (!StringUtil.isNullOrBlank(val1) && !StringUtil.isNullOrBlank(val2) && !val1.equals(val2)){//有值改成其他值
                    return buildLogVo(paramName,val1,val2);
                }
            }
    
            // 如果类型是Integer
            if (field.getGenericType().toString().equals("class java.lang.Integer")) {
                Integer val1 = (Integer) oldValue;
                Integer val2 = (Integer) newValue;
                if (null == val1 && null != val2){
                    return buildLogVo(paramName,"空",val2.toString());
                } else if (null != val1 && null == val2){
                    return buildLogVo(paramName,val1.toString(),"空");
                } else if (null != val1 && null != val2 && !val1.equals(val2)){
                    return buildLogVo(paramName,val1.toString(),val2.toString());
                }
            }
    
            // 如果类型是Double
            if (field.getGenericType().toString().equals("class java.lang.Double")) {
                Double val1 = (Double) oldValue;
                Double val2 = (Double) newValue;
                if (null == val1 && null != val2){
                    return buildLogVo(paramName,"空",val2.toString());
                } else if (null != val1 && null == val2){
                    return buildLogVo(paramName,val1.toString(),"空");
                } else if (null != val1 && null != val2 && !val1.equals(val2)){
                    return buildLogVo(paramName,val1.toString(),val2.toString());
                }
            }
    
            // 如果类型是Boolean 是封装类
            if (field.getGenericType().toString().equals("class java.lang.Boolean")) {
                Boolean val1 = (Boolean) oldValue;
                Boolean val2 = (Boolean) newValue;
                if (null == val1 && null != val2){
                    return buildLogVo(paramName,"空",val2.toString());
                } else if (null != val1 && null == val2){
                    return buildLogVo(paramName,val1.toString(),"空");
                } else if (null != val1 && null != val2 && !val1.equals(val2)){
                    return buildLogVo(paramName,val1.toString(),val2.toString());
                }
            }
    
            // 如果类型是boolean 基本数据类型不一样 这里有点说明如果定义名是 isXXX的 那就全都是isXXX的
            // 反射找不到getter的具体名
    //        if (field.getGenericType().toString().equals("boolean")) {
    //            Method m = (Method) object.getClass().getMethod(
    //                    field.getName());
    //            Boolean val = (Boolean) m.invoke(object);
    //            if (val != null) {
    //                System.out.println("boolean type:" + val);
    //            }
    //        }
    
            // 如果类型是Date
            if (field.getGenericType().toString().equals("class java.util.Date")) {
                Date val1 = (Date) oldValue;
                Date val2 = (Date) newValue;
                if (null == val1 && null != val2){
                    return buildLogVo(paramName,"空", DateUtil.dateFormatToString(val2, DateUtil.ISO_EXPANDED_DATE_FORMAT));
                } else if (null != val1 && null == val2){
                    return buildLogVo(paramName,DateUtil.dateFormatToString(val1,DateUtil.ISO_EXPANDED_DATE_FORMAT),"空");
                } else if (null != val1 && null != val2 && !val1.equals(val2)){
                    return buildLogVo(paramName,DateUtil.dateFormatToString(val1,DateUtil.ISO_EXPANDED_DATE_FORMAT),DateUtil.dateFormatToString(val2,DateUtil.ISO_EXPANDED_DATE_FORMAT));
                }
            }
            // 如果还需要其他的类型请自己做扩展
            return null;
        }
    
        private static TestLogVo buildLogVo(String paramName,String fmParam,String toParam){
            TestLogVo logVo = new TestLogVo();
            logVo.setParamName(paramName);
            logVo.setFmParam(fmParam);
            logVo.setToParam(toParam);
            return logVo;
        }
    }
    
    

    最后就是测试了,分别创建了多个UserBase模拟同一个实体类的不同修改,然后又创建了多个ClassBase模拟日志工具类可以适用不同的实体类

    package com.lyc.test;
    
    import com.lyc.bean.ClassBase;
    import com.lyc.bean.TestLogVo;
    import com.lyc.bean.UserBase;
    import com.lyc.util.DateUtil;
    import com.lyc.util.ListUtil;
    import com.lyc.util.LogUtil;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * Created by liyanchun on 2019/11/15.
     */
    public class TestReflectLog {
    
        public static void main(String[] args){
    
            testUserLog();
    
            System.out.println();
            System.out.println();
            System.out.println();
    
    //        testClassLog();
        }
    
    
        public static void testUserLog(){
    
            UserBase userBase = new UserBase();
            userBase.setUserName("张三");
            userBase.setAge(23);
            userBase.setBirthDay(DateUtil.strFormatToDate("1992-02-23",DateUtil.SIMPLE_FORMAT));
            userBase.setHeight(1.23);
            userBase.setWeight(123d);
    
            //测试 只改体重  不记日志 不审核
            UserBase userBase2 = new UserBase();
            userBase2.setUserName("张三");
            userBase2.setAge(23);
            userBase2.setBirthDay(DateUtil.strFormatToDate("1992-02-23",DateUtil.SIMPLE_FORMAT));
            userBase2.setHeight(1.23);
            userBase2.setWeight(222d);
    
    
            //测试  改身高、体重 记一条日志 不审核
            UserBase userBase3 = new UserBase();
            userBase3.setUserName("张三");
            userBase3.setAge(23);
            userBase3.setBirthDay(DateUtil.strFormatToDate("1992-02-23",DateUtil.SIMPLE_FORMAT));
            userBase3.setHeight(1.32);
            userBase3.setWeight(333d);
    
            //测试 改所有 记四条日志 需要审核
            UserBase userBase4 = new UserBase();
            userBase4.setUserName("李四");
            userBase4.setAge(64);
            userBase4.setBirthDay(DateUtil.strFormatToDate("1992-06-04",DateUtil.SIMPLE_FORMAT));
            userBase4.setHeight(1.64);
            userBase4.setWeight(164d);
    
            //测试 空值
            UserBase userBase5 = new UserBase();
    
    
            List<TestLogVo> testLogVoList = new ArrayList<TestLogVo>();
            //第一种情况
            Boolean result = LogUtil.buildLogByReflect(userBase,userBase2,testLogVoList);
            //第二种情况
    //        Boolean result = LogUtil.buildLogByReflect(userBase,userBase3,testLogVoList);
    		//第三种情况
    //        Boolean result = LogUtil.buildLogByReflect(userBase,userBase4,testLogVoList);
    		//第四种情况
    //        Boolean result = LogUtil.buildLogByReflect(userBase,userBase5,testLogVoList);
    		//第五种情况
    //        Boolean result = LogUtil.buildLogByReflect(userBase5,userBase,testLogVoList);
    
    
            System.out.println("更改用户信息是否需要审核result:" + result);
            System.out.println("更改用户信息生成的日志数量:" + testLogVoList.size());
            if (ListUtil.isNotEmpty(testLogVoList)){
                for (TestLogVo logVo : testLogVoList){
                    System.out.println("【" + logVo.getParamName() + "】从" + logVo.getFmParam() + "--->" + logVo.getToParam());
                }
            }
        }
    
    
        public static void testClassLog(){
    
            ClassBase classBase = new ClassBase();
            classBase.setClassNo("00001");
            classBase.setStudentNum(10);
            classBase.setClassTeacher("一班主任");
    
            //测试 只改班级学生总数 记一条日志 不需要审核
            ClassBase classBase2 = new ClassBase();
            classBase2.setClassNo("00001");
            classBase2.setStudentNum(11);
            classBase2.setClassTeacher("一班主任");
    
            //测试 更改所有字段 记三条日志 需要审核
            ClassBase classBase3 = new ClassBase();
            classBase3.setClassNo("00002");
            classBase3.setStudentNum(20);
            classBase3.setClassTeacher("二班主任");
    
    
            List<TestLogVo> testLogVoList = new ArrayList<TestLogVo>();
            //第一种情况
    //        Boolean result = LogUtil.buildLogByReflect(classBase,classBase2,testLogVoList);
    		//第二种情况
    //        Boolean result = LogUtil.buildLogByReflect(classBase,classBase3,testLogVoList);
            System.out.println("更改班级信息是否需要审核result:" + result);
            System.out.println("更改班级信息生成的日志数量:" + testLogVoList.size());
            if (ListUtil.isNotEmpty(testLogVoList)){
                for (TestLogVo logVo : testLogVoList){
                    System.out.println("【" + logVo.getParamName() + "】从" + logVo.getFmParam() + "--->" + logVo.getToParam());
                }
            }
        }
    }
    

    执行testUserLog()的第一种情况,测试结果:
    更改用户信息是否需要审核result:false
    更改用户信息生成的日志数量:0

    #########################################
    执行testUserLog()的第二种情况,测试结果:
    更改用户信息是否需要审核result:false
    更改用户信息生成的日志数量:1

    【身高】从1.23—>1.32

    #########################################
    执行testUserLog()的第三种情况,测试结果:
    更改用户信息是否需要审核result:true
    更改用户信息生成的日志数量:4

    【姓名】从张三—>李四
    【年龄】从23—>64
    【生日】从1992-02-23 00:00:00—>1992-06-04 00:00:00
    【身高】从1.23—>1.64

    #########################################
    执行testUserLog()的第四种情况,测试结果:
    更改用户信息是否需要审核result:true
    更改用户信息生成的日志数量:4

    【姓名】从张三—>空
    【年龄】从23—>空
    【生日】从1992-02-23 00:00:00—>空
    【身高】从1.23—>空

    #########################################
    执行testUserLog()的第五种情况,测试结果:
    更改用户信息是否需要审核result:true
    更改用户信息生成的日志数量:4

    【姓名】从空—>张三
    【年龄】从空—>23
    【生日】从空—>1992-02-23 00:00:00
    【身高】从空—>1.23

    》》》》》》》》》》》》》》》》》
    #########################################
    执行testClassLog()的第一种情况,测试结果:
    更改班级信息是否需要审核result:false
    更改班级信息生成的日志数量:1
    【班级学生总数】从10—>11

    #########################################
    执行testClassLog()的第二种情况,测试结果:
    更改班级信息是否需要审核result:true
    更改班级信息生成的日志数量:3
    【班级编号】从00001—>00002
    【班级学生总数】从10—>20
    【班主任】从一班主任—>二班主任

    到此个人觉得基本实现了关心的字段从空>非空、非空>空、值1>值2 都记录了对应的变化信息,而且变化的这些字段中如果有重要的需要重新审核也会给需要审核的标记,否则不需要审核标记,可以满足我最开始说的场景

    构建的testLogVoList可以直接存数据库或者转成json存到已有日志表的一个字段中,
    多个实体都有更改记日志的需求,只需要在对应实体的属性上加上自定义的这个注解,属性的名称可以自己定义

    里面可能还有不完善的地方,等我用到项目中再优化看看。

    展开全文
  • 关键数据表都加了个用户ID字段, 之前的功能都已经写好, 所以就在想怎么在最少改动代码的情况下实现给之前的所有查询增加一个查询条件=, 后来想到利用mybatis拦截器动态修改sql进行拼接多个查询。 下面就开始利用...

    MyBatis拦截器(自定义注解+实现多租户查询)

    前言:

    公司现有运营管理平台上的功能都要增加多租户, 原本功能都是单租户。

    就是要做数据隔离, 登录用户只能看到当前登录用户名下数据, 关键数据表都加了个用户ID字段, 之前的功能都已经写好, 所以就在想怎么在最少改动代码的情况下实现给之前的所有查询增加一个查询条件=值, 后来想到利用mybatis拦截器动态修改sql进行拼接多个查询。

    下面就开始利用来进行实现。 (技术框架<mybatis-plus.version>1.4.8</mybatis-plus.version>, 公司用的版本太低, 好像mybatis-plus2.1版本 也增加了多租户拦截器, 但是还是不能完全满足我现有需求)

    使用到的技术有: <mybatis-plus> , <jsqlparser>

    1.0 自定义MyBatis拦截器

    /**
     * @Author: ZhiHao
     * @Date: 2020/12/16 16:37
     * @Description: 代理商通用sql拼接拦截器(兼容之前查询, 仅对方法标记注解生效)
     * @Versions 1.0
     **/
    //@Component
    @Intercepts({@Signature(
            type = StatementHandler.class, //拦截构建sql语句的StatementHandler
            method = "prepare",   //里面的prepare方法
            args = {
                    Connection.class,  //方法的参数
                    Integer.class
            }
    )})
    public class AgentSqlInterceptor implements Interceptor {
    
        private Logger logger = LoggerFactory.getLogger(getClass());
    
     	// 是否进行拦截动态修改sql
        private boolean required;
    	// 表别名
        private String tableAlias;
    
        public AgentSqlInterceptor(boolean required, String tableAlias) {
            this.required = required;
            this.tableAlias = tableAlias;
        }
    
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            // 需要修改sql语句才拦截
            if (required) {
                StatementHandler statementHandler = (StatementHandler) PluginUtils.realTarget(invocation.getTarget());
                MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);
                // 先判断是不是SELECT操作
                MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
                if (!SqlCommandType.SELECT.equals(mappedStatement.getSqlCommandType())) {
                    return invocation.proceed();
                }
                BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
                String sql = boundSql.getSql();
                logger.info("之前sql语句:{}", sql);
                // 判断是否符合需要增加区分代理商查询条件
                sql = this.ifAgentQuery(sql);
                logger.info("代理商查询sql语句:{}", sql);
                // 最终将修改好的sql语句设置回去执行
                metaStatementHandler.setValue("delegate.boundSql.sql", sql);
            }
            return invocation.proceed();
        }
    
        @Override
        public Object plugin(Object target) {
            // 返回拦截器本身, 还是返回目标本身
            return target instanceof StatementHandler ? Plugin.wrap(target, this) : target;
        }
    
        @Override
        public void setProperties(Properties properties) {
    
        }
    
        /**
         * 判断是否需要进行拼接代理商查询条件
         * 有以下几种情况: (0 运营商, 不进行拼接查询全部) , (1 代理商并且是总仓管理员, 仅拼接代理商字段查询)
         * (2 代理商并且是分仓管理员, 需拼接代理商字段与仓库字段查询) , (3 代理商并且是总仓管理员, 需拼接代理商字段查询)
         *
         * @param sql
         * @return java.lang.String
         * @author: ZhiHao
         * @date: 2020/12/17
         */
        private String ifAgentQuery(String sql) {
            // sql解析器解析查询语句(也只有查询能进来)
            Select selectStatement = (Select) CCJSqlParserUtil.parse(sql);
            // 不是单表与多表直接查询
            if (!(selectStatement.getSelectBody() instanceof PlainSelect)) {
                return sql;
            }
            PlainSelect selectBody = (PlainSelect) selectStatement.getSelectBody();
            // 仅对枚举符合表, 并没有代理商字段的进行拼接sql
            Table table = this.ifConform(selectBody);
            if (table != null && this.doesItExist(selectBody.getWhere())) {
                // 进行拼接, 判断是否有where
                Expression where = selectBody.getWhere();
                // 获取查询语句表别名(兼容之前做好功能一个方法表别名不一致)
                String sqlTableAlias = table.getAlias() != null ? table.getAlias().getName() : null;
                String queryConditionsAndValues;
                if (where != null) {
                    // 获取代理商查询条件与值
                    queryConditionsAndValues = this.getQueryConditionsAndValues( table.getName(),
                            StringUtils.isNotBlank(sqlTableAlias) ? sqlTableAlias : this.tableAlias);
                     // 解析之前表达式, 使用弱引用
                    WeakReference<ExpressionDeParser> weakReference = new WeakReference<>(new ExpressionDeParser());
                    where.accept(weakReference.get());
                    // 获取之前表达式
                    StringBuilder buffer = weakReference.get().getBuffer();
                    // 拼接sql
                    buffer.append(queryConditionsAndValues);
                    // 设置回去
                    selectBody.setWhere(CCJSqlParserUtil.parseCondExpression(buffer.toString()));
                    weakReference.clear();
                } else {
                    // 获取查询条件与值
                    queryConditionsAndValues = " 1 = 1 " + this.getQueryConditionsAndValues(table.getName(),
                            StringUtils.isNotBlank(sqlTableAlias) ? sqlTableAlias : this.tableAlias);
                    //没有where情况
                    selectBody.setWhere(CCJSqlParserUtil.parseCondExpression(queryConditionsAndValues));
                }
                sql = selectStatement.toString();
            }
            return sql;
        }
        
        /**
         * 仅对dms_battery表查询并没有代理商字段的进行拼接sql,
         * 防止拦截器开启时, 兼容以后自定义查询语句包含查询字段的进行了拼接出错
         *
         * @param selectBody
         * @return boolean
         * @author: ZhiHao
         * @date: 2020/12/18
         */
        private Table ifConform(PlainSelect selectBody) {
            // 判断from后面是否符合需要拼接的表名
            FromItem fromItem = selectBody.getFromItem();
            if (fromItem instanceof Table) {
                Table table = (Table) fromItem;
                if (TenantTable.tableMap.get(table.getName()) != null) {
                    return table;
                }
            }
            // 上面from后面不满足则判断多表情况是否包含
            List<Join> joins = selectBody.getJoins();
            Table table = null;
            if (joins != null && joins.size() > 0) {
                Optional<Join> any = joins.stream().filter((join) -> {
                    FromItem rightItem = join.getRightItem();
                    if (rightItem instanceof Table) {
                        return TenantTable.tableMap.get(((Table) rightItem).getName()) != null ? true : false;
                    }
                    return false;
                }).findAny();
                table = any.isPresent() ? (Table) any.get().getRightItem() : null;
            }
            return table != null ? table : null;
        }
    
        /**
         * 判断之前sql是否存在了代理商查询字段
         *
         * @param sql
         * @return boolean
         * @author: ZhiHao
         * @date: 2020/12/21
         */
        private boolean doesItExist(Expression sql) {
            String str = sql != null ? sql.toString() : null;
            if (StringUtils.containsIgnoreCase(str, TenantTable.AGENT_ID.getColumns(null))
                    || StringUtils.containsIgnoreCase(str, TenantTable.DEPOT_ID.getColumns(null))) {
                return false;
            }
            return true;
        }
    
        private final Integer AGENT = 0; //代理商
        private final Integer OPERATOR = 1; //运营商
    
        /**
         * 根据表名构建条件
         *
         * @param tableName  表名
         * @param tableAlias 别名
         * @return java.lang.String
         * @author: ZhiHao
         * @date: 2020/12/21
         */
        public String getQueryConditionsAndValues(String tableName, String tableAlias) {
            StringBuilder builder = new StringBuilder();
            // 获取登录用户
            IDmsUserService dmsUserService = SpringUtils.getBean(IDmsUserService.class);
            DmsUser dmsUser = dmsUserService.getCurrentUser();
            Integer type = dmsUser.getType();
            Integer terminal = dmsUser.getTerminal();
            Integer agentOrOperatorId = dmsUser.getAgentOrOperatorId();
            Integer depotId = dmsUser.getDepotId();
            switch (TenantTable.tableMap.get(tableName)) {
                // 电池表
                case DMS_BATTERY:
                    // 是运营商并是总仓不做拼接可见全部
                    if (OPERATOR.equals(type) && TerminalEnums.WEB_ADMIN.getCode().equals(terminal)) {
                        return "";
                    }
                    // 是运营商并是分仓拼接可见分仓
                    if (OPERATOR.equals(type) && TerminalEnums.WEB.getCode().equals(terminal)) {
                        builder.append(" AND ")
                                .append(TenantTable.DEPOT_ID.getColumns(tableAlias))
                                .append(" = ")
                                .append(depotId)
                                .append(" ");
                        return builder.toString();
                    }
                    // 是代理商并是总仓拼接可见总+分仓
                    if (AGENT.equals(type) && TerminalEnums.WEB_ADMIN.getCode().equals(terminal)) {
                        builder.append(" AND ")
                                .append(TenantTable.AGENT_ID.getColumns(tableAlias))
                                .append(" = ")
                                .append(agentOrOperatorId)
                                .append(" ");
                        return builder.toString();
                    }
                    // 是代理商并是分仓拼接可见分仓
                    if (AGENT.equals(type) && TerminalEnums.WEB.getCode().equals(terminal)) {
                        builder.append(" AND ")
                                .append(TenantTable.AGENT_ID.getColumns(tableAlias))
                                .append(" = ")
                                .append(agentOrOperatorId)
                                .append(" AND ")
                                .append(TenantTable.DEPOT_ID.getColumns(tableAlias))
                                .append(" = ")
                                .append(depotId)
                                .append(" ");
                        return builder.toString();
                    }
                    // 设备表
                case DEVICE:
                    if (AGENT.equals(type)) {
                        builder.append(" AND ")
                                .append(TenantTable.AGENT_ID.getColumns(tableAlias))
                                .append(" = ")
                                .append(agentOrOperatorId)
                                .append(" AND ")
                                // 只查询柜子
                                .append(TenantTable.DEVICE_TYPE.getColumns(tableAlias))
                                .append(" = 1 ");
                        return builder.toString();
                    }
                default:
                    break;
            }
    
            return null;
        }
    
        public void setRequired(boolean required) {
            this.required = required;
        }
    
        public void setTableAlias(String tableAlias) {
            this.tableAlias = tableAlias;
        }
    }
    

    2.0 利用AOP+注解实现标记方法才进行拦截

    PS: 如果查询方法都是写在DAO层接口里面的, 可以不使用AOP (具体看扩展) , 因为使用到了mybatis-plus很多查询方法都是使用其提供的, 所以注解只能标记到service层,

    /**
     * @Author: ZhiHao
     * @Date: 2020/12/17 15:08
     * @Description: 需要增加代理商查询条件 agent_id = xx 的表名
     * @Versions 1.0
     **/
    @Aspect
    @Component
    public class AgentMethodAspect {
    
        private Logger log = LoggerFactory.getLogger(getClass());
    
        //会话工厂
        @Autowired
        private SqlSessionFactory sqlSessionFactory;
        
        private final StampedLock lock = new StampedLock();
    
        /**
         * 切入点
         *
         * @author: ZhiHao
         * @date: 2020/12/17
         */
        @Pointcut("@annotation(com.xxx.xxx.multitenant.RequiredTenant)")
        public void requiredTenant() {
    
        }
    
        /**
         * 环绕通知
         *
         * @param point
         * @return java.lang.Object
         * @author: ZhiHao
         * @date: 2020/12/17
         */
        @Around("requiredTenant()")
        public Object around(ProceedingJoinPoint point) {
            MethodSignature signature = (MethodSignature) point.getSignature();
            Method method = signature.getMethod();
            RequiredTenant requiredTenant = method.getAnnotation(RequiredTenant.class);
            String alias = requiredTenant.tableAlias();
            // 添加过滤器进sql会话工厂配置
            Configuration configuration = sqlSessionFactory.getConfiguration();
            // 判断是否有别名
            AgentSqlInterceptor agentSqlInterceptor = (AgentSqlInterceptor) configuration.getInterceptors().stream().filter(
                    interceptor -> interceptor instanceof AgentSqlInterceptor ? true : false
            ).findAny().get();
            
            try {
                 // 进行加锁, 控制并发将拦截设置为取消
                 if (lock.tryWriteLock(30, TimeUnit.SECONDS) != 0) {
                    // 设置拦截与别名
                    agentSqlInterceptor.setRequired(true);
                    agentSqlInterceptor.setTableAlias(StringUtils.isNotBlank(alias) ? alias : null);
                    // 继续执行方法
                    return point.proceed();
                }
            } catch (Throwable throwable) {
                throwable.printStackTrace();
                log.info("加锁失败:{}",throwable.getMessage());
            } finally {
                // 执行完毕都将其修改回未拦截标记注解其他请求
                agentSqlInterceptor.setRequired(false);
                // 释放锁
                lock.tryUnlockWrite();
            }
            return null;
        }
        
        /**
         * 仅做首次添加拦截器
         *
         * @author: ZhiHao
         * @date: 2020/12/24
         */
        @Override
        public void afterPropertiesSet() throws Exception {
            AgentSqlInterceptor agentSqlInterceptor = new AgentSqlInterceptor(false, null);
            sqlSessionFactory.getConfiguration().addInterceptor(agentSqlInterceptor);
            log.info("首次添加AgentSqlInterceptor拦截器:{}", agentSqlInterceptor);
        }
    }
    

    3.0 注解

    /**
     * @Author: ZhiHao
     * @Date: 2020/12/16 19:01
     * @Description: 是否需要代理商查询
     * @Versions 1.0
     **/
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface RequiredTenant {
    
        /**
         * 查询语句表别名(按需选择)
         * @return
         */
        String tableAlias() default "";
    }
    

    4.0 枚举

    /**
     * @Author: ZhiHao
     * @Date: 2020/12/17 10:08
     * @Description: 需要增加代理商查询条件 agent_id = xx 的表名
     * @Versions 1.0
     **/
    public enum TenantTable {
    
        DMS_BATTERY("dms_battery", null),
        DEVICE("device", null),
        // 代理商查询字段
        AGENT_ID(null, "agent_id"),
        // 仓库查询字段
        DEPOT_ID(null, "depot_id"),
        // 柜子类型查询字段
        DEVICE_TYPE(null, "type"),
        ;
    
        private String tableName;
    
        private String columns;
    
        TenantTable(String tableName, String columns) {
            this.tableName = tableName;
            this.columns = columns;
        }
    
        public static Map<String, TenantTable> tableMap;
    
        static {
            tableMap = Arrays.stream(TenantTable.values())
                    .collect(Collectors.toMap(tenantTable -> tenantTable.tableName,
                            tenantTable -> tenantTable, (tenantTable, tenantTable2) -> tenantTable2));
        }
    
        /**
         * 获取表名
         *
         * @return java.lang.String
         * @author: ZhiHao
         * @date: 2020/12/18
         */
        public String getTableName() {
            return tableName;
        }
    
        /**
         * 获取查询字段
         *
         * @param tableAlias 表别名
         * @return java.lang.String
         * @author: ZhiHao
         * @date: 2020/12/18
         */
        public String getColumns(String tableAlias) {
            if (StringUtils.isNotBlank(tableAlias)) {
                String str = tableAlias + "." + this.columns;
                return str;
            }
            return columns;
        }
    }
    

    5.0 Service层方法使用 (测试)

    	@RequiredTenant
        @Override
        public List<xxxx> getDepreciation(xxxx indexDataQueryDto) { 
        	// 自定义方法查询
            List<xxxxx> listDto = batteryDao.getDepreciation(xxx);
            // mybatis-plus提供方法
            xxxxxx Battery = selectById(xxx);
    }
    

    结果:

    首次添加AgentSqlInterceptor拦截器:com.gizwits.lease.multitenant.AgentSqlInterceptor@4bd8a2c7
    之前sql语句:SELECT IFNULL( db.life - TIMESTAMPDIFF(MONTH,db.initial_time,NOW()) , IFNULL(db.life - TIMESTAMPDIFF(MONTH,db.first_service_time,NOW()),IFNULL(db.life - TIMESTAMPDIFF(MONTH,db.ctime,NOW()),null))) month,
            count(1) number
            FROM dms_battery db WHERE db.status in (0,2,3,6,7,8) AND db.is_cancellation = 1    
            GROUP BY month
     代理商查询sql语句:SELECT IFNULL( db.life - TIMESTAMPDIFF(MONTH,db.initial_time,NOW()) , IFNULL(db.life - TIMESTAMPDIFF(MONTH,db.first_service_time,NOW()),IFNULL(db.life - TIMESTAMPDIFF(MONTH,db.ctime,NOW()),null))) month,
            count(1) number
            FROM dms_battery db WHERE 1 = 1  AND db.agent_id = 8  AND  db.status in (0,2,3,6,7,8) AND db.is_cancellation = 1
            GROUP BY month
    之前sql语句:SELECT COUNT(1) FROM dms_battery WHERE (is_cancellation = ? AND is_deleted = ? AND status NOT IN (?,?,?))
    代理商查询sql语句:SELECT COUNT(1) FROM dms_battery WHERE 1 = 1  AND agent_id = 8  AND  (is_cancellation = ? AND is_deleted = ? AND status NOT IN (?,?,?))
    

    扩展:

    Mybatis-自定义注解加拦截器

    1

    展开全文
  • 环境说明: JDK 1.5以上,GSon 2.2.2。 阅读对象: 假设读者已经了解Java注解的使用以及如何创建一个指定注解类型,读者对JSON、GSON有基本了解,对Java反射...字段中文名:字段值,比如:用户名:张三。 首...
    环境说明:
    JDK 1.5以上,GSon 2.2.2。

    阅读对象:
    假设读者已经了解Java注解的使用以及如何创建一个指定注解类型,读者对JSON、GSON有基本了解,对Java反射机制有所了解。

    需求概述:开发一个日志记录、显示功能,要求当对指定的实体类进行新增、删除、修改操作时,将变化的内容记录到日志表中。

    格式要求:
    字段中文名:字段值,比如:用户名:张三。

    首先新建两个实体类:

    package com.gson.tutorial;

    import java.util.Date;

    public class MySuperEntity {
    private long id;
    private String name;
    private Date date;

    public long getId() {
    return id;
    }
    public void setId(long id) {
    this.id = id;
    }
    public String getName() {
    return name;
    }
    public void setName(String name) {
    this.name = name;
    }
    public Date getDate() {
    return date;
    }
    public void setDate(Date date) {
    this.date = date;
    }
    }



    package com.gson.tutorial;

    import java.util.Date;

    import com.google.gson.annotations.Expose;
    import com.gson.tutorial.annotation.FieldComment;

    public class MyEntity {
    @Expose
    @FieldComment("用户ID")
    private long id;
    @Expose
    @FieldComment("用户名")
    private String userName;
    private String address;
    private Date date;
    private MySuperEntity entity;

    public long getId() {
    return id;
    }

    public void setId(long id) {
    this.id = id;
    }

    public String getUserName() {
    return userName;
    }

    public void setUserName(String userName) {
    this.userName = userName;
    }

    public String getAddress() {
    return address;
    }

    public void setAddress(String address) {
    this.address = address;
    }

    public Date getDate() {
    return date;
    }

    public void setDate(Date date) {
    this.date = date;
    }

    public MySuperEntity getEntity() {
    return entity;
    }

    public void setEntity(MySuperEntity entity) {
    this.entity = entity;
    }
    }


    上面的类中,字段上的@Expose是GSON的注解,表示该字段在进行JSON转换时保留,也就是没有该注解的字段将在进行JSON转换时被忽略。

    @FieldComment注解是一个自定义的注解类型,用于填写字段的中文说明,该自定义注解的源码为:

    package com.gson.tutorial.annotation;

    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;

    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface FieldComment {
    String value();
    }


    下面写一个测试用例:

    package com.gson.tutorial;

    import java.util.Date;

    import com.google.gson.Gson;
    import com.google.gson.GsonBuilder;

    public class Main {

    /**
    * @param args
    */
    public static void main(String[] args) {
    Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();

    MyEntity entity = new MyEntity();
    entity.setId(123456L);
    entity.setUserName("Test User");
    entity.setAddress("金茂中路");
    entity.setDate(new Date());

    String json = gson.toJson(entity);
    System.out.println("Using Expose:" + json);

    gson = new Gson();
    json = gson.toJson(entity);
    System.out.println("Unusing Expose:" + json);
    }

    }


    输出结果为:

    Using Expose:{"id":123456,"userName":"Test User"}
    Unusing Expose:{"id":123456,"userName":"Test User","address":"金茂中路","date":"Sep 23, 2012 11:33:42 PM"}

    可以看到,如果使用:
    Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();

    方式进行JSON转换,那么被@Expose注解的字段将被转成JSON,其他字段将被忽略。而传统的GSON转换方式则不会理会@Expose注解。

    接着进行格式转换输出:

    package com.gson.tutorial.annotation;

    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    import java.util.Date;

    import com.google.gson.Gson;
    import com.google.gson.GsonBuilder;
    import com.gson.tutorial.MyEntity;

    public class Main {
    /**
    * @param args
    * @throws ClassNotFoundException
    * @throws NoSuchMethodException
    * @throws InvocationTargetException
    * @throws IllegalAccessException
    */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static void main(String[] args) throws ClassNotFoundException,
    IllegalAccessException, InvocationTargetException {
    Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation()
    .create();

    MyEntity entity = new MyEntity();
    entity.setId(123456L);
    entity.setUserName("Test User");
    entity.setAddress("金茂中路");
    entity.setDate(new Date());

    String json = gson.toJson(entity);
    System.out.println("Using Expose:" + json);

    // -------------------------------------
    String className = "com.gson.tutorial.MyEntity";
    Class classDef = Class.forName(className);
    Object logObj = gson.fromJson(json, classDef);
    Field[] fields = classDef.getDeclaredFields();
    for (int i = 0; i < fields.length; i++) {
    boolean isPresent = fields[i]
    .isAnnotationPresent(FieldComment.class);
    if (isPresent) {
    // 取注解中的文字说明
    FieldComment comment = fields[i]
    .getAnnotation(FieldComment.class);
    String fieldComment = comment.value();

    // 取对象中字段的值
    fields[i].setAccessible(true); // 设置为可访问private字段
    Object fieldValue = fields[i].get(logObj);

    String content = String.format("%s:%s", fieldComment,
    fieldValue);
    System.out.println(content);
    }
    }
    }

    }


    输出结果:

    Using Expose:{"id":123456,"userName":"Test User"}
    用户ID:123456
    用户名:Test User
    展开全文
  • 自动填充优化功能原生mybatisplus只能做%s+1和now...需要在实体类字段上用原生注解@TableField设置fill=FieldFill.INSERTfill=FieldFill.UPDATE或fill=FieldFill.INSERT_UPDATE否则不会触发自定义填充mybatisplus-p...

    自动填充优化功能

    原生mybatisplus只能做%s+1和now两种填充,mybatisplus-plus在插入或更新时对指定字段进行自定义复杂sql填充。

    需要在实体类字段上用原生注解@TableField设置fill=FieldFill.INSERT

    fill=FieldFill.UPDATE或fill=FieldFill.INSERT_UPDATE否则不会触发自定义填充

    mybatisplus-plus使用@InsertFill注解触发插入时,执行注解中自定义的sql填充实体类字段

    mybatisplus-plus使用@UpdateFill注解触发更新时,执行注解中自定义的sql填充实体类字段

    从中央库引入jar:

    com.github.jeffreyning

    mybatisplus-plus

    0.0.1-RELEASE

    用法①

    在实体类字段上设置@InsertFill,在插入时对seqno字段自动填充复杂计算值 查询当前最大的seqno值并加3,转换成10位字符串,不够位数时用0填充

    @TableField(value="seqno",fill=FieldFill.INSERT )

    @InsertFill("select lpad(max(seqno)+3,10,'0') from test")

    private String seqno;

    用法②

    在实体类字段上设置@InsertFill @UpdateFill,插入和更新时使用当前时间填充

    @InsertFill("select now()")

    @UpdateFill("select now()")

    @TableField(value="update_time",fill=FieldFill.INSERT_UPDATE)

    private Date updateTime;

    注意:

    在启动类中使用@EnableMPP启动扩展自定义填充功能

    @SpringBootApplication

    @EnableMPP

    public class PlusDemoApplication {

    public static void main(String[] args) {

    SpringApplication.run(PlusDemoApplication.class, args);

    }

    }

    本文地址:https://blog.csdn.net/qq_37310941/article/details/110685827

    如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

    展开全文
  • 2.1、修改Bean每个字段上校验注解,设置group2.2、设置什么业务需要使用什么分组进行校验三、自定义校验注解 一、自带校验注解实现数据校验 1、web项目导入依赖 <!-- 该依赖默认导入了 hibernate-validator ...
  • RefreshScope注解</strong><strong>所以更改自定义配置字段正常更新。</strong></li>但是修改了数据库连接的url,请求数据依旧是之前的数据库。</li><li>客户端和服务端均能收到rabbitmq的消息,但是...
  • 场景描述:当我们查询一个类时,返回所有的字段值,然而当我们只取其中的某几个字段时,问题就来了,是不是要重新写SQL去查询?当然这也是可行的。spring给我们提供了一个相当不错的注解功能,为什么不去写一个...
  • 最近在做一个自定义注解+aop实现一个修改子表时通过注解+aop的方式同步更新主表相关字段;通过自定注解将sql语句拼接好后不知道如何获取springboot中的数据库连接对象执行sql语句,请大神指教(不想通过最原始的...
  • 手机端接收到的json字符串中存在的有英文双引号解析不了(感觉应该是前端处理的),前端处理不了,现在后端返回字段时把英文双引号修改成中文双引号 jackson 其实也可以调用自定义类,但是由于项目中返回值String 统一...
  • ​ left join方式,能关联的两张表的实体中关联字段名称必须一样,数据库字段可以不一样可以通@TableField注解来解决,right join 换个位置喽 其它方式还没有) ​ where 基本查询条件, sql函数 等 ​ 分页 查询 ​...
  • 看题主的问题,虽然直接每个对象每个字段都去get判空是...能将需要校验的字段筛选出来,那剩下的问题就是如何将所有的字段值取出来加以判断针对这两个问题,我认为问题1可以通过自定义注解来标示一下需要校验的字...
  • 核心就是通过反射和动态代理,将自定义注解 showColumn 的,在导出的时候做一下修改 直接上代码吧 正文 1. 为注解 Excel 添加属性 showColumn,默认值为true 2. 在原 ExcelUtil 中添加方法 /** * * 动态删除不...
  • 一般数据库的表结构都会有update_time,修改时间,因为这个字段基本与业务没有太大关联,因此开发过程中经常会忘记设置这两个字段,本插件就是来解决这个问题。同样的想生成id,create_time等操作都是可以以同样...
  • 1.利用反射技术获取注解属性,避免后台层次添加导致改动前端代码(例如:刚开发时 省份-》县-》乡,后期数据库更新为省份-》县-》乡-》街道,由于街道的新增可能导致前端代码的更新),本插件即使新增也无需更改...
  • Seam - 语境相关的组件[满江红20071230]............................................................................................................................ 1 Java EE 框架...........................
  • 本来想封装一个好用的,但是能力有限,能想到的方式只能是这样了,之前用原生的JDBC封装了自定义注解的一个CRUD,但是不支持事物(所以肯定行不通的),我初始的想法是通过实体类来进行CRUD,但是每个表的表名,字段都是不...
  • 而没有命中缓存时则是数据库中的+1不会一直加下去; ## 三.分析 1.此问题出现导致了返回的User对象里面的这个int类型的字段数据不正确,跟缓存的 命中情况有直接关系,命中缓存时得到的数值总是比没有命中是...
  • 本系统可对执行SQL语句的结果集批量逐行处理多字段值一起做为检索条件,从它表中取出想要的多字段(值或计算结果)数据对相应的目标多字段进行赋值,从而简化了SQL语句书写难度,减少查询时间,提高了综合性能。...
  • 自定义常用参数校验注解(如入参枚举,时间范围校验等) 给需要校验的参数字段添加相应的校验规则: @Data public class PermissionForm { private Integer groupId; /** * 字符串非空检验 */ @NotBlank ...
  • 乐优商城.xmind

    2019-03-31 17:26:39
    自定义异常、LyException 接口RuntimeException @Getter @NoArgsConstructor @AllArgsConstructor vo 异常结果处理对象、ExceptionResult @Data 构造方法ExceptionResult ly-item-service CategoryQuery ...
  • cms后台管理

    热门讨论 2012-06-26 10:41:19
    Jeecms是基于Spring注解,在自定义标签时对于实体类和dao service等注意注解的问题。 五 自定义标签及使用自己创建的表的实现过程 下面是我自己定义的标签mycontent_list 首先,在数据库里创建了一个jc_...
  • Java EE常用框架.xmind

    2020-06-19 16:08:35
    4,optional属性表示关联的该实体是否能够存在null,默认为ture,如果设置为false,则该实体不能为null, 5, mapperBy属性:指关系被维护端 1,@JoinColumn注释是保存表与表之间关系的字段 2,如果不设置...
  • 3.1 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的,这新对其他线程来说是立即可见的。(保证可见性) 3.2 禁止进行指令重排序。(保证有序性) 划个重点:volatile 关键字能保证...
  • 11.2 Prototype自定义对象和类 实例302 在HTML元素中增加CSS样式 实例303 利用Enumerable对象在页面中显示数组元素 实例304 使用Field对象操作表单域 实例305 通过Form对象实现让表单元素失效 实例306 使用Form....
  • 11.2 Prototype自定义对象和类 实例302 在HTML元素中增加CSS样式 实例303 利用Enumerable对象在页面中显示数组元素 实例304 使用Field对象操作表单域 实例305 通过Form对象实现让表单元素失效 实例306 使用Form....
  • 11.2 Prototype自定义对象和类 实例302 在HTML元素中增加CSS样式 实例303 利用Enumerable对象在页面中显示数组元素 实例304 使用Field对象操作表单域 实例305 通过Form对象实现让表单元素失效 实例306 使用Form....
  • Hibernate3的帮助文档

    2009-03-24 21:42:03
    6.2.3. 自定义值类型 6.3. SQL中引号包围的标识符 6.4. 其他元数据(Metadata) 6.4.1. 使用 XDoclet 标记 6.4.2. 使用 JDK 5.0 的注解(Annotation) 7. 集合类(Collections)映射 7.1. 持久化集合类(Persistent ...
  • VISIO 2007宝典 7/10

    2012-04-02 14:48:18
     6.1.4 在文本中显示字段信息   6.1.5 在页眉和页脚显示信息   6.1.6 向绘图中增加注释   6.2 选择和编辑文本   6.2.1 选择文本   6.2.2 选择成组文本   6.2.3 编辑文本   6.2.4 删除文本  ...
  • VISIO 2007宝典 10/10

    2012-04-02 14:59:04
     6.1.4 在文本中显示字段信息   6.1.5 在页眉和页脚显示信息   6.1.6 向绘图中增加注释   6.2 选择和编辑文本   6.2.1 选择文本   6.2.2 选择成组文本   6.2.3 编辑文本   6.2.4 删除文本  ...

空空如也

空空如也

1 2 3 4
收藏数 70
精华内容 28
关键字:

自定义注解修改字段值