虚拟机安装MongoDB请参看《CentOS7安装MongoDB4》
我使用的IDE是STS4,大家按照自己的习惯选择即可。
关键是pom.xml要加入:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
在application.properties中加入:
spring.data.mongodb.uri=mongodb://用户名:密码@IP:PORT/数据库
实体类
@Document(collection = "test_goods")
publicclass GoodsEntity implements Serializable {
privatestaticfinallongserialVersionUID = -805486477824888750L;
@Id
private String id;
private String goodsName;
privatelongcategoryId;
privateintgoodsStatus;
private String labels;
//省略get、set方法
}
其他都不解释了,一看就懂,这里要注意id这个字段。
如上配置,加上注解@Id的情况下,我们新建一条Document的时候,不需额外设置,会自动生成一个如下图的主键:
这是一个名为”_id”的ObjectId类型的主键,12个字节的BSON类型字符串。
4字节是UNIX时间戳,3字节表示MongoDB服务器,2字节是生成ID的进程,3字节是随机数。
这么做的好处是对分布式友好。并且,因为id中包含时间戳,天然的就带上了创建时间。我们可以通过
ObjectId id = new ObjectId(entity.getId());
System.out.println(id.getDate());
获取创建时间。
当然了,如果我们使用MongoDB是对传统DB的一个补充,在系统中还是希望将DB中的ID存入MongoDB的话,那就去除id字段的注解,处理的时候对ID字段做好设置即可。
Dao
@Component
publicclass GoodsDao {
@Autowired
private MongoTemplate mongoTemplate;
/**
* 新建
*
* @param entity
* @return
*/
public GoodsEntity add(GoodsEntity entity) {
returnmongoTemplate.save(entity);
}
/**
* 根据ID修改
*
* @param entity
* @return
*/
public UpdateResult upd(GoodsEntity entity) {
Query query = new Query(Criteria.where("id").is(entity.getId()));
Update update = new Update().set("goodsName", entity.getGoodsName()).set("categoryId", entity.getCategoryId())
.set("goodsStatus", entity.getGoodsStatus()).set("labels", entity.getLabels());
returnmongoTemplate.updateFirst(query, update, GoodsEntity.class);
}
/**
* 根据ID删除
*
* @param id
* @return
*/
public DeleteResult delById(longid) {
Query query = new Query(Criteria.where("id").is(id));
returnmongoTemplate.remove(query, GoodsEntity.class);
}
/**
* 根据主键获取详情
*
* @param id
* @return
*/
public GoodsEntity getById(longid) {
Query query = new Query(Criteria.where("id").is(id));
GoodsEntity entity = mongoTemplate.findOne(query, GoodsEntity.class);
returnentity;
}
/**
* 列出所有记录
*
* @return
*/
public List<GoodsEntity> listAll() {
List<GoodsEntity> entities = mongoTemplate.find(new Query(), GoodsEntity.class);
returnentities;
}
/**
* 根据某字段使用正则表达式模糊查询,且分页、ID倒序
*
* @param label
* @param pageNumber
* @param pageSize
* @return
*/
public List<GoodsEntity> queryPageByLabel(String label, intpageNumber, intpageSize) {
// 完全匹配
// Pattern pattern = Pattern.compile("^" + label + "$",
// Pattern.CASE_INSENSITIVE);
// 右匹配
// Pattern pattern = Pattern.compile("^.*\"+label+\"$",
// Pattern.CASE_INSENSITIVE);
// 左匹配
// Pattern pattern = Pattern.compile("^\"+label+\".*$",
// Pattern.CASE_INSENSITIVE);
// 模糊匹配
Pattern pattern = Pattern.compile("^.*" + MongoDBUtils.escapeExprSpecialWord(label) + ".*$",
Pattern.CASE_INSENSITIVE);
Query query = new Query(Criteria.where("labels").regex(pattern));
// ID倒序
query.with(new Sort(Sort.Direction.DESC, "id"));
// 分页
PageRequest pageableRequest = PageRequest.of(pageNumber, pageSize);
query.with(pageableRequest);
returnmongoTemplate.find(query, GoodsEntity.class);
}
/**
* 多查询条件,分页,ID倒序
*
* @param entity
* @param pageNumber
* @param pageSize
* @return
*/
public List<GoodsEntity> queryPage(GoodsEntity entity, intpageNumber, intpageSize) {
Criteria criteria = new Criteria();
if (!StringUtils.isEmpty(entity.getGoodsName())) {
Pattern pattern = Pattern.compile("^.*" + entity.getGoodsName() + ".*$", Pattern.CASE_INSENSITIVE);
criteria.and("goodsName").regex(pattern);
}
if (!StringUtils.isEmpty(entity.getLabels())) {
Pattern pattern = Pattern.compile("^.*" + entity.getLabels() + ".*$", Pattern.CASE_INSENSITIVE);
criteria.and("labels").regex(pattern);
}
if (entity.getCategoryId() > 0) {
criteria.and("categoryId").is(entity.getCategoryId());
}
if (entity.getGoodsStatus() > 0) {
criteria.and("goodsStatus").is(entity.getGoodsStatus());
}
Query query = new Query(criteria);
// 分页&ID倒序
PageRequest pageableRequest = PageRequest.of(pageNumber, pageSize, Sort.Direction.DESC, "id");
query.with(pageableRequest);
returnmongoTemplate.find(query, GoodsEntity.class);
}
}
个人感觉基本覆盖了大部分需求,不再对代码详细解释了。
主要是注意一下,我这次没有使用ObjectId,而是用DB的ID,所以这里的Entity的ID是long。
测试
@RunWith(SpringRunner.class)
@SpringBootTest
publicclass GoodsDaoTest {
@Autowired
private GoodsDao goodsDao;
@Test
publicvoid add() {
GoodsEntity entity = new GoodsEntity();
entity.setId(3); // 如果使用ObjectId,就不需要额外处理ID字段了。
entity.setCategoryId(5);
entity.setGoodsName("测试商品E");
entity.setGoodsStatus(1);
entity.setLabels("a,b,c,*,d");
GoodsEntity newEntity = goodsDao.add(entity);
JsonFormaterUtil.printFromObj(newEntity);
}
@Test
publicvoid upd() {
GoodsEntity entity = goodsDao.getById(1);
entity.setLabels("a,b,c,d");
JsonFormaterUtil.printFromObj(goodsDao.upd(entity));
}
@Test
publicvoid del() {
JsonFormaterUtil.printFromObj(goodsDao.delById(3));
}
@Test
publicvoid getById() {
JsonFormaterUtil.printFromObj(goodsDao.getById(1));
}
@Test
publicvoid listAll() {
JsonFormaterUtil.printFromObj(goodsDao.listAll());
}
@Test
publicvoid queryByLabel() {
JsonFormaterUtil.printFromObj(goodsDao.queryPageByLabel("*", 0, 2));
}
@Test
publicvoid queryPage() {
GoodsEntity entity = new GoodsEntity();
// entity.setCategoryId(5);
entity.setGoodsName("测试商品");
// entity.setGoodsStatus(1);
// entity.setLabels("a,b,c");
JsonFormaterUtil.printFromObj(goodsDao.queryPage(entity, 0, 10));
}
}
没什么好说的。
日志配置 因为我个人喜好在控制台打印出对DB操作的语句,所以对log配置进行了修改。
Spring Boot使用的是logback,在resources目录下创建logback.xml文件,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!--定义日志文件的存储绝对路径-->
<property name="LOG_HOME" value="d:/" />
<!-- 控制台输出 -->
<appender name="STDOUT"
class="ch.qos.logback.core.ConsoleAppender">
<encoder
class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{50} : %n%msg%n
</pattern>
</encoder>
</appender>
<!-- 按照每天生成日志文件 -->
<appender name="FILE"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名 -->
<FileNamePattern>${LOG_HOME}/mongodbdemo.log.%d{yyyy-MM-dd}.log
</FileNamePattern>
<!--日志文件保留天数 -->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder
class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} :
%msg%n
</pattern>
</encoder>
<!--日志文件最大的大小 -->
<triggeringPolicy
class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>10MB</MaxFileSize>
</triggeringPolicy>
</appender>
<!-- 日志输出级别 -->
<root level="ERROR">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</root>
<!-- MongoDB日志输出 -->
<logger
name="org.springframework.data.mongodb.core.MongoTemplate"
level="DEBUG" additivity="false">
<appender-ref ref="STDOUT" />
</logger>
</configuration>
关键点是配置MongoDB日志输出,其中name是输出日志的类,level设置成debug,才会将执行的语句输出出来,类似:
find using query: { "goodsName" : { "$regex" : "^.*测试商品.*$", "$options" : "i" } } fields: Document{{}} for class: class org.leo.mongodb.demo.entity.GoodsEntity in collection: test_goods
而加上additivity="false"是因为如果不加,上面的日志会在控制台上打印两次。
其他 通过正则表达式查询的时候,会遇上一些特殊字符(*,?等),需要转义一下,代码如下:
publicclass MongoDBUtils {
privatestaticfinal String[] fbsArr = { "\\", "$", "(", ")", "*", "+", ".", "[", "]", "?", "^", "{", "}", "|" };
/**
* regex对输入特殊字符转义
*
* @param keyword
* @return
*/
publicstatic String escapeExprSpecialWord(String keyword) {
if (!StringUtils.isEmpty(keyword)) {
for (String key : fbsArr) {
if (keyword.contains(key)) {
keyword = keyword.replace(key, "\\" + key);
}
}
}
returnkeyword;
}
}
索引:
db.getCollection("test_goods").createIndex({ "categoryId": 1 }, { "name": "idx_goods_categoryid" })
为categoryId创建索引,其中{ "categoryId": 1 }的1,代表按升序创建,-1代表降序。
创建复合索引,如下:
db.getCollection("test_goods").createIndex({ "categoryId": 1, "goodsStatus": -1 })
设置唯一索引,如下:
db.getCollection("test_goods").createIndex({ "categoryId": 1}, { "unique": true })