Spring Data
//安装MongoDB
sudo docker run -itd --name mongo -p 27017:27017 registry.cn-hangzhou.aliyuncs.com/ykd_project/mongo:7.0.12
//验证是否启动成功
sudo docker exec -it mongo mongosh admin
//创建数据库
use practice
//退出登录
exit
Spring Data CRUD
在配置完毕 MongoDB 以后,就可以编程操作了。
对数据库的操作一定要放在 @Service
类中,而不是放在 @Controller
类中;且 @Controller
类可以调用 @Service
类的方法,反之则不行。这是 SpringMVC
的经典架构设计理念。
@Service
类主要用于不易变的核心业务逻辑。@Controller
类与前端页面紧密配合,调用@Service
服务读写数据,从而响应前端请求,
这个设计大家可能目前没有体会,但是没关系,希望在学习、实战中持续理解和思考。
1. 新增数据
新增数据就是向数据库中插入一条数据。在 Java 世界中万物皆对象,所以,所谓数据就是实例对象。
import org.springframework.data.mongodb.core.MongoTemplate;
@Autowired
private MongoTemplate mongoTemplate;
public void test() {
Song song = new Song();
song.setSubjectId("s001");
song.setLyrics("...");
song.setName("成都");
mongoTemplate.insert(song);
}
在本课程第 2 章已经学过,使用 @Autowired
可以让系统自动注入 MongoTemplate
的实例。
只需要调用 mongoTemplate.insert()
方法就能把对象存入数据库。非常简单。
页面上打印出了新增的结果歌曲,系统会自动为 id
属性(主键)赋值:
{"songResult":{"id":"5e55db3a461b9b3c3e1d6a56","name":"成都","lyrics":"...","subjectId":"s001"}}
2. 查询数据
一条语句就可以了:
mongoTemplate.findById(songId, Song.class)
注意:findById()
方法第 1 个参数就是主键 id,第 2 个参数是具体的类,写法是 类名.class
3. 修改数据
修改数据的语句就略复杂一些了。因为修改的操作包括两个部分:
- 修改哪条数据?
- 哪个字段修改成什么值?
所以,修改操作也分为部分:修改条件和修改的字段。
// 修改 id=1 的数据
Query query = new Query(Criteria.where("id").is("1"));
// 把歌名修改为 “new name”
Update updateData = new Update();
updateData.set("name", "new name");
// 执行修改,修改返回结果的是一个对象
UpdateResult result = mongoTemplate.updateFirst(query, updateData, Song.class);
// 修改的记录数大于 0 ,表示修改成功
System.out.println("修改的数据记录数量:" + result.getModifiedCount());
先使用条件对象 Criteria
构建条件对象 Query
实例,然后在调用修改对象 Update
的方法 .set()
设置需要修改的字段。
最后调用 mongoTemplate.updateFirst(query, updateData, Song.class)
方法完成修改;第 3 个参数是具体的类。
本修改数据的演示中, 使用了约定:主键不能修改;且其它字段值为 null 表示不修改,值为长度为 0 的字符串 "" 表示清空此字段。
这样,我们就可以使用一个 Song 对象区分出“哪些字段需要修改”、“哪些字段不需要修改”、“哪些字段需要清除值”等多种情况。服务接口只需要 Song 对象一个参数即可,使用起来也比较简便。
大家需要思考和体会这段逻辑,在今后的工作中能够根据实际情况灵活运用哦。
4. 删除数据
删除数据也比较简单,只需要精确确定需要删除哪什么数据即可。
调用 mongoTemplate.remove()
方法即可删除数据,参数是对象,表示需要删除哪些数据。
示例代码:
Song song = new Song();
song.setId(songId);
// 执行删除
DeleteResult result = mongoTemplate.remove(song);
// 删除的记录数大于 0 ,表示删除成功
System.out.println("删除的数据记录数量:" + result.getDeletedCount());
创建一个对象并设置好属性值,作为删除的条件,符合条件的数据都将被删除。可以设置更多的属性值来提高精确性,但通过主键来删除数据,是保证不误删的一个比较好的办法。
删除数据一定要谨慎哦
由于第一步执行新增数据, ID 是成动态生的,所以一步不知道具体该删除哪一条数据,所以会失败。但是没关系,接下来的作业中自己实现并验证。
数据库操作中,用的最少的操作是删除,用的最多的操作就是查询了,除了根据主键查询,更多的是需要根据条件查询。
.imp-->im.java-->control
package fm.douban.app.control;
import com.alibaba.fastjson.JSON;
import fm.douban.model.Song;
import fm.douban.service.SongService;
import fm.douban.service.SubjectService;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SongListControl {
private static final Logger LOG = LoggerFactory.getLogger(SongListControl.class);
@Autowired
private SubjectService subjectService;
@Autowired
private SongService songService;
@PostConstruct
public void init() {
LOG.error("SongListControl 启动啦");
if (subjectService != null) {
LOG.info("subjectService 实例注入成功。");
} else {
LOG.info("subjectService 实例注入失败。");
}
}
@RequestMapping("/songadd")
public Map add() {
Map returnData = new HashMap();
Song song=new Song();
song.setLyrics("...");
song.setName("ykd");
song.setSubjectId("001");
Song songResult = songService.add(song);
returnData.put("songResult", songResult);
return returnData;
}
@RequestMapping("/songget")
public Map get(@RequestParam String id) {
Map returnData = new HashMap();
Song songResult = songService.get(id);
returnData.put("songResult", songResult);
return returnData;
}
@RequestMapping("/songmodify")
public Map modify(@RequestParam String id) {
Map returnData = new HashMap();
Song song = new Song();
// 必须指定修改哪一条数据
song.setId(id);
// 仅演示:用一个随机数代替歌词
song.setLyrics(String.valueOf(Math.random()));
boolean result = songService.modify(song);
returnData.put("modify_success", result);
return returnData;
}
@RequestMapping("/songdelete")
public Map delete(@RequestParam String id) {
Map returnData = new HashMap();
boolean result = songService.delete(id);
returnData.put("delete_success", result);
return returnData;
}
private Song buildSong() {
Song song = new Song();
// song 对象的 id 主键不要赋值,MongoDB 会自动填入唯一的随机字符串
song.setSubjectId("s001");
song.setLyrics("...");
song.setName("成都");
song.setId("001");
Song songResult = songService.add(song);
return songResult;
}
@RequestMapping("/songcheck")
public Map check() {
Map returnData = new HashMap();
// 新增一条数据,失败则中断
Song addedSong = buildSong();
String id = addedSong.getId();
returnData.put("add_song_id", id);
if (id != null) {
returnData.put("add_result", true);
} else {
returnData.put("add_result", false);
return returnData;
}
// 读取刚刚新增的数据
Song getResult = songService.get(id);
if (getResult != null) {
returnData.put("get_result", true);
} else {
returnData.put("get_result", false);
return returnData;
}
getResult.setName("成都-2");
getResult.setLyrics("......");
boolean modifyResult = songService.modify(getResult);
Song modifiedSong = songService.get(id);
if (modifyResult && modifiedSong != null && "成都-2".equals(modifiedSong.getName())
&& "......".equals(modifiedSong.getLyrics())) {
returnData.put("modify_result", true);
} else {
returnData.put("modify_result", false);
return returnData;
}
boolean delResult = songService.delete(id);
Song deletedSong = songService.get(id);
if (delResult && deletedSong == null) {
returnData.put("delete_result", true);
} else {
returnData.put("delete_result", false);
}
LOG.info("check song CRUD service result");
LOG.info(JSON.toJSONString(returnData));
return returnData;
}
}
package fm.douban.service.impl;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import fm.douban.model.Song;
import fm.douban.service.SongService;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
@Service
public class SongServiceImpl implements SongService {
private static final Logger LOG = LoggerFactory.getLogger(SongServiceImpl.class);
@Autowired
private MongoTemplate mongoTemplate;//实例注入
@Override
public Song add(Song song) {
return mongoTemplate.insert(song);
}
@Override
public Song get(String songId) {
return mongoTemplate.findById(songId,Song.class);
}
@Override
public List<Song> list(Song songParam) {
// 本节课暂时不用修改
List<Song> songs = new ArrayList<>();
return songs;
}
@Override
public boolean modify(Song song) {
Query query=new Query(Criteria.where("id").is(song.getId()));
Update update=new Update();
update.set("name",song.getName());
UpdateResult updateResult=mongoTemplate.updateFirst(query,update,Song.class);
return updateResult!=null&&updateResult.getModifiedCount()>0;
}
@Override
public boolean delete(String songId) {
Song song=new Song();
song.setId(songId);
DeleteResult result=mongoTemplate.remove(song);
return result!=null&&result.getDeletedCount()>0;
}
}
Spring Data Query
上节课学习到了根据主键 id 查询数据。但是显然只根据主键查询是不够的,通常情况下需要根据多条件查询。
条件查询就相对复杂一些,但功能也更强大。查询操作的核心方法是:
List<Song> songs = mongoTemplate.find(query, Song.class);
因为可能查询到多条数据,所以返回结果是对象的集合。第一个参数是查询对象 Query
实例;第二个参数就表示查询什么样的对象,写法是 类名.class
。
查询方法比较简单,但查询操作的复杂性在于条件,需要用构建好的 Criteria
条件对象的实例,来构建 Query
实例:
Query query = new Query(criteria);
而构建 Criteria
条件对象,一般有两种情况:
-
单一条件,用:
Criteria criteria1 = Criteria.where("条件字段名").is("条件值")
即可返回一个条件对象的实例。 -
组合条件,根据或(or)、且(and)的关系进行组合,多个子条件对象组合成一个总条件对象:
-
或(or)关系:
Criteria criteria = new Criteria(); criteria.orOperator(criteria1, criteria2);
-
且(and)关系:
Criteria criteria = new Criteria(); criteria.andOperator(criteria1, criteria2);
-
orOperator()
和andOperator()
的参数,都可以输入多个子条件,也可以输入子条件数组
-
当然,组合条件情况下,也可以多层组合,子条件也可以是组合而来的。
例如根据歌曲的专辑查询,并限定最多返回 10 条数据:
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Criteria;
public List<Song> list(Song songParam) {
// 总条件
Criteria criteria = new Criteria();
// 可能有多个子条件
List<Criteria> subCris = new ArrayList();
if (StringUtils.hasText(songParam.getName())) {
subCris.add(Criteria.where("name").is(songParam.getName()));
}
if (StringUtils.hasText(songParam.getLyrics())) {
subCris.add(Criteria.where("lyrics").is(songParam.getLyrics()));
}
if (StringUtils.hasText(songParam.getSubjectId())) {
subCris.add(Criteria.where("subjectId").is(songParam.getSubjectId()));
}
// 必须至少有一个查询条件
if (subCris.isEmpty()) {
LOG.error("input song query param is not correct.");
return null;
}
// 三个子条件以 and 关键词连接成总条件对象,相当于 name='' and lyrics='' and subjectId=''
criteria.andOperator(subCris.toArray(new Criteria[]{}));
// 条件对象构建查询对象
Query query = new Query(criteria);
// 仅演示:由于很多同学都在运行演示程序,所以需要限定输出,以免查询数据量太大
query.limit(10);
List<Song> songs = mongoTemplate.find(query, Song.class);
return songs;
}
这里用 Song 对象来表示查询条件。作为服务接口,使用对象做参数,具备比较好的扩展性和兼容性。例如,如果增加一个查询条件,就不需要增加方法参数,只需要为参数对象增加属性即可;否则所有的调用查询接口方法的代码都需要做修改,影响面可能很大,扩展性和兼容性都不好。
通常需求越复杂,组合条件就可能越复杂。大家需要仔细琢磨一下 Criteria
条件对象的运用,达到灵活运用的程度后,就可以根据需求任意组合,以满足多变的查询需求哦.
Spring Data 分页
分页是查询中最常用的功能,同时也为了防止一次查询的数据量太大而影响性能。
查询支持分页也比较简单,只需要调用 PageRequest.of()
方法构建一个分页对象,然后注入到查询对象即可。
PageRequest.of()
方法第一个参数是页码,注意从 0
开始计数,第一页的值是 0
;第二个参数是每页的数量。
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
Pageable pageable = PageRequest.of(0 , 20);
query.with(pageable);
对于分页来说,除了要查询结果以外,还需要查询总数,才能进一步计算出总共多少页,实现完整的分页功能。所以,还需要两个步骤:
- 调用
count(query, XXX.class)
方法查询总数。第一个参数是查询条件,第二个参数表示查询什么样的对象; - 根据结果、分页条件、总数三个数据,构建分页器对象。
import org.springframework.data.domain.Page;
import org.springframework.data.repository.support.PageableExecutionUtils;
// 总数
long count = mongoTemplate.count(query, Song.class);
// 构建分页器
Page<Song> pageResult = PageableExecutionUtils.getPage(songs, pageable, new LongSupplier() {
@Override
public long getAsLong() {
return count;
}
});
PageableExecutionUtils.getPage()
方法第一个参数是查询结果;第二个参数是分页条件对象;第三个参数稍微复杂一点,实现一个 LongSupplier
接口的匿名类,在匿名类的 getAsLong()
方法中返回结果总数。方法返回值是一个 Page
分页器对象,使用起来非常方便。
这里做了一次重构,SongService
中的 list()
方法,用自定义的专用的参数类 SongQueryParam
替换原来的 Song
做方法参数。因为分页参数不属于歌曲模型,所以必须重构。
当然,为了分页,返回值也重构为 Page<Song>
,表示方法返回分页结果对象。
实际上,在项目设计的时候就必须考虑到分页功能。本课程只是为了演示方便,在前面的课程中就没有用参数类。
control 中调用 SongService.list()
方法得分页结果对象后,可以调用 Page
的各个方法取得数据集和前端分页器的各种数据值。详情请点此查看文档。
最常用的方法是 getContent() 取得本页的数据集:
Page<Song> songResult = songService.list(queryParam);
List<Song> songs = songResult.getContent();
因为 Page 表示本页的数据对象是 Song ,所以 songResult.getContent() 返回一个列表,同样,泛型指定了对象也是 Song 。
Page
其它的方法是取得当前页码号、总页码数等,就不赘述了,对照上述文档的注释看即可。只是注释为英文,可能不习惯,需要一些耐心,配合有道词典等翻译软件理解。 重要的是 ,自己动手编码看一下各个方法的返回值,对照文档见就更容易理解了。
原文地址:https://blog.csdn.net/2401_86043214/article/details/144092623
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!