开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

Go语言 go-redis与watch redis 数据库连

发表于 2021-11-12

对redis 命令很熟悉的 可以直接跳过,因为这个库的api 命名基本上都和命令一一对应上了,很容易记

redis 数据库连接

github.com/go-redis/re…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
go复制代码var rdb *redis.Client
var ctx = context.Background()

func initClient() (err error) {
rdb = redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
DB: 0,
})
_, err = rdb.Ping(ctx).Result()
if err != nil {
return err
}
return nil
}

初学者要注意 外部声明了一个 rdb, init方法里面 一定得是rdb= 千万别写成rdb:=了 否则rdb就是个局部变量

外部引用就是nil了

另外还有redis的哨兵模式以及集群模式 的两种连接方法 这里就不演示了。有需要的可以自行查询

基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
go复制代码
func redisDemo() {
err := rdb.Set(ctx, "score", 100, 0).Err()
if err != nil {
panic(err)
}
val, err := rdb.Get(ctx, "score").Result()
if err != nil {
panic(err)
}
fmt.Println("score:", val)

val2, err := rdb.Get(ctx, "keytest").Result()
if err == redis.Nil {
// 这里主要是看这个key不存在的判定方法就可以了
fmt.Println("keytest does not exist")
} else if err != nil {
fmt.Println("get keytest failed")
} else {
fmt.Println("keytest:", val2)
}

}

总体上 redis的操作 还是挺简单的,可以自行探索,直接在goland中.一下就能看到对应的api
这里就不再一一演示了

redis-类似排行榜的操作

这个例子会比上面的例子稍微复杂一点,很多网站的类似的排行榜的操作 其实就是个zset, 写法就是这:

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
go复制代码func redisDemo2() {
zsetkey := "language_rank"
languages := []*redis.Z{
{Score: 90, Member: "java"},
{Score: 80, Member: "go"},
{Score: 70, Member: "js"},
{Score: 60, Member: "rust"},
{Score: 50, Member: "c++"},
}

num, err := rdb.ZAdd(ctx, zsetkey, languages...).Result()
if err != nil {
panic(err)
}
fmt.Println("num:", num)
// 增加值
newScore, err := rdb.ZIncrBy(ctx, zsetkey, 10, "go").Result()
if err != nil {
panic(err)
}
fmt.Println("newScore:", newScore)

// 取分数最高的3个
ret := rdb.ZRevRangeWithScores(ctx, zsetkey, 0, 2).Val()
for _, z := range ret {
fmt.Println("name:", z.Member,
" score:", z.Score)
}

// 取分数在一定范围之内的
op := redis.ZRangeBy{
Min: "80",
Max: "110",
}

ret = rdb.ZRangeByScoreWithScores(ctx, zsetkey, &op).Val()
for _, z := range ret {
fmt.Println(z.Member," ",z.Score)
}

}

pipeline

pipeline 主要就是网络优化,可以节省rtt,并不是事务,千万别搞错了,比如你要执行3个命令,那你正常操作就是需要3个rtt的网络时间,但是你可以把这3个 放到一个pipeline里面执行 那就只需要1个rtt的网络时间即可

同一时间有大量命令要执行的时候 就可以用这个pipeline了

pipeline也不是万能的,比如后面的操作依赖前面的操作的时候 就不适合了,例如我们要取一个值,然后根据这个值
来决定 set一个新的值,这2个操作 set操作 就依赖前面的get操作了

这种场景是没办法用pipeline的

事务 TxPipeline

redis是单线程的,单个命令是原子操作,但是来自不同客户端的命令是可以依次执行的,这个时候 我们就需利用
TxPipeline来确保我们的2个命令之间 不会有来自其他客户端的命令插入进来。

watch

这种场景也是常用的之一,比如我们下单抢购显卡,显卡现在这么少,你怎么确保用户下单的那一刻一定有库存呢?
那其实就是你下单的时候 watch一下库存的这个key,如果发现下单的过程中这个库存被crud了 那就直接返回呗
操作失败

注意key是可以传多个的

举个例子吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
go复制代码func watchDemo() {
key := "watch_count"
err := rdb.Watch(ctx, func(tx *redis.Tx) error {
n, err := tx.Get(ctx, key).Int64()
if err != nil && err != redis.Nil {
return err
}
_, err = tx.TxPipelined(ctx, func(pipeliner redis.Pipeliner) error {
pipeliner.Set(ctx, key, n+1, 0)
return nil
})
return err
}, key)

if err != nil {
fmt.Println("tx exec failed:",err)
return
}
fmt.Println("tx exec success")
}

这个函数单独执行是可以的,没问题

image.png

那如果我稍微改一下,在这个函数里面 sleep 几秒钟

然后在这个期间 我在redis-cli里面 去修改一下这个值 看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
go复制代码func watchDemo() {
key := "watch_count"
err := rdb.Watch(ctx, func(tx *redis.Tx) error {
n, err := tx.Get(ctx, key).Int()
if err != nil && err != redis.Nil {
return err
}
_, err = tx.TxPipelined(ctx, func(pipeliner redis.Pipeliner) error {
time.Sleep(5 * time.Second)
pipeliner.Set(ctx, key, n+1, 0)
return nil
})
return err
}, key)

if err != nil {
fmt.Println("tx exec failed:", err)
return
}
fmt.Println("tx exec success")
}

注意在这个时候 我们在事务的执行过程中 加了一个time sleep的操作 以保证我们可以有充足的时间 在redis-cli里面 set一下这个key 结果也是显而易见 这次操作肯定是失败的

注意,这段关于watch的代码 网上很多都是tx.Pipelined 这个是不对的,千万别被这个误导了,tx.Pipelined里面执行的不是事务 所以代码在这里是会失效的,一定得是 tx.TxPipelined 才行

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

人人都会设计模式:建造者模式 先上一个例子 再来一个例子 最

发表于 2021-11-12

「这是我参与11月更文挑战的第11天,活动详情查看:2021最后一次更文挑战」

本文被《从小工到专家的 Java 进阶之旅》收录。

你好,我是看山。

之前说过了单例模式,这周想说说建造者模式,它是另外一个比较常用的创建型设计模式。

每种设计模式的出现,都是为了解决一些编程不够优雅的问题,建造者模式也是这样。

维基百科解释是:建造者模式,Builder Pattern,又名生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。

先上一个例子

借用并改造下《Effective Java》中给出的例子:每种食品包装上都会有一个营养成分表,每份的含量、每罐的含量、每份卡路里、脂肪、碳水化合物、钠等,还可能会有其他N种可选数据,大多数产品的某几个成分都有值,该如何定义营养成分这个类呢?

重叠构造器

因为有多个参数,有必填、有选填,最先想到的就是定义多个有参构造器:第一个构造器只有必传参数,第二个构造器在第一个基础上加一个可选参数,第三个加两个,以此类推,直到最后一个包含所有参数,这种写法称为重叠构造器,有点像叠罗汉。还有一种常见写法是只写一个构造函数,包含所有参数。

代码如下:

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
java复制代码public class Nutrition {
private int servingSize;// required
private int servings;// required
private int calories;// optional
private int fat;// optional
private int sodium;// optional
private int carbohydrate;// optional

public Nutrition(final int servingSize, final int servings) {
this(servingSize, servings, 0, 0, 0, 0);
}

public Nutrition(final int servingSize, final int servings, final int calories) {
this(servingSize, servings, calories, 0, 0, 0);
}

public Nutrition(final int servingSize, final int servings, final int calories, final int fat) {
this(servingSize, servings, calories, fat, 0, 0);
}

public Nutrition(final int servingSize, final int servings, final int calories, final int fat, final int sodium) {
this(servingSize, servings, calories, fat, sodium, 0);
}

public Nutrition(final int servingSize, final int servings, final int calories, final int fat, final int sodium, final int carbohydrate) {
this.servingSize = servingSize;
this.servings = servings;
this.calories = calories;
this.fat = fat;
this.sodium = sodium;
this.carbohydrate = carbohydrate;
}

// getter
}

这种写法还可以有效解决参数校验,只要在构造器中加入参数校验就可以了。

如果想要初始化实例,只需要 new一下就行: newNutrition(100,50,0,35,0,10)。这种写法,不够优雅的地方是,当 calories和 sodium值为0的时候,也需要在构造函数中明确定义是0,示例中才6个参数,也能勉强接受。但是如果参数达到20个呢?可选参数中只有一个值不是0或空,写起来很好玩了,满屏全是0和null的混合体。

还有一个隐藏缺点,那就是如果同类型参数比较多,比如上面这个例子,都是 int类型,除非每次创建实例的时候仔细对比方法签名,否则很容易传错参数,而且这种错误编辑器检查不出来,只有在运行时会出现各种诡异错误,排错的时候不知道要薅掉多少根头发了。

想要解决上面两个问题,不难想到,可以通过set方法一个个赋值就行了。

set方式赋值

既然构造函数中放太多参数不够优雅,还有缺点,那就换种写法,构造函数只保留必要字段,其他参数的赋值都用setter方法就行了。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码public class Nutrition {
private final int servingSize;// required
private final int servings;// required
private int calories;// optional
private int fat;// optional
private int sodium;// optional
private int carbohydrate;// optional

public Nutrition(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}

// getter and setter
}

这样就可以解决构造函数参数太多、容易传错参数的问题,只在需要的时候 set指定参数就行了。

如果没有特殊需求,到这里可以解决大部分问题了。

但是需求总是多变的,总会有类似“五彩斑斓的黑”这种奇葩要求:

  1. 如果必填参数比较多,或者大部分参数是必填参数。这个时候这种方式又会出现重叠构造器那些缺点。
  2. 如果把所有参数都用set方法赋值,那又没有办法进行必填项的校验。
  3. 如果非必填参数之间有关联关系,比如上面例子中,脂肪 fat和碳水化合物 carbohydrate有值的话,卡路里 calories一定不会为0。但是使用现在这种设计思路,属性之间的依赖关系或者约束条件的校验逻辑就没有地方定义了。
  4. 如果想要把 Nutrition定义成不可变对象的话,就不能使用 set方法修改属性值。

这个时候就该祭出今天的主角了。

建造者模式

先上代码

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
java复制代码public class Nutrition {
private int servingSize;// required
private int servings;// required
private int calories;// optional
private int fat;// optional
private int sodium;// optional
private int carbohydrate;// optional

public static class Builder {
private final int servingSize;// required
private final int servings;// required
private int calories;// optional
private int fat;// optional
private int sodium;// optional
private int carbohydrate;// optional

public Builder(final int servingSize, final int servings) {
this.servingSize = servingSize;
this.servings = servings;
}

public Builder setCalories(final int calories) {
this.calories = calories;
return this;
}

public Builder setFat(final int fat) {
this.fat = fat;
return this;
}

public Builder setSodium(final int sodium) {
this.sodium = sodium;
return this;
}

public Builder setCarbohydrate(final int carbohydrate) {
this.carbohydrate = carbohydrate;
return this;
}

public Nutrition build() {
// 这里定义依赖关系或者约束条件的校验逻辑
return new Nutrition(this);
}
}

private Nutrition(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}

// getter
}

想要创建对象,只要调用 newNutrition.Builder(100,50).setFat(35).setCarbohydrate(10).build()就可以了。这种方式兼具前两种方式的优点:

  • 能够毫无歧义且明确 set指定属性的值;
  • 在 build方法或 Nutrition构造函数中定义校验方法,可以在创建对象过程中完成校验。

建造者模式的缺点就是代码变多了(好像所有的设计模式都有这个问题),这个缺点可以借助 lombok来解决,通过注解 @Builder,可以在编译过程自动生成对象的 Builder类,相当省事。

再来一个例子

接下来分析下《大话设计模式》中的一个例子,这个例子从代码结构上,和建造者模式有很大的出入,但是作者却把它归为建造者模式。下面我们就来看看究竟:现在需要画个小人,一个小人需要头、身体、左手、右手、左脚、右脚。

代码如下:

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
java复制代码public class Person {
private String head;
private String body;
private String leftHand;
private String rightHand;
private String leftLeg;
private String rightLeg;

// getter/setter
}

public class PersonBuilder {
private Person person = new Person();

public PersonBuilder buildHead() {
person.setHead("头");
return this;
}

public PersonBuilder buildBody() {
person.setBody("身体");
return this;
}

public PersonBuilder buildLeftHand() {
person.setLeftHand("左手");
return this;
}

public PersonBuilder buildRightHand() {
person.setRightHand("右手");
return this;
}

public PersonBuilder buildLeftLeg() {
person.setLeftLeg("左腿");
return this;
}

public PersonBuilder buildRightLeg() {
person.setRightLeg("右腿");
return this;
}

public Person getResult() {
return this.person;
}
}

但是,如果有个方法忘记调用了,比如画右手的方法忘记调用了,那就成杨过大侠了。这个时候就需要在 PersonBuilder之上加一个 Director类,俗称监工。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码public class PersonDirector {
private final PersonBuilder pb;

public PersonDirector(final PersonBuilder pb) {
this.pb = pb;
}

public Person createPerson() {
this.pb
.buildHead()
.buildBody()
.buildLeftHand()
.buildRightHand()
.buildLeftLeg()
.buildRightLeg();
return this.pb.getResult();
}
}

这个时候,对于客户端来说,只需要关注 Director类就行了,就相当于在客户端调用构造器之间,增加一个监工,一个对接人,保证客户端能够正确使用 Builder类。

细心的朋友可能会发现,我这里的 Director类的构造函数增加了一个 Builder参数,这是为了更好的扩展,比如,这个时候需要增加一个胖子 Builder类,那就只需要定义一个 FatPersonBuilder,继承 PersonBuilder,然后只需要将新增加的类传入 Director的构造函数即可。

这也是建造者模式的另一个优点:可以定义不同的 Builder类实现不同的构建属性,比如上面的普通人和胖子两个 Builder类。

最后来个总结

有的朋友会说,这两个例子结构差别很大,怎么能是同一个模式?

那我们来看看官方给出的建造者模式的类图:

图片

这样结构就比较清晰了,两个例子都包含 Product类和 Builder类(或子类),区别是,第一个例子对象的完整性操作交给了客户端,第二个例子由 Director类保障对象的完整。

我们来看看建造者的本质:构建状态完整、结构复杂的对象。

  • 结构复杂:如果只有几个属性,通过构造函数就能实现,只有属性多了,结构复杂,建造者模式才能体现价值,建议属性值超过6个时使用建造者模式。
  • 状态完整:状态是否完整可以通过客户端或 Director来管理,不会出现因为忘记调用 set或其他方法,是对象少定义一个属性。

从某种意义上说,建造者模式是为了弥补构造函数的不足出现的,主要优点是下面3项:

  1. 将一个复杂对象的创建过程封装起来,向客户端隐藏产品内部表现
  2. 允许对象通过多个步骤来创建,并可以改变过程
  3. 产品的实现可以变换,因为客户端只能看到一个抽象的接口

建造者模式作为一种比较实用的设计模式,应用场景主要是下面两个:

  • 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时
  • 当构造过程必须允许被构造的对象有不同的表示时

碰到上面两种情况,不要犹豫,果断使用建造者模式就行。

因为疫情的影响,今年的招聘和应聘变得与往年大不一样,原本金三银四的时间可能会延长到5、6月份,这样大家可以有更多的时间准备。但是也有一个不利于应聘者的情况是,随着大家准备的时间延长,基本功是否扎实就会使面试者的表现拉开距离,设计模式就是基本功中的一种。所以准备在最近开启“面向面试之设计模式”的系列,希望能够夯实自己基础的同时,帮助到更多的人。

推荐阅读

  • 单例模式
  • 建造者模式

本文被《Java 进阶》专栏收录。


你好,我是看山。游于码界,戏享人生。如果文章对您有帮助,请点赞、收藏、关注。欢迎关注公众号「看山的小屋」,发现不一样的世界。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Spring Boot(八):JUnit5 🧨

发表于 2021-11-12

「这是我参与11月更文挑战的第4天,活动详情查看:2021最后一次更文挑战」。

1

  1. JUnit 5 变化

⭐Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库

作为最新版本的 JUnit 框架,JUnit5 与之前版本的 JUnit 框架有很多的不同,由三个不同子项目的几个不同模块组成:

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

JUnit Platform: JUnit Platform 是在 JVM 上启动测试框架的基础,不仅支持 JUnit 自制的测试引擎,其他猜测是引擎也都可以接入。

JUnit Jupiter: JUnit Jupiter 提供了 JUnit 5 的鑫编程模型,是 JUnit 5 新特性的核心。内部包含了一个测试引擎,用于在 JUnit Platform 上运行。

JUnit Vintage: 由于 JUint 已经发展多年,为了照顾老的项目,JUnit Vintage 提供了兼容 JUnit4.x, Junit3.x 的测试引擎。

注意:

  • Spring Boot 2.4 以上版本移除了默认对 Vintage 的依赖。如果需要兼容 junit4 需要自行引入(不能使用 junit4 的功能 @Test)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
xml复制代码<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<!--移除Vintage依赖,不兼容JUnit4-->
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

1.1 JUnit4 & JUnit5

以前 JUnit4 的使用:

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
java复制代码import entity.Book;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
* @Author Author_Unknown
* @Date 2021/11/10 7:17
* @Description JUnit4使用注解的测试
*/
@RunWith(SpringJUnit4ClassRunner.class) // 指定测试框架的版本
@ContextConfiguration("classpath:bean1.xml")
public class JTest4 {

@Autowired
private Book book;

@Test
public void testBook() {
book.testAttr();
}

}

现在 JUnit5 的使用:

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
java复制代码import entity.Book;
//import org.junit.Test; // 不能在单一类型的导入中定义(即使用JUnit5就不能同时使用JUnit4)
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

/**
* @Author Author_Unknown
* @Date 2021/11/10 7:29
* @Description JUnit5的测试
*/
//@ExtendWith(SpringExtension.class) // (1)
//@ContextConfiguration("classpath:bean1.xml") // (2)
@SpringJUnitConfig(locations = "classpath:bean1.xml") // 等价于 (1) + (2)
public class JTest5 {

@Autowired
private Book book;

@Test
public void test() {
System.out.println(book);
}
}
  1. JUnit 5 的常用注解

JUnit 5 的注解与 JUnit 4 的注解有所变化:junit.org/junit5/docs…

  • @Test:表示方法是测试方法,但与 JUnit4 的 @Test 不同,它的职责非常单一,不能声明任何属性,拓展的测试将会由 Jupiter 提供额外测试
  • @ParameterizedTest:表示方法是参数化测试,下文介绍
  • @RepeatedTest:表示方法可重复执行,下文介绍
  • @DisplayName:为测试类或者测试方法设置展示名称
  • @BeforeEach:表示在每个单元测试之前执行
  • @AfterEach:表示在每个单元测试之后执行
  • @BeforeAll:表示在所有单元测试之前执行
  • @AfterAll:表示在所有单元测试之后执行
  • @Tag:表示单元测试类别,类似于 JUnit4 中的 @Categories
  • @Disabled:表示测试类或测试方法不执行,类似于 JUnit4 中的 @Ignore
  • @Timeout:表示测试方法运行如果超过指定时间将会返回错误
  • @ExtendWith:为测试类或测试方法提供扩展类引用

2.1 @RepeatedTest

1
2
3
4
5
6
7
8
9
java复制代码@SpringBootTest
class SpringBoot_Junit5 {

@Test
@RepeatedTest(3)
void test() {
System.out.println("@RepeatedTest..");
}
}

2.2 @DisplayName

1
2
3
4
5
6
7
8
9
10
11
java复制代码@DisplayName("JUnit5测试类")
@SpringBootTest
class SpringBoot_Junit5 {

@Test
@DisplayName("JUnit5测试方法")
void contextLoads() {
System.out.println("Say something..");
}

}

2.3 @BeforeEach & @AfterEach

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
java复制代码@DisplayName("JUnit5测试类")
@SpringBootTest
class SpringBoot_Junit5 {

@BeforeEach
public void beforeEachMethod() {
System.out.println("@BeforeEach..");
}

@AfterEach
public void afterEachMethod() {
System.out.println("@AfterEach..");
}

@Test
@DisplayName("JUnit5测试方法①")
void contextLoads() {
System.out.println("Say something..");
}

@Test
@DisplayName("JUnit5测试方法②")
void test2() {
System.out.println("Say what?");
}

}

2.4 @BeforeAll & @AfterAll

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
java复制代码@DisplayName("JUnit5测试类")
@SpringBootTest
class SpringBoot_Junit5 {

@BeforeEach
void beforeEachMethod() {
System.out.println("@BeforeEach..");
}

@AfterEach
void afterEachMethod() {
System.out.println("@AfterEach..");
}

@BeforeAll
static void beforeAll() {
System.out.println("@BeforeAll..");
System.out.println("============");
}

@AfterAll
static void afterAll() {
System.out.println("============");
System.out.println("@AfterAll..");
}

@Test
@DisplayName("JUnit5测试方法①")
void contextLoads() {
System.out.println("Say something..");
}

@Test
@DisplayName("JUnit5测试方法②")
void test2() {
System.out.println("Say what?");
}

}

2.5 @Disabled

1
2
3
4
5
6
java复制代码@Test
@DisplayName("JUnit5测试方法②")
@Disabled
void test2() {
System.out.println("Say what?");
}

2.6 @Timeout

@Timeout 规定超时时间,超过指定时间后会抛出异常。

1
2
3
4
5
6
7
java复制代码@Test
@DisplayName("JUnit5测试方法①")
@Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
void contextLoads() throws InterruptedException {
Thread.sleep(550);
System.out.println("Say something..");
}

  1. 断言机制

断言(Assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是 org.junit.jupiter.api.Assertions 的静态方法。

JUnit 5 内置的断言可以分成如下几个类别:

  • 检查业务逻辑返回的数据是否合理
  • 所有的测试运行结束以后,会有一个详细的测试报告

3.1 简单断言

用来对单个值进行简单的验证,如:

方法 说明
assertEquals 判断两个对象或两个原始类型是否相等
assertNotEquals 判断两个对象或两个原始类型是否不相等
assertSame 判断两个对象引用是否指向同一个对象
assertNotSame 判断两个对象引用是否指向不同的对象
assertTrue 判断给定的布尔值是否为 true
assertFalse 判断给定的布尔值是否为 false
assertNull 判断给定的对象引用是否为 null
assertNotNull 判断给定的对象引用是否不为 null

3.1.1 assertEquals() & assertNotEquals()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码@SpringBootTest
@DisplayName("测试断言机制")
public class SpringBoot_Assert {

int sum(int i, int j) {
return i + j;
}

@Test
@DisplayName("测试简单断言1:assertEquals")
void testSimpleAssertions1() {
int cal = sum(1, 5);
// 报错
Assertions.assertEquals(5, cal);
// Nothing happen
Assertions.assertNotEquals(6, cal, "sum()不符合预期、");
}

}

3.1.2 assertSame() & assertNotSame()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码@SpringBootTest
@DisplayName("测试断言机制")
public class SpringBoot_Assert {
@Test
@DisplayName("测试简单断言2:assertSame")
void testSimpleAssertions2() {
Object obj1 = new Object();
Object obj2 = new Object();
Assertions.assertNotSame(obj1, obj2);

Assertions.assertSame(obj1, obj2, "obj1!=obj2");

}
}

3.1.3 assertTrue/False() & assertNull/NotNull()

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
java复制代码@SpringBootTest
@DisplayName("测试断言机制")
public class SpringBoot_Assert {

boolean returnFlag() {
return false;
}

@Test
@DisplayName("测试简单断言3:assertTrue、assertNull")
void testSimpleAssertions() {
Object obj = null;

/**
* org.opentest4j.AssertionFailedError: 不为True ==>
* 期望:true
* 实际 :false
*/
Assertions.assertTrue(returnFlag(), "不为True");

/**
* org.opentest4j.AssertionFailedError: 着实为Null ==> expected: not <null>
*/
Assertions.assertNotNull(obj, "着实为Null");
}
}

3.2 数组断言

通过 assertArrayEquals 方法来判断两个对象或原始类型的数组是否相等。

1
2
3
4
5
java复制代码@Test
@DisplayName("测试数组数组断言:assertArrayEquals")
void testAssertArrayEquals() {
Assertions.assertArrayEquals(new int[]{1, 2, 3}, new int[]{3, 1, 2});
}

3.3 组合断言

assertAll 方法接受多个 org.junit.jupiter.api.Executable 函数式接口的实例作为要验证的断言,可以通过 lambda 表达式很容易的提供这些断言。

3.4 异常断言

在 JUnit4 时期,想要测试方法的异常情况时,需要用 @Rule 注解的 ExpectedException 变量还是比较麻烦的。而 JUnit5 提供了一种新的断言方式 Assertions.assertThrows(),配合函数式编程就可以进行使用。

1
2
3
4
5
java复制代码@Test
@DisplayName("异常断言:assertThrows")
void testAssertThrows() {
Assertions.assertThrows(ArithmeticException.class, () -> System.out.println(1 / 0));
}

3.5 超时断言

Junit5 还提供了 Assertions.assertTimeout() 为测试方法设置了超时时间。

1
2
3
4
5
java复制代码@Test
@DisplayName("超时异常:assertTimeout")
void testAssertTimeout() {
Assertions.assertTimeout(Duration.ofMillis(500), () -> Thread.sleep(1000));
}

3.6 快速失败

通过 fail 方法直接使得测试失败。

1
2
3
4
5
6
7
java复制代码@Test
@DisplayName("快速失败:fail")
void testFail() {
if (1 == 2) {
Assertions.fail("This should fail...");
}
}

  1. 前置条件

JUnit 5 中的前置条件类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止。

1
2
3
4
5
6
7
8
9
10
java复制代码@SpringBootTest
public class SpringBoot_Sumptions {
@Test
void testSumptions() {
Assumptions.assumeTrue(false, "false..");

System.out.println("后续..");
}

}

并没有打印 “后续..” 语句且无报错:

  1. 嵌套测试

JUnit 5 可以通过 Java 中的内部类和 @Nested 注解实现嵌套测试,从而可以更好地将相关的测试方法组织在一起。在内部类中可以使用 @BeforeEach 和 @AfterEach 注解,而且嵌套的层次没有限制。

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
java复制代码@SpringBootTest
@DisplayName("嵌套测试")
public class SpringBoot_Nested {

Stack<Object> stack;

@Test
void testNested1() {
System.out.println("Do nothing...");

// org.opentest4j.AssertionFailedError: stack is null.. ==> expected: not <null>
// Assertions.assertNotNull(stack, "stack is null..");
}

@Nested // @Nested不适用于方法
@DisplayName("When new")
class whenNew {
@BeforeEach
void initialize() {
stack = new Stack<>();
}

@Test
void testNested2() {
Assertions.assertNotNull(stack, "stack is null..");
}

@Test
@DisplayName("throws EmptyStackException when popped")
void throwsExceptionWhenPopped() {
Assertions.assertThrows(EmptyStackException.class, stack::pop);
}

@Nested
@DisplayName("after pushing an element")
class afterPushing {
String anElement = "an element";

@BeforeEach
void pushAnElement() {
stack.push(anElement);
}

@Test
@DisplayName("returns the element when popped and then stack is empty")
void returnElementWhenPopped() {
Assertions.assertEquals(anElement, stack.pop());
Assertions.assertTrue(stack.isEmpty());
}
}

}

}
  1. 参数化测试

参数化测试是 JUnit5 很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利。

利用 @ValueSource 等注解指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。

  • @ValueSource:为参数化测试指定入参来源,支持八大基础类以及 String 类型,Class 类型
  • @NullSource:表示为参数化测试提供一个 null 的入参
  • @EnumSource:表示为参数化测试提供一个枚举入参
  • @CsvFileSource:表示读取指定 CSV 文件内容作为参数化测试入参
  • @MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)

当然如果参数化测试仅仅只能做到指定普通的入参还达不到令人惊艳的地步。

⭐它真正的强大之处的地方在于可以支持外部的各类入参。如:CSV,YML,JSON 文件甚至方法的返回值也可以作为入参。只需要去实现 ArgumentsProvider 接口,任何外部文件都可以作为它的入参。

6.1 @ValueSource & @ParameterizedTest

1
2
3
4
5
6
7
8
9
10
11
java复制代码@SpringBootTest
public class SpringBoot_ParameterTest {

@ParameterizedTest
@DisplayName("参数化测试1: @ValueSource")
@ValueSource(ints = {1, 2, 3, 4, 5})
void testParameters1(int i) {
System.out.println(i);
}

}

6.2 @MethodSource & @ParameterizeTest

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码@SpringBootTest
public class SpringBoot_ParameterTest {

// 必须是static静态方法~
String[] parameterProvider() {
return new String[]{"hello", "world", "!"};
}

@ParameterizedTest
@DisplayName("参数化测试2: @MethodSource")
@MethodSource("parameterProvider") // 指定方法名
void testParameters2(String arr) {
System.out.println(arr);
}

}

  1. JUnit 迁移指南

在进行迁移的时候需要注意如下的变化:

  • 注解在 org.junit.jupiter.api 包中,断言在 org.junit.jupiter.api.Assertions 类中,前置条件在 org.junit.jupiter.api.Assumptions 类中
  • 把 @Before 和 @After 替换成 @BeforeEach 和 @AfterEach
  • 把 @BeforeClass 和 @AfterClass 替换成 @BeforeAll 和 @AfterAll
  • 把 @Ignore 替换成 @Disabled
  • 把 @Category 替换成 @Tag
  • 把 @RunWith、@Rule 和 @ClassRule 替换成 @ExtendWith

希望本文对你有所帮助🧠

欢迎在评论区留下你的看法🌊,我们一起讨论与分享🔥

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Java毕业设计--健康推广管理系统项目实战【Springb

发表于 2021-11-12

​

这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战」

前言:

健康管理不仅是一种理念,更是一种方法,是一套完善而彻底的服务程序,其目的是为了使患者和健康的人更好地拥有健康、恢复健康、促进健康,努力节约资金,有效降低医疗费用。健康管理具有以下主要功能:一是了解居民的身体年龄,判断疾病方向;第二,可以根据日常行为判断发病概率,在此基础上,医生可以提供降低慢性病风险的行为干预方案。三是对高危人群的健康状况进行长时间(终身)跟踪,最大限度地减少重大疾病的发生;第四,它可以节省时间和金钱在维持健康和提高医疗效率。

面向教师的健康管理平台的目的,对于个人来说,就是如何利用健康检查系统中的信息,为人们的健康保驾护航。而卫生信息管理和信息管理系统的使用,不仅需要具备基础医学知识、临床医学知识、流行病学知识、计算机技术、数理统计等综合素质的专业人员,还需要具备庞大的保健医学、预防医学、专业的临床医学、康复医学等,有资深专家团队支持,可为个人提供一系列健康管理服务。当今世界,数字化信息管理不是计算机,只有利用计算机技术,采用我国各高校统一标准的健康考试系统形式,开发高校健康考试系统系统软件,设置计算机编号的教师健康考试系统,制作教师健康体检档案,并详细记录体检过程中发现的健康问题及处理情况等,实现用户的健康体检系统信息在校园网信息的交换与共享,利用计算机技术,实现了用户健康检查系统的连续动态管理。健康信息管理系统以计算机为工具,通过对用户体检所获得的数据进行信息管理,将管理人员从繁琐的数据计算处理中解脱出来,帮助组用户更好地监督体检,从而全面提高质量。具体来说,系统可以对[用户的基本健康状况进行各种必要的统计和分析。

视频演示:Java毕业设计项目实战-健康推广信息管理系统.mp4

主要模块 :

普通用户: 用户登录、注册、修改密码、修改个人信息、查看主页健康模块信息、分类查看健康推广模块信息、查看详情信息、数据排行榜、相关信息推荐、收藏、评论、关注发布者、健康信息发布、取消收藏、取消关注、我的收藏列表、我的关注列表等主要功能

管理员: 管理员登陆、首页统计用户信息、登录信息。注册信息等、

推广类型管理:查看列表、 模糊搜索、添加、修改、删除

推广详情管理:查看列表、 模糊搜索、添加、修改、删除

管理员信息管理:查看和修改密码

通知公告管理:查看列表、 模糊搜索、添加、修改、删除

用户信息管理:查看列表、 模糊搜索 、删除

评论回复管理:查看列表、 模糊搜索、删除

功能截图:

用户登录:

​​ 首页:

分类:

发布健康推广信息:

输入标题、作者信息、分类以及封面图片和富文本编辑器的主要内容

详情:可以收藏和点击查看作者信息


评论回复收藏:

个人中心:包括基本信息、我的推广笔记、收藏夹、我的关注等


后台管理:


健康分类:

​​ 健康推广详情:

添加修改:

管理员信息:

)​​ 公告信息: )​​ 用户信息:

评论回复:

关键代码:

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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
Java复制代码/**
* 用户控制器
* @author lyy
*
*/
@RestController
@RequestMapping("/admin/user")
public class UserAdminController {

@Resource
private UserService userService;

@Value("${MD5Salt}")
private String salt; // md5加密盐

/**
* 根据ID查找用户
* @param userId
* @return
*/
@RequestMapping("/findById")
public Map<String, Object> findById(Integer userId) {
Map<String, Object> resultMap = new HashMap<String, Object>();
User user = userService.findById(userId);
resultMap.put("errorNo", 0);
resultMap.put("data", user);
return resultMap;
}

/**
* 分页查询用户
* @param user
* @param page
* @return
*/
@RequestMapping("/list")
public Map<String, Object> list(User user,
@RequestParam(value = "latelyLoginTimes", required = false) String latelyLoginTimes,
@RequestParam(value = "page", required = false) Integer page,
@RequestParam(value = "pageSize", required = false) Integer pageSize) {
String s_bregistrationDate = null; // 开始时间
String s_eregistrationDate = null; // 结束时间
if (StringUtil.isNotEmpty(latelyLoginTimes)) {
String[] strs = latelyLoginTimes.split(" - "); // 拆分时间段
s_bregistrationDate = strs[0];
s_eregistrationDate = strs[1];
}
List<User> userList = userService.list(user, s_bregistrationDate, s_eregistrationDate, page - 1, pageSize);
Long total = userService.getCount(user, s_bregistrationDate, s_eregistrationDate);
Map<String, Object> resultMap = new HashMap<String, Object>();
resultMap.put("errorNo", 0);
resultMap.put("data", userList);
resultMap.put("total", total);
return resultMap;
}

/**
* unfollow
* @param request
* @param userId
* @return
*/
@RequestMapping("/removeFocusUser")
public ModelAndView removeFocusUser(HttpServletRequest request, String userId) {
ModelAndView mav = new ModelAndView();
User user = (User) request.getSession().getAttribute("user");// 当前登录用户

String userIds = user.getUserIds();
List<String> tempList = Arrays.asList(userIds.split(","));
List<String> lineIdList = new ArrayList<>(tempList);
lineIdList.remove(userId);
String ret = StringUtils.join(lineIdList, ",");

user.setUserIds(ret);

userService.save(user);
mav.setViewName("redirect:/viewFocusUser");
return mav;
}

/**
* 关注用户
* @param request
* @param userId
* @return
*/
@RequestMapping("/addFocusUser")
public ModelAndView addFocusUser(HttpServletRequest request, String userId) {
ModelAndView mav = new ModelAndView();
User user = (User) request.getSession().getAttribute("user");// 当前登录用户

String userIds = user.getUserIds();
List<String> tempList = Arrays.asList(userIds.split(","));
List<String> lineIdList = new ArrayList<>(tempList);
lineIdList.add(userId);
String ret = StringUtils.join(lineIdList, ",");

user.setUserIds(ret);

userService.save(user);
mav.setViewName("redirect:/viewFocusUser");
return mav;
}

@RequestMapping("/addFocusUser/{userId}")
public ModelAndView addFocusUser(@PathVariable(value = "userId", required = false) Integer userId,
HttpSession session) {
ModelAndView mav = new ModelAndView();
User user = (User) session.getAttribute("user");// 当前登录用户

String userIds = user.getUserIds();
List<String> tempList = new ArrayList<>();
if (userIds != null) {
tempList = Arrays.asList(userIds.split(","));
}
List<String> lineIdList = new ArrayList<>(tempList);
lineIdList.add(userId.toString());
String ret = StringUtils.join(lineIdList, ",");

user.setUserIds(ret);

userService.save(user);
mav.setViewName("redirect:/viewFocusUser");
return mav;
}

/**
* 取消收藏
* @param request
* @return
*/
@RequestMapping("/removeCollection")
public ModelAndView removeCollection(HttpServletRequest request, String artId) {
ModelAndView mav = new ModelAndView();
User user = (User) request.getSession().getAttribute("user");// 当前登录用户

String artIds = user.getArticleIds();
List<String> tempList = Arrays.asList(artIds.split(","));
List<String> lineIdList = new ArrayList<>(tempList);
lineIdList.remove(artId);
String ret = StringUtils.join(lineIdList, ",");

user.setArticleIds(ret);

userService.save(user);
mav.setViewName("redirect:/viewCollection");
return mav;
}

/**
* 收藏
* @param request
* @return
*/
@RequestMapping("/addCollection")
public ModelAndView addCollection(HttpServletRequest request, String artId) {
ModelAndView mav = new ModelAndView();
User user = (User) request.getSession().getAttribute("user");// 当前登录用户
String artIds = user.getArticleIds();
List<String> tempList= Arrays.asList(artIds.split(","));
List<String> lineIdList = new ArrayList<>(tempList);
lineIdList.add(artId);
String ret = StringUtils.join(lineIdList, ",");
user.setArticleIds(ret);
userService.save(user);

mav.setViewName("redirect:/viewCollection");
return mav;
}

@RequestMapping("/delete")
public Map<String, Object> delete(Integer userId) {
Map<String, Object> resultMap = new HashMap<String, Object>();
userService.delete(userId);
resultMap.put("errorNo", 0);
return resultMap;
}
}

数据表设计:

数据库名: boot_health

文档版本: V1.0.0

文档描述: 数据库表设计描述

表hibernate_sequence

编号 名称 数据类型 长度 小数位 允许空值 主键 默认值 说明
1 next_val bigint 20 0 Y N

表t_admin

编号 名称 数据类型 长度 小数位 允许空值 主键 默认值 说明
1 admin_id int 10 0 N Y
2 head_portrait varchar 200 0 Y N
3 password varchar 200 0 Y N
4 phone varchar 200 0 Y N
5 sex varchar 50 0 Y N
6 signature varchar 500 0 Y N
7 true_name varchar 200 0 Y N
8 user_name varchar 200 0 Y N

表t_article

编号 名称 数据类型 长度 小数位 允许空值 主键 默认值 说明
1 article_id int 10 0 N Y
2 author varchar 200 0 N N
3 click int 10 0 Y N
4 comment_num int 10 0 Y N
5 content text 65535 0 Y N
6 image_name varchar 255 0 Y N
7 is_original int 10 0 Y N
8 is_top int 10 0 Y N
9 publish_date datetime 19 0 Y N
10 title varchar 200 0 N N
11 classify_id int 10 0 Y N
12 user_id int 10 0 Y N

表t_blogger

编号 名称 数据类型 长度 小数位 允许空值 主键 默认值 说明
1 blogger_id int 10 0 N Y
2 head_portrait varchar 200 0 Y N
3 motto varchar 500 0 Y N
4 nick_name varchar 200 0 Y N
5 site varchar 200 0 Y N
6 signature varchar 500 0 Y N

表t_classify

编号 名称 数据类型 长度 小数位 允许空值 主键 默认值 说明
1 classify_id int 10 0 N Y
2 classify_name varchar 200 0 N N

表t_comment

编号 名称 数据类型 长度 小数位 允许空值 主键 默认值 说明
1 comment_id int 10 0 N Y
2 comment_date datetime 19 0 Y N
3 content varchar 500 0 Y N
4 article_id int 10 0 Y N
5 user_id int 10 0 Y N

表t_link

编号 名称 数据类型 长度 小数位 允许空值 主键 默认值 说明
1 link_id int 10 0 N Y
2 link_email varchar 200 0 Y N
3 link_name varchar 200 0 Y N
4 link_url varchar 200 0 Y N
5 order_num int 10 0 Y N

表t_notice

编号 名称 数据类型 长度 小数位 允许空值 主键 默认值 说明
1 notice_id int 10 0 N Y
2 grade int 10 0 Y N
3 content varchar 500 0 Y N
4 publish_date datetime 19 0 Y N

表t_reply

编号 名称 数据类型 长度 小数位 允许空值 主键 默认值 说明
1 reply_id int 10 0 N Y
2 content varchar 500 0 Y N
3 reply_date datetime 19 0 Y N
4 comment_id int 10 0 Y N
5 user_id int 10 0 Y N

表t_timeline

编号 名称 数据类型 长度 小数位 允许空值 主键 默认值 说明
1 timeline_id int 10 0 N Y
2 content varchar 200 0 Y N
3 publish_date datetime 19 0 Y N
4 month varchar 200 0 Y N
5 year varchar 200 0 Y N
​

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Linux 小知识 丨目录文件常用命令 Linux 小知识

发表于 2021-11-12

这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战

Linux 小知识 丨常用文件命令

目录常用命令

常见命令 作用
ls 列出目录
cd 切换目录
pwd 显示目前的目录
mkdir 创建新目录
rmdir 删除空目录
cp 复制文件或目录
rm 删除文件或目录
mv 移动文件或目录

ls 命令_列出目录里的内容

选项参数

  • -a : 显示所有文件或目录 (包含隐藏)
  • -d : 仅列出目录本身, 而不是列出目录内的文件数据(常用)
  • -l : 长数据传列出, 包含文件的树形与权限等等数据(常用)

常用选项

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
bash复制代码[root@VM-8-10-centos ~]# ls -1
app
# list one file per line. Avoid '\n' with -q or -b
[root@VM-8-10-centos ~]# ls -l
total 12
drwxr-xr-x 3 root root 4096 11月 1 11:44 app

[root@VM-8-10-centos ~]# ls -a -1 # 全部显示包括隐藏的
.
..
app
.bash_history
.bash_logout
.bash_profile
.bashrc
.cache
.config

[root@VM-8-10-centos ~]# ls -al
total 132
权限 属主 属组 大小 最后一次访问时间 文件或目录名字
dr-xr-x---. 10 root root 4096 11月 4 10:48 .
dr-xr-xr-x. 19 root root 4096 11月 4 17:52 ..
drwxr-xr-x 3 root root 4096 11月 1 11:44 app
# d 表示目录
# - 表述文件

pwd 命令

输出当前目录路径

1
2
bash复制代码[root@VM-8-10-centos ~]# pwd
/root

cd 命令 [相对路径|绝对路径]_切换目录

1
2
3
4
5
6
7
8
9
10
11
bash复制代码# 相对路径
[root@VM-8-10-centos ~]# pwd
/root
[root@VM-8-10-centos ~]# cd app
[root@VM-8-10-centos app]# pwd
/root/app

# 绝对路径
[root@VM-8-10-centos app]# cd /root/app
[root@VM-8-10-centos app]# pwd
/root/app

mkdir 命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
bash复制代码mkdir [-p] 文件夹名字  创建目录
# 创建一个文件夹
[root@VM-8-10-centos app]# mkdir test
[root@VM-8-10-centos app]# ls -1
http
test

# 创建多级文件夹
[root@VM-8-10-centos app]# mkdir -p test2/aaa
[root@VM-8-10-centos app]# ls
http test test2
[root@VM-8-10-centos app]# cd test2
[root@VM-8-10-centos test2]# ls
aaa
[root@VM-8-10-centos test2]# cd aaa
[root@VM-8-10-centos aaa]# ls
[root@VM-8-10-centos aaa]# pwd
/root/app/test2/aaa

rmdir 命令

1
2
3
4
5
6
bash复制代码rmdir [-p] 文件夹名字   # 删除空的目录 
[root@VM-8-10-centos aaa]# cd ..
[root@VM-8-10-centos test2]# rmdir aaa
[root@VM-8-10-centos test2]# ls

rmdir -p bbb/ccc # 删除多级空目录 , 前提是 ccc 也是空的

