Spring Boot 项目【前后端分离】之后端实现加 LambdaQueryWrapper实现源码分析和手动模拟

news2024/11/18 13:55:45

 

目录

 Spring Boot 项目【前后端分离】 之架子搭建

技术栈

实现功能03-创建Spring Boot 后端项目

需求分析/图解

 思路分析

代码实现

1. 创建springboot_furn 项目

2. 修改pom.xml , 引入mybatis-plus 等相关依赖 

3. 创建application.yml 配置port & 配置DB 连接信息

4. 创建SpringbootApplication.java , 完成测试

实现功能04-添加家居

思路分析

代码实现

2. 创建util/Result.java 该工具类用于返回结果(json 格式),这个工具类

3. 创建bean/Furn.java

4.创建mapper/FurnMapper.java

5. 创建service/FurnService.java

6. 创建/service/impl/FurnServiceImpl.java

注意

7. 创建furn/controller/FurnController.java

8. Postman 完成测试, 查看furn 表是否添加了数据, 自己完成测试即可.

9.安装axios, 用于发送Ajax 请求给后台 

10.点击添加按钮, 可以出现添加家居的对话框, 修改views\HomeView.vue

 11. 完成测试: 看看点击新增按钮,能否正常的弹窗添加家居的对话框(含有表单)

12创建工具文件src\utils\request.js ,用于创建axios request 对象 

14. 修改HomeView.vue , 在methods 编写save 方法, 并测试会出现跨域问题

 14. 修改springboot_vue\vue.config.js 解决跨域问题,

修改Home.vue, 使用跨域请求, 并完成测试, 查看数据库,是否有新数据添加成功

提醒, 这里容易出现的问题

启动项目前后端程序

注意事项和细节

 实现功能05-显示家居信息

需求分析/图解

思路分析

代码实现

修改controller/FurnController.java ,

Postman 完成测试

修改HomeView.vue , 编写list 方法 

完成测试,看看是否可以显示家居列表信息.​编辑

修改src\utils\request.js 增加response 拦截器, 统一处理响应后结果

修改HomeView.vue , 简化返回处理​编辑

完成测试 ​编辑

7 实现功能06-修改家居信息

需求分析/图解

思路分析

代码实现

1. 修改FurnController.java , 处理修改请求, 并使用Postman 完成测试

2. 修改HomeView.vue , 编写handleEdit 方法, 回显数据并测试 

修改HomeView.vue , 修改save 方法,

 将修改家居信息功能, 回显家居表单数据, 改成从后端-DB 获取  

1. 修改FurnController.java , 处理根据id返回对应的furn对象,

修改HomeView.vue , 编写handleEdit 第二种方法, 回显数据并测试

8 实现功能07-删除家居信息

 需求分析/图解

思路分析

代码实现

1. 修改FurnController.java , 处理删除请求, 并使用Postman 完成测试 

2. 修改HomeView.vue , 编写handleDel 方法, 完成删除并测试

 3. 完成测试测试

9 实现功能08-分页显示列表

需求分析/图解

​编辑 思路分析

代码实现

创建MybatisPlusConfig.java 配置类,引入mybatis-plus 分页插件

修改FurnController.java 增加分页查询处理

为方便观察SQL, 配置MyBatis-Plus 日志输出, 修改resources\application.yml

使用Postman 进行测试, 看看分页查询是否OK 

修改HomeView.vue , 完成分页导航显示、分页请求

增加element-plus 分页控件 

完成测试

分页显示效果

实现功能09- 切换数据源为Druid

代码实现

1. 切换数据源为druid 修改pom.xml

2. 完成测试, 看看数据源是否切换成Druid

实现功能10-带条件查询分页显示列表 

需求分析/图解​编辑 思路分析

代码实现

1. 修改FurnController.java , 增加处理带条件分页查询

 2. 在数据库/表中增加测试数据, 方便进行条件查询

3. 使用Postman 完成测试

4. 修改HomeView.vue , 完成带条件分页查询 

测试分页条件查询

 实现功能11-添加家居表单前端校验

说明: 参考element-plus 表单验证

思路分析

代码实现

修改HomeView.vue , 增加表单验证处理代码

测试

修改Homeview.vue 当表单验证不通过时,不提交表单 

完成测试

 实现功能12-添加家居表单后端校验

 需求分析/图解

 2. 后端校验-需求分析, 当后端校验没有通过,会出现灰色框提示, 后台不真正入库数据

思路分析

代码实现

1. 修改pom.xml 引入hibernate-validator jar 文件 

 2. 修改Furn.java , 使用hibernate-validator

 3. 修改FurnController.java , 对save 方法进行完善

4. 修改HomeView.vue , 显示服务器校验返回的提示信息

完成测试: 添加家居表单后端校验

测试页面效果 

 LambdaQueryWrapper -使用说明

 模拟实现


 Spring Boot 项目【前后端分离】 之后端实现

技术栈

- 使用了前后端分离,前端的主体框架Vue3+后端的基础框架Spring-Boot

1.前端技术栈∶vue3+Axios+ElementPlus.

2.后端技术栈∶SpringBoot+MyBatis Plus

3.数据库-Mysql

4.项目依赖管理-Maven

5.分页-MyBatis Plus 分页插件

6.切换数据源DruidDataSources

7.在LambdaQueryWrapper 引出知识点lambda 方法引用的类名∶∶实例方法

8.项日前端我们使用到request 和response 拦截器,并且我们解决了跨域问题

 

实现功能03-创建Spring Boot 后端项目

需求分析/图解

● 项目前后端分离情况如图, 分成两个子项目(前端和后端)

 思路分析

1. 在springboot_vue 项目, 进行前端代码开发
2. 在springboot_furn 项目, 进行后端代码开发

代码实现

1. 创建springboot_furn 项目

2. 修改pom.xml , 引入mybatis-plus 等相关依赖 

    <!--导入springboot父工程-规定写法-->
    <parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.5.3</version>
    </parent>
    <dependencies>
        <!--引入web starter-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--引入mysql驱动: 这里使用版本仲裁 8.0.26-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>


        <!--引入配置处理器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
        </dependency>

        <!--引入lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!--引入test starter-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>

        <!--引入druid依赖-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.17</version>
        </dependency>

        <!--引入mybatis-plus starter-->
        <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3</version>
        </dependency>

3. 创建application.yml 配置port & 配置DB 连接信息

server:
  port: 9090
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 自己的密码
    url: jdbc:mysql://localhost:3306/springboot_vue?useSSL=true&useUnicode=true&characterEncoding=UTF-8
mybatis-plus:

4. 创建SpringbootApplication.java , 完成测试

(说明: 如果有自动生成的主启动程序,删除即可, 使用我们自己的.)

@MapperScan(basePackages = {"com.wyxedu.furn.mapper"})
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class,args);
    }
}

 


 

实现功能04-添加家居

思路分析

1. 完成后台代码从mapper -> service -> controller , 并对每层代码进行测试, 到controller这一层,使用Postman 发送http 请求完成测试 

2. 完成前台代码, 使用axios 发送json 数据给后台, 实现添加家居信息

代码实现

1. 创建数据库和表

-- 创建springboot_vue
DROP DATABASE IF EXISTS springboot_vue;
CREATE DATABASE springboot_vue;
USE springboot_vue;
-- 创建家居表
CREATE TABLE furn(
`id` INT(11) PRIMARY KEY AUTO_INCREMENT, ## id
`name` VARCHAR(64) NOT NULL, ## 家居名
`maker` VARCHAR(64) NOT NULL, ## 厂商
`price` DECIMAL(11,2) NOT NULL, ## 价格
`sales` INT(11) NOT NULL, ## 销量
`stock` INT(11) NOT NULL ## 库存
);
-- 初始化家居数据
INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock`)
VALUES(NULL , '北欧风格小桌子' , '熊猫家居' , 180 , 666 , 7);
INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock`)
VALUES(NULL , '简约风格小椅子' , '熊猫家居' , 180 , 666 , 7 );
INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock` )
VALUES(NULL , '典雅风格小台灯' , '蚂蚁家居' , 180 , 666 , 7 );
INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock` )
VALUES(NULL , '温馨风格盆景架' , '蚂蚁家居' , 180 , 666 , 7 );
SELECT * FROM furn;

2. 创建util/Result.java 该工具类用于返回结果(json 格式),这个工具类

在网上也可找到,直接拿来使用, SSM 项目也用过类似的工具类

public class Result<T> {
    private String code; //状态码
    private String msg; //对状态说明
    private T data; // 返回时,携带的数据, 为了扩展性好,使用泛型

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }


    //无参构造器
    public Result() {

    }

    //带参构造器-指定返回的data
    public Result(T data) {
        this.data = data;
    }

    //编写方法-返回需要的Result对象-表示成功的Result
    public static Result success() {
        Result result = new Result<>();
        result.setCode("200");
        result.setMsg("success");
        return result;
    }

    //编写方法-返回需要的Result对象-表示成功的Result,同时可以携带数据
    //如果需要在static方法使用泛型,需要在 static <T>
    public static <T> Result<T> success(T data) {
        Result<T> result = new Result<>(data);
        result.setCode("200");
        result.setMsg("success");
        return result;
    }

    //编写方法-返回需要的Result对象-表示失败的Result
    public static Result error(String code, String msg) {
        Result result = new Result<>();
        result.setCode(code);
        result.setMsg(msg);
        return result;
    }

    //编写方法-返回需要的Result对象-表示失败的Result,同时可以携带数据

    public static <T> Result<T> error(String code, String msg, T data) {
        Result<T> result = new Result<>(data);
        result.setCode(code);
        result.setMsg(msg);
        return result;
    }
}

3. 创建bean/Furn.java

这样我们添加数据的时候不需要指定id 注意这里设置了主键自增长

数据库这个字段也必须是自增长和主键

@Data
@TableName("furn")
public class Furn {

    //这里我们使用@TableId: 表主键标识
    //当我们在 private Integer id 上标识了@TableId
    //说明id 对应的就是表的id字段,而且是主键
    //type = IdType.AUTO 主键类型是自增长
@TableId(type = IdType.AUTO)
private Integer id;
private String name;
private String maker;
private Double price;
private Integer sales;
private Integer stock;
}

4.创建mapper/FurnMapper.java

/**
 * 如果是mybatis-plus 我们Mapper接口可以通过extends 接口BaseMapper
 * , 扩展功能
 */
//@Mapper因为主函数已经配置了扫描这个包的类和子包的类
public interface FurnMapper extends BaseMapper<Furn> {
    //如果你有其它的方法,可以再该接口声明
    //并在对应的Mapper.xml进行配置实现
}

5. 创建service/FurnService.java

public interface FurnService extends IService<Furn> {
    //如果有其它的需求,可以在该接口声明方法,然后再对应的实现类进行实现
    //在前面我们讲解springboot 整合 mybatis-plus讲过
}

6. 创建/service/impl/FurnServiceImpl.java

@Service
public class FurnServiceImpl
        extends ServiceImpl<FurnMapper, Furn>
        implements FurnService {
}

注意

如果这个上面的为什么要继承这个接口和为什么要继承这个类 这个我的博客spring -boot整合mybits-plus中已经非常详细的介绍了 可以去看一下然后在继续观看   链接

7. 创建furn/controller/FurnController.java

说明
1. 我们的前端如果是以json格式来发送添加信息furn

那么我们需要使用@RequestBody, 才能将数据封装到对应的bean,

同时保证http的请求头的content-type是对应

2. 如果前端是以表单形式提交了,则不需要使用@RequestBody, 才会进行对象参数封装, 同时保证http的请求头的 content-type是对应

@RestController
public class FurnController {
    @Autowired
    FurnService furnService;
    @PostMapping("/save")
    public Result<?> save(@RequestBody Furn furn) {
        furnService.save(furn);
        return Result.success();
    }
}

8. Postman 完成测试, 查看furn 表是否添加了数据, 自己完成测试即可.

 

9.安装axios, 用于发送Ajax 请求给后台 

npm i axios -S

 这里输入命令 如果已经安装过的就可以不需要安装

10.点击添加按钮, 可以出现添加家居的对话框, 修改views\HomeView.vue

说明el-dialog 从Dialog 对话框获取, 表单代码从Form 表单获取,组合一下并调整一下即可

 

<!-- 添加家居的弹窗
说明:
1. el-dialog :v-model="dialogVisible" 表示对话框, 和dialogVisible 变量双向
绑定,控制是否显示对话框
2. el-form :model="form" 表示表单,数据和form 数据变量双向绑定
3. el-input v-model="form.name" 表示表单的input 空间,名字为name 需要和
后台Javabean 属性一致
-->
<el-dialog title="提示" v-model="dialogVisible" width="30%">
    <el-form :model="form" label-width="120px">
        <el-form-item label="家居名">
            <el-input v-model="form.name" style="width: 80%"></el-input>
        </el-form-item>
        <el-form-item label="厂商">
            <el-input v-model="form.maker" style="width: 80%"></el-input>
        </el-form-item>
        <el-form-item label="价格">
            <el-input v-model="form.price" style="width: 80%"></el-input>
        </el-form-item>
        <el-form-item label="销量">
            <el-input v-model="form.sales" style="width: 80%"></el-input>
        </el-form-item>
        <el-form-item label="库存">
            <el-input v-model="form.stock" style="width: 80%"></el-input>
        </el-form-item>
    </el-form>
    <template #footer>
        <span class="dialog-footer">
            <el-button @click="dialogVisible = false">取消</el-button>
            <el-button type="primary" @click="save">确定</el-button>
        </span>
    </template>
