SpringMVC的Controller层没有加@ResponseBody注解报错说明

请求代码示例:

1
2
3
4
5
6
7
8
@Controller
public class FirstController {

@GetMapping(path = "/helloWorld")
public String helloWorld() {
return "hello world";
}
}

后端返回404报错:

1
2
3
4
5
6
7
{
"timestamp": "2023-03-10 16:37:27",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/helloWorld"
}

原因分析

原因在于不加@ResponseBody注解springmvc框架会认为方法返回的是一个 ModelAndViewer对象,即视图对象,那么它就会去找这么一个对象,找不到则报404错。

解决方案

  • 类上面使用@RestController注解,包括了 @Controller 和@ResponseBody 两个注解。

  • 类上面使用 @Controller ,方法上加上@ResponseBody注解(附:@ResponseBody注解的作用:将java对象转为json格式的数据,如果不加,则SpringMVC会以为是页面)


fetchSize参数介绍

fetchSize是JDBC API中用于控制数据检索时一次取回的记录条数的参数。

作用

当我们从数据库中检索大量数据时,如果不适当地使用fetchSize参数,会导致内存泄漏和网络拥塞等问题。因此,通过设置适当大小的fetchSize值,我们可以减少每次查询时需要检索的数据条数,从而降低程序的内存和网络消耗。

底层原理

当我们执行PreparedStatement对象的executeQuery()方法时,JDBC驱动程序将向数据库发送一个查询请求,并从结果集中获取数据。在这个过程中,fetchSize参数的值对于内部JDBC驱动程序如何处理数据具有重要影响。

通常情况下,JDBC驱动程序将一次从数据库中检索fetchSize个记录,并将它们添加到ResultSet对象中。然后,在ResultSet对象中的记录用尽之前,JDBC驱动程序将不断地向数据库发送请求以获取更多的记录。在每次请求结束后,JDBC驱动程序将自动释放ResultSet对象中的前面的记录,从而避免引起内存泄漏。

总之,通过适当地设置fetchSize参数,我们可以最大限度地利用JDBC驱动程序的优势,从而提高检索数据的效率,避免内存泄漏和网络瓶颈问题。

使用方法

在使用JDBC API从数据库中检索数据时,可以通过以下方式设置fetchSize参数:

1
2
3
4
String sql = "SELECT * FROM my_table";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setFetchSize(1000); // 设置fetchSize为1000
ResultSet rs = pstmt.executeQuery();

如上所示,我们可以通过调用PreparedStatement对象的setFetchSize()方法来设置fetchSize值。在该例子中,我们将fetchSize设置为1000条记录。

另外,我们也可以在SQL语句中直接指定fetchSize:

1
2
3
<select resultType="com.example.model.User" fetchSize="100">
SELECT * FROM user_table
</select>

如上所示,我们可以在MyBatis的Mapper文件中使用fetchSize属性指定fetchSize的值。

注意事项

  • 在实际应用中,fetchSize的最佳值取决于许多因素,如数据量、内存大小、网络带宽等。通常情况下,建议使用较小的值(例如100或1000)以避免内存泄漏和网络瓶颈。
  • 如果需要处理大量数据,则可以逐步增加fetchSize的值以提高性能,但要注意不要超出系统资源的极限限制。
  • fetchSize只对可滚动的ResultSet对象有效。对于非可滚动ResultSet对象和Statement对象,fetchSize被忽略并且所有结果都将一次性返回。
  • 对于某些数据库驱动程序,fetchSize可能会被忽略或无法按预期工作。因此,在使用fetchSize时需要注意测试和验证。

Vue插槽详解

插槽基本使用

插槽的用途就和它的名字一样:有一个缺槽,我们可以插入一些东西。

插槽 slot 通常用于两个父子组件之间,最常见的应用就是我们使用一些 UI 组件库中的弹窗组件时,弹窗组件的内容是可以让我们自定义的,这就是使用了插槽的原理。

我们在项目中新建一个 child.vue 文件,用来当作子组件,它的代码也非常的简单。

child.vue 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div class="child-box">
<p>我是子组件</p>
<!-- 插槽 -->
<slot></slot>
</div>
</template>
<style>
.child-box {
display: flex;
flex-direction: column;
align-items: center;
}
</style>

然后我们直接在 App.vue 引用该子组件,代码如下:

1
2
3
4
5
6
7
8
9
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<child></child>
</template>


<script setup lang="ts">
import Child from "./child.vue";
</script>

输出结果:

img

child 子组件代码非常的简单,唯一不同的里面我们添加了一对<slot></slot>标签,这就是插槽标签。这对标签就好比我们在 child 组件内部挖了一个槽出来,我们接下来就可以往这个槽里面放置一些内容。

那么我们如何往这个槽里面放置内容呢?

我们在 App.vue 里面通过一对闭合标签<child></child>调用了子组件,我们都知道 HTML 标签之间是可以插入内容的,虽然 child 不是 HTML 自带的标签,但是它却有着类似的特征,比如我们往<child></child>之间插入一点内容,代码如下:

1
2
3
4
5
6
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<child>
<div>小猪课堂</div>
</child>
</template>

输出结果:

img

我们发现在<child></child>标签之间插入的内容被渲染出来了,那么这是为何呢?

前面我们说 slot 就是挖了一个槽出来,可以放置东西,这里我们在父组件中添加的 div 便就是我们要添加的东西,子组件中 slot 标签被替换为了我们插入的 div 元素。这就是插槽的最基本使用。

结论:

为了更好理解插槽,简单总结为以下几点:

  • slot 是 Vue3 中的内置标签。
  • slot 相当于给子组件挖出了一个槽,可以用来填充内容。
  • 父组件中调用子组件时,子组件标签之间的内容元素就是要放置的内容,它会把 slot 标签替换掉。

最简单的理解:我们的使用 U 盘需要将 U 盘插入 USB 口中,此时 USB 口就是插槽,U 盘就是插口。在 Vue 中,<slot></slot>就是电脑插槽,父组件的内容就可以理解为 U 盘插口。

插槽默认内容

我们通常将插槽比作一个占位符,有内容进来时,自动把 slot 给替换掉。但是,如果没有内容进来时,那么应该渲染什么呢?

在很多场景下都会有这种需求,比如 UI 组件库中的弹窗,如果我们没有传入弹窗的头部或者底部,那么弹窗便会有默认的样式效果,这里就用到了 slot 的默认内容功能。

修改一下 child.vue 代码:

1
2
3
4
5
6
7
8
9
<template>
<div class="child-box">
<p>我是子组件</p>
<!-- 插槽 -->
<slot>
<p>我是默认内容</p>
</slot>
</div>
</template>

当我们的 App.vue 没有向 child 组件传入内容时,会是什么效果呢?

App.vue 代码如下:

1
2
3
4
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<child></child>
</template>

输出结果:

img

可以看到子组件同样渲染了内容,而且就是 slot 标签内的内容。那么我们我们往 child 组件传入一点内容会是什么效果呢?

App.vue 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<child>
<div>{{ message }}</div>
</child>
</template>


<script setup lang="ts">
import { ref } from "vue";
import Child from "./child.vue";
const message = ref("小猪课堂");
</script>

输出结果:

img

可以看到此时页面上渲染的 App.vue 父组件传入的内容了。

总结:

从上面例子不难看出,slot 标签内的内容就是默认内容,也就是当父组件没有传递给子组件内容时,子组件就会默认渲染 slot 内部的内容,但是 slot 标签是不会渲染出来的。

具名插槽

很多时候我们子组件中都不止只有一个 slot,比如弹窗组件,我们可能允许调用者同时传入 header、content、footer 等等,这个时候如果子组件中只有一个 slot,那么这么多内容该如何区分,或者说该如何渲染呢?

基本使用

这个时候为了区分插槽与内容的对应关系,我们可以分别给 slot 和内容都加上一个名字,插入插槽的时候大家按照名字区分好就可以了。

我们给 child 组件添加上多个 slot,并且给每个 slot 取上一个名字。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div class="child-box">
<p>我是子组件</p>
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>

上段代码中我们添加了 3 个 slot 插槽,并且给其中两个 slot 标签添加了一个 name 属性,也就是每个插槽的名字。需要注意的是,上段代码中有一个插槽我们没有添加 name 属性,这个时候 Vue 会隐式的将这个插槽命名为“default”,接下来就是我们父组件 App.vue 添加内容了。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<child>
<template v-slot:header>
<div>我是 header:{{ message }}</div>
</template>
<div>我没有名字:{{ message }}</div>
<template v-slot:footer>
<div>我是 footer:{{ message }}</div>
</template>
</child>
</template>

输出结果:

img

既然 slot 有了名字,那么我们在父组件中传入内容时就要和名字关系对应起来,我们采用 v-slot:header 指令的形式找到对应的插槽,需要注意的是该指令需要作用在 template 元素上。从上图可以看出,我们传入的内容都渲染到了对应的插槽内,没有命名的插槽渲染了我们传入的未添加指令的内容。

4.2 简写

在 Vue 中,很多指令都有简写形式,v-slot:name 指令也有简写形式,比如看我们下面的示例。

原写法:

1
2
<template v-slot:footer>
</template>

简写法:

1
2
<template #footer>
</template>

接下来我们在借用官网的一张图来清楚的了解具名插槽中的父子组件关系。

img

其中 BaseLayout 为一个子组件,就和我们 child 组件一样。

4.3 默认插槽与具名插槽混用

当一个子组件中既有具名插槽,又有默认插槽时,该如何渲染呢?

前面我们说默认插槽会被隐式的命名为 default,所以我们传入内容时可以将插槽名字改为 defalut 即可。

修改 child 组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<child>
<template v-slot:header>
<div>我是 header:{{ message }}</div>
</template>
<template v-slot:default>
<div>我没有名字:{{ message }}</div>
<div>我没有名字:{{ message }}</div>
<div>我没有名字:{{ message }}</div>
</template>
<template v-slot:footer>
<div>我是 footer:{{ message }}</div>
</template>
</child>
</template>

虽然说子组件中有一个 slot 没有取名字,但是默认可以用 default 来代表它,就好比人刚出生的时候没有名字,但是他又一个默认属性:“人”。

当然,既然大家都是默认的,在父组件中你也可以不用名字,这个时候内容会默认传入到未命名插槽中去。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<child>
<template v-slot:header>
<div>我是 header:{{ message }}</div>
</template>
<div>我没有名字:{{ message }}</div>
<div>我没有名字:{{ message }}</div>
<div>我没有名字:{{ message }}</div>
<template v-slot:footer>
<div>我是 footer:{{ message }}</div>
</template>
</child>
</template>

输出结果:

img

动态插槽名

前面我们给插槽命名的时候都是直接写死的,其实我们有时候可以动态给插槽命名的,以满足更多的业务场景。

代码如下:

1
2
3
4
5
6
7
8
9
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
<!-- 缩写为 -->
<template #[dynamicSlotName]>
...
</template>
</base-layout>

上段代码就是在父组件中采用动态插槽名传入内容的示例。