rm 命令

1
2
3
4
5
6
7
8
9
10
11
12
13
bash复制代码rm [选项] 文件/目录  #删除文件或目录
-i 删除前逐一询问确认
-f 即使原文件属性设为只读,也可以直接删除, 无需逐一确认
-r 将目录及一下文件逐一删除

rm -rf *

[root@VM-8-10-centos test2]# touch aaa.txt
[root@VM-8-10-centos test2]# ls -1
aaa.txt
[root@VM-8-10-centos test2]# rm aaa.txt
rm: remove regular empty file 'aaa.txt'? yes
[root@VM-8-10-centos test2]# ls

touch 命令

1
bash复制代码touch aaa.txt # 创建文件

cp 命令

1
2
3
4
5
6
7
8
9
bash复制代码# 语法: cp [选项] 数据源 目的地  文件复制
# 参数选项
-a # 此选项通常在复制目录时使用,但它保留链接,文件属性,并复制目录下的所有内容
-d # 复制时保留链接(相当于windows中的快捷方式)。
-f # 覆盖已经存在的目标文件而不给出提示
-i # 与 -f 相反,在覆盖目标文件之前给出提示,要求用户确认是否覆盖,回答"Y"时目标文件将被覆盖。
-p # 除复制文件的内容外还把修正改时间和访问权限也复制到新文件中
-r/R # 若给出的源文件是一个目录文件,此时将复制该目录下所有的子目录和文件
-I # 不复制文件,只是生成链接文件(创建快捷方式)

示例

1
2
bash复制代码cp aaa/* ccc #只能拷贝文件
cp -r aaa/* ccc # 拷贝全部包括文件夹

mv 命令

1
2
3
4
5
bash复制代码# 相当于windows中的剪切功能
# 语法: mv [选项] 数据源 目的地 改名 | 移动文件或文件夹
# 参数选项
-i # 若指定目录已有同名文件,则先询问是否覆盖旧文件
-f # 若指定目录已有同名文件,直接覆盖不提示

示例

1
2
bash复制代码mv a.txt b.txt # 重命名
mv a.txt /home/a.txt # 移动(剪切) a.txt 到 home 下
命令格式 运行结果
mv 文件名 文件名 将源文件名改为目标文件名
mv 文件名 目录名 将文件移动到目标目录
mv 目录名 目录名 目标目录已存在,将源目录移动到目标目录,目标目录不存在则改名
mv 目录名 文件名 出错

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

get一下如何在linux系统里给磁盘分区

发表于 2021-11-12

1、查看磁盘表

1
bash复制代码fdisk -l /dev/sdb

2、进入磁盘

1
bash复制代码fdisk /dev/sdb

进入磁盘显示如下图

image.png
可以输入m查看命令操作

image.png
输入d是删除分区命令,n是创建分区的命令,q是退出磁盘操作命令,p是控制台输出所有磁盘列表,w是保存所创建的分区

3、创建分区

输入n按回车,将会提示你选择磁盘类型 p(primary)是主分区的意思,e(extended)是逻辑分区的意思。

image.png

4、设置分区大小

这里我们输入p创建主分区,选择区号回车或者直接回车,输入分区起始字节回车,输入分区空间大小204800我这是按字节为单位的,再按回车

image.png

5、查看分区列表

查看分区列表输入p

image.png

6、保存

退出磁盘前记得先要输入w按回车保存所创建的分区
image.png


a3d62791e4c5f386a41a7df37714323a.jpeg

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

mysql select 语句执行流程

发表于 2021-11-12

mysql 官方说明文档请参考: 官方说明文档.

mysql sql 执行流程图

在这里插入图片描述

建立连接

mysql 服务端默认监听的端口是 3306,客户端可以使用不同的协议和服务端建立连接(常见的协议有:TCP、Unix socket 等)。

查看连接详情
  • 命令
1
sql复制代码show global status like 'Thread%';
  • 查询结果以及解释
字段 含义
Threads cached 缓存中的线程连接数
Threads connected 当前打开的连接数
Threads created 为处理连接创建的线程数
Threads running 非睡眠状态的连接数,通常指并发连接数

客户端每产生一个连接或者一个会话,在服务端就会创建一个线程来处理,故可以通过线程来看连接情况。
几个线程的详细介绍可以参考:Mysql查看状态,连接数,线程数以及Mysql性能监控工具doDBA的使用以及优化.

连接关键参数
  • 超时时间,默认都是28800秒,8小时
1
2
3
4
sql复制代码--非交互式超时时间,如 JDBC 程序
show global variables like 'wait timeout';
--交互式超时时间,如数据库工具
show global variables like 'interactive timeout';
  • 最大连接数:在5.7版本中默认是151个,最大可以设置成100000
1
sql复制代码show variables like 'max connections';

查询缓存

mysql 查询缓存查询功能比较鸡肋,在 5.7 版本中默认是关闭的,8.+ 版本以后已经被弃用,主要原因有如下两点:

  • 第一个是它要求SQL语句必须一 模一样,中间多一个空格,字母大小写不同都被认为是不同的的SQL。
  • 第二个是表里面任何一条数据发生变化的时候,这张表所有缓存都会失效,所以对 于有大量数据更新的应用,不适合。

解析器

语法解析

将sql语句拆分成一个一个的单词

词法解析(Parser)

对SQL做一些语法检查,比如单引号有没有闭合, 然后根据MySQL定义的语法规则,根据SQL语句生成解析树,如下图所示:
在这里插入图片描述

预处理器(Preprocessor)

预处理器会检査生成的解析树,解决解析器无法解析的语义的问题。比如,它会检査表和列名是否存在,检査名字和别名,保证没有歧义。

预处理器处理的结果是得到一棵新的解析树。

优化器(Optimizer)

一条SQL语句可以有很多种执行方式,最终返回相同的结果,他们是等价的。优化器的目的就是根据解析树生成不同的执行计划(Execution Plan),然后选 择一种最优的执行计划,MySQL里面使用的是基于开销(cost)的优化器,选择开销最小的一种执行(有时候不能光从sql判断查询是否走索引也是这个原因)。

  • 可以使用这个命令査看査询的开销:
1
sql复制代码show status like 'Last query cost';

优化器处理后主要得到执行计划。

MySQL提供了一个执行计划的工具。我们在SQL语句前面加上EXPLAIN,就可以看到执行计划,例如:

1
sql复制代码EXPLAIN select name from user where id=1;

存储引擎

存储引擎主要负责数据的存储
不同的存储引擎提供了统一标准的api接口,供执行器操作。所以 mysql 的存存储引擎是动语选择的。

存储引擎存放文件详情
  • 查看mysql的数据存放路径(每个数据库是一个文件夹)
1
sql复制代码show variables like 'datadir';

不同的存储引擎文件的存储形式如下:
在这里插入图片描述

  • 不同的存储引擎存放文件说明
    • frm 存放的是表结构定义的文件,每个存储引擎都有
    • 不同的存储引擎存放数据的方式不一样,产生的文件也不一样
      • innodb只有一个数据文件
      • memory是放在内存中,故磁盘没有文件
      • myisam 是两个文件,table_name.MYD 存放数据,table_name.MYI存放索引
各个存储引擎之间的对比
  • MylSAM (3个文件)
    应用范围比较小。表级锁定限制了读/写的性能,因此在Web和数据仓库配置中,它通常用于只读或以读为主的工作。
    特点:
    • 支持表级别的锁(插入和更新会锁表)。
    • 不支持事务。拥有较高的插入(insert)和查询(select)速度。
    • 存储了表的行数(count速度更快)。
      怎么快速向数据库插入100万条数据?
      有一种先用MylSAM插入数据,然后修改存储引擎为InnoDB的操作。)
      适合:只读之类的数据分析的项目。

-InnoDB (2个文件)
mysql 5.7中的默认存储引擎。InnoDB是一个事务安全(与ACID兼容)的MySQL 存储引擎,它具有提交、回滚和崩溃恢复功能来保护用户数据。InnoDB行级锁(不升级 为更粗粒度的锁)和Oracle风格的一致非锁读提高了多用户并发性和性能。InnoDB将 用户数据存储在聚集索引中,以减少基于主键的常见查询的I/O。为了保持数据完整性, InnoDB还支持外键引用完整性约束。
特点:

  • 支持事务
  • 支持外键,因此数据的完整性、一致性更高。
  • 支持行级别的锁和表级别的锁
  • 支持读写并发写不阻塞读(MVCC)
  • 特殊的索引存放方式,可以减少I/O,提升査询效率

详情请参考:dev.mysql.com/doc/refman/…
在这里插入图片描述
5.5.5之后,mysql默认的存储引擎是InnoDB

执行器(执行引擎)

执行引擎,利用存储引擎提供的相应的API来完成操作,将存储引擎返回的处理的数据返回给客户端

总结

MySQL分成三层:跟客户端对接的连接层,真正执行操作的服务层,和跟硬件打交道的存储引擎层。

  • 连接层
    我们的客户端要连接到MySQL服务器3306端口,必须要跟服务端建立连接,管理所有的连接,验证客户端的身份和权限,这些功能就在连接层完成。
  • 服务层
    连接层会把SQL语句交给服务层,这里面又包含一系列的流程:比如查询缓存的判断、根据SQL调用相应的接口,对我们的SQL语句进行词法和语 法的解析(比如关键字怎么识别,别名怎么识别,语法有没有错误等等)。然后就是优化器,MySQL底层会根据一定的规则对我们的SQL语句进行优化,最后再交给执行器去执行。
  • 存储引擎
    存储引擎就是我们的数据真正存放的地方,在MySQL里面支持不同的存储弓I擎。再往下就是内存或者磁盘。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

捋一捋python日期时间处理(下) 开发中常用的日期操作还

发表于 2021-11-11

「这是我参与11月更文挑战的第9天,活动详情查看:2021最后一次更文挑战」

正式的Python专栏第33篇,同学站住,别错过这个从0开始的文章!

前篇我们稍微学习了Python中时间的获取,这次继续学习日期的时区转换,格式化等等。

开发中常用的日期操作还有哪些?

  • 时区转换显示
  • 日期格式化
  • 秒数 与 日期 与 字符串的转换

我们经常会用到,比如全球化的业务根据不同客户显示不同时间(格式等)

在python 主要有下面两个模块涵盖了常用日期处理

1
2
arduino复制代码import time
import calender

我们看看这两个模块。

时间处理中的类型转换:struct_time vs str

Python中创建一个时间,具体来说创建一个struct_time 需要一个9个元素的元组来构造。

asctime 函数帮我们把这种类型的时间格式化为字符串。

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
perl复制代码#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/11/10 22:49 上午
# @Author : LeiXueWei
# @CSDN/Juejin/Wechat: 雷学委
# @XueWeiTag: CodingDemo
# @File : createtime.py
# @Project : hello


import time

# fixed time: time.struct_time(tm_year=2021, tm_mon=11, tm_mday=10, tm_hour=22, tm_min=55, tm_sec=11, tm_wday=16, tm_yday=16, tm_isdst=16)
the9fields = (2021, 11, 10, 22, 55, 11, 16, 16, 16)
fixed = time.struct_time(the9fields)
print("fixed time:", fixed)
print("type:", type(fixed))

result = time.asctime(the9fields) # 类似struct_time,需要9个元素构成的元组参数。
print("asc time:", result)
print("type:", type(result))

localtime = time.localtime()
print("local time:", localtime)
print("type:", type(localtime))
print("asc time:", time.asctime(localtime))

运行效果如下:

屏幕快照 2021-11-12 上午12.14.14.png

这个ticks就是从0时刻计算,至今的秒数累计。

可以隔一秒运行这个程序,每次ticks值加上1(近似)

指定输入来构造时间:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
python复制代码#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/11/10 22:49 上午
# @Author : LeiXueWei
# @CSDN/Juejin/Wechat: 雷学委
# @XueWeiTag: CodingDemo
# @File : createtime.py
# @Project : hello


import time

#fixed time: time.struct_time(tm_year=2021, tm_mon=11, tm_mday=10, tm_hour=22, tm_min=55, tm_sec=11, tm_wday=16, tm_yday=16, tm_isdst=16)
fixed = time.struct_time((2021, 11, 10, 22, 55, 11, 16, 16, 16))
print("fixed time:", fixed)

运行效果如下:

屏幕快照 2021-11-12 上午12.14.14.png

时间与字符串转换

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
perl复制代码#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/11/10 22:49 上午
# @Author : LeiXueWei
# @CSDN/Juejin/Wechat: 雷学委
# @XueWeiTag: CodingDemo
# @File : createtime2.py
# @Project : hello


import time

sec = 3600 # 纪元开始后的一个小时(GMT 19700101凌晨)

#
gmtime = time.gmtime(sec)
print("gmtime:", gmtime) # GMT
print("type:", type(gmtime))
print(time.strftime("%b %d %Y %H:%M:%S", gmtime))
print(time.strftime("%Y-%m-%d %H:%M:%S %Z", gmtime)) # 打印日期加上时区

print("*" * 16)
localtime = time.localtime(sec)
print("localtime:", localtime) # 本地时间
print("type:", type(localtime))

print(time.strftime("%b %d %Y %H:%M:%S", localtime))
print(time.strftime("%Y-%m-%d %H:%M:%S %Z", localtime)) # 打印日期加上时区

#试试其他格式
print(time.strftime("%D", localtime))
print(time.strftime("%T", localtime))

下面是运行结果:

屏幕快照 2021-11-12 上午1.10.05.png

对于时间格式化函数(strftime) 它不会理会你传入的时间(struct_time)是哪个时区的,照样给你输出,也是正确的。

但是我们写程序拿数据的时候,必须把时区信息原样返回到用户端,或者是UI端,最后由客户端本地时区设置进行调整显示。

总结

Python 日期处理还是挺充足的,多练习练习吧。

对了,喜欢Python的朋友,请关注学委的 Python基础专栏 or Python入门到精通大专栏

持续学习持续开发,我是雷学委!

编程很有趣,关键是把技术搞透彻讲明白。

欢迎关注微信,点赞支持收藏!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

SpringBoot项目为什么需要引入大量的starter?

发表于 2021-11-11

「这是我参与11月更文挑战的第11天,活动详情查看:2021最后一次更文挑战」

为什么我们在使用SpringBoot框架开发Java Web应用需要引入大量的starter?例如,我们引入Redis就在Maven中导入spring-boot-starter-data-redis。大家都知道SpringBoot的核心功能是自动装配,简化配置,我们通过starter实现SpringBoot自动装配的功能。那么我们如何去构建自己的starter呢?

1 前言

SpringBoot现在几乎占据的Java的大半壁江山,它的优势显而易见,它通过自动装配功能为我们简化了Spring繁杂的配置,并且内嵌Tomcat让我们启动Web项目不需要去自己配置Tomcat,这些都能大大提高我们的开发效率和代码质量。至于我们为什么在使用SpringBoot框架构建项目时,导入其它依赖都是什么什么starter?其实,这些starte就为我们实现了SpringBoot自动装配的功能,下面我们将一起将一下自动装配功能如何实现,自己怎样去构建一个SpringBoot的starter应用。

2 @EnableConfigurationProperties实现自动装配

2.1 创建一个starter项目

通过Maven创建一个项目

在pom文件中添加对应的依赖

1
2
3
4
5
6
7
8
9
10
11
12
xml复制代码<dependencies>
      <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <version>1.18.22</version>
      </dependency>
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
          <version>2.5.6</version>
      </dependency>
  </dependencies>

2.2 创建一个需要自动装配的Bean

使用@EnableConfigurationProperties注解

创建一个类这个类最后是可以通过配置文件自动装配的,添加注解@EnableConfigurationProperties时会报错,因为这个是需要将当前对象定义为Spring的一个组件,但是我们不是通过@Component注解注册成为Spring组件的。

1
2
3
4
5
6
7
8
9
10
11
12
13
less复制代码/**
* @author zhj
*/
@Data
@ConfigurationProperties(prefix = "com.zhj.vo.student")
public class Student {
​
   private Long id;
​
   private String name;
​
   private Integer age;
}

