Java后端编码开发规范

代码规范

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 做前缀。

数据库:

  1. 表名、字段名、索引等数据结构定义大小写: Oracle大写, MySQL小写。名称使用英文+下划线,并控制总长度,如 user_name。表名建议采用“模块标识_”前缀,如 bas_user(如果模块库独立可省略模块名标识)。

  2. SQL使用标准SQL,避免出现数据库特定的语法,便于切换数据库进行复用。

  3. 禁止手动拼接SQL语句,利用Mybatis等ORM框架的动态SQL实现。 参数使用#{} (避免${}产生SQL注入问题)。

sql优化

  1. 禁止程序中的SQL使用并行计算 /+parallel(t,n)/

  2. 禁止动态拼接时强加 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>
  1. 未经评审不可直接使用视图、触发器、存储过程 SQL JOIN表数量不超过3张,超过3张表需要经过评审 (拆分成多次单表查询、主表冗余、程序绑定id-name映射、根据条件动态JOIN等)。

  2. 禁止使用数据库处理函数 decode(),改为Java枚举或Map定义,通过id进行绑定 decode(client.TYPE, 1, ‘私客’, 2, ‘店组公客’, 3, '组团公客‘)。

  3. 合理创建索引,并尽量避免不走索引的情况: 如

    • LIKE右/任意匹配(‘%xx’, ‘%xx%’)不走索引, 换为“精确匹配=”或固定前缀的左匹配’张%’
    • 不等条件(!=、<>、NOT)不走索引,应尽量避免(转换成IN/BETWEEN等)
    • IS (NOT) NULL 不走索引,应尽量避免(如字段给定默认值,避免NULL)
    • 索引列使用函数或隐式转换都将导致索引失效,如 to_char(create_date,‘yyyymmdd’) = ‘20190102’

逻辑代码规范

  1. 废弃的/无用的代码一律直接删除,禁止以注释等方式保留。如需查看历史代码,通过SVN/Git的history找回(无用的代码会干扰团队成员的阅读/或被误调,越积越多会导致代码维护成本增高)。

  2. 接口类中的方法不需添加 public 修饰符。

  3. 需要序列化的Bean类统一实现Serializable接口并用IDE生成serialVersionUID。

  4. 常用字符串统一定义在常量类里,如: “utf-8”, “yyyyMMdd”。

  5. 避免数字类型比较的坑: 统一采用equals进行比较其值,不用==进行比较,避免踩坑。

  6. if/else/for/while语句后必须使用大括号,即使只有一行代码。(需求总是变化的,一行是暂时的)。

  7. 嵌套层次过多的代码块利用反向思维缩减层次。

  8. 方法单一职责: 单个方法代码行数控制在100行以内,超长的需要拆分(拆分成多个方法或类)。

  9. 避免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()
  10. 遵循: Don’t Repeat Yourself,即 DRY 原则。避免进行简单的复制粘贴修改,当出现重复代码时思考是否封装

    • 当代码中存在大量重复代码时,一旦代码逻辑变动将很容易导致顾此失彼,产生bug,非常不利于维护
  11. Bean属性拷贝推荐用Spring BeanCopier或者Mapstruct,避免Apache BeanUtils或调用setter。

  12. 禁止在循环中执行耗时的操作,在循环中执行SQL语句/调用外部服务。

  13. 需要多次使用的可复用对象将对象单独定义,禁止多次调用取不同属性。如:

	String name = userService.getUser(id).getName();
 	Long deptId = userService.getUser(id).getDepeId();

  替换为:

 	User user = userService.getUser(id); 
 	String name = user.getName();
  1. 可异步执行的耗时操作采用异步处理:使用Spring @Async 或 MQ,或夜间Timer定时。

  2. 常用数据考虑缓存,存入Redis,设置缓存过期时间。

  3. 需要保证写一致性的逻辑,在外层方法上添加事务 @Transactional(rollbackFor = Exception.class)。

异常与日志

  1. 调用外部服务等可能异常的代码块,用 try/catch 代码块捕获并在catch中记录异常跟踪日志及业务逻辑处理

  2. 禁止吞掉异常信息:

    • 禁止catch里不做任何记录和处理,吞掉异常及其堆栈信息
    • 禁止: logger.error(“XXX操作异常”) 或 logger.error(“XXX操作异常”+e) 或 e.printStackTrace()
    • 正确:logger.error(“XXX操作异常”, e)
  3. 对于非预期的条件,尽量增加else记录跟踪日志

  4. 禁止通过System.*.out()打印日志(单元测试例外)

  5. 日志记录logger需使用Slf4J代理声明,禁止绑死具体日志系统的API,避免后期更换日志组件导致代码的大量改动,采用了lombok,可用 @Slf4j 注解

  6. 对 trace/debug/info 级别的日志输出,必须使用占位符形式,避免直接String拼接异常信息(即使日志级别不匹配也会执行拼接操作空耗资源)。

正确写法如下:
 	log.debug("当前用户id: {} ,操作对象: {}=>{} ", userId, objectType, objectId);

 	或条件输出形式如:

	if(log.isDebugEnabled()){
	
		log.debug("当前用户id:+id+” ,操作对象:+ objectType +=>+ objectId);
	
	 }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值