</el-dialog>
</div>
</template>
//增加数据, 一定要, 否则你会发现,在后面弹出的表单不能输入数据
data() {
    return {
        form: {},
//增加方法
methods: {
    add() {
        this.dialogVisible = true
        this.form = {}
    }
}
//增加点击新增的按钮事件
<div style="margin: 10px 0">
    <el-button type="primary" @click="add">新增</el-button>
    <el-button>其它</el-button>
</div>

 11. 完成测试: 看看点击新增按钮,能否正常的弹窗添加家居的对话框(含有表单)

12创建工具文件src\utils\request.js ,用于创建axios request 对象 

// 引入axios 包
// 重要提示:如果在启动前端项目,提示找不到axios , 把光标放在import axios from 'axios' 的'axios', 会有一个修复提示, 导入axios, 小伙伴点击, 导入即可正常使用
import axios from 'axios'
// 通过axios 创建对象
const request = axios.create({
    timeout: 5000
})
// request 拦截器
// 1. 可以对请求做一些处理
// 2. 比如统一加token,Content-Type 等
request.interceptors.request.use(config => {
    config.headers['Content-Type'] = 'application/json;charset=utf-8';
    return config
}, error => {
    return Promise.reject(error)
});
//导出
export default request

 

14. 修改HomeView.vue , 在methods 编写save 方法, 并测试会出现跨域问题

<script>
// 引入request 组件
import request from "@/utils/request";
//methods 增加方法.
    save() {
        // =======说明====...
        request.post("http://localhost:10001/save", this.form).then(res => {
            console.log(res)
            this.dialogVisible = false
        })
    }
</script>

 14. 修改springboot_vue\vue.config.js 解决跨域问题,

因为修改了配置文件, npm serve 需要重启, 否则不能识别

const {defineConfig} = require('@vue/cli-service')
module.exports = defineConfig({
    transpileDependencies: true
})
module.exports = {
    devServer: {
        port: 10000, // 启动端口
        //解读如果我们请的地址 /api/save => 代理到 http://localhost:8080/ssm/save
        proxy: {                    //设置代理,必须填
            '/api': {               //设置拦截器  拦截器格式   斜杠+拦截器名字,名字可以自己定
                target: 'http://localhost:8080/ssm',  //代理的目标地址, 就是/api 代替 http://localhost:10001/
                changeOrigin: true,                 //是否设置同源,输入是的, 浏览器就允许跨域
                pathRewrite: {                      //路径重写
                    '/api': ''                      //选择忽略拦截器里面的单词
                }
            }
        }
    }
}

修改Home.vue, 使用跨域请求, 并完成测试, 查看数据库,是否有新数据添加成功

1. 将form 表单提交给/api/save 的接口

2. /api/save 等价http∶//locaLhost∶10001/save

3. 如果成功,就进入then 方法

4.res 就是返回的信息

5.查看mysql 看看数据是否保存

request.post("/api/save", this.form).then(res => {
        console.log(res)
        this.dialogVisible = false
    })
}

提醒, 这里容易出现的问题

1) 一定要确定request.post("/api/save") 被代理后的url , 是项目后台服务对应提供的API接口url ,  否则报404

2) 当跨域执行时请求,浏览器还是提示http://localhost:5927/api/xxx , 所以不要认为是api没有替换你的配置.

启动项目前后端程序

使用跨域请求, 并完成测试, 查看数据库,是否有新数据添加成功

注意事项和细节

1. Postman 测试时, 要指定content-type ,否则会报错415

2. 如果需要将提交的json 数据, 封装到对应的Javabean, 需要配置@RequestBody , 否则会报错500

3. 如果需要返回json 数据, 需要在方法上, 配置@ResponseBody , 否则会报错404 

 


 

 实现功能05-显示家居信息

需求分析/图解

思路分析

1. 完成后台代码从mapper -> service -> controller , 并对代码进行测试
2. 完成前台代码, 使用axios 发送http 请求,返回所有家居数据, 将数据绑定显示 

代码实现

修改controller/FurnController.java ,

增加获取所有家居信息[说明:分页功能后面再完成]

    @RequestMapping("/furns")
    public Result listFurns() {
        List<Furn> furns = furnService.list();
        return Result.success(furns);
    }

Postman 完成测试

修改HomeView.vue , 编写list 方法 

//修改一下el-table
<el-table : data="tableData" stripe style="width: 100%">
    <el-table-column
        prop="id"
        label="ID"
        sortable
    >
    </el-table-column>
    <el-table-column
        prop="name"
        label="家居名"
    >
    </el-table-column>
    <el-table-column
        prop="maker"
        label="厂家">
    </el-table-column>
    <el-table-column
        prop="price"
        label="价格">
    </el-table-column>
    <el-table-column
        prop="sales"
        label="销量">
    </el-table-column>
    <el-table-column
        prop="stock"
        label="库存">
    </el-table-column>
    <el-table-column fixed="right" label="操作" width="100">
        <template #default="scope">
            <el-button @click="handleEdit(scope.row)" type="text">编辑</el-button>
        <el-button type="text">删除</el-button>
    </template>
</el-table-column>
</el - table >
    //修改一下tableData: []
    data() {
    return {
        form: {},
        dialogVisible: false,
        search: '',
        tableData: []
    }
}
//在created() 调用list() 完成页面数据获取
created() {
    this.list()
}
//编写list() method
list() { //请求显示家居列表-带检索
    request.get("/api/furns").then(res => {
        //绑定tableData, 显示在表格
        this.tableData = res.data.extend.furnsList
    })
}
//在save() 调用后,调用list() 刷新页面
save() {
    // =======说明====...
    request.post("/api/save", this.form).then(res => {
        console.log(res)
        this.dialogVisible = false
        this.list()
    })
}

完成测试,看看是否可以显示家居列表信息.

修改src\utils\request.js 增加response 拦截器, 统一处理响应后结果

//引入axios
import axios from "axios";
//通过axios创建对象-request对象,用于发送请求到后端
const request = axios.create({
    timeout: 5000
})
 
//request拦截器的处理
//1. 可以对请求做统一的处理
//2. 比如统一的加入token, Content-Type等
request.interceptors.request.use(config => {
    config.headers['Content-Type'] = 'application/json;charset=utf-8'
    return config
}, error => {
    return Promise.reject(error)
})
 
 
//response拦截器
//可以在调用接口响应后,统一的处理返回结果
request.interceptors.response.use(
    response => {
        let res = response.data
        //如果返回的是文件,就返回
        if (response.config.responseType === 'blob') {
            return res
        }
        //如果是string, 就转成json对象
        if (typeof res === 'string') {
            //如果res 不为null, 就进行转换成json对象
            res = res ? JSON.parse(res) : res
        }
        return res;
    },
    error => {
        console.log("err", error)
        return Promise.reject(error);
    }
)
 
//导出request对象, 在其它文件就可以使用
export default request

修改HomeView.vue , 简化返回处理

完成测试 

 


 

7 实现功能06-修改家居信息
 

需求分析/图解

思路分析

1. 完成后台代码从mapper -> service -> controller , 并对代码进行测试

2. 完成前台代码, 回显家居信息[方式1: 直接将点击的表格当前行的数据进行回显, 方式

        2.2.1根据当前行的id(家居id), 到db 查询对应的数据,进行回显],再使用axios 发送http 请求,更新数据, 将数据绑定显示 

代码实现

