一、说明
本博客基于《慕课网-2小时快速上手SpringBoot》,本地工程对应:E:\01.study\04.springboot\imooc2hours\hello-springboot
springboot是啥这里不过多介绍,自行google,本篇文章作为springboot系列文章的开篇之作,意在短时间内完成一个最简单springboot的入门示例,让读者可以对其大致有个了解,每部分深入详细介绍将在后续文章中介绍。
二、新建一个springboot项目,简单运行helloworld
这里使用IDEA创建,也可以使用spring官网(http://start.spring.io)在线创建
1、新建一个springboot项目
2、输入groupID、artifactID,next
3、选择一个非SNAPSHOT版本,这里使用2.1.X而不是2.2.X
4、输入项目名称,选择路径(路径不要有中文、不要有空格),点击finish
5、创建项目后,添加一个Controller
package com.mzj.hellospringboot;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/hello")
public String say(){
return "mazhongjia";
}
}
6、启动项目:
方式1:运行自动创建的:HelloSpringbootApplication类
方式2:通过进入项目根目录,执行mvn spring-boot:run命令启动
方式3:①先将项目打包:mvn clean package -Dmaven.test.skip=true -U②运行生成的jar包:java -jar target/xxxx.jar
7、测试
1)通过浏览器访问:http://127.0.0.1:8080/hello
2)通过curl访问:curl http://127.0.0.1:8080/hello
三、配置简介
1、配置文件路径
springboot的配置文件有两种类型:①application.properties②application.yml(推荐)
maven项目位于:src\main\resources\application.properties
2、代码注解中引入配置项
1)在yml中自定义配置项:注意value值前面要有空格(yml格式)
2)使用配置项:
单个配置项使用@Vlaue注解引用
3、配置中引入配置
4、配置太多、太分散时使用【对象配置类】进行优化
1)给配置定义一个前缀:这里时myconfig(注意这里前缀不能大些字母)
注意:前缀下的配置项要前面空2格
2)建立【对象配置类】
使用@Component @ConfigurationProperties(prefix = "myconfig")注解,其中prefix值为前缀
package com.mzj.hellospringboot;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
@Component
@ConfigurationProperties(prefix = "myconfig")
public class MyConfig {
private BigDecimal minMoney;
private String description;
public BigDecimal getMinMoney() {
return minMoney;
}
public void setMinMoney(BigDecimal minMoney) {
this.minMoney = minMoney;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
3)使用类中,通过Autowired自动装载
@RestController
public class HelloController {
@Autowired
private MyConfig myConfig;
@GetMapping("/hello")
public String say(){
return "mazhongjia : " + myConfig.getMinMoney()+"----"+myConfig.getDescription();
}
}
4)测试结果:
这种简化写法的好处是:不用在每一个变量上使用@Value,一个个的注解、分散定义
什么时候单个定义、什么时候统一定义一个配置类中?看业务需要
5、多环境支持
application-dev.yml
server:
port: 8081
servlet:
context-path: /mazhongjia
myconfig:
minMoney: 0.001
description: 我们的配置项是${myconfig.minMoney}吧
application-fac.yml
server:
port: 8081
servlet:
context-path: /mazhongjia
myconfig:
minMoney: 1
description: 我们的配置项是${myconfig.minMoney}吧
application.yml
spring:
profiles:
active: dev
spring.profiles.active改成fac:
在发布生产时,可以不用修改application.yml,使用:指定-Dspring.profiles.active=dev去设置:
①通过mvn clean package打包后,允许jar包时执行如下命令:
java -jar -Dspring.profiles.active=fac target/hello-springboot-0.0.1-SNAPSHOT.jar
四、Controller
1、类相关注解
注解 | 说明 |
@Controller | 处理http请求 |
@RestController | Spring4之后新加的注解,原来返回json(字符串)需要@ResponseBody+@Controller,ResponseBody可以修饰在类上,也可以修饰在方法上 现在,只需要@RestController这一个注解即可 也就是@RestController=@ResponseBody+@Controller |
@RequestMapping | 配置url映射 |
什么情况下使用@ResponseBody+@Controller?
答:在同一个Controller中,有的方法想返回html网页时
比如:下面的Controller声明中返回的hh会被识别为字符串
而下面的Controller声明(去掉@ResponseBody)返回的hh会被识别为html文件名
2、能够返回网页
说明1:类似于spring mvc中InternalResourceViewResolver效果:controller返回字符串,对应到网页文件名
说明2:此种方式使用较少,因为现在都是前后端相分离的开发模式
说明3:通常以模板渲染的方式实现:
1)增加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2)增加html网页
3)Controller注解为@Controller
4)Controller对应方法返回hh
HelloController4Html.java
package com.mzj.hellospringboot;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HelloController4Html {
@Autowired
private MyConfig myConfig;
@GetMapping("/hello1")
public String say(){
return "hh";
}
}
5)启动springboot,输入网址:http://localhost:8081/mazhongjia/hello1
结果:
3、不同的URL都请求到同一个controller的同一个方法
HelloController.java
@GetMapping({"hello2","hello3"})
public String say2ForOne(){
return "say2ForOne : " + myConfig.getMinMoney()+"----"+myConfig.getDescription();
}
运行结果:启动服务器,访问:http://localhost:8081/mazhongjia/hello3与http://localhost:8081/mazhongjia/hello2
结果相同:
4、Controller中一般在类上通过@RequestMapping注解定义这个Controller的不同method相同的URL部分
5、Controller中方法注解
5.1 Controller方法上使用@RequestMapping,而不是@GetMapping、@PostMapping,可以支持Get+Post两种类型请求访问
但是这种方式不好,最佳实践:使用@GetMapping、@PostMapping替代@RequestMapping,两种注解实现方式不一样
5.2 获取请求中参数
注解名 | 说明 |
@PathVariable | 获取url中的数据 |
@RequestParam | 从请求中获取(获取请求参数的值),body或者?后面的数据 |
1)@PathVariable
此种方式URL比较简洁
HelloController.java
@GetMapping({"/say/{id}")
public String say2PathVariable(@PathVariable("id") Integer id){
return "id : " + id;
}
2)@RequestParam
HelloController.java
@GetMapping("/say2")
public String say2RequestParam(@RequestParam("id") Integer id){
return "id:RequestParam : " + id;
}
注意:使用@RequestParam方式时,方法参数注解中的"id"对应url中?后面的key
扩展:使用@RequestParam方式时,参数设置非必须,不传时设置默认值
HelloController.java
@GetMapping("/say3")
public String say2RequestParam2(@RequestParam(value = "id",required = false,defaultValue = "-1") Integer id){
return "id:RequestParam2 : " + id;
}
注意:如果请求类型为Post,则传递的参数的方式可以多样化(通过URL param携带、通过body携带....等等)
以下两种POST请求设置参数id的位置,都可以在服务端通过@RequestParam接收(上图)
这是因为,springboot框架对post类型请求传递参数进行了兼容,只要使用@RequestParam注解,就可以实现对不同位置传递参数的接收。
这里推荐用参数在body的x-www-form-urlencoded的方式:因为多数人使用这种方式:)
五、数据库访问:Spring-Data-JPA
JPA(Java Persistence API):定义了一系列对象持久化的标准,目前实现这一规范的产品有Hibernate、TopLink等。
Spring-Data-JPA就是springboot对hibernate的整合,让开发者更加方便的使用:不用写一行sql语句,就可以完成对数据库的操作。
示例:通过实现如下四个URL请求(restful风格的API),来演示spring-data-jpa的使用
1、获取红包列表:GET、/luckymoneys
2、创建一个红包:POST、/luckymoneys
3、通过id查询红包:GET、/luckymoneys/id
4、通过id更新红包:PUT、/luckymoneys/id
1、引入依赖
- springboot中spring data jpa依赖
- 使用的数据库依赖,这里是mysql
<!--spring data jpa-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
2、增加数据库配置
application.yml中增加:
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/luckymoney
username: root
password: 123456
注意:设置数据库驱动时有两个选项:
要选择新版本驱动,不要选择旧版本
3、增加jpa的配置
jpa:
hibernate:
ddl-auto: update
show-sql: true
其中ddl-auto:
ddl-auto:create----每次运行该程序,如果表已经存在,则先删除,再创建 ddl-auto:create-drop----每次程序结束的时候会清空表 ddl-auto:update----每次运行程序,如果表已经存在,则不会删除,表内有数据不会清空,只会更新 ddl-auto:validate----运行程序会校验数据与数据库的字段类型是否相同,不同会报错 |
show-sql:控制台上显示执行的sql语句,方便开发调试
4、新建数据库
报错处理
此时启动springboot会报错:
You must configure either the server or JDBC driver (via the serverTimezone configuration property) to use a more specifc time zone value if you want to utilize time zone support.
解决办法:
此时,可以正常启动.
5、创建实体类(不需要手工建表,通过实体类spring-data-jpa可以自动创建表)
package com.mzj.hellospringboot.luckymoney;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.math.BigDecimal;
@Entity
public class Luckymoney {
@Id//声明该属性为主键
@GeneratedValue//声明该列自动生成
private Integer id;
private BigDecimal money;
/**
* 发送方
*/
private String producer;
/**
* 接收方
*/
private String consumer;
public Luckymoney() {
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public BigDecimal getMoney() {
return money;
}
public void setMoney(BigDecimal money) {
this.money = money;
}
public String getProducer() {
return producer;
}
public void setProducer(String producer) {
this.producer = producer;
}
public String getConsumer() {
return consumer;
}
public void setConsumer(String consumer) {
this.consumer = consumer;
}
}
其中使用的注解,都是JPA中的标准注解(javax.persistence包下):
@Id注解:设置表的主键
@GeneratedValue注解:设置该列自增(自增会自动创建序列)
@Entity注解:
创建实体类后,启动springboot会自动创建对应表:
- 如果表自增列对应的序列存在则先删除
- 如果表存在则先删除
- 创建序列
- 创建表
6、示例开发
1)创建Controller:LuckymoneyController
用@RestController注解标记类
2)创建DAO接口:JPA中约定叫XXRepository:LuckmoneyRepository
继承extends JpaRepository<A,B>,其中泛型:A:对应的数据库实体类,B:数据库主键类型
继承该接口后,就自带了很多通用的方法,可以直接使用,如果需要特殊的再进行扩展
package com.mzj.hellospringboot.luckymoney;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* 泛型:A:对应的数据库实体类,B:数据库主键类型
*/
public interface LuckmoneyRepository extends JpaRepository<Luckymoney,Integer> {
}
3)在Controller中Autowired这个DAO接口
4)使用Dao接口实现业务逻辑
- 获取红包列表:GET、/luckymoneys
- 创建一个红包:POST、/luckymoneys
- 通过id查询红包:GET、/luckymoneys/id
- 通过id更新红包:PUT、/luckymoneys/id
完整的Controller如下:
package com.mzj.hellospringboot.luckymoney;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;
@RestController
public class LuckymoneyController {
@Autowired
private LuckmoneyRepository luckmoneyRepository;
/**
* 获取红包列表
*
* @return
*/
@GetMapping("/luckymoneys")
public List<Luckymoney> list() {
return luckmoneyRepository.findAll();
}
/**
* 创建红包
*/
@PostMapping("/luckymoneys")
public Luckymoney create(@RequestParam("producer") String producer,
@RequestParam("money") BigDecimal money) {
Luckymoney luckymoney = new Luckymoney();
luckymoney.setProducer(producer);
luckymoney.setMoney(money);
return luckmoneyRepository.save(luckymoney);
}
/**
* 通过id查询红包
*
* @param id
* @return
*/
@GetMapping("/luckymoneys/{id}")//这里的id对应@PathVariable("id")的id
public Luckymoney findById(@PathVariable("id") Integer id) {
return luckmoneyRepository.findById(id).orElse(null);//如果查不到返回null
}
/**
* 更新红包
*
* @param id
* @param consumer
* @return
*/
@PutMapping("/luckymoneys/{id}")
public Luckymoney update(@PathVariable("id") Integer id,
@RequestParam("consumer") String consumer) {
//更新操作需要首先查询出需要更新的整条记录,然后设置修改的内容
Optional<Luckymoney> optionalLuckymoney = luckmoneyRepository.findById(id);
//在使用这个对象进行更新
if (optionalLuckymoney.isPresent()) {//判断是否查询到这个id的记录
Luckymoney luckymoney = optionalLuckymoney.get();
luckymoney.setConsumer(consumer);
return luckmoneyRepository.save(luckymoney);//更新与创建使用的都是save接口,区别是,是否传递主键
}
return null;
}
}
使用postman测试
- 获取红包列表:localhost:8081/mazhongjia/luckymoneys
- 创建一个红包:localhost:8081/mazhongjia/luckymoneys producer:mazhongjia123,,,,money:100
- 通过id查询红包:localhost:8081/mazhongjia/luckymoneys/1
- 通过id更新红包:localhost:8081/mazhongjia/luckymoneys/5 consumer:XIAOMAGE
7、事务
事务操作一般都放在Service层中
1)创建LuckymoneyService
2)用@Service注解修饰该类
3)在Service中Autowired这个DAO接口
4)在Service中编写业务代码(这里是发送两个红包),并用@Transactional
注解修饰这个方法
注:要使用spring包的Transactional注解
完整service代码:
package com.mzj.hellospringboot.luckymoney;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
@Service
public class LuckymoneyService {
@Autowired
private LuckmoneyRepository luckmoneyRepository;
@Transactional
public void createTwo(){
Luckymoney luckymoney1 = new Luckymoney();
luckymoney1.setProducer("mazhongjia");
luckymoney1.setMoney(new BigDecimal("520"));
luckmoneyRepository.save(luckymoney1);
Luckymoney luckymoney2 = new Luckymoney();
luckymoney2.setProducer("mazhongjia");
luckymoney2.setMoney(new BigDecimal("1314"));
luckmoneyRepository.save(luckymoney2);
}
}
5)为Service编写Controller方法
/**
* 事务示例:一起发送两个红包,要么全成功,要么全失败
*/
@PostMapping("/luckymoneys/two")
public void createTwo(){
luckymoneyService.createTwo();
}
6)在Controller中Autowired这个Service
@Autowired
private LuckymoneyService luckymoneyService;
原理:这里所说的事务指的是数据库事务,比如mysql,所以事务的功能,是数据库提供的,而不是java程序的事务,这里的注解是程序帮助我们自动执行commit、回滚等操作,最终这些提交都是提交给数据库的,所以需要数据库首先支持事务,有些数据库不支持事务,即使这里加了注解也没有用;至于mysql比较特殊,默认建表是MyISAM引擎,这种引擎下是不支持事务的,需要修改表的引擎为InnoDB引擎才支持事务。
实际开发中,事务应用非常广泛,比如买东西时创建订单与扣库存,必须在一个事务中,要么全成功,要么全失败。