day01

HttpServlet中的service方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

//获取http request的method参数,其实就是html的form标签
//中method属性对应的字符串
String method = req.getMethod();
long errMsg;
//判断请求方式
if(method.equals("GET")) {
//获取最后被修改时间
errMsg = this.getLastModified(req);
if(errMsg == -1L) {
/**如果servlet不支持http request header的if-modified-since属性
* 则继续处理
**/
this.doGet(req, resp);
} else {
//如果支持这个属性
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader("If-Modified-Since");
} catch (IllegalArgumentException var9) {
ifModifiedSince = -1L;
}
/**
* 如果客户端的文件最后修改时间和服务器端的文件最后修改时间一致则返回304不需要修改状态
* 这样服务器就不返回html,浏览器读取本地缓存文件,否则重新获取服务器端的对应html文件
**/
if(ifModifiedSince < errMsg / 1000L * 1000L) {
this.maybeSetLastModified(resp, errMsg);
this.doGet(req, resp);
} else {
resp.setStatus(304);
}
}
} else if(method.equals("HEAD")) {
errMsg = this.getLastModified(req);
this.maybeSetLastModified(resp, errMsg);
this.doHead(req, resp);
} else if(method.equals("POST")) {
this.doPost(req, resp);
} else if(method.equals("PUT")) {
this.doPut(req, resp);
} else if(method.equals("DELETE")) {
this.doDelete(req, resp);
} else if(method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if(method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
//如果请求不是以上的所有请求方式,该方法就会响应501错误,也就是不支持这种请求
String errMsg1 = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{method};
errMsg1 = MessageFormat.format(errMsg1, errArgs);
resp.sendError(501, errMsg1);
}

}

// 把ServletRequest和Response转成Http请求和响应
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
try {
request = (HttpServletRequest)req;
response = (HttpServletResponse)res;
} catch (ClassCastException var6) {
throw new ServletException("non-HTTP request or response");
}
this.service(request, response);
}

tips : HEAD: 与GET类似,但服务器在响应中只返回HTTP头部。
Options: 允许客户端查看服务器支持的HTTP方法。
Trace: 回显服务器接收到的请求。

java后端中的响应式编程

响应式编程的基石-响应流

  • 响应流必须无阻塞
  • 响应流必须是一个数据流
  • 必须可以异步执行
  • 处理背压(生产者可以感受到消费者反馈的消费压力,并根据压力进行动态调整生产速率)

Publiher

由于响应流的特点,我们不能再返回一个简单的POJO对象来表示结果了。必须返回一个类似Java中的Future的概念,在有结果可用时通知消费者进行消费响应。

Reactive Stream规范中这种被定义为Publisher<T>Publisher<T>是一个可以提供0-N个序列元素的提供者,并根据其订阅者Subscriber<? super T>的需求推送元素。一个Publisher<T>可以支持多个订阅者,并可以根据订阅者的逻辑进行推送序列元素。下面这个Excel计算就能说明一些Publisher<T>的特点。

alt text

A1-A9就可以看做Publisher<T>及其提供的元素序列。A10-A13分别是求和函数SUM(A1:A9)、平均函数AVERAGE(A1:A9)、最大值函数MAX(A1:A9)、最小值函数MIN(A1:A9),可以看作订阅者Subscriber

Flux和Mono的处理方式

网关的GlobalExcetionHandler

tips : ServerWebExchange是Spring WebFlux框架中用于表示HTTP请求和响应的接口。ServerWebExchange接口提供了一组方法来访问HTTP请求和响应的相关信息,包括请求头、请求体、响应头和响应体等。它还提供了一些方法来操作请求和响应,例如设置或获取请求头、请求体、响应头和响应体等。

OKHttpClient

OKHttp是一个高效的HTTP客户端,具有以下默认特性:

  • 支持HTTP/2,允许所有同一个主机地址的请求共享同一个socket连接
  • 连接池减少请求延时
  • 透明的GZIP压缩减少响应数据的大小
  • 缓存响应内容,避免一些完全重复的请求