1. 修改FurnController.java , 处理修改请求, 并使用Postman 完成测试

    /**
     * 说明
     * 1. @PutMapping 我们使用Rest风格,因为这里是修改的请求,使用put请求
     * 2. @RequestBody : 表示前端/客户端 发送的数据是以json格式来发送
     */
    @PutMapping("/update")
    public Result update(@RequestBody Furn furn) {
        //这个updateById是mybatis-plus提供
        furnService.updateById(furn);
        return Result.success();
    }

 

2. 修改HomeView.vue , 编写handleEdit 方法, 回显数据并测试 

handleEdit(row) {
    //说明
    //1. JSON.stringify(row) 将row 转成json 字符串
    //2. JSON.parse(xx) 将字符串转成json 对象
    //3. 为什么这样做? 其实JSON.parse(JSON.stringify(row)) 就是对row 进行了深拷贝
    //4. 这样表格中的行数据和弹出框的数据就是独立的了
    this.form = JSON.parse(JSON.stringify(row))
    this.dialogVisible = true
    }
    //触发handleEdit 方法
    <el-button size="mini" @click="handleEdit(scope.row)" type="primary">编辑</el-button>

修改HomeView.vue , 修改save 方法,

处理修改请求, 说明更新成功的消息框, 不需要做额外处理, 直接使用this.$message 即可

save() {
    //增加处理修改逻辑
    if (this.form.id) {
        request.put("/api/update", this.form).then(res => {
            if (res.code === 200) {//如果code 为200
                this.$message({ //弹出更新成功的消息框
                    type: "success",
                    message: "更新成功"
                })
            } else {
                this.$message({//弹出更新失败信息
                    type: "error",
                    message: res.msg
                })
            }
            this.list() //刷新列表
            this.dialogVisible = false
        })
    } else {//添加
        //=======说明======
        //1. 将form 表单提交给/api/save 的接口
        //2. /api/save 等价http://localhost:10001/save
        //3. 如果成功,就进入then 方法
        //4. res 就是返回的信息
        //5. 查看Mysql 看看数据是否保存
        request.post("/api/save", this.form).then(res => {
            this.dialogVisible = false
            this.list()
        })
    }
}

 

 将修改家居信息功能, 回显家居表单数据, 改成从后端-DB 获取  

1. 修改FurnController.java , 处理根据id返回对应的furn对象,

并使用Postman 完成测试 

    //增加方法[接口],根据id,返回对应的家居信息
    //如何设计? 依然使用url占位符+@PathVariable
    @GetMapping("/find/{id}")
    public Result findById(@PathVariable Integer id) {
        Furn furn = furnService.getById(id);
        log.info("furn={}", furn);
        return Result.success(furn);//返回成功的信息-携带查询到furn信息
    }

修改HomeView.vue , 编写handleEdit 第二种方法, 回显数据并测试

 request.get("/api/find/" + row.id).then(res => {
        if (res.code === "200") {//查询成功
          //取出数据绑定this.form
          this.form = res.data
        } else {
          this.$message({
            type: "error",
            message: res.msg //小伙伴也可以 res.msg来提示
          })
        }
      })
      this.dialogVisible = true //显示对话框
    }

注意在ssm中我们是res.extend.furn

是因为我们在ssm项目中定义的返回类中的数据集合是extend所以用extend

 

 

但是我们这个项目中返回数据的类的数据名字是 data就是一个自定义泛型放什么类型就是什么  

所以我们这里是用res.data就好了

这里为什么没有frun是我们传数据的类型是介绍frun 所以mybits-plus就自动封装了

上面ssm的是一个集合然后多了一个数据类型是obiect 然后放进去的是frun

所以还要在提取一层数据

 


 

 

 

8 实现功能07-删除家居信息

 需求分析/图解

思路分析

1. 完成后台代码从mapper -> service -> controller , 并对代码进行测试
2. 完成前台代码,使用axios 发送http 请求,删除数据, 将数据绑定显示

代码实现

1. 修改FurnController.java , 处理删除请求, 并使用Postman 完成测试 

使用url占位符+@PathVariable 配合使用 => springmvc的博客有详细介绍  链接

    //处理删除
    //使用url占位符+@PathVariable 配合使用 => springmvc的博客有详细介绍
    //使用rest 风格 ->del方式
    @DeleteMapping("/del/{id}")
    public Result del(@PathVariable Integer id) {
        //说明removeById 是Mybatis-Plus提供
        furnService.removeById(id);
        return Result.success();
    }

2. 修改HomeView.vue , 编写handleDel 方法, 完成删除并测试

//处理删除方法
handleDel(id) {
    request.delete("/api/del/" + id).then(res => {
        if (res.code === 200) {
            this.$message({
                type: "success",
                message: "删除成功"
            })
        } else {
            this.$message({
                type: "error",
                message: res.msg
            })
        }
        this.list() // 刷新列表
    })
}
    //响应删除点击
    <template #default="scope">
    <el-button size="mini" @click="handleEdit(scope.row)" type="primary">编辑</el-button>
    <!--增加popcomfirm 控件,确认删除-- >
    <el-popconfirm
        title="确定删除吗?" @confirm="handleDel(scope.row.id)" >
            <template #reference>
                <el-button size="small" type="danger">删除</el-button>
            </template>
    </el-popconfirm>
    </template >

 3. 完成测试测试

 


 

9 实现功能08-分页显示列表

需求分析/图解

 思路分析

1. 后台使用MyBatis-plus 分页插件完成查询
2. 修改FurnController , 增加处理分页显示代码
3. 完成前台代码,加入分页导航,并将分页请求和后台接口结合

代码实现

创建MybatisPlusConfig.java 配置类,引入mybatis-plus 分页插件

@Configuration
public class MybatisPlusConfig {