2.3 自动装配类实现

@Configuration是需要进行Bean注册的类

@EnableConfigurationProperties({Student.class}) 将该Bean注册进去

1
2
3
4
5
6
7
8
less复制代码/**
* 自动装配类
* @author zhj
*/
@Configuration // 需要进行Bean注册的
@EnableConfigurationProperties({Student.class}) //Bean注册
public class AutoConfiguration {
}

2.4 编写测试项目

pom文件导入测试需要的依赖

1
2
3
4
5
6
7
8
9
10
11
xml复制代码<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.12</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <version>2.5.6</version>
</dependency>

编写配置文件

1
2
3
4
5
6
7
yaml复制代码com:
zhj:
  vo:
    student:
      id: 1
      name: '小明'
      age: 12

编写测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
less复制代码/**
* 自动注入测试类
* @author zhj
*/
@RunWith(SpringRunner.class) // junit4 测试环境
@WebAppConfiguration // 启动web运行环境
@SpringBootTest(classes = AutoConfigApplication.class) // 指定启动类
public class AutoConfigTest {
​
   @Autowired
   @Qualifier("com.zhj.vo.student-com.zhj.vo.Student") // 前缀-类名 注入
   private Student student;
​
   @Test
   public void test01() {
       System.out.println(student);
  }
}

可以看到Bean通过配置文件成功注入Spring容器中,可以获取到Student对象

Student(id=1, name=小明, age=12)

3 @import 实现自动注入

@import注解的主要作用就是将Bean注入Spring容器

3.1 方式一 直接制定Bean的导入

1 修改需要自动装配类

1
2
3
4
5
6
7
8
less复制代码/**
* 自动装配类
* @author zhj
*/
@Configuration // 需要进行Bean注册的
@Import({Student.class}) //Bean注册
public class AutoConfiguration {
}

