芝法酱学习笔记(0.3)——SpringBoot下的增删改查

零、前言

书接上回,我们搭建了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,"三单位","有一个大单位,中单位,小单位");@EnumValue@Getterprivate 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, "换货单","换货单,门店把不好的货物退给中间商,拿到一个新的货物,相当于一个销售一个退货");@EnumValue@Getterprivate 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写了好多,分为request,response,mapperIn,mapperOut。
这里一定不要嫌烦,这种做法可以使代码结构清晰,并且易于复用。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;List<String> groups;boolean joinItem;boolean groupItem;EConsignReportOrderType consignOrderType;EOrderType orderType;boolean isPage;Integer offset;Integer limit;public ConsignMapperIn(){isGroup = true;groups = new ArrayList<String>();joinItem = false;consignOrderType = EConsignReportOrderType.BILL_TIME;orderType = EOrderType.NONE;isPage = true;selectItemInfo = false;selectCustomerInfo = false;selectSalesInfo = false;}
}

xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="indi.zhifa.study2024.class002.busy.generalTest.business.report.consign.mapper.ConsignMapper"><sql id="select_col">SUM(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_ratio<if test="In.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</if><if test="not In.isGroup">,ch.id, ch.bill_no, ch.bill_type, ch.bill_time</if><if test="In.selectItemInfo">,cd.item_id, cd.item_name<if test="In.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></if><if test="In.selectCustomerInfo">,ch.customer_id,ch.customer_name</if><if test="In.selectSalesInfo">,sales_id,sales_name</if></sql><select id="consignReport"resultType="indi.zhifa.study2024.class002.busy.generalTest.business.report.consign.entity.mapperOut.ConsignMapperOut">SELECT <include refid="select_col"/>FROMconsign_head ch JOIN consign_detail_simple cd ON ch.id = cd.bill_id<if test="In.joinItem">JOIN item_define itm ON cd.item_id = itm.id</if><where>ch.bill_time BETWEEN #{In.billTimeBegin} AND #{In.billTimeEnd}<if test="In.itemName != null and In.itemName != ''">AND cd.item_name like CONCAT('%', #{In.itemName}, '%')</if><if test="In.itemId != null">AND cd.item_id = #{In.item_id}</if><if test="In.mainType != null and In.mainTyp != '' and In.joinItem">AND itm.main_type = #{In.mainTyp}</if><if test="In.segmentType != null and In.segmentType != '' and In.joinItem">AND itm.segment_type = #{In.segmentType}</if><if test="In.brand != null and In.brand != '' and In.joinItem">AND itm.brand = #{In.brand}</if><if test="In.customerId != null">AND ch.customer_id = #{In.customerId}</if><if test="In.customerName != null and In.customerName != ''">AND ch.customer_name like CONCAT('%', #{In.customerName}, '%')</if><if test="In.salesId != null">AND ch.sales_id = #{In.salesId}</if><if test="In.salesName != null and In.salesName != ''">AND ch.sales_name = #{In.salesName}</if></where><if test="In.isGroup">GROUP BY<foreach item="item" index="index" collection="In.groups" separator=",">${item}</foreach></if><if test="In.orderType.name() != 'NONE'">ORDER BY ${In.consignOrderType.getKey()} ${In.orderType.getKey()}</if><if test="In.isPage">LIMIT #{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_SUBSTITUTION

4.2 mybatis的xml的位置配置

首先在pom的build标签中,加入如下配置:
该配置的目的,是把mybatis的xml,放到mybatis的目录下

<build><resources><resource><directory>src/main/java/indi/zhifa/study2024/class002/busy/generalTest/business/report</directory><includes><include>**/*.xml</include></includes><targetPath>${project.build.directory}/classes/mybatis</targetPath></resource><resource><directory>src/main/resources</directory><includes><include>**/*.*</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,显得太过单薄。所以代码讲解并不是本章重点。所以,这里还是展示我的码云,大家可以下载后观看。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/149586.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

洛汗2搬砖攻略:VMOS云手机一键搬砖辅助教程!

在《洛汗2》的世界中&#xff0c;玩家往往需要长时间刷怪、任务和升级&#xff0c;手动操作往往会耗费大量时间和精力。这时候&#xff0c;使用VMOS云手机来辅助游戏&#xff0c;将是一个极佳的选择。VMOS云手机专为《洛汗2》提供了专属定制版云手机&#xff0c;内置游戏安装包…

2.Spring-容器-注入

注册&#xff1a;将组件放入容器中&#xff1b; 注入&#xff1a;让容器按需进行操作&#xff1b; 一、Autowired&#xff1a;自动注入组件 原理&#xff1a;Spring调用容器的getBean 二、Qualifier 精确指定 精确指定&#xff1a;如果容器中组件存在多个&#xff0c;则使用…

【Linux】ubuntu 16.04 搭建jdk 11 环境(亲测可用)

目录 0.环境 1.题外话 2.详细 0.环境 windows11 主机 Virtual Box 7.0 ubuntu 16.04系统 想搭建个 jdk11的环境&#xff0c;用于项目 1.题外话 因为虚拟机与主机传输文件不方便&#xff0c;所以可以尝试用共享文件夹的方式传输&#xff0c;亲测可用&#xff0c;参考以下博…

LeetCode题练习与总结:二叉树的最近公共祖先--236

一、题目描述 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个节点 p、q&#xff0c;最近公共祖先表示为一个节点 x&#xff0c;满足 x 是 p、q 的祖先且 x 的深度尽可能大&#xff08;一个节点也…

windows 安装配置nginx

进入到对应的nginx的配置文件里 80的这个server块里 添加 location / { return 301 https://$host$request_uri; # 301 永久重定向到 HTTPS } (如果是http重定向https&#xff0c;其他的可以删了 只保留截图中的内容) https的server块里面添加 证书的…

SLM7888兼容FAN7888—— 低压三相半桥驱动的理想之选

SLM7888系列型号&#xff1a; SLM7888CH&#xff1a;SOP20W SLM7888MD&#xff1a;TSSOP20 SLM7888是一款高压、高速的功率MOSFET和IGBT驱动器&#xff0c;它提供三个独立的高边、低边输出驱动信号便于用于三相电路。采用专有的高压集成电路和锁存免疫CMOS技术&…

二分查找算法(5) _山脉数组的峰顶索引

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 二分查找算法(5) _山脉数组的峰顶索引 收录于专栏【经典算法练习】 本专栏旨在分享学习算法的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c;…

< 微积分Calculus >

微积分 微分是把整体分拆为小部分来求它怎样改变 积分是把小部分连接在一起来求整体有多大&#xff0c;可以用来求面积、体积、中点和很多其他有用的东西。 lim极限 函数f(x) -> Q(x) y&#xff1a;x变量&#xff0c;f函数&#xff0c;Q(x)函数体&#xff08;多项式&am…

Centos下安装Maven(无坑版)

Linux 安装 Maven Maven 压缩包下载与解压 华为云下载源&#xff0c;自行选择版本 下面的示例使用的是 3.8.1 版本 wget https://repo.huaweicloud.com/apache/maven/maven-3/3.8.1/binaries/apache-maven-3.8.1-bin.tar.gz解压 tar -zxvf apache-maven-3.8.1-bin.tar.gz移…

Centos安装helm

Helm 是查找、分享和使用软件构建 Kubernetes 的最优方式。 两种安装方式&#xff0c;二进制安装、脚本安装。脚本安装服务器在下载安装包可能会下载失败。 脚本安装 官网提供了脚本安装 $ curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/sc…

清华大学开源 CogVideoX-5B-I2V 模型,以支持图生视频

CogVideoX 是源于清影的开源视频生成模型。 下表列出了我们在此版本中提供的视频生成模型的相关信息。 Model NameCogVideoX-2BCogVideoX-5BCogVideoX-5B-I2V (This Repository)Model DescriptionEntry-level model, balancing compatibility. Low cost for running and second…

基于Nginx搭建点播直播服务器

实现直播和点播离不开服务器⽀持&#xff0c;可以使用开源的NGINX服务器搭建直播和点播服务。 当然&#xff0c;NGINX本身是不⽀持视频的&#xff0c;需要为NGINX增加相应的RTMP模块进行支持。 1、下载nginx和rtmp模块 # nginx wget ht tp://nginx.org/download/nginx-1.18.…

Nginx反向代理简介,作用及配置;Nginx负载均衡简介,作用及配置;

一&#xff0c;Nginx反向代理 1.1简介 反向代理服务器位于用户与目标服务器之间&#xff0c;但是对于用户而言&#xff0c;反向代理服务器就相当于目标服务器&#xff0c;即用户直接访问反向代理服务器就可以获得目标服务器的资源。同时&#xff0c;用户不需要知道目标服务器的…

认知杂谈77《简单:通往高手的技巧》

内容摘要&#xff1a;          在信息爆炸、关系复杂的时代&#xff0c;简单是复杂背后的真谛。简单如“112”&#xff0c;是智慧的朴素呈现。简单有强大力量&#xff0c;像清泉般纯净&#xff0c;如“我爱你”简单却有力&#xff0c;基础财务知识也体现其在理财中的作…

java项目开发Spring框架

简化开发、框架整合、节约成本&#xff1b;官网网址&#xff1a;http://spring.io 耦合度高。 IOC 对象外部引入 1.导入Spring坐标 <dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-orm</artifactId…

2024全国研究生数学建模竞赛(数学建模研赛)ABCDEF题深度建模+全解全析+完整文章

全国研究生数学建模竞赛&#xff08;数学建模研赛&#xff09;于9月21日8时正式开赛&#xff0c;赛程4天半&#xff0c;咱这边会在开赛后第一时间给出对今年的6道赛题的评价、分析和解答。包括ABCDEF题深度建模全解全析完整文章&#xff0c;详情可以点击底部的卡片来获取哦。 …

Zookeeper+消息队列(kafka)

目录 一、Zookeeper概述 1、Zookeeper概念 2、Zookeeper工作机制 3、Zookeeper数据结构 4、Zookeeper 应用场景 5、Zookeeper 选举机制 5.1、第一次启动选举机制 5.2、非第一次启动选举机制 二、部署 Zookeeper 集群 1、部署环境 2、安装 zookeeper 软件 3、设置主…

【第十八章:Sentosa_DSML社区版-机器学习之协同过滤】

【第十八章&#xff1a;Sentosa_DSML社区版-机器学习之协同过滤】 1.算子介绍 协同过滤是推荐系统中常用的一种方法。该算法旨在填补用户-产品关联矩阵中缺少的项。在算法中&#xff0c;用户和产品都是通过一组少量的潜在因素描述&#xff0c;这些潜在因素可以用于预测用户-产…

JavaWeb--纯小白笔记06:使用Idea创建Web项目,Servlet生命周期,注解,中文乱码解决

使用Idea创建一个web项目----详细步骤配置&#xff0c;传送门&#xff1a;http://t.csdnimg.cn/RsOs7 src&#xff1a;放class文件 web&#xff1a;放html文件 out&#xff1a;运行过后产生的文件 一创建一个新的web项目(配置好了后)&#xff1a; 在src创建一个文件…

AI辅助编码工具如何影响着程序员开发群体

AI辅助编码工具的出现对程序员开发群体产生了深远的影响&#xff0c;有一些初步基础的程序员&#xff0c;可以借助AI工具的加持&#xff0c;生产效率大大提升&#xff0c;达到中高级程序员的水平。 这些影响可以从多个角度来分析&#xff1a; 提高开发效率&#xff1a; AI工具…