    /**
     * 梳理
     * 1、注入MybatisPlusInterceptor 对象/bean
     * 2. 在MybatisPlusInterceptor bean 加入分页插件 PaginationInnerInterceptor
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        //这里分页需要指定数据库类型,因为不同的DB,分页SQL语句不同
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

修改FurnController.java 增加分页查询处理

    /**
     * @param pageNum  显示第几页 ,默认1
     * @param pageSize 每页显示几条记录 , 默认5
     * @return
     */
    //分页查询的接口/方法
    @GetMapping("/furnsByPage")
    public Result listFurnsByPage(@RequestParam(defaultValue = "1") Integer pageNum,
                                  @RequestParam(defaultValue = "5") Integer pageSize) {

        //这里通过page方法,返回Page对象, 对象中就封装了分页数据
        Page<Furn> page = furnService.page(new Page<>(pageNum, pageSize));
        //这里我们注意观察,返回的page数据结构是如何的?这样你才能指定在前端如何绑定返回的数据
        return Result.success(page);
    }

为方便观察SQL, 配置MyBatis-Plus 日志输出, 修改resources\application.yml

mybatis-plus:
  configuration:
    #这里我们配置输出底层的sql,方便我们观察sql
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

使用Postman 进行测试, 看看分页查询是否OK 

浏览器http://localhost:10000/furnsByPage

-------看后端sql--------
==> Preparing: SELECT COUNT(*) FROM furn
==> Parameters:
<== Columns: COUNT(*)
<== Row: 5
<== Total: 1
==> Preparing: SELECT id,name,maker,price,sales,stock FROM furn LIMIT ?
==> Parameters: 3(Long)
<== Columns: id, name, maker, price, sales, stock 

<== Row: 1, 北欧风格小桌子, 熊猫家居, 180.00, 666, 7
<== Row: 2, 简约风格小椅子, 熊猫家居, 180.00, 666, 7
<== Row: 3, 典雅风格小台灯, 蚂蚁家居, 180.00, 666, 7
<== Total: 3

修改HomeView.vue , 完成分页导航显示、分页请求

增加element-plus 分页控件 

//增加element-plus 分页控件
<div style="margin: 10px 0">
    <el-pagination
@size-change="handlePageSizeChange"
    @current-change="handleCurrentChange"
    :current-page="currentPage"
    :page-sizes="[5,10]"
    :page-size="pageSize"
    layout="total, sizes, prev, pager, next, jumper"
 
:total="total">
</el-pagination>
</div >
</div >
</template >
    //增加分页初始化数据
    data() {
    return {
        currentPage: 1,
        pageSize: 5,
        total: 10,
        //修改list(), 换成分页请求数据
        list() { //请求显示家居列表-不带检索
            request.get("/api/furnsByPage", {
                params: {
                    pageNum: this.currentPage,
                    pageSize: this.pageSize
 
                }
            }).then(res => {
                //绑定tableData, 显示在表格
                this.tableData = res.extend.pageInfo.list
                this.total = res.extend.pageInfo.total
            })
        }
//增加方法, 处理记录的变化, 这两个方法是和分页控件绑定的.
//处理每页显示多少条记录变化
handlePageSizeChange(pageSize) {
            this.pageSize = pageSize
            this.list()
        }
        ,
        //处理当前页变化, 比如点击分页连接,或者go to 第几页
        handleCurrentChange(pageNum) {
            this.currentPage = pageNum
            this.list()
        }

完成测试

启动项目后台服务furns_ssm
启动项目前台ssm_vue
浏览器: http://localhost:10000/

分页显示效果

● 测试分页显示效果, 浏览器: http://localhost:10000/

 


 

实现功能09- 切换数据源为Druid

代码实现

1. 切换数据源为druid 修改pom.xml

创建配置文件config\DruidDataSourceConfig.java

提醒: 为什么我们配置/注入指定的数据源, 就替换了默认的数据源

再springboot 切换数据源时的博客有,讲过底层机制 链接

@Configuration
@Slf4j
public class DruidDataSourceConfig {

    //配置/注入DruidDataSource

    @ConfigurationProperties("spring.datasource")
    @Bean
    public DataSource dataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();
        log.info("数据源={}", druidDataSource.getClass());
        return druidDataSource;
    }
}

这个最上面建项目的时候就已经导入了这里只是提醒一下和单独拿处理这个的作用 

        <!--引入druid依赖-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.17</version>
        </dependency>

2. 完成测试, 看看数据源是否切换成Druid


 

实现功能10-带条件查询分页显示列表 

需求分析/图解 思路分析

1. 完成后台代码从mapper -> service -> controller , 并对代码进行测试
2. 完成前台代码,使用axios 发送http 请求,完成带条件查询分页显示

代码实现

1. 修改FurnController.java , 增加处理带条件分页查询

   /**
     * @param pageNum  显示第几页
     * @param pageSize 每页显示几条记录
     * @param search   检索条件: 家居名 , 默认是“”, 表示不带条件检索,正常分页
     * @return
     */
    @GetMapping("/furnsBySearchPage")
    public Result listFurnsByConditionPage(
            @RequestParam(defaultValue = "1") Integer pageNum,
            @RequestParam(defaultValue = "5") Integer pageSize,
            @RequestParam(defaultValue = "") String search) {

        //先创建QueryWrapper, 可以将我们的检索条件封装到QueryWrapper
        QueryWrapper<Furn> queryWrapper = Wrappers.query();
        //判断search 是否有内容
        if (StringUtils.hasText(search)) {
            queryWrapper.like("name", search);
        }

        Page<Furn> page = furnService.page(new Page<>(pageNum, pageSize), queryWrapper);

        return Result.success(page);
    }

 2. 在数据库/表中增加测试数据, 方便进行条件查询

INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock` )
VALUES(NULL , '简约沙发1' , '蚂蚁家居' , 180 , 666 , 7 );
INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock` )
VALUES(NULL , '简约沙发2' , '蚂蚁家居' , 180 , 666 , 7 );
INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock` )
VALUES(NULL , '简约沙发3' , '蚂蚁家居' , 180 , 666 , 7 );
INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock` )
VALUES(NULL , '简约沙发4' , '蚂蚁家居' , 180 , 666 , 7 );
INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock` )
VALUES(NULL , '简约沙发5' , '蚂蚁家居' , 180 , 666 , 7 );
INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock` )
VALUES(NULL , '简约沙发6' , '蚂蚁家居' , 180 , 666 , 7 );
INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock` )
VALUES(NULL , '简约沙发7' , '蚂蚁家居' , 180 , 666 , 7 );
INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock` )
VALUES(NULL , '简约沙发8' , '蚂蚁家居' , 180 , 666 , 7 );

3. 使用Postman 完成测试

http://localhost:10000/furnsBySearchPage?search=沙发

4. 修改HomeView.vue , 完成带条件分页查询 

< !--功能区域-->
    <div style="margin: 10px 0">
        <i class="el-icon-add-location"></i>
        <el-button type="primary" @click="add">新增</el-button>
</div>
 
< !--搜索区域-->
    <div style="margin: 10px 0">
        <el-input v-model="search" placeholder=" 请输入关键字" style="width: 20%"
            clearable></el-input>
        <el-button type="primary" style="margin-left: 5px" @click="list">检索</el-button>
</div>

===========在数据池,增加search 变量=====

==========修改list 方法,请求带条件分页的API 接口=== 

    list() {//显示家居信息,
      // request.get("/api/furns").then(res => {
      //   console.log("res=", res)
      //   //将返回的数据和tableData绑定
      //   this.tableData = res.data
      // })
      //分页查询 + 带条件
      request.get("/api/furnsBySearchPage2", {
        params: {
          pageNum: this.currentPage,
          pageSize: this.pageSize,
          search: this.search
        }
      }).then(res => {
        // console.log("res=", res)
        //将返回的数据和tableData绑定
        this.tableData = res.data.records
        //修改total
        this.total = res.data.total
      })
    }

测试分页条件查询

启动项目后台服务springboot-furn
启动项目前台springboot_vue
浏览器: http://localhost:9875/

带条件分页查询显示效果
● 测试带条件分页查询显示效果, 浏览器: http://localhost:10000/

 


 

 实现功能11-添加家居表单前端校验

说明: 参考element-plus 表单验证

思路分析

1. 完成前台代码,使用ElementPlus 的表单rules 验证即可
2. 参考ElementPlus 的表单验证文档 

代码实现

修改HomeView.vue , 增加表单验证处理代码

==========增加对表单各个字段的校验规则=========

