如何写好 Java 业务代码?这也是有很多规范的..(2)

简介: 如何写好 Java 业务代码?这也是有很多规范的..(2)

加分项的规范

乐观锁与悲观锁的使用

乐观锁(使用Spring AOP+注解基于CAS方式实现java的乐观锁)设置重试次数以及重试时间,在简单的对象属性修改使用乐观锁,示例如下:

@Transactional(rollbackFor = Exception.class)
@OptimisticRetry
public void updateGoods(GoodsUpdateDto dto) {
    Goods existGoods = this.getGoods(dto.getCode());
    // 属性逻辑判断 //
    if (0 == goodsDao.updateGoods(existGoods, dto)) {
        throw new OptimisticLockingFailureException("update goods optimistic locking failure!");
    }
}

悲观锁在业务场景比较复杂,关联关系比较多的情况下使用。例如修改SKU属性时,需要修改商品的价格,库存,分类,等等属性,这时可以对关联关系的聚合根产品进行加锁,代码如下:

@Transactional
public void updateProduct(Long id,ProductUpdateDto dto){
    Product existingProduct;
    // 根据产品id对数据加锁
    Assert.notNull(existingProduct = lockProduct(id), "无效的产品id!");
    // TODO 逻辑条件判断 
    // TODO 修改商品属性,名称,状态
    // TODO 修改价格
    // TODO 修改库存
    // TODO 修改商品规格
}

读写分离的使用

开发中,经常使用mybatisplus实现读写分离。常规的查询操作,就走从库查询,查询请求可以不加数据库事务,例如列表查询,示例如下:

    @Override
    @DS("slave_1")
    public List<Product> findList(ProductQuery query) {
        QueryWrapper<Product> queryWrapper = this.buildQueryWrapper(query);
        return this.baseMapper.selectList(queryWrapper);
    }

mybatisplus动态数据源默认是主库,写操作为了保证数据一直性,需要加上事务控制。简单的操作可以直接加上@Transactional注解,如果写操作涉及到非必要的查询,或者使用到消息中间件,reids等第三方插件,可以使用声明式事务,避免查询或者第三方查询异常造成数据库长事务问题。


示例,产品下线时,使用reids生成日志code,产品相关写操作执行完成后,发送消息,代码如下:

public void offlineProduct(OfflineProductDto dto){
    // TODO 修改操作为涉及到的查询操作
    // TODO 使用redis生成业务code
    // 使用声明式事务控制产品状态修改的相关数据库操作
    boolean status = transactionTemplate.execute(new TransactionCallback<Boolean>() {
        @Nullable
        @Override
        public Boolean doInTransaction(TransactionStatus status) {
              try {
                 // TODO 更改产品状态
              } catch (Exception e) {
                 status.setRollbackOnly();
                 throw e;
              }
              return true;
           }
        });
    // TODO 使用消息中间件发送消息
}

数据库自动给容灾

结合配置中心,简单实现数据库的自动容灾。以nacous配置中心为例,如何使用Nacos实现数据库连接的自动切换?。在springboot启动类加上@EnableNacosDynamicDataSource配置注解,即可无侵入的实现数据库连接的动态切换,示例如下:


推荐一个 Spring Boot 基础教程及实战示例:https://github.com/javastacks/spring-boot-best-practice

@EnableNacosDynamicDataSource
public class ProductApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProductApplication.class, args);
    }
}

测试用例的编写

基于TDD的原则,结合junit和mockito实现服务功能的测试用例,为什么要写单元测试?基于junit如何写单元测试?。添加或者修改对象时,需要校验入参的有效性,并且校验操作以后的对象的各类属性。以添加类目的api测试用例为例,如下,添加类别,成功后,校验添加参数以及添加成功后的属性,以及其他默认字段例如状态,排序等字段,源码如下:

// 添加类别的测试用例
@Test
@Transactional
@Rollback
public void success2addCategory() throws Exception {
    AddCategoryDto addCategoryDto = new AddCategoryDto();
    addCategoryDto.setName("服装");
    addCategoryDto.setLevel(1);
    addCategoryDto.setSort(1);
    Response<CategorySuccessVo> responseCategorySuccessVo = this.addCategory(addCategoryDto);
    CategorySuccessVo addParentCategorySuccessVo = responseCategorySuccessVo.getData();
    org.junit.Assert.assertNotNull(addParentCategorySuccessVo);
    org.junit.Assert.assertNotNull(addParentCategorySuccessVo.getId());
    org.junit.Assert.assertEquals(addParentCategorySuccessVo.getPid(), ROOT_PID);
    org.junit.Assert.assertEquals(addParentCategorySuccessVo.getStatus(), CategoryEnum.CATEGORY_STATUS_DOWN.getValue());
    org.junit.Assert.assertEquals(addParentCategorySuccessVo.getName(), addCategoryDto.getName());
    org.junit.Assert.assertEquals(addParentCategorySuccessVo.getLevel(), addCategoryDto.getLevel());
    org.junit.Assert.assertEquals(addParentCategorySuccessVo.getSort(), addCategoryDto.getSort());
}
// 新增类目,成功添加后,返回根据id查询CategorySuccessVo
public CategorySuccessVo add(AddCategoryDto addCategoryDto, UserContext userContext) {
    Category addingCategory = CategoryConverter.INSTANCE.add2Category(addCategoryDto);
    addingCategory.setStatus(CategoryEnum.CATEGORY_STATUS_DOWN.getValue());
    if (Objects.isNull(addCategoryDto.getLevel())) {
        addingCategory.setLevel(1);
    }
    if (Objects.isNull(addCategoryDto.getSort())) {
        addingCategory.setSort(100);
    }
    categoryDao.insert(addingCategory);
    return getCategorySuccessVo(addingCategory.getId());
}
也需要对添加类目的参数进行校验,例如,名称不能重复的校验,示例如下:
// 添加类目的入参
public class AddCategoryDto implements Serializable {
private static final long serialVersionUID = -4752897765723264858L;
// 名称不能为空,名称不能重复
@NotEmpty(message = CATEGORY_NAME_IS_EMPTY, groups = {ValidateGroup.First.class})
@EffectiveValue(shouldBeNull = true, message = CATEGORY_NAME_IS_DUPLICATE, serviceBean = NameOfCategoryForAddValidator.class, groups = {ValidateGroup.Second.class})
@ApiModelProperty(value = "类目名称", required = true)
private String name;
@ApiModelProperty(value = "类目层级")
private Integer level;
@ApiModelProperty(value = "排序")
private Integer sort;
}
//添加失败的校验校验测试用例
@Test
public void fail2addCategory() throws Exception {
    AddCategoryDto addCategoryDto = new AddCategoryDto();
    addCategoryDto.setName("服装");
    addCategoryDto.setLevel(1);
    addCategoryDto.setSort(1);
    // 名称为空
    addCategoryDto.setName(null);
    Response<CategorySuccessVo> errorResponse = this.addCategory(addCategoryDto);
    org.junit.Assert.assertNotNull(errorResponse);
    org.junit.Assert.assertNotNull(errorResponse.getMsg(), CATEGORY_NAME_IS_EMPTY);
    addCategoryDto.setName("服装");
    // 成功添加类目
    this.addCategory(addCategoryDto);
     // 名称重复
    errorResponse = this.addCategory(addCategoryDto);
    org.junit.Assert.assertNotNull(errorResponse);
    org.junit.Assert.assertNotNull(errorResponse.getMsg(), CATEGORY_NAME_IS_DUPLICATE);
}
相关文章
|
7月前
|
Java 开发工具
【Azure Storage Account】Java Code访问Storage Account File Share的上传和下载代码示例
本文介绍如何使用Java通过azure-storage-file-share SDK实现Azure文件共享的上传下载。包含依赖引入、客户端创建及完整示例代码,助你快速集成Azure File Share功能。
519 6
|
8月前
|
IDE Java 关系型数据库
Java 初学者学习路线(含代码示例)
本教程为Java初学者设计,涵盖基础语法、面向对象、集合、异常处理、文件操作、多线程、JDBC、Servlet及MyBatis等内容,每阶段配核心代码示例,强调动手实践,助你循序渐进掌握Java编程。
1102 3
|
8月前
|
安全 Java 应用服务中间件
Spring Boot + Java 21:内存减少 60%,启动速度提高 30% — 零代码
通过调整三个JVM和Spring Boot配置开关,无需重写代码即可显著优化Java应用性能:内存减少60%,启动速度提升30%。适用于所有在JVM上运行API的生产团队,低成本实现高效能。
1022 3
|
8月前
|
Java API 开发工具
【Azure Developer】Java代码实现获取Azure 资源的指标数据却报错 "invalid time interval input"
在使用 Java 调用虚拟机 API 获取指标数据时,因本地时区设置非 UTC,导致时间格式解析错误。解决方法是在代码中手动指定时区为 UTC,使用 `ZoneOffset.ofHours(0)` 并结合 `withOffsetSameInstant` 方法进行时区转换,从而避免因时区差异引发的时间格式问题。
388 4
|
9月前
|
人工智能 监控 安全
智慧工地解决方案,java智慧工地程序代码
智慧工地系统融合物联网、AI、大数据等技术,实现对施工现场“人、机、料、法、环”的全面智能监控与管理,提升安全、效率与决策水平。
269 2
|
10月前
|
Java 数据安全/隐私保护
快手小红书抖音留痕工具,自动留痕插件工具,java代码开源
这个框架包含三个核心模块:主操作类处理点赞评论、配置管理类和代理管理类。使用时需要配合
|
7月前
|
Java 数据处理 API
为什么你的Java代码应该多用Stream?从循环到声明式的思维转变
为什么你的Java代码应该多用Stream?从循环到声明式的思维转变
364 115
|
8月前
|
Java
java入门代码示例
本文介绍Java入门基础,包含Hello World、变量类型、条件判断、循环及方法定义等核心语法示例,帮助初学者快速掌握Java编程基本结构与逻辑。
652 0
|
7月前
|
安全 Java 编译器
为什么你的Java代码需要泛型?类型安全的艺术
为什么你的Java代码需要泛型?类型安全的艺术
266 98