2 修改测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
less复制代码/**
* 自动注入测试类
* @author zhj
*/
@RunWith(SpringRunner.class) // junit4 测试环境
@WebAppConfiguration // 启动web运行环境
@SpringBootTest(classes = AutoConfigApplication.class) // 指定启动类
public class AutoConfigTest {
​
   @Autowired
   private Student student;
​
   @Test
   public void test01() {
       System.out.println(student);
  }
}

发现这样也是可以通过配置文件将Bean注入Spring容器中

3.2 方式二 使用ImportSelector注入Bean

如果需要注册的类很多,第一种方式就得将所有需要注入的Bean一一列出来

1 创建DefaultImportSelector实现ImportSelector接口

1
2
3
4
5
6
7
8
9
typescript复制代码/**
* @author zhj
*/
public class DefaultImportSelector implements ImportSelector {
   @Override
   public String[] selectImports(AnnotationMetadata importingClassMetadata) {
       return new String[] {"com.zhj.vo.Student"};
  }
}

2 修改需要自动装配类

1
2
3
4
5
6
7
8
less复制代码/**
* 自动装配类
* @author zhj
*/
@Configuration // 需要进行Bean注册的
@Import({DefaultImportSelector.class})
public class AutoConfiguration {
}

3.3 方式三 使用ImportBeanDefinitionRegistrar注入Bean

以上方式都是Spring容器负责了Bean的注册,我们可以通过ImportBeanDefinitionRegistrar自己去向Spring容器注入Bean

1 创建DefaultImportBeanDefinitionRegister 实现ImportBeanDefinitionRegistrar接口

1
2
3
4
5
6
7
8
9
10
11
java复制代码/**
* @author zhj
*/
public class DefaultImportBeanDefinitionRegister implements ImportBeanDefinitionRegistrar {
​
   @Override
   public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
       RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Student.class); // 配置bean
       registry.registerBeanDefinition("studentInstance", rootBeanDefinition); // Bean 注册
  }
}

2 修改需要自动装配类

1
2
3
4
5
6
7
8
less复制代码/**
* 自动装配类
* @author zhj
*/
@Configuration // 需要进行Bean注册的
@Import({DefaultImportBeanDefinitionRegister.class})
public class AutoConfiguration {
}

4 实现跨项目自动配置

上述自动装配的实现都是通过starter项目的配置文件,将bean注入,并在starter项目中进行测试。那么我们如何

4.1 添加依赖

1
2
3
4
5
xml复制代码<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-configuration-processor</artifactId>
  <version>2.5.6</version>
</dependency>

4.2 编译项目

使用Maven编译项目会产生spring-configuration-metadata.json这个文件

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
json复制代码{
 "groups": [
  {
     "name": "com.zhj.vo.student",
     "type": "com.zhj.vo.Student",
     "sourceType": "com.zhj.vo.Student"
  }
],
 "properties": [
  {
     "name": "com.zhj.vo.student.age",
     "type": "java.lang.Integer",
     "sourceType": "com.zhj.vo.Student"
  },
  {
     "name": "com.zhj.vo.student.id",
     "type": "java.lang.Long",
     "sourceType": "com.zhj.vo.Student"
  },
  {
     "name": "com.zhj.vo.student.name",
     "type": "java.lang.String",
     "sourceType": "com.zhj.vo.Student"
  }
],
 "hints": []
}

4.3 修改自动装配类修改

使自动装配类可以自动注入Bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
less复制代码/**
* 自动装配类
* @author zhj
*/
@Configuration // 需要进行Bean注册的
@Import({DefaultImportBeanDefinitionRegister.class})
public class AutoConfiguration {
​
   // 自动注册Bean
   @Bean(name = "Students")
   public List<String> getNameList() {
       List list = new ArrayList();
       list.add("小明");
       list.add("小红");
       list.add("小李");
       return list;
  }
}

4.4 spring.factories 文件

固定存放位置src/main/resources/META-INF/spring.factories

这个文件就是支持不同文件自动装配的核心文件。

添加内容,指定自动装配的类

1
ini复制代码org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.zhj.config.AutoConfiguration

4.5 其它Web项目引入spring-boot-auto-config-starter

1
2
3
4
5
6
7
xml复制代码<dependencies>
  <dependency>
      <groupId>com.zhj</groupId>
      <artifactId>spring-boot-auto-config-starter</artifactId>
      <version>1.0-SNAPSHOT</version>
  </dependency>
</dependencies>

4.6 测试

  • 将vo也就是Student写入web项目
1
2
3
4
5
6
7
8
9
10
11
12
13
less复制代码/**
* @author zhj
*/
@Data
@ConfigurationProperties(prefix = "com.zhj.vo.student")
public class Student {
​
   private Long id;
​
   private String name;
​
   private Integer age;
}
  • 将配置写入web项目
1
2
3
4
5
6
7
yaml复制代码com:
zhj:
  vo:
    student:
      id: 1
      name: '小明'
      age: 12
  • 构建测试接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
kotlin复制代码/**
* @author zhj
*/
@RestController
public class HelloController {
​
   @Autowired
   private Student student;
​
   @GetMapping("/hello")
   public String hello() {
       return "hello "+ student;
  }
}
  • 结果

image.png

5 总结

本文就通过自己构建一个SpringBoot的简单的starter项目,让我们去理解SpringBoot的自动装配。SpringBoot为开发者提供了多种Bean装配的方式,我们需要做的就是理解这些自动装配机制,并且能够灵活应用在企业的开发中,可以开发自己开发starter,充分利用SpringBoot的优势,让我们的项目也可以通过简单的配置,就将Bean注入Spring容器中,供我们灵活应用这些Bean。spring.factories这个文件也是重中之重,让我们可以轻松的跨项目向Spring容器注入Bean。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Springboot整合Redis Springboot整合

发表于 2021-11-11

「这是我参与11月更文挑战的第9天,活动详情查看:2021最后一次更文挑战」

Springboot整合

Springboot操作数据:spring-data jpa jdbc

SpringData也是和SpringBoot齐名的

说明:在springboot2.x之后,原来使用的jedis被替换为了lettuce

jedis:采用的直连,多个线程操作的话,是不安全的,如果想要避免不安全,使用jedis poll连接池 更像BIO模式

lettuce:采用netty,实例可以再多个线程中进行共享,不存在线程不安全的情况!可以减少线程数据了,更像NIO模式

Springboot所有的配置类,都有一个自动配置类

RedisAutoConfiguration

image-20210225235015425.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java复制代码public class RedisAutoConfiguration {
public RedisAutoConfiguration() {
}

@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}

@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}

image-20210225235142083.png

RedisConfig

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
java复制代码@Configuration
public class RedisConfig {

@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(factory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}

序列化

1
java复制代码public class Student implements Serializable {}

用jdk序列化会变成前面有转义字符的,默认使用的是jdk序列化

一般不使用原生的api操作

RedisUtils

Redis.conf详解

启动的时候,通过配置文件启动

快照

持久化,在规定时间内,执行了多少次操作,则会持久化到文件.rdb .aof

redis是内存数据库,如果没有持久化,那么数据断电即失

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
yaml复制代码protected-mode no #保护模式
port 6379
daemonize yes
supervised no
pidfile /var/run/redis_6379.pid
#日志级别
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably)
# warning (only very important / critical messages are logged)
loglevel notice
​
logfile "" #日志文件名
databases 16 #一共有16个数据库
​

SNAPSHOTTING(快照)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bash复制代码#如果900s内,如果至少一个key进行了修改,我们进行持久化操作
save 900 1
save 300 10
save 60 10000
​
#
stop-writes-on-bgsave-error yes
​
rdbcompression yes
​
rdbchecksum yes
​
dbfilename dump.rdb
​
dir ./

SECURITY(安全)

1
arduino复制代码requirepass foobared #redis设置密码

CLIENTS(客户端)

1
复制代码maxclients 10000

APPEND ONLY MODE(append模式)

1
2
3
4
5
6
perl复制代码appendonly no
# The name of the append only file (default: "appendonly.aof")
appendfilename "appendonly.aof"
# appendfsync always
appendfsync everysec
# appendfsync no
1
2
3
4
5
bast复制代码stop-writes-on-bg
rdbcompresssion yes #是否压缩rdb文件,需要消耗一些cpu资源
rdbchecksum yes #保存rdb文件的时候是否进行校验
dir ./ #rdb文件保存目录
REp  #主从复制

config set 密码

auth 123456 使用密码进行登录

限制CLIENT

maxclients 10000 #设置连接最大客户端

maxmemory-polity noeviction#内存达到满的处理策略 #移除一些key

APPEND ONLY 模式 aof配置

appendonly no #默认不开启aof模式的,默认是使用rdb方式持久化的,在大部分情况下rdb是够用的

appendfilename “appendonly.aof” #持久化文件的名字

appendfsync everysec #每秒执行一次sync,可能会丢失1s的数据

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…366367368…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%