SpringBoot整合MongoDB
之前我们讲解了MongoDB在不同环境的安装和MongoDB的CRUD以及Robo3T可视化工具操作MongoDB,今天我们来看看实际业务中整合MongoDB的场景。
本文我们主要是通过两种方法实现SpringBoot整合MongoDB,最后写一个小demo来理解MongoDB在实际业务中的应用。
整合
新建项目
新建一个SpringBoot项目,只需要勾选SpringWeb和SpringDataMongoDB的模块即可
通过Robo3T连接MongoDB数据库,确保数据库可用,也可以实时监测数据库情况。
在MongoDB中新建一个databases,一会儿要用。
SpringBoot中配置类
因为MongoDB默认是无密码的,我们这边没密码就空着
#写服务器地址,本地就localhost
spring.data.mongodb.host=localhost
#用户名密码不同的库需要不同的认证,这里是在 admin 库中
spring.data.mongodb.authentication-database=admin
#有账户和密码就写,没有就空着
spring.data.mongodb.username=
spring.data.mongodb.password=
spring.data.mongodb.port=27017
#这里写刚才创建的数据库
spring.data.mongodb.database=feng
我们先用MongoRepository接口来实现整合的方法
在项目中创建一个实体类
package com.feng.pojo;
/**
* <h3>spirngboot-mongodb-test</h3>
* <p></p>
*
* @author : Nicer_feng
* @date : 2020-10-12 09:34
**/
public class User {
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User(Integer id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
创建一个UserDao接口,这个接口要继承MongoRepository接口
我们可以发现MongoRepository接口中已经封装好了一些基础的方法,查询,增加等
UserDao.java
package com.feng.dao;
import com.feng.pojo.User;
import org.springframework.data.mongodb.repository.MongoRepository;
/**
* <h3>spirngboot-mongodb-test</h3>
* <p></p>
*
* @author : Nicer_feng
* @date : 2020-10-12 09:35
**/
public interface UserDao extends MongoRepository<User,Integer> {
//啥也不用干,继承上面那个接口就行
}
然后我们去测试类测试增加数据
测试连接和插入
package com.feng;
import com.feng.dao.UserDao;
import com.feng.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SpirngbootMongodbTestApplicationTests {
@Autowired
UserDao userDao;
@Test
void contextLoads() {
}
@Test
public void addUser(){
User user = new User();
user.setId(1);
user.setName("feng");
userDao.insert(user);
}
}
运行测试方法,可以发现测试成功
在Robo3T中查询数据库,发现增加数据成功
当然我们也可以不用他MongoRepository封装的方法,我们可以自定义方法
自定义接口方法
我们在MongoDB中多插入两个数据
在UserDao.java中加入方法
public interface UserDao extends MongoRepository<User,Integer> {
List<User> findUserByNameContaining(String name);
}
测试类实现
package com.feng;
import com.feng.dao.UserDao;
import com.feng.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
class SpirngbootMongodbTestApplicationTests {
@Autowired
UserDao userDao;
//******
@Test
void findUserByNameContaining(){
List<User> all = userDao.findAll();
System.out.println(all);
List<User> feng = userDao.findUserByNameContaining("fe");
System.out.println(feng);
}
}
查询结果
除了继承MongoRepository接口,我们还可以直接调用MongoTemplate,和RedisTemplate差不多,就是封装好的模板,直接创建对象调用对应方法即可。
MongoTemplate实现
直接去测试类
package com.feng;
import com.feng.dao.UserDao;
import com.feng.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.mongodb.core.MongoTemplate;
import java.util.List;
@SpringBootTest
class SpirngbootMongodbTestApplicationTests {
//****************
@Autowired
MongoTemplate mongoTemplate;
@Test
void testTemplate(){
User user = new User();
user.setId(4);
user.setName("冯半仙");
mongoTemplate.insert(user);
List<User> all = mongoTemplate.findAll(User.class);
System.out.println(all);
}
}
可以看到findAll方法的参数必须是实体类的Class,所以我们传入User.class
测试运行
可以看到数据插入成功,并且查询成功
实际业务情况
我们知道非关系型数据库MongoDB一般就是做缓存功能,来提高web的程序的查询速度,将热点数据放在非关系型数据库中,而不是每次都去关系型数据库中查询的(如mysql),以下我们讲一个小demo,来模拟MongoDB做缓存的情况。但我们要知道实际业务中远比这复杂,比如缓存的时效性,哪些是热点数据(哪些数据需要缓存)等,这里的小demo并不考虑这些,只是参考一下思想
我们在进行查询时,缓存和数据库一般是这样配合的
MongoDB做缓存小Demo
数据库
懒得新建项目也可以拿刚才的直接测哈,在数据里找了一圈发现有一个叫blog的比较合适,先看看数据表长什么样
建表语句
CREATE TABLE `blog` (
`id` varchar(50) NOT NULL COMMENT '博客id',
`title` varchar(100) NOT NULL COMMENT '博客标题',
`author` varchar(30) NOT NULL COMMENT '博客作者',
`create_time` datetime NOT NULL COMMENT '创建时间',
`views` int(30) NOT NULL COMMENT '浏览量'
) ENGINE=InnoDB DEFAULT CHARSET=utf8
在SpringBoot项目中加入mysql依赖
<!--JDBC-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--MYSQL-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
在配置文件application.properties中加入mysql配置
spring.datasource.username=root
#自己的mysql数据库密码
spring.datasource.password=*****
#自己把上面的表建立在哪个库中,就连哪个库
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
创建实体类
Blog.java
package com.feng.pojo;
import java.util.Date;
/**
* <h3>spirngboot-mongodb-test</h3>
* <p></p>
*
* @author : Nicer_feng
* @date : 2020-10-12 10:10
**/
public class Blog {
private Integer id;
private String title;
private String author;
private Date create_time;
private Integer views;
public Blog() {
}
public Blog(Integer id, String title, String author, Date create_time, Integer views) {
this.id = id;
this.title = title;
this.author = author;
this.create_time = create_time;
this.views = views;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public Date getCreate_time() {
return create_time;
}
public void setCreate_time(Date create_time) {
this.create_time = create_time;
}
public Integer getViews() {
return views;
}
public void setViews(Integer views) {
this.views = views;
}
@Override
public String toString() {
return "Blog{" +
"id=" + id +
", title='" + title + '\'' +
", author='" + author + '\'' +
", create_time=" + create_time +
", views=" + views +
'}';
}
}
测试
直接去SpringBoot测试类,我们什么都先用模板类实现,这里面我们先测试一个查询,代码逻辑就是先从缓存(MongoDB)中查,如果缓存中有,直接返回,如果缓存中没有,我们去关系型数据库(MySQL)中查询,并将查询的结果存入缓存中,下次再查询直接返回结果。
package com.feng;
import com.feng.dao.UserDao;
import com.feng.pojo.Blog;
import com.feng.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
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.jdbc.core.JdbcTemplate;
import java.sql.SQLException;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
@SpringBootTest
class SpirngbootMongodbTestApplicationTests {
//********
@Autowired
MongoTemplate mongoTemplate;
@Autowired
JdbcTemplate jdbcTemplate;
@Test
void getBlogByTitle() throws SQLException {
String title = "Java如此简单2";
Query query = new Query(Criteria.where("title").is(title));
Blog one = mongoTemplate.findOne(query, Blog.class);
if(one!=null){
System.out.println("从缓存数据库直接拿的数据:"+one);
}else{
String sql = "SELECT * FROM mybatis.blog WHERE blog.title = ?";
List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql,"Java如此简单2");
System.out.println("从mysql数据库拿的数据:"+maps.toString());
Map<String, Object> map = maps.get(0);
Set<String> strings = map.keySet();
System.out.println(strings);
Blog blog = new Blog();
blog.setId(Integer.valueOf(map.get("id").toString()));
blog.setTitle((String) map.get("title"));
blog.setAuthor((String) map.get("author"));
blog.setCreate_time((Date) map.get("create_time"));
blog.setViews((Integer) map.get("views"));
System.out.println(blog.toString());
mongoTemplate.insert(blog);
//这里不能直接把从jdbc模板类返回的对象存到MongoDB中,因为queryForList返回的对象并不是我们的实体类对象,我们要手动转存再存到缓存中
//mongoTemplate.insert(maps);
System.out.println("mysql拿的数据存到缓存中啦!");
}
}
}
第一次查询,缓存中无结果,在数据库中查到,并存入缓存中
第二次查询,直接从缓存中拿到结果并返回
那既然思路和测试没问题,咱模拟的像一点,整个业务出来吧
在项目中创建controller和service文件夹
分别创建BlogController和BlogService
BlogController.java
package com.feng.controller;
import com.feng.pojo.Blog;
import com.feng.service.BlogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
* <h3>spirngboot-mongodb-test</h3>
* <p></p>
*
* @author : Nicer_feng
* @date : 2020-10-12 11:27
**/
@RestController
public class BlogController {
@Autowired
BlogService blogService;
@GetMapping("getBlog/{title}")
public Blog getBlog(@PathVariable String title){
Blog blogByTitle = blogService.getBlogByTitle(title);
return blogByTitle;
}
}
- 我们这里类名使用@RestController注解声明该类为一个控制器,并且返回JSON字符串给前端。
BlogService.java
package com.feng.service;
import com.feng.pojo.Blog;
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.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* <h3>spirngboot-mongodb-test</h3>
* <p></p>
*
* @author : Nicer_feng
* @date : 2020-10-12 11:27
**/
@Service
public class BlogService {
@Autowired
MongoTemplate mongoTemplate;
@Autowired
JdbcTemplate jdbcTemplate;
public Blog getBlogByTitle(String title){
Query query = new Query(Criteria.where("title").is(title));
Blog one = mongoTemplate.findOne(query, Blog.class);
if(one!=null){
System.out.println("从缓存数据库直接拿的数据:"+one);
}else{
String sql = "SELECT * FROM mybatis.blog WHERE blog.title = ?";
List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql,title);
System.out.println("从mysql数据库拿的数据:"+maps.toString());
Map<String, Object> map = maps.get(0);
Set<String> strings = map.keySet();
System.out.println(strings);
Blog blog = new Blog();
blog.setId(Integer.valueOf(map.get("id").toString()));
blog.setTitle((String) map.get("title"));
blog.setAuthor((String) map.get("author"));
blog.setCreate_time((Date) map.get("create_time"));
blog.setViews((Integer) map.get("views"));
mongoTemplate.insert(blog);
one=blog;
System.out.println("mysql拿的数据存到缓存中啦!");
}
return one;
}
}
这里面的逻辑跟这张图一致
- @Service 表示该类为一个service(事务处理),可以被注入到其他对象(Spring帮你管理)。
- @Autowired表示要注入对象的意思。而MongoTemplate 就是已经封装好在Spring中操作MongoDB的对象。
测试
启动SpringBoot启动类,访问8080端口
第一次我们查询缓存中有的数据,第二次查询缓存中不存在的数据,可以看到第一次直接返回缓存中的结果,而第二次是先从数据库中拿,然后将数据保存到缓存中,再返回给前端
缓存需要注意的问题
我们是用缓存保存热点数据的时候可能很少有更新或者删除的请款,但并不代表我们遇不到这种情况,所以我们在BlogService中还要添加一个updateBlogByTitle(String title,Integer views),方法主要是用来更新数据库和MongoDB缓存中的数据库
缓存数据的修改
注意缓存一致性
在BlogSerivce中添加update方法
public boolean updateBlogByTitle(String title,Integer views){
try {
Query query = new Query(Criteria.where("title").is(title));
Update update = new Update();
update.set("views",views);
mongoTemplate.upsert(query,update,Blog.class);
String sql = "update mybatis.blog set views=? where title=?";
int isUpdate = jdbcTemplate.update(sql, views, title);
if(isUpdate==0){
return false;
}
} catch (Exception e) {
return false;
}
return true;
}
controller配置对应视图层
@GetMapping("updateBlog")
public String updateBlog(String title,Integer views){
boolean b = blogService.updateBlogByTitle(title, views);
return b?"update success":"update failed";
}
运行
运行SpringBoot主类,访问项目地址
http://localhost:8080/updateBlog?title=Java如此简单&views=5533
可以发现缓存和数据库中的数据均更新成功
缓存数据的删除
service添加删除逻辑
public boolean deleteBlogByTitle(String title){
try {
Query query = new Query(Criteria.where("title").is(title));
mongoTemplate.remove(query, Blog.class);
String sql = "delete from mybatis.blog where title = ?";
int isDel = jdbcTemplate.update(sql, title);
if(isDel==0){
return false;
}
} catch (Exception e) {
return false;
}
return true;
}
controller添加视图
@GetMapping("delete/{title}")
public String deleteBlog(@PathVariable String title){
boolean b = blogService.deleteBlogByTitle(title);
return b?"delete success":"delete failed";
}
测试
访问 http://localhost:8080/delete/MongoDB如此简单 发现缓存和数据库中的数据均被删除
最后添加的逻辑也基本一致,这里不再赘述,感兴趣的同学可以继续测试!做缓存时最重要的就是数据的一致性,千万不要出现缓存和数据库中的数据不一致的情况。