办公OA系统

swagger

swagger配置文件.apis(RequestHandlerSelectors.basePackage("com.travalsky.hdrd"))修改扫描包的路径。

注解

自定义注解

Java目前只内置了三种标准注解,以及四种元注解。

1、@Target
表示支持注解的程序元素的种类,注解该用于什么地方,ElementType 注解修饰的元素类型,使用ElementType枚举类来表示:

CONSTRUCTOR:构造器的声明
FIELD:域声明(包括enum实例)
LOCAL_VARIABLE:局部变量声明
METHOD:方法声明
PACKAGE:  包声明
PARAMETER:参数声明
TYPE:类,接口(包括注解类型)或enum声明
ANNOTATION_TYPE:注解类型声明
TYPE_PARAMETER:类型参数声明  从jdk1.8开始  、 hide1.8
 TYPE_USE:类型的使用 从jdk1.8开始 、 hide1.8

2、@Retention
表示保留时间的长短,需要在什么级别保存该注解信息, RetentionPolicy参数包括:

SOURCE:注解将被编译器丢弃。
CLASS:注解在class文件中可用,但会被VM丢弃。
RUNTIME:VM将在运行期也保留注解,因此可通过反射机制读取注解的信息。

3、@Documented
将此注解包含在Javadoc中。 表示使用该注解的元素应被javadoc或类似工具文档化,它应用于类型声明,类型声明的注解会影响客户端对注解元素的使用。如果一个类型声明添加了Documented注解,那么它的注解会成为被注解元素的公共API的一部分。

4、@Inherited
允许子类继承父类中的注解。表示一个注解类型会被自动继承,如果用户在类声明的时候查询注解类型,同时类声明中也没有这个类型的注解,那么注解类型会自动查询该类的父类,这个过程将会不停地重复,直到该类型的注解被找到为止,或是到达类结构的顶层(Object)。

通过例子验证如何使用注解

通过编写代码,更能了解注解,对注解的概念更能清楚。首先,编写一个自定义注解类BindView

/**
 * @Author lu an
 * Create Date is  2019/10/9
 * Des  绑定view的注解
 */
//保存的级别到运行时期
@Retention(RetentionPolicy.RUNTIME)
//目标->field 字段
@Target(ElementType.FIELD)
public @interface BindView {
    int viewId() default 0;//默认0
    boolean onClick() default false;//default表示默认
    boolean onLongClick() default false;//default表示默认
}

default表示默认值 ,也可以不编写默认值的。下面实现一个注解工具实现类:

/**
 * @Author lu an
 * Create Date is  2019/10/9
 * Des  注解工具类
 */
public class InjectUtils {
    public static void init(Activity ctx){
        //获取Class对象
        Class clazz = ctx.getClass();
        //获取字段变量
        Field[]fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            //判断是否绑定了BindView
            if(field.isAnnotationPresent(BindView.class)){
                //获取到BindView对象
                BindView bindView = field.getAnnotation(BindView.class);
                //通过getAnnotation方法取出标记了注解的字段viewId
                int viewId = bindView.viewId();
                if(viewId>0){
                    field.setAccessible(true);
                    //获取到view
                    View childView = ctx.findViewById(viewId);
                    if (childView != null) {
                        //绑定的点击监听事件
                        if (bindView.onClick() && ctx instanceof View.OnClickListener) {
                            childView.setOnClickListener((View.OnClickListener) ctx);
                        }
                        if (bindView.onLongClick() && ctx instanceof View.OnLongClickListener) {
                            childView.setOnLongClickListener((View.OnLongClickListener) ctx);
                        }
                    }
                }
            }
        }
    }
}

通过反射方式,通过getAnnotation方法取出标记了注解的字段viewId,得到了viewId后,通过上下文findViewById找到对应的View控件,bindView.onClick() 对应的@BindView(viewId = R.id.btn_annotation,onClick = true)中的onClick值,ctx instanceof View.OnClickListener则表示Activity是否实现了点击接口.

下面就编写一个Activity类实现注解绑定view及其点击事件:

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    //绑定view控件 和 绑定view的点击事件
    @BindView(viewId = R.id.btn_annotation,onClick = true)
    Button mBtnAnnotation;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //注解初始化
        InjectUtils.init(this);
    }
    @Override
    public void onClick(View v) {
        Toast.makeText(this,"Binding View Success",Toast.LENGTH_SHORT).show();
    }
}

@BindView(viewId = R.id.btn_annotation,onClick = true)表示绑定view控件 和 绑定view的点击事件,如果只单单绑定view,如@BindView(viewId = R.id.btn_annotation)即可。