配置的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Configuration
public class RestTemplateConfig {
@Resource
private HttpConfig httpConfig;

/**
* 没有实例化RestTemplate时,初始化RestTemplate
* @return
*/
@ConditionalOnMissingBean(RestTemplate.class)
@Bean
public RestTemplate restTemplate(){
return new RestTemplate(getClientHttpRequestFactory());
}

/**
* 使用OkHttpClient作为底层客户端
* @return
*/
private ClientHttpRequestFactory getClientHttpRequestFactory(){
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(httpConfig.getConnTimeout(), TimeUnit.SECONDS)
.writeTimeout(httpConfig.getWriteTimeout(), TimeUnit.SECONDS)
.readTimeout(httpConfig.getReadTimeout(), TimeUnit.SECONDS)
.sslSocketFactory(SSLSocketConfig.getSSLSocketFactory(), SSLSocketConfig.getX509TrustManager())
.hostnameVerifier(SSLSocketConfig.getHostnameVerifier())
.build();
return new OkHttp3ClientHttpRequestFactory(okHttpClient);
}
}

day02

MySQL相关查询语句

1
2
3
4
desc table_name; --查看表结构

ALTER TABLE your_table_name
MODIFY COLUMN id INT NOT NULL FIRST; --将ID列移动到第一行

实体类映射工具MapStruct

tips : MapStruct的注解也是Mapper,此时导入的包是org.mapstruct.Mapper,而非Mybatis的包。

在后端开发中一般会将实体类经过转换后(DO->RespVO(DTO))才返回给前端,前端提交过来的对象也需要经过转换Entity实体才做存储;通常使用的BeanUtils.copyProperties方法也比较粗暴,不仅效率低下(使用反射)而且仅映射相同名的属性,多数情况下还需要手动编写对应的转换方法实现。

插件MapStruct以接口方法结合注解优雅实现对象转换,MapStruct生成器生成代码以更贴近原生的Setter、Getter方法处理属性映射更为高效。

简单用例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Mapper
public interface UserConvert {
UserConvert INSTANCE = Mappers.getMapper(UserConvert.class);
@Mapping(source = "name", target = "userName") //这个注解不写也可以,只有当字段名称不一样的时候需要手动写
@Mapping(target = "username", ignore = true) //表示忽略字段
@Mapping(target = "createdAt", expression = "java(java.time.LocalDateTime.now())") //也可以映射常量
UserVO toVO(User entity);
}

// 使用示例
@Test
public void contextLoads() {
User user = new User(0, "Tester", 1, "上海市徐汇区");
UserVO userVO = UserConvert.INSTANCE.toVO(user);
}

linux开机自启动jar包的流程

1
2
3
4
5
6
crontab -e

#在文件中添加以下内容,表示开机后100秒后执行后面的命令
#其中/home/dmx/dmxStartup.sh是执行启动jar包的shell脚本

@reboot sleep 100 && /home/dmx/dmxStartup.sh

day03

Redis相关知识

引入Lua脚本的目的

  1. 保证原子性
    在 Redis 中,所有的 Lua 脚本是作为一个原子操作执行的。换句话说,在脚本执行期间,Redis 不会处理其他客户端的命令。
  2. 减少网络延迟
    Redis 是一个基于客户端-服务器的系统,每个命令都需要客户端与服务器之间进行一次通信。如果一个复杂的操作需要多个命令,频繁的网络通信会导致延迟。通过 Lua 脚本,可以将多个命令合并到一个脚本中发送给服务器,减少网络往返次数,从而提高性能。
  3. 执行复杂逻辑和操作灵活性
    Lua 脚本提供了一种在 Redis 服务器端编写复杂逻辑的方法,允许在执行 Redis 命令时加入条件判断、循环等控制结构。

在SpringBoot项目中使用Lua脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;
static {
UNLOCK_SCRIPT = new DefaultRedisScript<>();
UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));
UNLOCK_SCRIPT.setResultType(Long.class);
}

public void unLock() {
//使用lua脚本保证操作原子性
stringRedisTemplate.execute(UNLOCK_SCRIPT
, Collections.singletonList(KET_PREFIX+name)
,ID_PREFIX+Thread.currentThread().getId());
}

Redis的key分类形式

alt text

day04

tips : @TableId注解默认是自增的

和下列配置有关:

1
2
3
4
5
6
7
8
9
10
11
12
mybatis-plus:
configuration:
map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。
global-config:
db-config:
id-type: NONE # “智能”模式,基于 IdTypeEnvironmentPostProcessor + 数据源的类型,自动适配成 AUTO、INPUT 模式。
# id-type: AUTO # 自增 ID,适合 MySQL 等直接自增的数据库
# id-type: INPUT # 用户输入 ID,适合 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库
# id-type: ASSIGN_ID # 分配 ID,默认使用雪花算法。注意,Oracle、PostgreSQL、Kingbase、DB2、H2 数据库时,需要去除实体类上的 @KeySequence 注解
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
type-aliases-package: ${travel.info.base-package}.dal.dataobject # travel.info.base-package在dal(mysql)包下面

