controller层(控制器层):
作用:负责输出和输入,接收前端数据,把结果返回给前端。
1.处理用户请求,接收用户参数
2.调用service层处理业务,返回响应
service层(业务逻辑层):
1.封装业务逻辑,为controller层提供服务
2.调用dao层或mapper层处理数据。
3.总控,所有事物的切入点
dao层(数据访问层):
1.主要职责是对数据库进行增删改查,不包含业务逻辑,直接与数据库交互。
ps(传统是:dao层和service层的代码是功能性的,是一个个函数,service去调用dao层,bo是作为值在传递,只有get和set方法)。
(满血模型,service变成了协调者。把service层一个方法中的代码切割成bo对象中若干个方法。把代码块分得更小。更自然容易理解重用性提高。)
dao层负责生成bo对象,bo对象用于承接service层分配的任务。
mapper层:
主要职责:数据存储或获取数据。
mapper层负责访问具体数据。
ps1:mapper层和controller层和面向对象没太多关系。主要战场是在service和dao层。
ps2:oomall是六边形体系结构,沿用了mvc体系结构,强调领域模型(bo),应用(service),尽量要让领域模型部分变得肥大,尽量把service和dao层代码放到bo中,把尽可能多的代码放到其中。
颜色的具体含义:
白色存在mysql数据库,蓝色表示从其它部分拿到的,黄色表示存在mongo数据库,绿色部分是全用java写的代码。
调用逻辑:
用户请求发送到controller层,controller层去调用service层,service层调用dao层或mapper层,从数据库返回数据。
dto(data transfer object)数据传输对象:【controller层】
用于服务层之间数据传输。
包含数据对象,主要是变量定义和get、set方法。
dto可以封装需要传输的数据。
vo(view object)视图对象:【controller层】
用于controller层和前端数据传输。
同样包含数据对象,主要是变量定义和get、set方法。
在controller层将数据传递给前端展示。
bo(business object)业务对象:【dao层】
封装业务逻辑中的数据。
包含业务逻辑。
包含业务逻辑的数据以及与这些数据相关的业务方法。
po(persistent object)持久化对象:【mapper层】
用于表示数据库中的一条记录,与数据库表结构对应
po通常与数据库表中字段一一对应
一般用在mapper层或dao层与数据库交互
oomall项目特点:包含业务逻辑的bo对象写在了dao层中,称之为满血模型。
3.1 创建者
对象A由谁创建。
如果B包含A(整体和局部关系,如组合关系。比如上级地区创建下级地区。),B记录A,B用到A,B有A的初始化数据,则由对象B创建对象A。
3.2 信息专家
问题:分配职责给对象的基本原则是什么?
“知者为之”谁具备完成职责所需的信息,就由谁来承担职责。
比如:api的分配
3.3 模板方法设计模式
模板方法是定义在抽象类中的、把基本操作方法组合在一起形成一个总算法或总行为的方法。
基本方法是实现算法各个步骤的方法。
写在父类中的填空题,子类不同会给空填上不同内容。
3.4 策略模式
定义一系列算法,把它们封装在对象内部,并且可以任意替换。
最好在变化点使用,为了将系统和变化点松耦合,将变化点从代码中提出。用多态方式实现不同的子类,以便日后增加新算法。
老师例子:shop模块的打包算法。用策略模式把会变化的点抽离出来,用了低耦合和多态,让其满足依赖倒置原则和Liskov可替换原则,使得算法部分满足面向对象的终极目标开闭原则。
3.5 桥接器模式
将问题分解成独立的两个部分,任意组合形成最后的解决方案。
1.开闭原则
对于扩展开放,对修改关闭。
每一次的修改不会动到已有的代码,不会影响已有代码,已经写好的代码都不用动,想扩展,不需要去动其它代码。
不论新增还是修改影响面都很小,修改只波及到一小部分代码。
抽象(新设一个抽象接口,把所有的接口都统一到该接口上)、约束(多态),封装、变化(间接)
判断点和演进点,可能会修改的地方,使用开闭原则。
2.Liskov可替换原则
继承必须确保超类所拥有的性质在子类中仍然成立(父类和子类)。
这意味着去overwrite父类的方法时,不能去随意overwrite,必须要让父类具备的性质在子类中依然成立。
不能违背父类中的性质,父类有的性质在子类中依然有,子类可以加入自己的性质。
满足Liskov替换原则必然满足开闭原则。
继承是耦合度很高的行为,Liskov替换原则要求继承不能修改父类的行为。
3.依赖倒置原则(激进)
面向接口编程,不面向实现编程。
高层模块不应该依赖低层模块,两者都应该依赖其抽象。抽象不应该依赖细节,细节应该依赖抽象。(人说的话就是要做接口,依赖接口)。
简单来说就是:只调用接口,不去调用具体的实现。
满足依赖倒置原则必然满足Liskov可替换原则:因为接口里只定义了方法,没有定义任何的实现。
4.单一职责原则(激进)
一个类有且仅有一个引起它变化的原因,否则类应该被拆分。
职责:每一个业务的方法,把职责分配给一个对象。
5.接口隔离原则(激进)
客户端不应该被迫依赖于它不使用的方法。(人话说:接口不能对应有它不需要的方法,要把接口拆分为多个小接口,然后逐个依赖,直到形成原子性的,完美的配对)
6.迪米特原则(激进)
不跟“陌生人”说话,只与你的直接朋友交谈。
如果两个软件实体无需直接通信,就不应该发生直接的相互调用,可以通过第三方转发该调用。
7.合成复用原则(激进)
优先考虑组合或聚合等关联关系来实现,其次才考虑继承关系实现。
4.1 使用模式:
创建者+信息专家
4.2 知识点:
— 缓存只在service和dao对象中做,bo对象不做缓存。
— 缓存不存相同东西。
— 动态模型是面向对象的血和肉,动态模型是顺序图,静态模型是类图。
— exception分支不会测到,所有分支都要测到。从功能角度去测试。
4.3 阅读源码:
— 1.如何创建对象。2.如何增和查(createregion,findbyid)。3.看dao层代码如何缓存(regionservice,region(getparentregion,getancestor,changestatus))。
— 要看javaee缓存的知识
4.4 模块结构:
controller【dtp】【vo】
dao【bo】
mapper【po】
4.5 流程图分析:
1、2、3、8是面向功能的,因为此时还没有对象。
service层职责分配,如何把要实现的功能交给不同部分去做。service外面向功能,service内面向对象。增删改等第1步要做的是变出对象。
以后只画从service层开始到dao层结束。
dao层提供对象。
loop循环10代表10级行政区,
4.1 使用模式:
工厂方法+适配器模式
4.2 知识点:
— 做判断时机的标准是,如果当前已有信息足够进行判断,则进行判断。
— 如果报红要编译一下compile
— redis的操作是在core/mapper/redisUtil下
— openfeign借用了controller中所有的注解,定义了自身去调其它平台的接口,用restful风格。
— controller是定义了别人来调我们应该提供一个什么样的restful风格。
— 多态:共性的放在父类里,个性的放在子类里。(有子类的类是父类,父类有子类共性的属性)。
— 值对象:不是满血对象,只有值。
4.3 阅读源码:
— 1.阅读一下登录授权的代码,关注AOP。2.dao层channeldao的代码。
— 要看javaee微服务的知识。
4.4 模块结构:
config:
controller:【dto】【vo】
dao:【bo】【channel】
mapper:【generator】【manual】【openfeign】
service:
channel是支付渠道。
generator是自动生成的代码,mannual是自己写的代码。
openfeign(基于restful的api)是外部接口。
4.5 流程图分析:
第3步创建者,第6步adaptor是一个适配器接口,满足间接和多态,满足可替换达成开闭,遵循依赖倒置,不满足接口隔离(所有接口合在一起),第5步取出的是满血对象。
4.1 使用模式:
策略模式+桥接器模式
4.2 知识点:
— 当要求插入数据不重复时,可以直接利用mysql的唯一索引的特性。
— 商铺和模板原本是一对多,现在对象模型变成一对一,只记默认模板计算运费。
— 父类白色,子类黄色,把对象一劈两半,一般存mysql,一半存mongo,拿到运费模板要看到底下所有地区,nosql只支持主键查询功能,不能直接看到运费模板下所有主键,要在mysql中记录nosql中所有的主键,索性把共用属性也放到mysql中。nosql的好处是能将数据量搞到最大。
— 订单模块订单的数量级很大,所以在订单要用mongo,在大数量的前提下,用mongo的查询和插入速度会快。
4.3 阅读源码:
1.RegionTemplateDao中的insert函数。weightTemplateDao的insert函数。
4.4 模块结构:
config
controller【dto】【vo】:
dao【bo】【openfeign】【template】
mapper【openfeign】【po】
service【listener】
service中listener中存放的是MQ的内容。bo对象分为两个包,白色在根目录底下,黄色在template底下,绿色在divide底下;和mongo有关的放在template下,无关的放在根目录下。openfeign因为有调region模块,写在mapper下。
4.5 流程图分析:
用了多态的方法去支持两种模板类型,分为运费模板和重量模板,定义了虚拟的接口:
4.1 使用模式:
4.2 知识点:
— 打折优惠活动是变化点和演进点。
— 缓存了Category、Product、Onsale、各类Activity以及它们的关系。
4.3 阅读源码:
1.javaee布隆过滤器
4.4 模块结构:
config
controller【dto】【vo】
dao【activity】【bo】【onsale】【openfeign】
mapper【jpa】【mongo】【openfeign】【po】
model【strategy】
service【listener】
4.5 流程图分析:
对1来说,设置了一个OnSaleExecutor接口,使得调用Product和OnSale时通过接口来访问,满足依赖倒置原则,进而满足Liskov可替换原则,进而满足开闭原则。Excutor不同,会得到不同的Onsale对象。
对2来说,ProductFactory是从ProductDao中的build方法抽离出来,形成一个独立的抽象类,对于底下不同的Excutor,在做build的时候,放入的excute不一样,做成不同的子类,build出不同的executor。运用了多态的设计模式。
运用了工厂方法的设计模式,SpecOnSaleProductFactory和ValidSaleProductFactory和NoOnSaleProductFactory是工程,然后SpecOnSaleExecutor和ValidOnSaleExecutor和NoOnSaleExecutor是产品。
一、写代码思路:
1.先理清选做部分,各个模块间的调用关系。
2.从比较简单和孤立的部分开始。
二、学习心得:
— 面向对象一定要新建对象。
— region模块的集成测试可以作为案例进行学习。
— 用mysql 10秒400个请求已经严重阻塞,用redis进行缓存第1次速度会慢,后面会快。bo、po理论上都可以存,但redis一般只存bo,而且是存贫血bo,仅仅存属性,关系也不存,存dto是最快的,但不能存dto,因为属性改名了不能及时反应。
— 蓝色是调其它模块的api,黄色是mongodb,白色是mysql,绿色是redis。
三、注解:
1.@PathVariable用于将URL中的变量绑定到控制器处理方法的参数上。
2.@RequestParam用于将请求参数映射到控制器方法的参数上。
3. @Repository注解表明这个接口是一个Spring Data Repository,即一个持久层的抽象,用于访问数据源中的数据。
4. @RestController是一个特殊的控制器注解,表明这个类是一个控制器,并且返回的数据都将自动被转换为JSON或XML等格式,并写入HttpResponse中。
5.@RequestMapping用于定义请求和控制器方法之间的映射关系,可以作用在类或方法上,指定请求的URL、HTTP方法、请求参数、头部等信息。可以将一个HTTP GET请求映射到一个特定的处理方法。
6.@Autowired用于自动注入Spring容器管理的bean。当在字段、构造器、方法上使用@Autowired时,Spring将会在容器中查找并自动提供相匹配的bean实例。‘’
7.@GetMapping用于处理HTTP的GET请求,对应一个处理GET请求的方法。
四、redis语句:
redisUtil.hasKey(参数):检查Redis中是否存在指定的键,返回一个布尔值表示是否存在。
redisUtil.decr(参数):对指定的键对应的值进行减1操作,并返回减1后的值。
redisUtil.set(参数):设置指定的键值对到Redis中,可以设置键的过期时间。
redisUtil.get(参数):从Redis中获取指定键对应的值。
redisUtil.del(参数):从Redis中删除指定的键。
五、本机配置redis和nacos
1. redis在windows中的启动路径:
先输入redis-server redis.windows.conf,
然后重新开启页面redis-server --service-install redis.windows.conf
开启服务器:redis-server --service-start
进入redis服务器:redis-cli.exe
2. nacos在windows中的启动路径:E:\nacos\Nacos\distribution\target\nacos-server-2.3.0-BETA\nacos\bin,输入startup.cmd -m standalone启动服务器。
探究点1:dto、vo、bo、po、数据库字段,实体类,之间有什么关联??
发现如下几条规律:
1.在属性的完整性和规模上看:PO>BO>DTO>VO(Po对象含有dto、vo、bo对象的几乎所有属性。规模最大。)
2.推测后面新增的属性服务于业务的需要。
探究点2:controller、service、dao、mapper层返回的一般是什么?
之前:
controller -> ReturnObject(封装类)
service -> Dto对象
dao-> Customer(Bo对象)
mapper->Po对象
现在
controller -> ReturnObject(封装类)
service -> Bo对象
测试心得:
1.测试测的是测试方法中有用到的类,覆盖率看的是进入了多少方法,执行了多少代码
2.覆盖率看的是占比,提高覆盖率方法有2,1.删除无关的变量,get set方法等,甚至注解。2.编写更多测试用例。
测试语法:
1.这行代码使用Mockito框架模拟了一个条件。当调用redisUtil
这个模拟对象的hasKey
方法,并且传入任意的字符串时,模拟返回结果是false
。
Mockito.when(redisUtil.set(Mockito.anyString(), Mockito.any(), Mockito.anyLong())).thenReturn(true);
知识点1:Mockito.anyString()用于匹配字符型参数的值。Mockito.any()用于匹配任何参数的类型和值。Mockito.anyLong()用于匹配长整型型参数的值。
知识点2:.thenReturn(true)是Mockito语法的一部分,它定义了当匹配到模拟条件时应该返回的值。
2.这里定义了一个JSON格式的字符串,它代表了创建客户时传入的数据,包含用户名、密码、名字和手机号。
String body = "{\"userName\":\"699275\",\"password\":\"123456\",\"name\":\"test\",\"mobile\":\"12345678900\"}";
知识点3:反斜杠(\)是转义字符,用于允许引号字符(")出现在字符串字面量中。在JSON中,所有的字符串都需要用双引号包围,而Java字符串字面量也以双引号定义,所以需要用反斜杠来转义内部的双引号,以防止它们被解释为字符串的终结符。
3.使用MockMvc对象执行一个HTTP POST请求。/customer
代表了请求的URL。
this.mockMvc.perform(MockMvcRequestBuilders.post("/customer");
知识点4:this.mockMvc.perform是Spring MVC测试框架的一部分,用于执行一个模拟的HTTP请求。
知识点5:MockMvcRequestBuilders.post是用来构建一个HTTP POST请求的方法。
4.设置一个预期条件,使用JSON路径表达式检查返回的JSON对象中errno
字段的值是否与ReturnNo.CUSTOMER_NAMEEXIST.getErrNo()
返回的值相同。
.andExpect(MockMvcResultMatchers.jsonPath("$.errno", is(ReturnNo.CUSTOMER_NAMEEXIST.getErrNo())))
MockMvcResultMatchers.content()用于验证HTTP响应的内容。测试方法中使用.andExpect
结合MockMvcResultMatchers
来声明对响应的期望,如果响应不符合这些期望,测试将会失败。
知识点6:.content中用于装之前定义String类型的body中的字节流,会以方法体的形式传输。
.content(body.getBytes(StandardCharsets.UTF_8))
测试样例如下:
@Test
public void updateCustomerById1() throws Exception{
Mockito.when(redisUtil.hasKey(Mockito.anyString())).thenReturn(false);
Mockito.when(redisUtil.set(Mockito.anyString(), Mockito.any(), Mockito.anyLong())).thenReturn(true);
String body = "{\"name\":\"赵永波\",\"mobile\":\"13159235541\"}";
this.mockMvc.perform(MockMvcRequestBuilders.post("/customers")
.header("authorization", customerToken)
.content(body.getBytes(StandardCharsets.UTF_8))
.contentType(MediaType.APPLICATION_JSON_VALUE))
.andExpect(MockMvcResultMatchers.content().contentType("application/json;charset=UTF-8"))
.andDo(MockMvcResultHandlers.print())
.andReturn().getResponse().getContentAsString();
}
读代码心得:
1.看层次,由外向内,controller->service->dao->mapper
2.看传入的参数类型
3.看返回参数的类型
4.看调用内一层的方法
5.看业务逻辑
画时序图心得:
1.生命线框的长度要注意,service层生命线框最长,要等所有后续步骤执行完才能结束。
2.方框里customer:Customer,前者是对象,后者是实体类。
3.箭头上写的是后面一层中的方法,意思是前者调用后者中的方法。
Spring Data JPA数据库访问接口心得:
CustomerPoMapper
接口继承自JpaRepository
,这意味着继承了一系列的标准CRUD操作,并且可以通过声明方法名的方式来自定义查询。
findByUserName(String userName)
:
findBy
告诉Spring Data JPA这是一个查询方法。UserName
是实体类CustomerPo
的一个属性(这里假设有userName
字段),Spring Data JPA会按照这个字段进行查找。String userName
是查询条件的值。SELECT * FROM customer_po WHERE user_name = ?
。findByUserNameAndMobile(String userName, String mobile)
:
findBy
同上。UserNameAndMobile
表示要根据userName
和mobile
两个字段进行查询。SELECT * FROM customer_po WHERE user_name = ? AND mobile = ?
。数据库与实体类命名心得:
Java实体类中,通常采用驼峰命名法(CamelCase),其中每个单词的首字母大写,且没有下划线。例如,creatorId
和creatorName
。
在数据库中,下划线命名法(snake_case),其中单词之间使用下划线分隔,例如 creator_id
和 creator_name
。
原因是:大多数数据库对大小写不敏感,下划线命名可以提高数据库的可读性。
可以通过注解来指定实体类属性(Java中的字段)和数据库表列之间的映射关系。
@Column(name = "creator_id") // 指定数据库列名为 creator_id
private Long creatorId;
导入数据库和表的技巧:
1.打开mysql
2.source .sql文件
非空处理心得:
Optional
是Java中的一个返回值类型,其中 Optional
是一个容器对象,用于包含非空对象。使用 Optional
作为返回值意味着当你调用一个返回此类型的方法时,你将得到一个 Optional
对象。调用者可以使用诸如 isPresent()
, isEmpty()
, get()
, orElse()
等方法来检查值是否存在,并且在不同的情况下执行相应的操作。 get()
方法是获取Optional容器里面的CustomerPo对象。
cloneFactory心得:
1.删掉@Data注解
2.实体类手写get set方法
3.@CopyFrom(CustomerPo.class)