中国特种做业人员备案考核是什么网站,网站防站,广州推广网站,装潢设计公司排行零、前言
书接上回#xff0c;我们搭建了windows下的开发环境#xff0c;并给出了一个hello world级别的多模块SpringBoot项目。 毕竟java后端开发#xff0c;离不开数据库的操作#xff0c;为方便后面内容的讲解#xff0c;这里再做一期铺垫#xff0c;core模块下新增一…零、前言
书接上回我们搭建了windows下的开发环境并给出了一个hello world级别的多模块SpringBoot项目。 毕竟java后端开发离不开数据库的操作为方便后面内容的讲解这里再做一期铺垫core模块下新增一个core-mpenhance模块该模块引入mybatis-plus并对其进行一定规范封装。基于该模块我们编写一些crud的业务代码。
一、软件安装
说到数据库操作windows端我一般会装2个软件。powerdesigner和workbench。
1.1 powerdesigner
powerdesigner是一个数据库设计软件下载地址可以去这位网友那里下载。关于powerdesigner的用法织法先前的博客有讲解。
2.2 workbench
可以去这里下载。为什么我不说navcat因为workbench是免费的而且满足使用需求何必再用需要破解的软件呢尤其是在上市公司和国企的小伙伴。
二、构思一个业务
本次笔记打算实现一个简化了的供应商货物管理系统。
2.1 供应商管理系统简介
在市场上商品的流通中有4个角色厂商、中间商、零售商、用户。我们现在要做的就是中间商用的一个管理系统。 中间商从厂商进货到自己的仓库然后通过自己的业务员把货物卖向一个个的零售商。
2.2 这次开发的核心系统
2.2.1 商品定义系统
定义一个个的商品商品包括名称商品单位转换比商品所属的大类型、小类型和品牌等。 这里比较不好理解的是商品的转换比我在这里多讲一些。 商品的单位类型有3种如下枚举所示
RequiredArgsConstructor
EnumDesc(name 商品单位类型,desc 商品单位类型,defaultIdx 2,defaultItem 大小单位)
public enum EItemUnitType {SINGLE_UNIT(1,单单位,只有一个小单位),NORMAL_UNIT(2,大小单位,有一个大单位有一个小单位),TRIPLE_UNIT(3,三单位,有一个大单位中单位小单位);EnumValueGetterprivate final int code;Getterprivate final String name;Getterprivate final String desc;
}比如最常见的NORMAL_UNIT就是一个大单位一个小单位比如1箱24袋箱就是大单位袋是小单位转换比是24. 在业务员录单时一般录入大单位价格。统计销售量也通常会统计大单位数量。 商品定义表设计如图所示
2.2.2 商品录单
该系统的核心业务业务员去门店销售商品。由于这里是学习贴会做很多简化先不去考虑预售等情况。 销售单一共3个类型如下枚举
RequiredArgsConstructor
EnumDesc(name 销售单类型,desc 销售单类型,defaultIdx 1,defaultItem 销售单)
public enum EConsignType {CONSIGN(1,销售单,销售单该单据从中间商出售商品到门店),RETURN(2,退货单,该单据从门店退货到中间商),EXCHANGE(3, 换货单,换货单门店把不好的货物退给中间商拿到一个新的货物相当于一个销售一个退货);EnumValueGetterprivate final int code;Getterprivate final String name;Getterprivate final String desc;
}商品录单的数据结构一个头表一个明细表。一个销售单可能包括多个商品的销售。数据库设计如图所示
2.2.3 库存数据
在门店的商品是有库存概念的。销售时销售数量不能超过库存。本次由于简化就不再引入进货单的概念了仅仅记录商品的库存和当前的成本价格。 商品的价格取决于每次进货时的数量与价格。这种价格叫移动加权平均。比如原先库存中100个大单位原价100元再进货100个大单位单价为150。此时该商品的成本价格为125每个大单位。 库存表设计如图所示
2.2.4 销售报表
销售报表中需要根据不同维度分组。如商品Id品牌门店业务员。也有复合分组门店商品。在查询时会有时间范围商品名、业务员名、门店等筛选条件。 销售报表的返回内容无非是销售的数量、销售额、成本、利润等信息。并且支持按某种维度排序。
三、代码结构
3.1 mp-enhence
在core模块下建一个mp-enhence的子模块用于放置mysql相关操作的库。 由于该业务既有增删改查又有报表相关的复杂连表分组查询我们ORM选用mybatis-plus。 该模块如图所示 由于该模块的实现在之前的博客中已经写过这里就不再赘述 芝法酱躺平攻略3—— 搭建基于mybatis-plus的开发框架 芝法酱躺平攻略4—— powerdesigner与mybatis-plus生成代码
3.2 代码结构的思考
大概讲一下代码位置的规划。分层设计虽然是一个看上去很干净的设计但在实际开发中不同模块间总会有相互关联很难把每个模块拆成一个个的微服务。通常情况下我们仅仅会针对大功能做服务划分。并且微服务的拆分其实完全可以体现在controller上基层的业务代码通过包引用方式引入。不同微服务仅仅是暴露不同的接口。 这样的情况下现在的实际开发中代码就会更偏向单体风格。然后有些程序员理解不深刻硬要把代码做成分层的风格比如所有的po在一个包内所有service在一个包内所有的mapper在一个包内。报表的mybatis的xml再放一个文件夹中。刚开始开发觉得没什么但当业务量膨胀后就会发现这样的结构写起代码十分痛苦。一个模块想找一下他相关的类简直远隔千山万水。同时也使得在实际执行时基础程序员倾向于写更少的类。一个结构体里包含多个功能的字段然后代码就成了屎山极难维护。 这里展示一下我们这次的代码结构 我们详细看一下报表系统的结构。我们可以看到entity写了好多分为requestresponsemapperInmapperOut。 这里一定不要嫌烦这种做法可以使代码结构清晰并且易于复用。mapper的入参出参一定要和controller的分开不然代码会变得很不好理解。那还不如全用Map传递了事。既然都用map传递了为什么不放弃Java改用python写代码呢 具体可以在最后的代码展示中来看。
3.3 mybatis-plus还是原生mybatis
对于一般单表的增删改查业务我更倾向于用mybatis-plus减少不必要的代码量快速开发。 而对于报表类的业务会牵扯大量表连接分组聚合函数等情况。这时使用原生的mybatis。
3.4 报表代码的复用性
由于该报表会有各个维度的分组查找不同维度的查找返回字段也不相同。如果针对每个函数写一套xml的sql那实在太麻烦了。毕竟mybatis支持动态sql。要不要查询商品相关信息如何分组等完全可以在mapper的入参中体现。 如何设置ConsignMapperIn这个代码可以放在Request的类里。由于多数设置如分页、时间范围等也是通用的所以可以写在Request的积累里。 我这里展示一部分代码供大家参考
BaseConsignReportRequest
public abstract class BaseConsignReportRequest extends BasePageRequest {Long itemId;String itemName;String mainType;String segmentType;String brand;Long customerId;String customerName;Long salesId;String salesName;LocalDateTime timeBegin;LocalDateTime timeEnd;EConsignReportOrderType consignOrderType;EOrderType orderType;public BaseConsignReportRequest(){timeBegin LocalDateTime.now().minusDays(3l);timeEnd LocalDateTime.now();consignOrderType EConsignReportOrderType.BILL_TIME;orderType EOrderType.DESC;}public ConsignMapperIn toMapperIn(){ConsignMapperIn consignMapperIn new ConsignMapperIn();consignMapperIn.setItemId(itemId);consignMapperIn.setItemName(itemName);consignMapperIn.setMainType(mainType);consignMapperIn.setSegmentType(segmentType);consignMapperIn.setBrand(brand);consignMapperIn.setCustomerId(customerId);consignMapperIn.setCustomerName(customerName);consignMapperIn.setSalesId(salesId);consignMapperIn.setSalesName(salesName);consignMapperIn.setConsignOrderType(consignOrderType);consignMapperIn.setOrderType(orderType);Instant instantBeg timeBegin.toInstant(ZoneOffset.UTC);consignMapperIn.setBillTimeBegin(instantBeg.getEpochSecond());Instant instantEnd timeEnd.toInstant(ZoneOffset.UTC);consignMapperIn.setBillTimeEnd(instantEnd.getEpochSecond());if(size 0){consignMapperIn.setPage(true);consignMapperIn.setOffset((current-1)*size);consignMapperIn.setLimit(size);}else{consignMapperIn.setPage(false);}consignMapperIn onConsignMapperInInit(consignMapperIn);return consignMapperIn;}protected abstract ConsignMapperIn onConsignMapperInInit(ConsignMapperIn pConsignMapperIn);}ConsignMapperIn
Data
public class ConsignMapperIn {String itemName;Long itemId;String mainType;String segmentType;String brand;Long customerId;String customerName;Long salesId;String salesName;Long billTimeBegin;Long billTimeEnd;boolean selectItemInfo;boolean selectCustomerInfo;boolean selectSalesInfo;boolean isGroup;ListString groups;boolean joinItem;boolean groupItem;EConsignReportOrderType consignOrderType;EOrderType orderType;boolean isPage;Integer offset;Integer limit;public ConsignMapperIn(){isGroup true;groups new ArrayListString();joinItem false;consignOrderType EConsignReportOrderType.BILL_TIME;orderType EOrderType.NONE;isPage true;selectItemInfo false;selectCustomerInfo false;selectSalesInfo false;}
}xml
?xml version1.0 encodingUTF-8 ?
!DOCTYPE mapperPUBLIC -//mybatis.org//DTD Mapper 3.0//ENhttps://mybatis.org/dtd/mybatis-3-mapper.dtd
mapper namespaceindi.zhifa.study2024.class002.busy.generalTest.business.report.consign.mapper.ConsignMappersql idselect_colSUM(CASE WHEN cd.sale_type 1 or cd.sale_type 0 THEN cd.cost ELSE 0 END) AS sale_cost,SUM(CASE WHEN cd.sale_type 1 THEN cd.amount ELSE 0 END) AS sale_amount,SUM(CASE WHEN cd.sale_type 2 THEN -cd.cost ELSE 0 END) AS return_cost,SUM(CASE WHEN cd.sale_type 2 THEN -cd.amount ELSE 0 END) AS return_amount,SUM(cd.amount) AS amount,SUM(cd.amount - cd.cost ) AS profit,SUM(cd.cost) AS cost,SUM(cd.amount - cd.cost ) / SUM(cd.amount) AS profit_ratioif testIn.groupItem,SUM(CASE WHEN cd.sale_type 1 or cd.sale_type 0 THEN cd.count ELSE 0 END) AS sale_cnt,SUM(CASE WHEN cd.sale_type 2 THEN -cd.count ELSE 0 END) AS return_cnt,SUM(cd.count) AS nest_cnt/ifif testnot In.isGroup,ch.id, ch.bill_no, ch.bill_type, ch.bill_time/ifif testIn.selectItemInfo,cd.item_id, cd.item_nameif testIn.joinItem,itm.unit_type,itm.small_unit_name,itm.mid_unit_name,itm.large_unit_name,itm.large_convert,itm.mid_convert,itm.main_type,itm.segment_type,itm.brand/if/ifif testIn.selectCustomerInfo,ch.customer_id,ch.customer_name/ifif testIn.selectSalesInfo,sales_id,sales_name/if/sqlselect idconsignReportresultTypeindi.zhifa.study2024.class002.busy.generalTest.business.report.consign.entity.mapperOut.ConsignMapperOutSELECT include refidselect_col/FROMconsign_head ch JOIN consign_detail_simple cd ON ch.id cd.bill_idif testIn.joinItemJOIN item_define itm ON cd.item_id itm.id/ifwherech.bill_time BETWEEN #{In.billTimeBegin} AND #{In.billTimeEnd}if testIn.itemName ! null and In.itemName ! AND cd.item_name like CONCAT(%, #{In.itemName}, %)/ifif testIn.itemId ! nullAND cd.item_id #{In.item_id}/ifif testIn.mainType ! null and In.mainTyp ! and In.joinItemAND itm.main_type #{In.mainTyp}/ifif testIn.segmentType ! null and In.segmentType ! and In.joinItemAND itm.segment_type #{In.segmentType}/ifif testIn.brand ! null and In.brand ! and In.joinItemAND itm.brand #{In.brand}/ifif testIn.customerId ! nullAND ch.customer_id #{In.customerId}/ifif testIn.customerName ! null and In.customerName ! AND ch.customer_name like CONCAT(%, #{In.customerName}, %)/ifif testIn.salesId ! nullAND ch.sales_id #{In.salesId}/ifif testIn.salesName ! null and In.salesName ! AND ch.sales_name #{In.salesName}/if/whereif testIn.isGroupGROUP BYforeach itemitem indexindex collectionIn.groups separator,${item}/foreach/ifif testIn.orderType.name() ! NONEORDER BY ${In.consignOrderType.getKey()} ${In.orderType.getKey()}/ifif testIn.isPageLIMIT #{In.offset},#{In.limit}/if/select
/mapper四、出现的一些问题
4.1 分组问题
按照我写的这个sql在不经任何设置时就会报error 1055的sql错误。 这时我们在workbench中先运行这个命令
SELECT sql_mode而后我们把返回值中的ONLY_FULL_GROUP_BY去掉 而后进入linux的/etc/mysql vim my.cnf 在最下面加这一段
sql_mode STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION4.2 mybatis的xml的位置配置
首先在pom的build标签中加入如下配置 该配置的目的是把mybatis的xml放到mybatis的目录下
buildresourcesresourcedirectorysrc/main/java/indi/zhifa/study2024/class002/busy/generalTest/business/report/directoryincludesinclude**/*.xml/include/includestargetPath${project.build.directory}/classes/mybatis/targetPath/resourceresourcedirectorysrc/main/resources/directoryincludesinclude**/*.*/include/includes/resource/resources/build点击编译观察target文件夹
在application.yml中加入如下配置
mybatis-plus:mapper-locations: classpath:mybatis/**/xml/*.xml这样一来我们就不需要把xml写到一个文件夹下可以和mapper放在一起。
4.3 打印sql
在mybatis-plus下加一段配置即可
mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl五、代码展示
我们这节属于过渡章节仅仅是不想让测试项目只有hello world显得太过单薄。所以代码讲解并不是本章重点。所以这里还是展示我的码云大家可以下载后观看。