tableData: [],
    rules: {
    name: [
        { required: true, message: '请输入称家居名', trigger: 'blur' }
    ],
        maker: [
            { required: true, message: '请输入称制造商', trigger: 'blur' }
        ],
            price: [
                { required: true, message: '请输入价格', trigger: 'blur' },
                { pattern: /^(([1-9]\d*)|(0))(\.\d+)?$/, message: '请输入数字', trigger: 'blur' }
            ],
                sales: [
                    { required: true, message: '请输入销量', trigger: 'blur' },
                    { pattern: /^(([1-9]\d*)|(0))$/, message: '请输入数字', trigger: 'blur' }
                ],
                    stock: [
                        { required: true, message: '请输入库存', trigger: 'blur' },
                { pattern: /^(([1-9]\d*)|(0))$/, message: '请输入数字', trigger: 'blur' }
                    ]
}

==========指定将创建的规则应用到form 表单, 注意名称要对应=========


 添加家居的弹窗
说明:
1. el-dialog :v-model="dialogVisible" 表示对话框, 和dialogVisible 变量双向绑定,控制是否显示对话框

2. el-form :model="form" 表示表单,数据和form 数据变量双向绑定

3. el-input v-model="form.name" 表示表单的input 空间, 名字为name 需要和后台Javabean 属性一致

测试

就可以看到验证规则生效了【是光标离开输出框时,出现校验效果,因为是trigger:'blur' 事件】, 但是用户提交还是能成. 

修改Homeview.vue 当表单验证不通过时,不提交表单 

 

========修改save()===========

save() {
    //增加处理修改逻辑
    if (this.form.id) {
        request.put("/api/update", this.form).then(res => {
            if (res.code === 200) {//如果code 为200
                this.$message({ //弹出更新成功的消息框
                    type: "success",
                    message: "更新成功"
                })
            } else {
                this.$message({//弹出更新失败信息
                    type: "error",
                    message: res.msg
                })
            }
            this.list() //刷新列表
            this.dialogVisible = false
        })
    } else {//添加
        //表单数据校验是否
        this.$refs['form'].validate((valid) => {
            if (valid) {
                //=======说明======
                //1. 将form 表单提交给/api/save 的接口
                //2. /api/save 等价http://localhost:10001/save
                //3. 如果成功,就进入then 方法
                //4. res 就是返回的信息
                //5. 查看Mysql 看看数据是否保存
                request.post("/api/save", this.form).then(res => {
                    this.dialogVisible = false
                    this.list()
                })
            } else {
                this.$message({//弹出更新失败信息
                    type: "error",
                    message: "验证失败,不提交"
                })
                return false
            }
        })
    }
}

===========修改add()============

add() {
    this.dialogVisible = true
    this.form = {}
    this.$refs['form'].resetFields()//将添加验证提示消息,清空
}

完成测试


 实现功能12-添加家居表单后端校验

 需求分析/图解

1. 为什么前端校验了,后端还需要校验?-看使用Postman 添加数据, 破前端校验机制. 

 

 

 2. 后端校验-需求分析, 当后端校验没有通过,会出现灰色框提示, 后台不真正入库数据

思路分析

1. 后台使用JSR303 数据校验,引入hibernate-validator.jar ,在SpringMVC的博客有详细的 链接 
2. 前台使用ElementPlus 进行数据绑定,并显示错误信息 

代码实现

1. 修改pom.xml 引入hibernate-validator jar 文件 

<!-- JSR303 数据校验支持 引入hibernate-validator-->
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.1.0.Final</version>
</dependency>

 2. 修改Furn.java , 使用hibernate-validator

/**
 * 如果Furn类名和表 furn名字不能对应,可以通过前面讲解的@TableName
 * 如果可以对应上,可以不写
 */
@Data
//@TableName("furn")
public class Furn {

    //这里我们使用@TableId: 表主键标识
    //当我们在 private Integer id 上标识了@TableId
    //说明id 对应的就是表的id字段,而且是主键
    //type = IdType.AUTO 主键类型是自增长
    @TableId(type = IdType.AUTO)
    private Integer id;
    //根据自己的业务需求,来配置相应的注解

    //如果是对String进行非空校验,我们应该使用@NotEmpty
    @NotEmpty(message = "请输入家居名")
    private String name;
    @NotEmpty(message = "请输入厂商名")
    private String maker;

    @NotNull(message = "请输入数字")
    @Range(min = 0, message = "价格不能小于0")
    private BigDecimal price;

    @NotNull(message = "请输入数字")
    @Range(min = 0, message = "销量不能小于0")
    private Integer sales;

    @NotNull(message = "请输入数字")
    @Range(min = 0, message = "库存不能小于0")
    private Integer stock;
}

 3. 修改FurnController.java , 对save 方法进行完善

    @PostMapping("/save")
    public Result save(@Validated @RequestBody Furn furn, Errors errors) {

        //如果出现校验错误, sboot 底层会把错误信息,封装到errors

        //定义map ,准备把errors中的校验错误放入到map,如果有错误信息
        //就不真正添加,并且将错误信息通过map返回给客户端-客户端就可以取出显示
        HashMap<String, Object> map = new HashMap<>();

        List<FieldError> fieldErrors = errors.getFieldErrors();
        //遍历 将错误信息放入到map , 当然可能有,也可能没有错误
        for (FieldError fieldError : fieldErrors) {
            map.put(fieldError.getField(), fieldError.getDefaultMessage());
        }
        if (map.isEmpty()) { //说明没有校验错误,正常添加
            log.info("furn={}", furn);
            furnService.save(furn);
            return Result.success(); //返回成功信息
        } else {
            return Result.error("400", "后端校验失败~", map);
        }

    }

4. 修改HomeView.vue , 显示服务器校验返回的提示信息

===========在数据池,增加显示错误信息变量========

