办公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三个组合接口。