1. 技术准备
技术架构
• Spring Boot
• Spring、Spring MVC、MyBatis
• Redis、Kafka、Elasticsearch
• Spring Security、Spring Actuator
开发环境
• 构建工具:Apache Maven
• 集成开发工具:IntelliJ IDEA
• 数据库:MySQL、Redis
• 应用服务器:Apache Tomcat
• 版本控制工具:Git
2. 搭建开发环境
Apache Maven
• 可以帮助我们构建项目、管理项目中的jar包
• Maven仓库:存放构件的位置
- 本地仓库:默认是 ~/.m2/repository
- 远程仓库:中央仓库、镜像仓库、私服仓库
• 示例:安装、配置、常用命令
http://maven.apache.org
安装配置版本:apache-maven-3.6.1-bin.zip
改成阿里云的镜像仓库
在conf文件夹下修改settings.html
<mirror>
<id>alimaven</id>
<mirrorOf>central</mirrorOf>
<name>aliyun maven</name>
<url>https://maven.aliyun.com/repository/central</url>
</mirror>
配置好后在系统环境变量path上新建一条
E:\work\apache-maven-3.6.1-bin\apache-maven-3.6.1\bin
输入命令行:mvn -version 出现以下信息就是安装成功了。(首先要配置好JDK)
IntelliJ IDEA
• 目前最流行的Java集成开发工具
• 示例:安装、配置、创建项目
http://www.jetbrains.com/idea
Spring Initializr
• 创建 Spring Boot 项目的引导工具
• 示例:创建“论坛社区”项目
https://start.spring.io
Spring Boot 入门示例
• Spring Boot 核心作用
- 起步依赖、自动配置、端点监控
• 示例 - 一个简单的处理客户端请求案例
@ResponseBody 是一个在 Spring 框架中常用的注解,主要用于 Web 层的控制器(Controller)方法上。它的主要功能是将方法的返回值直接写入 HTTP 响应的主体(Body)部分,而不是使用视图解析器(ViewResolver)生成视图。这意味着,当一个控制器方法被标注了 @ResponseBody,Spring 会将该方法的返回值转换成适合 HTTP 响应的内容类型,通常是 JSON 或 XML。
以下是 @ResponseBody 注解的一些关键点:
自动序列化:当方法的返回值是一个 Java 对象时,@ResponseBody 会使用一个合适的 HttpMessageConverter(如 MappingJackson2HttpMessageConverter 或 Jaxb2RootElementHttpMessageConverter)来将对象序列化为 JSON 或 XML 格式,具体取决于客户端请求头中的 Accept 字段或默认配置。
避免视图渲染:在没有 @ResponseBody 的情况下,控制器方法的返回值通常会被视为视图名,然后通过视图解析器找到对应的视图模板进行渲染。但是,使用 @ResponseBody 后,这个流程被绕过,直接返回数据。
支持多种数据类型:@ResponseBody 支持返回各种类型的数据,包括原始字符串、字节数组、基本数据类型、Java 对象等。对于非对象类型的简单数据,可以直接写入响应体;对于复杂对象,会进行序列化处理。
AJAX 和 RESTful API:@ResponseBody 经常用于构建 AJAX 应用程序或 RESTful 服务,因为这些场景通常不需要完整的 HTML 页面作为响应,而是需要数据。
示例代码:
java
@RestController // 或者 @Controller + @ResponseBody 在类级别
public class MyController {
@GetMapping("/data")
@ResponseBody // 也可以省略,因为类级别有 @RestController
public MyData getData() {
MyData data = new MyData();
data.setValue("Hello, World!");
return data;
}
}
在这个示例中,getData() 方法的返回值 MyData 会被序列化成 JSON 格式(假设客户端请求接受 JSON),并直接写入 HTTP 响应的 Body 部分。
需要注意的是,@ResponseBody 通常与 @RestController 或 @Controller 结合使用,后者会应用于整个控制器类,意味着类中的所有方法都会默认使用 @ResponseBody 行为。如果某个方法不希望有这样的行为,可以在方法级别使用 @ModelAttribute 或直接返回一个 ModelAndView 对象。
3.Spring入门
Spring全家桶
• Spring Framework
• Spring Boot
• Spring Cloud
• Spring Cloud Data Flow
https://spring.io
Spring Framework
• Spring Core
- IoC、AOP
• Spring Data Access - Transactions、Spring MyBatis
• Web Servlet - Spring MVC
• Integration - Email、Scheduling、AMQP、Security
Spring IoC
• Inversion of Control
- 控制反转,是一种面向对象编程的设计思想。
• Dependency Injection - 依赖注入,是IoC思想的实现方式。
• IoC Container - IoC容器,是实现依赖注入的关键,本质上是一个工厂。
Spring框架的IOC(Inversion of Control,控制反转)容器是其核心特性之一,它负责管理应用程序的组件和它们之间的依赖关系。IOC容器简化了组件的配置和管理,使得开发人员能够更专注于业务逻辑而非底层基础设施。
1. 控制反转 (Inversion of Control)
在传统的编程模式中,对象会主动地去创建依赖的实例或者从某个地方获取所需的实例,这种模式下,对象控制着依赖的创建和获取过程。而在控制反转中,这种控制权被“反转”了,对象不再控制依赖的创建和获取,而是由一个外部的容器(在Spring中就是IOC容器)来负责。对象只需要声明它所需要的依赖,剩下的工作都交给IOC容器来完成。
2. Spring的IOC容器
Spring框架提供了两种主要的IOC容器:
-
BeanFactory:这是最基本的IOC容器,它实现了BeanFactory接口。BeanFactory负责读取配置元数据,创建和管理bean以及处理bean之间的依赖注入。然而,BeanFactory并不提供按需加载和单例缓存等功能,这使得它在实际应用中不如ApplicationContext强大和方便。
-
ApplicationContext:这是一个高级的BeanFactory,它继承了BeanFactory的功能,并添加了更多的特性,比如国际化支持、事件发布机制、资源访问机制等。ApplicationContext是Spring推荐使用的容器,因为它提供了更多的企业级功能。
3. Bean的生命周期
在Spring中,bean的生命周期由容器控制。从bean的创建到销毁,Spring提供了多个回调方法,让开发者可以自定义bean的初始化和销毁逻辑。例如,你可以指定一个方法作为初始化方法,在bean创建完成后立即调用;同样,也可以指定一个销毁方法,在容器关闭时调用。
4. 依赖注入 (Dependency Injection)
依赖注入是IOC的一个重要实现方式,它允许在运行时将依赖关系注入到对象中,而不是在编译时就确定。Spring支持三种依赖注入方式:
- 构造器注入:依赖通过构造函数传入。
- 属性注入:依赖通过setter方法设置。
- 字段注入:依赖直接注入到字段中,通常使用@Autowired注解。
5. 配置元数据
Spring的配置元数据可以通过XML文件、注解或Java配置类来提供。这些元数据描述了bean的定义、依赖关系以及配置信息,IOC容器会读取这些元数据来创建和装配bean。
Spring的IOC容器通过控制反转和依赖注入,使得应用程序的结构更加松耦合,提高了代码的可测试性和可维护性。
@EnableAutoConfiguration:自动配置,不用人工配置也能够启动服务
@ComponentScan:自动扫描,自动装配bean(扫描配置所在的包以及子包的bean)。还要有@controller和@service注解,@repository,@component才会被扫描
4.Spring MVC入门
HTTP
• HyperText Transfer Protocol
• 用于传输HTML等内容的应用层协议
• 规定了浏览器和服务器之间如何通信,以及通信时的数据格式。
https://www.ietf.org
https://developer.mozilla.org/zh-CN
HTTP请求是客户端(通常是浏览器)向服务器发送的一种请求,以获取或操作资源。HTTP请求有两种主要类型:GET和POST。
GET请求:用于从服务器检索信息。信息在URL中可见,且有长度限制。GET请求可以被缓存,保留在浏览历史中,并在浏览器的"后退"按钮中保留。
POST请求:用于向服务器发送数据,通常用于表单提交。数据在请求体中发送,不显示在URL中,且没有长度限制。POST请求不会被缓存,也不会保留在浏览器的历史记录中。
三层架构(面试)
-
表现层、业务层、数据访问层
• MVC -
Model:模型层
-
View:视图层
-
Controller:控制层
• 核心组件 -
前端控制器:DispatcherServlet
MVC,全称为Model-View-Controller,是一种广泛使用的软件架构模式,主要用于开发易于维护和修改的用户界面。MVC将应用程序分为三个核心组成部分:Model(模型):模型负责管理应用程序的数据逻辑,以及对数据的存储、读取和更新等操作。它是应用程序的核心,独立于任何用户界面。模型直接与数据打交道,当数据发生变化时,它会通知视图和控制器。
View(视图):视图负责展示模型中的数据给用户,它是用户看到并交互的界面。视图从模型获取数据,然后以某种形式展示出来。当模型数据改变时,视图也会自动更新。
Controller(控制器):控制器是模型和视图之间的协调者,它接收用户的输入并调用模型和视图去完成用户的需求。控制器处理用户请求,控制应用程序的流程,并决定使用哪个视图来展示数据。
MVC模式的主要优点包括:
提高了代码的复用性和可维护性。
分离了关注点,使得每个部分都专注于其核心功能,简化了开发和测试。
支持多个视图共享同一模型,使得数据可以在不同的界面展示。
控制器可以处理来自不同视图的请求,提高了程序的灵活性。
DispatcherServlet是Spring MVC框架中的核心组件,它扮演着前端控制器的角色。在Spring MVC架构中,所有的HTTP请求首先都会被转发到DispatcherServlet。它的主要职责包括:
初始化上下文:加载配置文件,初始化HandlerMapping、HandlerAdapter、ViewResolver等组件。
请求分发:接收HTTP请求,通过HandlerMapping找到合适的处理器(Controller中的方法),并将其与请求映射起来。
处理请求:调用相应的处理器处理请求。处理器处理完请求后,返回一个ModelAndView对象,其中包含了要渲染的视图名和模型数据。
解析视图:通过ViewResolver找到具体的视图组件,将ModelAndView中的模型数据填充到视图中,生成最终的响应结果。
生成响应:将视图渲染的结果返回给客户端。
DispatcherServlet的工作流程如下:
客户端发送HTTP请求到服务器,请求会被转发到DispatcherServlet。
DispatcherServlet根据请求的URL找到对应的HandlerMapping,从而确定处理该请求的Controller和方法。
DispatcherServlet调用HandlerAdapter执行Controller的方法。
Controller处理完请求后,返回一个ModelAndView对象给DispatcherServlet。
DispatcherServlet使用ViewResolver找到具体的视图组件,并将ModelAndView中的数据填充到视图中。
最终,视图渲染的结果被DispatcherServlet包装成HTTP响应,返回给客户端。
通过DispatcherServlet,Spring MVC能够很好地实现MVC模式,将业务逻辑、数据处理和视图渲染分离,提高代码的可读性和可维护性。
Thymeleaf
• 模板引擎
- 生成动态的HTML。
• Thymeleaf - 倡导自然模板,即以HTML文件为模板。
• 常用语法 - 标准表达式、判断与循环、模板的布局。
https://www.thymeleaf.org
相关代码:
package com.nowcoder.community.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.text.SimpleDateFormat;
@Configuration
public class AlphaConfig {
@Bean
public SimpleDateFormat simpleDateFormat() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
}
package com.nowcoder.community.controller;
import com.nowcoder.community.service.AlphaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.*;
@Controller
@RequestMapping(“/alpha”)
public class AlphaController {
@Autowired
private AlphaService alphaService;
@RequestMapping("/hello")
@ResponseBody
public String sayHello() {
return "Hello Spring Boot.";
}
@RequestMapping("/data")
@ResponseBody
public String getData() {
return alphaService.find();
}
@RequestMapping("/http")
public void http(HttpServletRequest request, HttpServletResponse response) {
// 获取请求数据
System.out.println(request.getMethod());
System.out.println(request.getServletPath());
Enumeration<String> enumeration = request.getHeaderNames();
while (enumeration.hasMoreElements()) {
String name = enumeration.nextElement();
String value = request.getHeader(name);
System.out.println(name + ": " + value);
}
System.out.println(request.getParameter("code"));
// 返回响应数据
response.setContentType("text/html;charset=utf-8");
try (
PrintWriter writer = response.getWriter();
) {
writer.write("<h1></h1>");
} catch (IOException e) {
e.printStackTrace();
}
}
// GET请求
// /students?current=1&limit=20
@RequestMapping(path = "/students", method = RequestMethod.GET)
@ResponseBody
public String getStudents(
@RequestParam(name = "current", required = false, defaultValue = "1") int current,
@RequestParam(name = "limit", required = false, defaultValue = "10") int limit) {
System.out.println(current);
System.out.println(limit);
return "some students";
}
// /student/123
@RequestMapping(path = "/student/{id}", method = RequestMethod.GET)
@ResponseBody
public String getStudent(@PathVariable("id") int id) {
System.out.println(id);
return "a student";
}
// POST请求
@RequestMapping(path = "/student", method = RequestMethod.POST)
@ResponseBody
public String saveStudent(String name, int age) {
System.out.println(name);
System.out.println(age);
return "success";
}
// 响应HTML数据
@RequestMapping(path = "/teacher", method = RequestMethod.GET)
public ModelAndView getTeacher() {
ModelAndView mav = new ModelAndView();
mav.addObject("name", "张三");
mav.addObject("age", 30);
mav.setViewName("/demo/view");
return mav;
}
@RequestMapping(path = "/school", method = RequestMethod.GET)
public String getSchool(Model model) {
model.addAttribute("name", "北京大学");
model.addAttribute("age", 80);
return "/demo/view";
}
// 响应JSON数据(异步请求)
// Java对象 -> JSON字符串 -> JS对象
异步请求是一种在网络编程和Web开发中常见的技术,它允许在不阻塞主线程的情况下发送HTTP请求并处理响应。与同步请求相比,异步请求不会暂停程序的执行等待响应,而是继续执行后续的代码,直到响应到达后再处理结果。这种非阻塞的特性对于提高用户体验和程序响应速度至关重要,特别是在处理耗时的网络操作时。
异步请求的优点
提高应用性能:异步请求可以让应用在等待网络响应的同时执行其他任务,避免了长时间的等待导致的UI冻结或程序卡顿。
改善用户体验:在Web应用中,用户可以继续与页面交互,即使某些数据正在后台加载。
资源高效利用:异步处理可以更有效地利用系统资源,避免线程在等待I/O操作时处于空闲状态。
@RequestMapping(path = "/emp", method = RequestMethod.GET)
@ResponseBody
public Map<String, Object> getEmp() {
Map<String, Object> emp = new HashMap<>();
emp.put("name", "张三");
emp.put("age", 23);
emp.put("salary", 8000.00);
return emp;
}
@RequestMapping(path = "/emps", method = RequestMethod.GET)
@ResponseBody
public List<Map<String, Object>> getEmps() {
List<Map<String, Object>> list = new ArrayList<>();
Map<String, Object> emp = new HashMap<>();
emp.put("name", "张三");
emp.put("age", 23);
emp.put("salary", 8000.00);
list.add(emp);
emp = new HashMap<>();
emp.put("name", "李四");
emp.put("age", 24);
emp.put("salary", 9000.00);
list.add(emp);
emp = new HashMap<>();
emp.put("name", "王五");
emp.put("age", 25);
emp.put("salary", 10000.00);
list.add(emp);
return list;
}
}
package com.nowcoder.community.dao;
import org.springframework.stereotype.Repository;
@Repository(“alphaHibernate”)
public class AlphaDaoHibernateImpl implements AlphaDao {
@Override
public String select() {
return “Hibernate”;
}
}
package com.nowcoder.community.dao;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Repository;
@Repository
@Primary
public class AlphaDaoMyBatisImpl implements AlphaDao{
@Override
public String select() {
return “MyBatis”;
}
}
package com.nowcoder.community.service;
import com.nowcoder.community.dao.AlphaDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@Service
//@Scope(“prototype”)
public class AlphaService {
@Autowired
private AlphaDao alphaDao;
public AlphaService() {
System.out.println("实例化AlphaService");
}
@PostConstruct
public void init() {
System.out.println("初始化AlphaService");
}
@PreDestroy
public void destroy() {
System.out.println("销毁AlphaService");
}
public String find() {
return alphaDao.select();
}
}
package com.nowcoder.community;
import com.nowcoder.community.dao.AlphaDao;
import com.nowcoder.community.service.AlphaService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import java.text.SimpleDateFormat;
import java.util.Date;
@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
public class CommunityApplicationTests implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Test
public void testApplicationContext() {
System.out.println(applicationContext);
AlphaDao alphaDao = applicationContext.getBean(AlphaDao.class);
System.out.println(alphaDao.select());
alphaDao = applicationContext.getBean("alphaHibernate", AlphaDao.class);
System.out.println(alphaDao.select());
}
@Test
public void testBeanManagement() {
AlphaService alphaService = applicationContext.getBean(AlphaService.class);
System.out.println(alphaService);
alphaService = applicationContext.getBean(AlphaService.class);
System.out.println(alphaService);
}
@Test
public void testBeanConfig() {
SimpleDateFormat simpleDateFormat =
applicationContext.getBean(SimpleDateFormat.class);
System.out.println(simpleDateFormat.format(new Date()));
}
@Autowired
@Qualifier("alphaHibernate")
private AlphaDao alphaDao;
@Autowired
private AlphaService alphaService;
@Autowired
private SimpleDateFormat simpleDateFormat;
@Test
public void testDI() {
System.out.println(alphaDao);
System.out.println(alphaService);
System.out.println(simpleDateFormat);
}
}
<form method="post" action="/community/alpha/student">
<p>
姓名: <input type="text" name="name">
</p>
<p>
年龄: <input type="text" name="age">
</p>
<p>
<input type="submit" value="保存">
</p>
</form>
application
ServerProperties
server.port=8080
server.servlet.context-path=/community
ThymeleafProperties
spring.thymeleaf.cache=false
MyBatis入门
安装数据库
• 安装MySQL Server
• 安装MySQL Workbench
https://dev.mysql.com/downloads/mysql
https://dev.mysql.com/downloads/workbench
下载解压后,添加my.ini文件
[mysql]
default-character-set=utf8
[mysqld]
port=3306
basedir=E:\work\mysql-8.0.38-winx64\mysql-8.0.38-winx64(修改为MySQL的路径)
max_connections=20
character-set-server=utf8
default-storage-engine=INNODB
然后在系统变量path里配置环境变量
E:\work\mysql-8.0.38-winx64\mysql-8.0.38-winx64\bin
以管理员身份运行cmd
cd E:\work\mysql-8.0.38-winx64\mysql-8.0.38-winx64\bin
mysqld --initialize --console(保存好数据库密码password!!!)
A temporary password is generated for root@localhost: ixNM&0%99Crt
mysqld --install
得在管理员身份运行才能建表成功
MySQL服务启动失败,检查是否是端口号冲突!!!!
MyBatis
• 核心组件
- SqlSessionFactory:用于创建SqlSession的工厂类。
- SqlSession:MyBatis的核心组件,用于向数据库执行SQL。
- 主配置文件:XML配置文件,可以对MyBatis的底层行为做出详细的配置。
- Mapper接口:就是DAO接口,在MyBatis中习惯性的称之为Mapper。
- Mapper映射器:用于编写SQL,并将SQL和实体类映射的组件,采用XML、注解均可实现。
• 示例 - 使用MyBatis对用户表进行CRUD操作。
http://www.mybatis.org/mybatis-3
http://www.mybatis.org/spring
到maven repository官网搜索MySQL复制依赖
com.mysql mysql-connector-j 8.0.33继续搜索mybatis
org.mybatis.spring.boot mybatis-spring-boot-starter 3.0.3package com.nowcoder.community;
import com.nowcoder.community.dao.UserMapper;
import com.nowcoder.community.entity.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.Date;
@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
public class MapperTests {
@Autowired
private UserMapper userMapper;
@Test
public void testSelectUser() {
User user = userMapper.selectById(101);
System.out.println(user);
user = userMapper.selectByName("liubei");
System.out.println(user);
user = userMapper.selectByEmail("[email protected]");
System.out.println(user);
}
@Test
public void testInsertUser() {
User user = new User();
user.setUsername("test");
user.setPassword("123456");
user.setSalt("abc");
user.setEmail("[email protected]");
user.setHeaderUrl("http://www.nowcoder.com/101.png");
user.setCreateTime(new Date());
int rows = userMapper.insertUser(user);
System.out.println(rows);
System.out.println(user.getId());
}
@Test
public void updateUser() {
int rows = userMapper.updateStatus(150, 1);
System.out.println(rows);
rows = userMapper.updateHeader(150, "http://www.nowcoder.com/102.png");
System.out.println(rows);
rows = userMapper.updatePassword(150, "hello");
System.out.println(rows);
}
}
application
#ServerProperties
server.port=8080
server.servlet.context-path=/community
#ThymeleafProperties
spring.thymeleaf.cache=false
#DataSourceProperties
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/community?characterEncoding=utf-8&useSSL=false&serverTimezone=Hongkong
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.maximum-pool-size=15
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.idle-timeout=30000
#MybatisProperties
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.nowcoder.community.entity
mybatis.configuration.useGeneratedKeys=true
mybatis.configuration.mapUnderscoreToCamelCase=true
#logger
logging.level.com.nowcoder.community=debug
user-mapper.xml
<?xml version="1.0" encoding="UTF-8" ?><sql id="insertFields">
username, password, salt, email, type, status, activation_code, header_url, create_time
</sql>
<sql id="selectFields">
id, username, password, salt, email, type, status, activation_code, header_url, create_time
</sql>
<select id="selectById" resultType="User">
select <include refid="selectFields"></include>
from user
where id = #{id}
</select>
<select id="selectByName" resultType="User">
select <include refid="selectFields"></include>
from user
where username = #{username}
</select>
<select id="selectByEmail" resultType="User">
select <include refid="selectFields"></include>
from user
where email = #{email}
</select>
<insert id="insertUser" parameterType="User" keyProperty="id">
insert into user (<include refid="insertFields"></include>)
values(#{username}, #{password}, #{salt}, #{email}, #{type}, #{status}, #{activationCode}, #{headerUrl}, #{createTime})
</insert>
<update id="updateStatus">
update user set status = #{status} where id = #{id}
</update>
<update id="updateHeader">
update user set header_url = #{headerUrl} where id = #{id}
</update>
<update id="updatePassword">
update user set password = #{password} where id = #{id}
</update>
标签:--,Spring,Boot,System,import,println,org,public
From: https://blog.csdn.net/weixin_52297290/article/details/140087523