代码规范
Model:
1.model分为“业务对象(表对象)”、“VO对象(前端入参接收对象)”、“DTO对象(后端出参返回对象)”
2.业务对象(表对象):
1)命名方式:TableName,表名(驼峰)
2)类名上方需添加***@Entity***、@Table(name=“表名”)注解,和数据表绑定
3)主键列必须存在,且需注释***@Id***
4)所有属性,需在get方法上添加***@Column***(name=“列名”)
3.Vo对象(主要用于接收前端传回的参数)
1)命名方式:方法名+Vo
2)类名上方需添加***@ApiModel***(value=“类名”,description=“对象描述”)
3)所有属性需添加***@ApiModelProperty***(value=“属性描述”)
4)如果需要接收分页参数,继承PageVo
import com.github.pagehelper.PageHelper;
import lombok.Data;
import java.io.Serializable;
@Data
public class PageVo implements Serializable {
private static final long serialVersionUID = 2479129237262008069L;
/**
* 分页页码
*/
private Integer pageNum;
/**
* 每页条数
*/
private Integer pageSize;
/**
* 模糊查询 字符串
*/
private String searchStr;
/**
* 开始时间字符串
*/
private String start;
/**
* 结束时间字符串
*/
private String end;
/**
* 如果有分页参数则进行分页 如果没有这不进行分页
*/
public void startPage() {
if (hasPage()) {
PageHelper.startPage(this.getPageNum(), this.getPageSize());
}
}
/**
* 判断是否需要分页
*
*/
public boolean hasPage() {
// 如果缺少一个参数 都不能进行分页返回false
return this.pageNum != null && this.pageSize != null;
}
}
4.Dto对象(主要用于将数据返回给前端)
1)命名方式:方法名+Dto
2)类名上方需添加***@ApiModel***(value=“类名”,description=“对象描述”)
3)所有属性需添加***@ApiModelProperty***(value=“属性描述”)
4)如果需要“基础字段”,则继承BaseDto
import lombok.Data;
import java.util.List;
/**
* @ClassName BaseDTO
* @Description T0D0
* @Author Kai
* @Date 2023/3/1 16:24
* @Version 1.0
**/
@Data
public class BaseDTO<T> {
// 当前页
private Integer currentPage = 1;
// 每页显示的总条数
private Integer pageSize = 10;
// 总条数
private Integer totalNum;
// 是否有下一页
private Integer isMore;
// 总页数
private Integer totalPage;
// 开始索引
private Integer startIndex;
// 分页结果
private List<T> items;
public BaseDTO() {
super();
}
public BaseDTO(Integer currentPage, Integer pageSize, Integer totalNum) {
super();
if (currentPage != null && currentPage > 0) {
this.currentPage = currentPage;
}
if (pageSize != null && pageSize > 0) {
this.pageSize = pageSize;
}
this.totalNum = totalNum;
this.totalPage = (this.totalNum + this.pageSize - 1) / this.pageSize;
this.startIndex = (this.currentPage - 1) * this.pageSize;
this.isMore = this.currentPage >= this.totalPage ? 0 : 1;
}
}
Controller:
1.controller类需添加如下注解:
@RestController
@RequestMapping(“接口地址前缀”)
@Api(tags = “接口类描述”,value=“类名”)
2.入参需用vo对象接受(4个参数以上必须使用vo对象接收,4个参数以下可自行处理)
3.出参需用dto对象返回(4个参数以上必须使用dto,4个以下可自行处理)
4.入参和出参均不可使用业务对象(表对象)
5.controller中不允许出现mapper对象,所有数据库查询通过service处理
6.查询方法注解使用***@GetMapping***,其他事务类方法使用***@PostMapping***
7.所有controller方法使用Result 作为返回信息,可使用Result工具类进行数据封装处理。
9.所有controller方法需添加***@ApiOperation***注解,说明方法作用,方便前后端开发人员接口对接
10.如果方法不需要token验证,添加***@Auth***(verifyToken=false)
11.命名方式:TableNameController,表名(驼峰)+Controller(非必须规范,也可根据业务情况进行命名)
service 接口:
1.实现类需添加***@Service***(“tableNameService”)注解
2.需要事务的方法需添加***@Transactional***注解
3.业务类异常使用ServiceException抛出
4.命名方式:TableNameService,表名(驼峰)+Service
5.分页查询方法示例
public SinoPageInfo<SearchBootTestDto> searchBootTest(SearchBootTestVo vo) {
SinoPageUtil.startPage(vo);
List<SearchBootTestDto> list = bootTestMapper.searchBootTest(vo);
return SinoPageUtil.of(list);
}
mapper:
1.如果需要特殊查询,则需要新建mapper接口以及mapper.xml
2.Mapper接口需放到mapper包下,mapper.xml需放到mapper.xml包下
3.命名方式:TableNameMapper(.xml),表名(驼峰)+Mapper
方法命名规约:
1) 获取单个对象的方法用 get 做前缀。
2) 获取多个对象的方法用 list/search(分页) 做前缀。
3) 获取统计值的方法用 count 做前缀。
4) 插入的方法用 save 做前缀。
5) 删除的方法用 delete 做前缀。
6) 修改的方法用 update 做前缀。
数据库:
-
表名、字段名、索引等数据结构定义大小写: Oracle大写, MySQL小写。名称使用英文+下划线,并控制总长度,如 user_name。表名建议采用“模块标识_”前缀,如 bas_user(如果模块库独立可省略模块名标识)。
-
SQL使用标准SQL,避免出现数据库特定的语法,便于切换数据库进行复用。
-
禁止手动拼接SQL语句,利用Mybatis等ORM框架的动态SQL实现。 参数使用#{} (避免${}产生SQL注入问题)。
sql优化
-
禁止程序中的SQL使用并行计算 /+parallel(t,n)/
-
禁止动态拼接时强加 1=1 之类的写法,如WHERE 1=1。使用Mybatis动态SQL标签实现
<select id="selectByStudentSelective" resultMap="BaseResultMap" parameterType="com.homejim.mybatis.entity.Student">
select
<include refid="Base_Column_List" />
from student
< where>
<if test="name != null and name !=''">
and name like concat('%', #{name}, '%')
</if>
<if test="sex != null">
and sex=#{sex}
</if>
</where>
</select>
-
未经评审不可直接使用视图、触发器、存储过程 SQL JOIN表数量不超过3张,超过3张表需要经过评审 (拆分成多次单表查询、主表冗余、程序绑定id-name映射、根据条件动态JOIN等)。
-
禁止使用数据库处理函数 decode(),改为Java枚举或Map定义,通过id进行绑定 decode(client.TYPE, 1, ‘私客’, 2, ‘店组公客’, 3, '组团公客‘)。
-
合理创建索引,并尽量避免不走索引的情况: 如
- LIKE右/任意匹配(‘%xx’, ‘%xx%’)不走索引, 换为“精确匹配=”或固定前缀的左匹配’张%’
- 不等条件(!=、<>、NOT)不走索引,应尽量避免(转换成IN/BETWEEN等)
- IS (NOT) NULL 不走索引,应尽量避免(如字段给定默认值,避免NULL)
- 索引列使用函数或隐式转换都将导致索引失效,如 to_char(create_date,‘yyyymmdd’) = ‘20190102’
逻辑代码规范
-
废弃的/无用的代码一律直接删除,禁止以注释等方式保留。如需查看历史代码,通过SVN/Git的history找回(无用的代码会干扰团队成员的阅读/或被误调,越积越多会导致代码维护成本增高)。
-
接口类中的方法不需添加 public 修饰符。
-
需要序列化的Bean类统一实现Serializable接口并用IDE生成serialVersionUID。
-
常用字符串统一定义在常量类里,如: “utf-8”, “yyyyMMdd”。
-
避免数字类型比较的坑: 统一采用equals进行比较其值,不用==进行比较,避免踩坑。
-
if/else/for/while语句后必须使用大括号,即使只有一行代码。(需求总是变化的,一行是暂时的)。
-
嵌套层次过多的代码块利用反向思维缩减层次。
-
方法单一职责: 单个方法代码行数控制在100行以内,超长的需要拆分(拆分成多个方法或类)。
-
避免NPE(NullPointException)的一些建议:
- equals比较将非空对象前置:如"true".equals(request.getParameter(“isXx”)),即使后者为空也不会导致NPE。
- 数据库字段可空的映射属性使用包装类型定义:如基本数据类型的int映射到数据库的null值将产生NPE,而用吧包装类型 Integer 则不会。
- 可能为空的变量进行必要判空,并在非预期条件下打印必要的跟踪日志,不但避免NPE,还非常便于跟踪调试。
- 级联调用 obj.getA().getB().getC() 易产生 NPE,先进行判空或使用 JDK8 的 Optional 类包装。
- 调用Dubbo接口拿到返回值时,进行判空。
- 封装统一的判空类用于常用类型的判空,代码需要判空时统一调用即可。如 XX.isEmpty(), XX.isNotEmpty()
-
遵循: Don’t Repeat Yourself,即 DRY 原则。避免进行简单的复制粘贴修改,当出现重复代码时思考是否封装
- 当代码中存在大量重复代码时,一旦代码逻辑变动将很容易导致顾此失彼,产生bug,非常不利于维护
-
Bean属性拷贝推荐用Spring BeanCopier或者Mapstruct,避免Apache BeanUtils或调用setter。
-
禁止在循环中执行耗时的操作,在循环中执行SQL语句/调用外部服务。
-
需要多次使用的可复用对象将对象单独定义,禁止多次调用取不同属性。如:
String name = userService.getUser(id).getName();
Long deptId = userService.getUser(id).getDepeId();
替换为:
User user = userService.getUser(id);
String name = user.getName();
-
可异步执行的耗时操作采用异步处理:使用Spring @Async 或 MQ,或夜间Timer定时。
-
常用数据考虑缓存,存入Redis,设置缓存过期时间。
-
需要保证写一致性的逻辑,在外层方法上添加事务 @Transactional(rollbackFor = Exception.class)。
异常与日志
-
调用外部服务等可能异常的代码块,用 try/catch 代码块捕获并在catch中记录异常跟踪日志及业务逻辑处理
-
禁止吞掉异常信息:
- 禁止catch里不做任何记录和处理,吞掉异常及其堆栈信息
- 禁止: logger.error(“XXX操作异常”) 或 logger.error(“XXX操作异常”+e) 或 e.printStackTrace()
- 正确:logger.error(“XXX操作异常”, e)
-
对于非预期的条件,尽量增加else记录跟踪日志
-
禁止通过System.*.out()打印日志(单元测试例外)
-
日志记录logger需使用Slf4J代理声明,禁止绑死具体日志系统的API,避免后期更换日志组件导致代码的大量改动,采用了lombok,可用 @Slf4j 注解
-
对 trace/debug/info 级别的日志输出,必须使用占位符形式,避免直接String拼接异常信息(即使日志级别不匹配也会执行拼接操作空耗资源)。
正确写法如下:
log.debug("当前用户id: {} ,操作对象: {}=>{} ", userId, objectType, objectId);
或条件输出形式如:
if(log.isDebugEnabled()){
log.debug("当前用户id: “+id+” ,操作对象: “+ objectType +”=> “+ objectId);
}