data() {
    return {
    //存放错误信息
    serverValidErrors: {},

================修改save()方法,显示错误提示============

save() {
    //增加处理修改逻辑
    if (this.form.id) {
        request.put("/api/update", this.form).then(res => {
            if (res.code === 200) {//如果code 为200
                this.$message({ //弹出更新成功的消息框
                    type: "success",
                    message: "更新成功"
                })
            } else {
                this.$message({//弹出更新失败信息
                    type: "error",
                    message: res.msg
                })
            }
            this.list() //刷新列表
            this.dialogVisible = false
        })
    } else {//添加
        //表单数据校验是否
        this.$refs['form'].validate((valid) => {
            if (valid) {
                //=======说明======
                //1. 将form 表单提交给/api/save 的接口
                //2. /api/save 等价http://localhost:10001/save
                //3. 如果成功,就进入then 方法
                //4. res 就是返回的信息
                //5. 查看Mysql 看看数据是否保存
                request.post("/api/save", this.form).then(res => {
                    if (res.code === 200) {
                        this.dialogVisible = false
                        this.list()
                    } else if (res.code === 400) {
                        this.serverValidErrors.name = res.extend.errorMsg.name;
                        this.serverValidErrors.sales = res.extend.errorMsg.sales;
                        this.serverValidErrors.price = res.extend.errorMsg.price;
                        this.serverValidErrors.maker = res.extend.errorMsg.maker;
                        this.serverValidErrors.stock = res.extend.errorMsg.stock;
                    }
                })
            } else {
                this.$message({//弹出更新失败信息
                    type: "error",
                    message: "验证失败,不提交"
                })
                return false
            }
        })
    }
}

==========修改add()方法,清空错误信息=========

add() {
    this.dialogVisible = true
    this.form = {}
    this.$refs['form'].resetFields()//将上传验证消息,清空
    this.serverValidErrors = {}
    },

========修改对话框,显示后台返回的校验错误信息========

<!--添加家居的弹窗
说明:
1. el-dialog :v-model="dialogVisible" 表示对话框, 和dialogVisible 变量双向绑定,控制是否
显示对话框
2. el-form :model="form" 表示表单,数据和form 数据变量双向绑定
3. el-input v-model="form.name" 表示表单的input 空间,名字为name 需要和后台Javabean
属性一致
-->
<el-dialog title="提示" v-model="dialogVisible" width="30%">
    <el-form :model="form" :rules="rules" ref="form" label-width="120px">
        <el-form-item label="家居名" prop="name">
            <el-input v-model="form.name" style="width: 60%"></el-input>
            {{ serverValidErrors.name }}
        </el-form-item>
        <el-form-item label="厂商" prop="maker">
            <el-input v-model="form.maker" style="width: 60%"></el-input>
            {{ serverValidErrors.maker }}
        </el-form-item>
        <el-form-item label="价格" prop="price">
            <el-input v-model="form.price" style="width: 60%"></el-input>
            {{ serverValidErrors.price }}
        </el-form-item>
        <el-form-item label="销量" prop="sales">
            <el-input v-model="form.sales" style="width: 60%"></el-input>
            {{ serverValidErrors.sales }}
        </el-form-item>
        <el-form-item label="库存" prop="stock">
            <el-input v-model="form.stock" style="width: 60%"></el-input>
            {{ serverValidErrors.stock }}
        </el-form-item>
    </el-form>
    <template #footer>
        <span class="dialog-footer">
            <el-button @click="dialogVisible = false">取消</el-button>
            <el-button type="primary" @click="save">确定</el-button>
        </span>
    </template>
</el-dialog>

完成测试: 添加家居表单后端校验

2、这时, 后台返回添加失败的提示信息 

启动项目前台ssm_vue 

浏览器: http://localhost:10000/

测试页面效果 

测试完毕后, 记得把valid 改成正常逻辑.

 


 

 LambdaQueryWrapper -使用说明

    //我们编写方法,使用LambdaQueryWrapper封装查询条件,完成检索

    @GetMapping("/furnsBySearchPage2")
    public Result listFurnsByConditionPage2(
            @RequestParam(defaultValue = "1") Integer pageNum,
            @RequestParam(defaultValue = "5") Integer pageSize,
            @RequestParam(defaultValue = "") String search) {

        //说明:关于lambda表达式, 我们这里使用的是 类名::实例方法
        //是lambda方法引用中一个不太容易理解的知识点

        //后面我们使用到每个lambda表达式式时候,就会有针对性进行讲解-这样理解的就非常深刻
        //的心得体会: 多用几次,就熟悉了,不用背


        //解读

        //1. Furn::getName 就是通过lambda表达式引用实例方法 getName
        //2. 这里就是把  Furn::getName 赋给 SFunction<T,R> 函数式接口 ? 函数式接口一会再说明
        //3. 看看  SFunction<T,R> 源码
        /**
         * @FunctionalInterface
         * public interface SFunction<T, R> extends Function<T, R>, Serializable {
         * }
         * 父接口
         * @FunctionalInterface
         * public interface Function<T, R> {
         *    R apply(T t); //抽象方法: 表示根据类型T的参数,获取类型R的结果
         *
         *    //后面还有默认实现方法
         * }
         *4. 传入 Furn::getName 后, 就相当于实现了 SFunction<T, R> 的apply方法
         *5. 底层会根据 传入的 Furn::getName 去得到该方法的对应的属性映射的表的字段, 可以更加灵活
         *6. 回顾一下mybatis 在xxMapper.xml 中有 ResultMap 会体现 Bean的属性和表的字段的映射关系
         * <resultMap id="IdenCardResultMap" type="IdenCard">
         *         <id property="id" column="id"/>
         */


        //创建LambdaQueryWrapper,封装检索询件
        LambdaQueryWrapper<Furn> lambdaQueryWrapper = Wrappers.<Furn>lambdaQuery();

        //判断search
        if (StringUtils.hasText(search)) {
            //后面会解读 Furn::getName, 这里会引出一系列的知识
            //lambdaQueryWrapper.like(Furn::getName,search);

            //换一个写法-小伙伴可能会清晰, 这时使用依然是正确
            SFunction<Furn, Object> sf = Furn::getName;
            lambdaQueryWrapper.like(sf, search);
        }

        Page<Furn> page = furnService.page(new Page<>(pageNum, pageSize), lambdaQueryWrapper);
        log.info("page={}", page.getRecords());
        return Result.success(page);
    }

 模拟实现

package com.wyxedu.furn.lambda;

public class Test {
    public static void main(String[] args) {

        //传统的方式来实现HspFunction/得到一个实现接口的对象 可以使用
        //匿名内部类
        //HspFunction<Desk, String> hf = new HspFunction<Desk, String>() {
        //    @Override
        //    public String apply(Desk desk) {
        //        return "hello desk";
        //    }
        //};
        //String val = hf.apply(new Desk());
        //System.out.println("val-" + val);

        HspFunction<Desk,String> hf2 = Desk::getBrand;

        String val2 = hf2.apply(new Desk());
        System.out.println("val2-" + val2);

        HspFunction<Desk,Object> hf3 = Desk::getId;
    }
}

//定义一个函数式接口: 有且只有一个抽象方法的接口
//我们可以使用@FunctionalInterface 来标识一个函数式接口
//HspFunction是一个函数式接口 (是自定义泛型接口)

@FunctionalInterface
interface HspFunction<T, R> {
    R apply(T t); //抽象方法: 表示根据类型T的参数,获取类型R的结果

    //public void hi();

    //函数式接口,依然可以有多个默认实现方法
    default public void ok() {
        System.out.println("ok");
    }
}

@FunctionalInterface
interface HspInterface {
    public void hi();
}

class Desk { //Bean
    private String name = "wyx desk";
    private String brand = "北京牌";
    private Integer id = 10;

    public Integer getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getBrand() {
        return brand;
    }


}

感谢大家的耐心阅读

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

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

相关文章

【数据结构】KMP算法:计算next与nextval函数值(图解)

例&#xff1a;计算模式串"abaabcac"的KMP算法中next函数值 由函数定义 n e x t [ j ] { 0 , j 1 M a x { k ∣ 1 < k < j 且 " t 1 t 2 ⋅ ⋅ ⋅ t k − 1 " " t j − k 1 t j − k 2 ⋅ ⋅ ⋅ t j − 1 " } 1 , k 1 next[j]\left…

asp.net高校运动会管理系统的设计与实现

本高校运动会管理系统是针对我院当前运动会工作需要而开发的B/S模式的网络系统&#xff0c;涉及到运动会赛前的报名录入准备与分组编排、赛中的成绩处理、赛后的成绩汇总与团体总分的统计。它将是一个完整统一、技术先进、高效稳定、安全可靠的基于Internet/Intranet的高校运动…

一、Git安装(Git+TortoiseGit图形化)

Git 是一个开源的分布式版本控制系统&#xff0c;用于敏捷高效地处理任何或小或大的项目。 Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。 Git 与常用的版本控制工具 CVS, Subversion 等不同&#xff0c;它采用了分布式版本库的方式…

《计算机网络—自顶向下方法》 Wireshark实验(八):ICMP 协议分析

ICMP&#xff08;Internet Control Message Protocol&#xff09;网络控制报文协议。它是 TCP/IP 协议簇的一个子协议&#xff0c;用于在 IP 主机、路由器之间传递控制消息。控制消息是指网络通不通、主机是否可达、路由是否可用等网络本身的消息。这些控制消息虽然并不传输用户…

进程概念

目录 冯诺依曼体系结构 操作系统OS 系统调用和库函数概念 进程 task_struct内容分类 组织进程 初识fork 进程状态 Z(zombie)-僵尸进程 孤儿进程 进程优先级 环境变量 和环境变量相关的命令 环境变量的组织方式 程序地址空间 冯诺依曼体系结构 关于冯诺依曼&…

Linux 防火墙 iptables

iptables概述 Linux 系统的防火墙 &#xff1a;IP信息包过滤系统&#xff0c;它实际上由两个组件netfilter 和 iptables组成。 主要工作在网络层&#xff0c;针对IP数据包。体现在对包内的IP地址、端口、协议等信息的处理上。 iptables是Linux系统防火墙的一种&#xff0c;是Ce…

SpringBoot【开发实用篇】---- 整合第三方技术(消息)

SpringBoot【开发实用篇】---- 整合第三方技术&#xff08;消息&#xff09; 消息的概念Java处理消息的标准规范JMSAMQPMQTTKafka 购物订单发送手机短信案例订单业务短息处理业务 SpringBoot整合ActiveMQ安装整合 SpringBoot整合RabbitMQ安装整合&#xff08;direct模型&#x…

【操作系统复习】第7章 输入/输出系统1

I/O系统管理的主要对象 ➢ I/O设备和对应的设备控制器 I/O系统的主要任务 ➢ 完成用户提出的I/O请求 ➢ 提高I/O速率 ➢ 改善I/O设备的利用率 I/O系统的上、下接口 ➢ I/O系统接口&#xff08;上接口&#xff09; ➢ 软件/硬件接口&#xff08;下接口&#xff09…

实验三 传感器目标识别

【实验目的】 1、了解环境感知传感器目标识别的目的和方法&#xff0c; 掌握MATLAB中的目标检测方法。 2、了解MATLAB的目标检测器和检测函数&#xff0c;掌握车辆识别、行人识别、交通标志识别和道路识别等目标识别方法。 【实验性质】 验证性实验。 【实验要求】 MATLAB 202…

Kubernetes实战入门

文章目录 一、组件介绍&#xff08;一&#xff09;master主控节点&#xff08;二&#xff09;node工作节点 二、k8s核心概念&#xff08;一&#xff09;pod&#xff08;二&#xff09;controller&#xff08;三&#xff09;service 三、搭建k8s集群&#xff08;一&#xff09;基…

6.1 Python面向对象设计及应用

1 类和对象 对象是具有某些特性和功能的具体事物的抽象。每个对象都具有描述其特征的属性及附属于它的行为。如&#xff1a;一个人有姓名、性别、身高、体重等特征描述&#xff0c;也有走路、说话、学习、开车等行为。 每个对象都有一个类&#xff0c;类是创建对象实例的模板&…

基于springboot家具商城系统

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SpringBoot 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 前言 基于springboot家具商…

js数组排序的两种方法

1. 冒泡排序 原理&#xff1a;一次比较两个相邻的数&#xff0c;如果不符合规则互换位置&#xff0c;一次比较就能够将最大或最小的值放在数组最后一位继续对除【最后一位】之外的所有元素重复上述过程。 let arr [22,1,43,12,75,32]; for(let i 0; i < arr.length - 1;…

MySQL一条查询语句是怎么执行的?MySQL 的架构是什么样子?

先谈谈MySQL的架构&#xff0c;这样自然就搞清楚一条语句是怎么执行的了 首先&#xff0c;MySQL分为客户端&#xff0c;服务端&#xff0c;存储引擎 客户端&#xff1a; ● Java程序啊&#xff0c;可视化连接工具 Navicat啊等等&#xff0c;就是客户端&#xff1b; 服务端&…

Vivado 下 IP核 之ROM 读写

目录 Vivado 下 IP核 之ROM 读写 1、实验简介 2、ROM IP 核简介 3、ROM IP 核配置 3.1、创建 ROM 初始化文件 3.2、单端口 ROM 的配置 3.3、双端口 ROM 的配置 3.4、ROM IP 核的调用 &#xff08;1&#xff09;ROM 顶层模块代码 &#xff08;2&#xff09;ROM IP 核仿…

lua-5.3.6源码安装

参考博客有https://blog.csdn.net/m0_53157173/article/details/124653430和http://blog.chinaunix.net/uid-14824714-id-3125340.html。 https://www.lua.org/download.html下载网址。点击当前网址中的“download”超链接可以下载以前的版本。 cat /etc/redhat-release看一下…

408考研计算机之计算机组成与设计——计算机层次系统概述2

目录 一、 冯诺依曼机基本思想 二、计算机的功能部件 1、输出输入设备 2、存储器 3、运算器 4、控制器​​​​​​​ 三、指令执行过程的描述 一、 冯诺依曼机基本思想 首先&#xff0c;第一个问题&#xff0c;冯诺依曼是谁&#xff1f;小编第一次知道这个名字&#xff…

Qt将十二位整形十进制转换成十六进制,在转为ascii字符,并下发串口。在接受端完整还原这个十二位的十进制数。

可以按照以下步骤进行操作&#xff1a; 将十进制数123456789012转换成十六进制字符串&#xff1a; QString hexString QString("%1").arg(123456789012ull, 0, 16);其中&#xff0c;%1表示替换第1个参数&#xff0c;0表示输出的最小位数为0&#xff0c;16表示输出…

Capturing Omni-Range Context for Omnidirectional Segmentation总结笔记

Capturing Omni-Range Context for Omnidirectional Segmentation&#xff08;捕获全范围上下文进行全方位分割&#xff09; 目录 一、论文出发点 二、论文核心思想 三、论文工作中主要问题 四、方法论 五、实验 六、结论 一、论文出发点 大多数用于分析城市环境的分割…

springboot+swagger项目中,controller引入@NotEmpty等校验注解的问题

springboot项目 springbootswagger项目中&#xff0c;controller层如果使用对基本数据类型使用 NotEmpty Length 等校验注解&#xff0c;controller会获取不到值&#xff0c;加了RequestBody后可以获取到了&#xff0c;但是前端传值content-type必须是text/plain。所以建议con…