当你点击Button时就会弹出Binding View Success。

注解@query的使用

@query的定义

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Query {

    // Dai Weiguo 2017/8/7 基本对象的属性名
    String propName() default "";
    // Dai Weiguo 2017/8/7 查询方式
    Type type() default Type.EQUAL;

    /**
     * 多字段模糊搜索,仅支持String类型字段,多个用逗号隔开, 如@Query(blurry = "email,username")
     */
    String blurry() default "";

    enum Type {
        // jie 2019/6/4 相等
        EQUAL
        // Dai Weiguo 2017/8/7 大于等于
        , GREATER_THAN
        // Dai Weiguo 2017/8/7 小于等于
        , LESS_THAN
        // Dai Weiguo 2017/8/7 中模糊查询
        , INNER_LIKE
        // Dai Weiguo 2017/8/7 左模糊查询
        , LEFT_LIKE
        // Dai Weiguo 2017/8/7 右模糊查询
        , RIGHT_LIKE
        // Dai Weiguo 2017/8/7 小于
        , LESS_THAN_NQ
        // jie 2019/6/4 包含
        , IN
        // 不等于
        ,NOT_EQUAL
        // between
        ,BETWEEN
        // 不为空
        ,NOT_NULL
        // 查询时间
        ,UNIX_TIMESTAMP
    }

}

注解的实现类

@Slf4j
@SuppressWarnings({"unchecked", "all"})
public class QueryHelpPlus {

    public static <R, Q> QueryWrapper getPredicate(R obj, Q query) {
        QueryWrapper<R> queryWrapper = new QueryWrapper<R>();
        if (query == null) {
            return queryWrapper;
        }
        try {
            List<Field> fields = getAllFields(query.getClass(), new ArrayList<>());
            for (Field field : fields) {
                boolean accessible = field.isAccessible();
                field.setAccessible(true);
                Query q = field.getAnnotation(Query.class);
                if (q != null) {
                    String propName = q.propName();
                    String blurry = q.blurry();
                    String attributeName = isBlank(propName) ? field.getName() : propName;
                    attributeName = humpToUnderline(attributeName);
                    Class<?> fieldType = field.getType();
                    Object val = field.get(query);
                    if (ObjectUtil.isNull(val) || "".equals(val)) {
                        continue;
                    }
                    // 模糊多字段
                    if (ObjectUtil.isNotEmpty(blurry)) {
                        String[] blurrys = blurry.split(",");
                        //queryWrapper.or();
                        queryWrapper.and(wrapper -> {
                            for (int i=0;i< blurrys.length;i++) {
                                String column = humpToUnderline(blurrys[i]);
                                //if(i!=0){
                                    wrapper.or();
                                //}
                                wrapper.like(column, val.toString());
                            }
                        });
                        continue;
                    }
                    String finalAttributeName = attributeName;
                    switch (q.type()) {
                        case EQUAL:
                            //queryWrapper.and(wrapper -> wrapper.eq(finalAttributeName, val));
                            queryWrapper.eq(attributeName, val);
                            break;
                        case GREATER_THAN:
                           queryWrapper.ge(finalAttributeName, val);
                            break;
                        case LESS_THAN:
                            queryWrapper.le(finalAttributeName, val);
                            break;
                        case LESS_THAN_NQ:
                           queryWrapper.lt(finalAttributeName, val);
                            break;
                        case INNER_LIKE:
                           queryWrapper.like(finalAttributeName, val);
                            break;
                        case LEFT_LIKE:
                           queryWrapper.likeLeft(finalAttributeName, val);
                            break;
                        case RIGHT_LIKE:
                           queryWrapper.likeRight(finalAttributeName, val);
                            break;
                        case IN:
                            if (CollUtil.isNotEmpty((Collection<Long>) val)) {
                               queryWrapper.in(finalAttributeName, (Collection<Long>) val);
                            }
                            break;
                        case NOT_EQUAL:
                           queryWrapper.ne(finalAttributeName, val);
                            break;
                        case NOT_NULL:
                           queryWrapper.isNotNull(finalAttributeName);
                            break;
                        case BETWEEN:
                            List<Object> between = new ArrayList<>((List<Object>) val);
                           queryWrapper.between(finalAttributeName, between.get(0), between.get(1));
                            break;
                        case UNIX_TIMESTAMP:
                            List<Object> UNIX_TIMESTAMP = new ArrayList<>((List<Object>)val);
                            if(!UNIX_TIMESTAMP.isEmpty()){
                                SimpleDateFormat fm = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                                long time1 = fm.parse(UNIX_TIMESTAMP.get(0).toString()).getTime()/1000;
                                long time2 = fm.parse(UNIX_TIMESTAMP.get(1).toString()).getTime()/1000;
                                queryWrapper.between(finalAttributeName, time1, time2);
                            }
                            break;
                        default:
                            break;
                    }
                }
                field.setAccessible(accessible);
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }

        return queryWrapper;
    }



    private static boolean isBlank(final CharSequence cs) {
        int strLen;
        if (cs == null || (strLen = cs.length()) == 0) {
            return true;
        }
        for (int i = 0; i < strLen; i++) {
            if (!Character.isWhitespace(cs.charAt(i))) {
                return false;
            }
        }
        return true;
    }

    private static List<Field> getAllFields(Class clazz, List<Field> fields) {
        if (clazz != null) {
            fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
            getAllFields(clazz.getSuperclass(), fields);
        }
        return fields;
    }

    /***
     * 驼峰命名转为下划线命名
     *
     * @param para
     *        驼峰命名的字符串
     */

    public static String humpToUnderline(String para) {
        StringBuilder sb = new StringBuilder(para);
        int temp = 0;//定位
        if (!para.contains("_")) {
            for (int i = 0; i < para.length(); i++) {
                if (Character.isUpperCase(para.charAt(i))) {
                    sb.insert(i + temp, "_");
                    temp += 1;
                }
            }
        }
        return sb.toString();
    }

使用方法

业务层实现类
@Override
public List<Resume> queryAll(ResumeQueryCriteria criteria){
    return baseMapper.selectList(QueryHelpPlus.getPredicate(Resume.class, criteria));
}
控制层实现类
    @Log("查询简历")
    @ApiOperation("查询简历/高级筛选/职位筛选")
    @GetMapping
    @AnonymousAccess
//  @PreAuthorize("@el.check('resume:list')")
    public ResponseEntity<Object> getResumes(ResumeQueryCriteria criteria, Pageable pageable){
        return new ResponseEntity<>(resumeService.queryAll(criteria, pageable), HttpStatus.OK);
    }

功能实现

查询

User user = userService.getById(id);//对应接口类
Evaluation evaluation = evaluationMapper.selectById(evaluationDto.getId());

增加和修改

this.saveOrUpdate(evaluation);

通用Mapper接口方法以及说明

基础接口 Select

接口:SelectMapper
方法:List select(T record);

说明:根据实体中的属性值进行查询,查询条件使用等号

接口:SelectByPrimaryKeyMapper
方法:T selectByPrimaryKey(Object key);

说明:根据主键字段进行查询,方法参数必须包含完整的主键属性,查询条件使用等号

接口:SelectAllMapper
方法:List selectAll();

说明:查询全部结果,select(null)方法能达到同样的效果

接口:SelectOneMapper
方法:T selectOne(T record);

说明:根据实体中的属性进行查询,只能有一个返回值,有多个结果是抛出异常,查询条件使用等号

接口:SelectCountMapper
方法:int selectCount(T record);

说明:根据实体中的属性查询总数,查询条件使用等号

基础接口Insert

接口:InsertMapper
方法:int insert(T record);

说明:保存一个实体,null的属性也会保存,不会使用数据库默认值

接口:InsertSelectiveMapper
方法:int insertSelective(T record);

说明:保存一个实体,null的属性不会保存,会使用数据库默认值

基础接口Update

接口:UpdateByPrimaryKeyMapper
方法:int updateByPrimaryKey(T record);

说明:根据主键更新实体全部字段,null值会被更新

接口:UpdateByPrimaryKeySelectiveMapper
方法:int updateByPrimaryKeySelective(T record);

说明:根据主键更新属性不为null的值

基础接口Delete

接口:DeleteMapper
方法:int delete(T record);

说明:根据实体属性作为条件进行删除,查询条件使用等号

接口:DeleteByPrimaryKeyMapper
方法:int deleteByPrimaryKey(Object key);

说明:根据主键字段进行删除,方法参数必须包含完整的主键属性

Base组合接口

接口:BaseSelectMapper
方法:包含上面Select的4个方法

接口:BaseInsertMapper
方法:包含上面Insert的2个方法

接口:BaseUpdateMapper
方法:包含上面Update的2个方法

接口:BaseDeleteMapper
方法:包含上面Delete的2个方法

CURD组合接口

接口:BaseMapper
方法:继承了base组合接口中的4个组合接口,包含完整的CRUD方法

Example方法

接口:SelectByExampleMapper
方法:List selectByExample(Object example);

说明:根据Example条件进行查询
重点:这个查询支持通过Example类指定查询列,通过selectProperties方法指定查询列

接口:SelectCountByExampleMapper
方法:int selectCountByExample(Object example);

说明:根据Example条件进行查询总数

接口:UpdateByExampleMapper
方法:int updateByExample(@Param(“record”) T record, @Param(“example”) Object example);

说明:根据Example条件更新实体record包含的全部属性,null值会被更新

接口:UpdateByExampleSelectiveMapper
方法:int updateByExampleSelective(@Param(“record”) T record, @Param(“example”) Object example);

说明:根据Example条件更新实体record包含的不是null的属性值

接口:DeleteByExampleMapper
方法:int deleteByExample(Object example);

说明:根据Example条件删除数据

Example组合接口

接口:ExampleMapper
方法:包含上面Example中的5个方法

Condition方法

Condition方法和Example方法作用完全一样,只是为了避免Example带来的歧义,提供的的Condition方法

接口:SelectByConditionMapper
方法:List selectByCondition(Object condition);

说明:根据Condition条件进行查询

接口:SelectCountByConditionMapper
方法:int selectCountByCondition(Object condition);

说明:根据Condition条件进行查询总数

接口:UpdateByConditionMapper
方法:int updateByCondition(@Param(“record”) T record, @Param(“example”) Object condition);

说明:根据Condition条件更新实体record包含的全部属性,null值会被更新

接口:UpdateByConditionSelectiveMapper
方法:int updateByConditionSelective(@Param(“record”) T record, @Param(“example”) Object condition);

说明:根据Condition条件更新实体record包含的不是null的属性值

接口:DeleteByConditionMapper
方法:int deleteByCondition(Object condition);

说明:根据Condition条件删除数据

Condition组合接口

接口:ConditionMapper
方法:包含上面Condition中的5个方法

RowBounds

默认为内存分页,可以配合PageHelper实现物理分页

接口:SelectRowBoundsMapper
方法:List selectByRowBounds(T record, RowBounds rowBounds);

说明:根据实体属性和RowBounds进行分页查询

接口:SelectByExampleRowBoundsMapper
方法:List selectByExampleAndRowBounds(Object example, RowBounds rowBounds);

说明:根据example条件和RowBounds进行分页查询

接口:SelectByConditionRowBoundsMapper
方法:List selectByConditionAndRowBounds(Object condition, RowBounds rowBounds);

说明:根据example条件和RowBounds进行分页查询,该方法和selectByExampleAndRowBounds完全一样,只是名字改成了Condition

RowBounds组合接口

接口:RowBoundsMapper
方法:包含上面RowBounds中的前两个方法,不包含selectByConditionAndRowBounds

Special特殊接口

这些接口针对部分数据库设计,不是所有数据库都支持

接口:InsertListMapper
方法:int insertList(List recordList);

说明:批量插入,支持批量插入的数据库可以使用,例如MySQL,H2等,另外该接口限制实体包含id属性并且必须为自增列

接口:InsertUseGeneratedKeysMapper
方法:int insertUseGeneratedKeys(T record);

说明:插入数据,限制为实体包含id属性并且必须为自增列,实体配置的主键策略无效

MySQL专用

接口:MySqlMapper
继承方法:int insertList(List recordList);
继承方法:int insertUseGeneratedKeys(T record);

说明:该接口不包含方法,继承了special中的InsertListMapper和InsertUseGeneratedKeysMapper

SQLServer专用

由于sqlserver中插入自增主键时,不能使用null插入,不能在insert语句中出现id。
注意SqlServer的两个特有插入方法都使用了
@Options(useGeneratedKeys = true, keyProperty = “id”)
这就要求表的主键为id,且为自增,如果主键不叫id可以看高级教程中的解决方法。
另外这俩方法和base中的插入方法重名,不能同时存在!
如果某种数据库和SqlServer这里类似,也可以使用这些接口(需要先测试)。
经测试,PostgreSQL可以采用。

接口:InsertMapper
方法:int insert(T record);

说明:插入数据库,null值也会插入,不会使用列的默认值

接口:InsertSelectiveMapper
方法:int insertSelective(T record);

说明:插入数据库,null的属性不会保存,会使用数据库默认值

接口:SqlServerMapper

说明:这是上面两个接口的组合接口。

Mapper接口

接口:Mapper
该接口兼容Mapper2.x版本,继承了BaseMapper, ExampleMapper, RowBoundsMapper三个组合接口。