day05

tips: MybatisX快速生成接口、实体类以及xml的操作

在后端项目中,先建库还是先建表

  • 如果你的项目规模较大、数据库设计较为复杂,可以先设计 MySQL 数据表,确保数据结构清晰、稳定。
  • 如果你正在开发一个相对灵活或敏捷的项目,且使用了 ORM 工具,则可以先创建实体类,通过代码生成数据库表。
    对于很多现代 Java 后端项目,开发者往往选择先创建实体类,然后通过 ORM 工具自动生成数据库表的方式,灵活应对业务变化,并且可以减少维护工作。

根据实体类自动建表的方案(JPA)

在连接数据库的时候会自动创建,(其实感觉写完实体类,让chatgpt帮你写下sql建表也行。)

1.导入JPA依赖与配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!--MySQL依赖 用于jdbc连接-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>

<!--jpa依赖 自动建表-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<!--mybatis依赖 用于操作数据库的crud,可以不用到,若是业务需要可以改为mybatis-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>

JPA配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--配置数据库连接与jpa的yml配置-->

spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_plus?userSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
password: root
username: root
jpa:
database: mysql
show-sql: true
hibernate:
ddl-auto: update
#create----每次运行该程序,没有表格会新建表格,表内有数据会清空
#create-drop----每次程序结束的时候会清空表
#update----每次运行程序,没有表格会新建表格,表内有数据不会清空,只会更新
#validate----运行程序会校验数据与数据库的字段类型是否相同,不同会报错
dialect: org.hibernate.dialect.MySQL5InnoDBDialect

2.创建实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Data
@Entity
@Table(name = "demo_user")
@TableName("demo_user")
public class User {
@TableId(type = IdType.AUTO)
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "userid",columnDefinition = "int comment '主键ID'")
private Integer userId;
@Column(name = "name")
private String name;
@Column(name = "age")
private Integer age;
@Column(name = "email")
private String email;

@Column(name = "phone")
private String phone;
}

3. 注解属性

alt text

建表时候的要求

  1. 表名:见名知意,如user_name;
  2. 大小写:尽量都使用小写字母;
  3. 分隔符:尽量不使用空格和特殊字符,数据库名、表名和列名之间用下划线分隔;
  4. 字段名称:见名知意,尽量避免用不容易理解的缩写或拼音,统一字段,比如状态都用status
  5. 普通索引和联合索引用ix_前缀,唯一索引用ux_前缀。
  6. 字段类型:根据业务尽量选占用存储小的字段类型
  7. 字段长度:varchar和char类型长度为字符长度,其余类型代表的都是字节长度。
  8. 字段个数:每张表不超过20个字段,复杂的拆成多张表。
  9. NOT NULL:在定义字段时,尽量明确该字段是否Not Null。因为在innodb中,需要额外空间存储NULL值;其次NULL值可能会导致索引失效;最后NULL值只能用is null或者is not null判断,“==”判断永远返回False
  10. 外键:互联网系统中一般不设置外键。
  11. 时间字段:优先使用datetime字段,其保存时间范围更大。
  12. 索引:在经常查询的字段上建立索引,同时也考虑索引的数量和长度,避免影响性能,单表索引数量不超过5个
  13. 唯一索引:使用频率很高,一般在不怎么更新的字段上加唯一索引(比如部门、机构等),创建唯一索引时,相关字段不能出现NULL值,不然唯一索引会失效。
  14. 大字段:如用户评论等,不建议使用text、blob类型(定义VarChar类型比较合理),可以考虑拆表或者存储在其他地方。

唯一索引

唯一索引特点

  • 唯一索引的值必须是唯一的,不允许重复。
  • 唯一索引可以提高数据的访问速度,因为Mysql会对唯一索引进行优化。
  • 唯一索引可以通过UNIQUE INDEX关键词在表中指定,也可以在创建表之后使用ALTER TABLE语句添加唯一索引。

唯一索引使用场景

唯一索引适合用于需要保证数据列唯一性的情况,例如用户表中的邮箱字段。唯一索引可以避免插入重复的数据,保证数据的一致性和准确性。