Spring_前传(1)
软件环境:
- IDE:IDEA
- 插件:spring Initializer
- mongoDB:v5.0.6
- 驱动项目类型:校园网项目
使用Lombok简化领域对象
领域对象(Domain Object)也被称为实体类,代表了业务的状态,贯穿表现层、业务层和持久层,最终持久化到数据库,简单理解领域对象就是数据库的表所对应的类。
在校园网项目中,通知通告就是一个领域对象:
public class Notice
{
private String id;
private String content;
private Integer hit;
private String title;
private String createdTime;
private String updatedTime;
private String publisher;
private String updateUser;
…
}
Lombok是一个Java库,能自动插入编辑器并构建工具,简化Java开发。通过添加注解的方式,不需要为类编写getter或eques方法,同时可以自动化日志变量。
通过IDEA 插件初始化的springboot在Gradle的中可以看见已经自动添加了Lombok依赖下载:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
//HERE
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
implementation 'org.mongodb:mongodb-driver-sync'
}
Lombok 注解地作用:
-
@Data注解的功能等同于**@Getter @Setter@RequiredArgsConstructor**
所有的final属性以及标识为**@NonNull的属性@ToString** @EqualsAndHashCode这些注解的功能之和 ,注解在类上, 为类提供读写属性, 此外还提供了 equals()、hashCode()、toString() 方法
-
@Builder可以为类实现建造者模式 Builder详解 注释为你的类生成相对略微复杂的构建器API。
-
@Clearup在创建资源对象时省略close方法的调用
-
使用val和var简化变量的定义
-
使用**@Log**、@Log4j2等注解简化日志编写
当然不止这些注解,对于Lombok 最简单的理解就是通过注解来简化代码的书写
SpringBoot数据访问
Spring Data是一个总体项目,它以一致的编程模型为 大多数流行的数据访问技术(包括JPA,MongoDB, Redis,Cassandra,Solr和ElasticSearch)提供数据访问支持 :
本项目珠海要依赖于MongoDB所以主要讨论其与MongoDB的框架
在通过插件构造的框架中添加依赖实现其对MongoDB的支持。
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
implementation 'org.mongodb:mongodb-driver-sync'
MongoDB与NoSQL
NoSQL(非关系数据库):
区别于传统的Mysql数据库其构建是基于非关系类型的。
优势:
可以支持超大规模数据存储,灵活的数据模型可以很好地支持Web2.0应用,具有强大的横向扩展能力。
劣势:
缺乏数学理论基础,复杂查询性能不高,大都不能实现事务强一致性,很难实现数据 完整性,技术尚不成熟,缺乏专业团队的技术支持,维护较困难等。
MongoDB就是典型的非关系数据库。由C++语言编写的,是一个基于分布式文件存储的开源数据库系统。在高负载的情况下,添加更多的节点,可以保证服务器性能。
Mongodb的优点:
-
易于使用
面向文档的数据库使用更灵活的“文档”模型取代了“行”的概念,通过嵌入文档和
数组,面向文档的方式可以仅用一条记录来表示复杂的层次关系,这与使用现代面向对 象语言的开发人员思考数据的方式非常契合 -
易于扩展
MongoDB 的设计采用了横向扩展,面向文档的数据模型使跨多台服务器拆分数据更加 容易,MongoDB
会自动平衡跨集群的数据和负载,自动重新分配文档,并将读写操作 路由到正确的机器上 -
功能丰富
MongoDB 是通用型数据库,除了创建、读取、更新和删除数据外,它还提供了数据库 管理系统的常见功能和许多其他独特的功能 -
性能卓越
MongoDB在其 WiredTiger 存储引擎中使用了机会锁,以最大限度地提高并发和吞吐 量,它会使用尽可能多的
RAM(内存)作为缓存,并尝试为查询自动选择正确的索引
MongoDB的存储解析:
面向文档的数据库,在MongoDB中基本的概念是文档、集合、数据库。
SQL术语/概念 | MongoDB术语/概念 | 解释/说明 |
---|---|---|
database | database | 数据库 |
table | collection | 数据库表/集合 |
row | document | 数据记录行/文档 |
column | field | 数据字段 |
index | index | 索引 |
table joins | 表连接,MongoDB不支持 | |
primary key | primary key | 主键,MongoDB自动将_id字段设置为主键 |
直观解释:
集合就是 MongoDB 文档组,类似于 RDBMS (关系数据库管理系统:Relational Database Management System)中的表格。 集合存在于数据库中,集合没有固定的结构,这意味着你在对集合可以插入不同格 式和类型的数据,但通常情况下我们插入集合的数据都会有一定的关联性。
更多内容:MongoDB
Spring Boot使用MongoDB
Spring 与 MongoDB的连接配置文件:
对于需要连接的MongoDB,spring框架一般通过外部的配置文件,如 *.properties或者 *.yml文件来对应用进行配置。 * yml的语法更简洁,本课程推荐使用yml格式配置文件。
删除src/main/recources目录下idea自动创建 application.properties 配置文件,并在该目 录下新建 application.yml文件。
输入以下内容
spring:
data:
mongodb:
uri: mongodb://localhost:端口/数据库名称
下面开始以实战形式建立连接与操作数据库显示在前台页面。
Spring Data实战
项目要求:
- 在项目中实现在页面中显示存储在MongoDB中的通知通告信息
- 参照Notice领域对象的实现,在项目顶层包下创建domain包,在domain包下创建校园网中的其它领域对象,包括Carousel(图片新闻)、Collaboration(校企合作)、Download(下载中心),Dynamic(学院动态)、Employment(就业情况),Graduate(校友风采),MemberStudent(团学工作)、Overview(学院概况)、TeacherResearch(教研情况)
- 参照NoticeRepository的实现,为每个领域对象创建对应的Repository接口
- 在项目顶层包下创建controller包,在controller包下创建通知通告信息的控制器类NoticeController(使用@Controller注解),在该类中创建处理通知通告信息列表、增加、删除、修改、查询(通告ID)请求的方法list、add、delete、update和find。
- 在resources目录的templates文件夹中创建Thymeleaf模板页面,实现通知通告信息的列表、增加、删除、修改、查询(通告ID)功能
- 在浏览器中测试项目。
首先分析第一个是如何实现的:
在项目中实现在页面中显示存储在MongoDB中的通知通告信息
首先domain包中存在一个Notice类
package com.example.springdemo.domain;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
@Data
@Builder
//以上已经说过
@Document
//@Document注解是spring Data mongodb提供的一个注解,标注在实体类上,类似于hibernate的entity注解,标明由mongo来维护该表。把一个java类声明为mongodb的文档,可以通过collection参数指定这个类对应的文档。
public class Notice {
@Id//自动生成的主键ID 主键,不可重复,自带索引,可以在定义的列名上标注,需要自己生成并维护不重复的约束。
private String id;
private String title;
private String content;
}
这里就是前面所说的领域对象的创建,满足这些就要考虑如何将领域对象传输入持久层,也就是如何传输到数据库中。要传输就要通过Spring Data MongoDB中Repository接口来实现传输功能。
定义访问数据库的Repository接口,就需要继承 Spring Data Mongo 的 MongoRepository接口来实现其中的功能。
对于本此时用的mongodb的专有接口实际上是多次继承而来的,其结构如下:
- CrudRepository——Crud就是增、删、改、查的缩写,即Create、Read、Update、Delete 的首字母连在一起,该接口定义了基础的增、删、改、查操作
- PagingAndSortingRepository——从命名上就可以看出是定义了分页和排序的操作,它继承 了CrudRepository之后又提供了分页和排序的操作
- MongoRepository——和上面两个通用的接口不同,该接口是一个和数据库相关的接口,它继 承了PagingAndSortingRepository接口,同时封装了一些MongoDB特有的方法
- 自定义的访问数据库的Repositoty接口,只要继承MongoRepository接口,甚至不需要添加任 何一行代码,就可以完成基本的增、删、改、查操作
- 自定义Repository接口中,可以直接使用方法名关键字进行查询操作,例如:
List<Notice> findByTitleLikeOrderByUpdatedTimeDesc(String title);
编写针对Notice领域对象对应的Repository接口
在项目的顶层包下新建一个repository的软件包,在该包中新建一个继承了 MongoRepository的NoticeRepository接口,代码如下所示:
package com.example.springdemo.repository;
import com.example.springdemo.domain.Notice;
import org.springframework.data.mongodb.repository.MongoRepository;
public interface NoticeRepository extends MongoRepository<Notice,String>
{
}
实际上只用继承本接口即可,不需要写具体的增删改查操作。
修改NoticeController类,使该类通过NoticeRepository访问数据库中的数据,代码如下所示:
/*
@RestController注解,相当于@Controller+@ResponseBody两个注解的结合,返回json数据不需要在方法前面加@ResponseBody注解了,但使用@RestController这个注解,就不能返回jsp,html页面,视图解析器无法解析jsp,html页面
使用@Controller 注解,在对应的方法上,视图解析器可以解析return 的jsp,html页面,并且跳转到相应页面
若返回json等内容到页面,则需要加@ResponseBody注解
*/
@Controller
@AllArgsConstructor
public class NoticeController
{
private final NoticeRepository noticeRepository;
@GetMapping("/notices")
public String listNotice(Model model)
{
val list=noticeRepository.findAll();
model.addAttribute("notices",list);
return "test";
}
}
在项目的顶层包中创建util软件包,在改包中新建InitDatabase类,该类在数据库中为集 合添加初始数据,代码如下所示:
package com.example.springdemo.util;
import com.example.springdemo.domain.Notice;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.stereotype.Component;
/*
@Controller 控制器(注入服务)
用于标注控制层,相当于struts中的action层
@Service 服务(注入dao)
用于标注服务层,主要用来进行业务的逻辑处理
@Repository(实现dao访问)
用于标注数据访问层,也可以说用于标注数据访问组件,即DAO组件
@Component (把普通pojo实例化到spring容器中,相当于配置文件中的 )
依赖注入(Dependency Injection):它是 Spring 框架核心 IOC 的具体实现。在编写程序时,通过控制反转,把对象的创建交给了 Spring,但是代码中不可能出现没有依赖的情况。IOC 解耦只是降低他们的依赖关系,但不会消除。例如:业务层仍会调用持久层的方法。那这种业务层和持久层的依赖关系,在使用 Spring 之后,就让 Spring 来维护了。简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取。
@Component注解表明一个类会作为组件类,并告知Spring要为这个类创建bean。
*/
@Component
public class InitDatabase {
@Bean//@Bean注解告诉Spring这个方法将会返回一个对象,这个对象要注册为Spring应用上下文中的bean。通常方法体中包含了最终产生bean实例的逻辑。我们在标有该注解的方法中定义产生这个bean的逻辑。下面就注册了多个bena对象到容器中
public CommandLineRunner init(MongoOperations operations){
return arg->{
if(operations.findAll(Notice.class).isEmpty()){
operations.insert(Notice.builder()
.title("title1")
.content("content1").build());
operations.insert(Notice.builder()
.title("title2")
.content("content2").build());
operations.insert(Notice.builder()
.title("title3")
.content("content3").build());
}
};
}
}
- 参照Notice领域对象的实现,在项目顶层包下创建domain包,在domain包下创建校园网中的其它领域对象,包括Carousel(图片新闻)、Collaboration(校企合作)、Download(下载中心),Dynamic(学院动态)、Employment(就业情况),Graduate(校友风采),MemberStudent(团学工作)、Overview(学院概况)、TeacherResearch(教研情况)
- 参照NoticeRepository的实现,为每个领域对象创建对应的Repository接口
这个就很简单不再赘述:直接一个一个创建就ok。和Notice只是名字有修改。
- 在项目顶层包下创建controller包,在controller包下创建通知通告信息的控制器类NoticeController(使用@Controller注解),在该类中创建处理通知通告信息列表、增加、删除、修改、查询(通告ID)请求的方法list、add、delete、update和find。
- 在resources目录的templates文件夹中创建Thymeleaf模板页面,实现通知通告信息的列表、增加、删除、修改、查询(通告ID)功能
(1)@RequestMapping
@RequestMapping如果没有指定请求方式,将接收Get、Post、Head、Options等所有的请求方式.
(2)@GetMapping
@GetMapping是一个组合注解,是@RequestMapping(method = RequestMethod.GET)的缩写。该注解将HTTP Get 映射到 特定的处理方法上。
(3)@PostMapping
@PostMapping是一个组合注解,是@RequestMapping(method = RequestMethod.POST)的缩写。get方式的安全性较Post方式要差些,包含机密信息的话,建议用Post数据提交方式;(请求的结果有持续性的副作用,例如,数据库内添加新的数据行。)
(4)@PutMapping
和PostMapping作用等同,都是用来向服务器提交信息。如果是添加信息,倾向于用@PostMapping,如果是更新信息,倾向于用@PutMapping。两者差别不是很明显。
由此可见对于浏览器发来的请求POST比较合适作为传递内容方式。对于后端首先要解决向前端传输显示内容:
通过 NoticeRepository 类和 @GetMapping("/notices") 来实现相应页面的请求并将内容显示,通过NoticeRepository 类获取数据库内容并将数据库内容放到 list 里面再通过html中标记的 notices 来实现显示和后端的链接。
private final NoticeRepository noticeRepository;
@GetMapping("/notices")
public String list(Model model)
{
val list=noticeRepository.findAll();
model.addAttribute("notices",list);
return "listNotice";
}
在前端我们通过 data-th-each= 标签遍历notices的内容并将notices的内容通过table标签完全的显示出来,但是前端和后端的变量是如何通过一个相同的变量连接在一起的呢?这是spring框架所做的工作,只要理解,前后端能通过相同的变量连接在一起。
<table style="text-align:left" >
<th style="text-align:left">
<td style="width:400px;">编号</td>
<td style="width:200px">标题</td>
<td style="width:200px">内容</td>
</th>
<!--连接前后端的notices-->
<tr data-th-each="item:${notices}" >
<td style="width:400px" data-th-text="${item.id}"></td>
<td style="width:200px" data-th-text="${item.title}"></td>
<td style="width:200px" data-th-text="${item.content}"></td>
<td style="width:200px">
</td>
</tr>
</table>
题目要求还有其他四种操作方式,那是如何让后端识别不同的操作呢?这就需要通过在表单中加入一个隐藏的参数:_method,设置value值来传递信息
<input type ="hidden" name="_method" value="delete" />
我们通过< form > 和< input >标签来向后端传输数据,详细解释看注释:
<!--from标签指定了action和请求的办法,这里使用的是POST请求。-->
<form action="/notices" method="POST">
<!--input标签指定了传输的数据类型,使用隐式方便向后端传输数据,在后端直接在方法里填入参数和name值相同的变量就可直接获取-->
<input type ="hidden" name="_method" value="delete" />
<!--获取用户输入的id号码-->
id:<input type="text" name="id">
<br>
<!--对产生的按钮设置显示值-->
<input type="submit" value="Submit">
</form>
后端就直接在方法的形参中获取同名的参数内容即可:
@PostMapping("/notices")
public String postcallback
(Model model,String _method,String id,String content,String title)
//通过隐式参数获取想要的信息
{
if(_method.equals("delete"))
{
Notice deletn =null;
if (!id.equals(""))
deletn = noticeRepository.findById(id).get();
if (deletn != null)
noticeRepository.delete(deletn);
}
else if (_method.equals("add"))
{
if (!title.equals("") && !content.equals("") )
noticeRepository.insert(Notice.builder()
.title(title)
.content(content)
.id(null).build());
}
else if (_method.equals("find"))
{
if (!id.equals("") )
model.addAttribute("findnotices",noticeRepository.findById(id).get());
}
val list=noticeRepository.findAll();
model.addAttribute("notices",list);
return "listNotice";
}
补充:HTML的标签定义
< table >标签定义 HTML 表格
< tr > 元素定义表格行,< th > 元素定义表头,< td > 元素定义表格单元。
更复杂的 HTML 表格也可能包括 < caption >、< co l>、< colgroup >、< thead >、< tfoot> 以及 < tbody> 元素。
< form > 标签用于为用户输入创建 HTML 表单。
表单能够包含 input 元素,比如文本字段、复选框、单选框、提交按钮等等。
表单还可以包含 menus、textarea、fieldset、legend 和 label 元素。
表单用于向服务器传输数据。
< input > 标签用于搜集用户信息。
根据不同的 type 属性值,输入字段拥有很多种形式。输入字段可以是文本字段、复选框、掩码后的文本控件、单选按钮、按钮等等。
补充:一些报错的解决办法。
运行后报错:
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2022-03-06 12:11:08.452 ERROR 18420 --- [ restartedMain] o.s.boot.SpringApplication : Application run failed
java.lang.IllegalStateException: Failed to execute CommandLineRunner。
解决办法:
找到MongoDB的安装目录:在栏中输入CMD打开CMD
输入以下命令:
mongod --dbpath 数据库路径
如图: 数据库不一定和Mong0DB安装的位置相同,需要找到自己安装的位置
输入后可以看见开始运行其服务:
打开COMPASS 直接点击链接,连接后可以看见正常运行:
打开后可以看见对应数据: