Java项目中MongoDb学习和使用总结

5,400 阅读7分钟

最近在项目中使用MongoDb先简单介绍MongoDb

MongoDb是什么?

MongoDB是一个基于分布式文件存储的数据库。由C++语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。
MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。

干什么?

这个简单,肯定是存储数据。MongoDB 将数据存储为一个文档,数据结构由键值(key=>value)对组成。MongoDB 文档类似于 JSON 对象。字段值可以包含其他文档,数组及文档数组。

怎么用?

mongoDb的基础操作命令

自行百度吧。。。
mongoDb菜鸟教程

项目中如何进行MongoDb的CRUD

可以使用两种方式MongoRepository方式和mongoTemplate方式

MongoRepository方式

  1. 这种方式只有查询和新增,而且只需要创建仓库继承接口MongoRepository,按照规则创建方法就行。
public interface LogRepository extends MongoRepository<Log>{
    
}

看一下MongoRepository源码里有什么方法

@NoRepositoryBean
public interface MongoRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
    <S extends T> List<S> save(Iterable<S> var1);

    List<T> findAll();

    List<T> findAll(Sort var1);

    <S extends T> S insert(S var1);

    <S extends T> List<S> insert(Iterable<S> var1);

    <S extends T> List<S> findAll(Example<S> var1);

    <S extends T> List<S> findAll(Example<S> var1, Sort var2);
}
  1. 所以save和findAll可以直接使用,Sort为排序。如果想根据条件查询的话可以按照规则输入方法名。当需要根据实体类中的属性查询时,MongoRepository提供的方法已经不能满足,我们需要在Repository仓库中定义方法,定义方法名的规则为:find + By + 属性名(首字母大写),例如:
/**
	 * 根据地址查询
	 * 
	 * @param address
	 * @author zyc
	 * @return 
	 */
	List<AlarmLog> findByAddress(String address);
	
    /**
     * 根据地址查询带分页
     *
     * @param address
     * @author zyc
     * @return 
     */
	Page<AlarmLog> findByAddress(String address, Pageable pageable);
  1. 当需要根据实体类中的属性进行模糊查询时,我们也需要在PersonRepository仓库中定义方法,模糊查询定义方法名的规则为:find + By + 属性名(首字母大写) + Like

	 /**
     * 根据地址查询带分页
     *
     * @param address
     * @author zyc
     * @return 
     */
	Page<AlarmLog> findByAddressLike(String address, Pageable pageable);
  1. 查询指定的值在上面写注解。其中value是查询的条件,?0这个是占位符,对应着方法中参数中的第一个参数,如果对应的是第二个参数则为?1。fields是我们指定的返回字段,其中id是自动返回的,不用我们指定,json中{‘address’:1}的1代表true,也就是代表返回的意思。
@Query(value="{'address':?0}",fields="{'address':1}")
public Page<Person> findByAddressLike(String address,Pageable pageable);

MongoTemplate方式

个人感觉这种方式更加全面一点,更好理解。

  1. pom引入mongoDB的依赖 pom.xml:
        <dependency> 
	        <groupId>org.springframework.boot</groupId>
	        <artifactId>spring-boot-starter-data-mongodb</artifactId>
	    </dependency>
  1. 配置文件配置连接 application.yml:
server: 
	  port: 8080
	spring:
	  data:
	    mongodb:
	      uri: mongodb://localhost:27017/test01
	  application:
	    name: myserver
  1. 在需要向mongoDB CRUD时引入MongoTemplate
@Repository("userDao")
public class UserDaoImpl implements UserDao {

	/**
	 * 由springboot自动注入,默认配置会产生mongoTemplate这个bean
	 */
	@Autowired
	private MongoTemplate mongoTemplate;

	@Override
	public void insert(User user) {
		//插入数据id自增(mongoDB中存在自己的_id,如果不自己定义自增,会默认返回mongoDB中的_id,我看网上说是不建议将_id自增的,因为高并发会影响性能。从这看的https://segmentfault.com/q/1010000006019599)
		mongoTemplate.insert(user);
	}

	@Override
	public void update(User user) {
		//根据id修改 update user set name=user.getName(),age=user.getAge() where id =user.getId()
		mongoTemplate.updateFirst(new Query(Criteria.where("id").is(user.getId())),
				new Update().set("name", user.getName()).set("age", user.getAge()), User.class);
	}

	@Override
	public List<User> findAll() {
		//select * from A
		return mongoTemplate.findAll(User.class);

	}

	@Override
	public User findOne() {
		//select * from A LIMIT 1
		return mongoTemplate.findOne(new Query(), User.class);// 第一条
	}

	@Override
	public User findById(Long id) {
		//Select * from A where a.id=id;
		return mongoTemplate.findById(id, User.class);// id查询
	}

	@Override
	public List<User> find() {
		//select name from A where name='10'
		DBObject dbObject = new BasicDBObject();
		dbObject.put("name", "10"); // 查询条件
		BasicDBObject fieldsObject = new BasicDBObject();
		// 指定返回的字段
		fieldsObject.put("name", true);
		return mongoTemplate.find(new BasicQuery(dbObject, fieldsObject), User.class);
	}

	@Override
	public List<User> findOr() {
		//select * from A where name='10' or age=13
		return mongoTemplate.find(
				new Query(new Criteria().orOperator(Criteria.where("name").is("10"),Criteria.where("age").is(11))),
				User.class);
	}
	@Override
	public List<User> findAnd(){
		//select * from A where name='13' and id=1
		return mongoTemplate.find(new  Query(Criteria.where("name").is("13").and("id").is(1)), User.class);
	}
	@Override
	public List<User> findOrAnd() {
		//select * from A where a.id=1 and (age=9 or name =13)
		return mongoTemplate.find(
				new Query(Criteria.where("id").is(1).andOperator(new Criteria().orOperator(new Criteria().orOperator(Criteria.where("age").is(10),Criteria.where("name").is("13"))))),
				User.class);
	}
	@Override
	public List<User> findDayuXiaoyu(){
		//select * from A where  age>1 and age<10
		//gte大于等于 lte小于等于
		//ge大于 lt小于
		//ne 不等于
		Query ge_lt =  new Query(Criteria.where("age").gt(1).lt(10));//大于等于
		Query gte_lte =  new Query(Criteria.where("age").gte(1).lte(10));//小于等于
		Query ne =  new Query(Criteria.where("age").ne(5));//不等于
		return mongoTemplate.find(ne, User.class);
	}
	/**
	 * 模糊查询  regex
			完全匹配  "^hzb$"
			右匹配    "^.*hzb$"
			左匹配    "^hzb.*$"
			双开       "^.*hzb.*$"
	 */
	@Override
	public List<User> findLike(){
		Query query =  new Query(Criteria.where("name").regex("^.*2$"));
		return mongoTemplate.find(query, User.class);
	}
	
	(自己使用碰到的问题)
	碰到模糊查询的问题发现并不是好用,碰到一些检索带特殊符号的不可用,解决方案:判断模糊查询条件里面是否有特殊符号,加上转义字符。
	Pattern pattern = Pattern.compile(".*?" + escapeExprSpecialWord(alarmLogCondition.getMessage()) + ".*");
        criteria = Criteria.where("message").regex(pattern);
        query.addCriteria(criteria);
        List<AlarmLog> alarmLogs = mongoOperations.find(query, AlarmLog.class);
            
    	private String escapeExprSpecialWord(String keyword) {
		if (StringUtils.isNotBlank(keyword)) {
			String[] fbsArr = { "\\", "$", "(", ")", "*", "+", ".", "[", "]", "?", "^", "{", "}", "|" };
			for (String key : fbsArr) {
				if (keyword.contains(key)) {
					keyword = keyword.replace(key, "\\" + key);
				}
			}
		}
		return keyword;
	}
	@Override
	public List<User> findInNot(){
		//in 和not in
		Query in =  new Query(Criteria.where("age").in(5,6,7));
		//in
		Query notIn =  new Query(Criteria.where("age").nin(5,6,7));
		//not in
		return mongoTemplate.find(notIn, User.class);
	}
	@Override
	public List<User> findDie(){
		Query findDie =  new Query(Criteria.where("age").mod(9, 1));
		//取模(取余) age % 5==1
		return mongoTemplate.find(findDie, User.class);
	}
	@Override
	public List<User> findAllMatching(){
		Query findAllMatching =  new Query(Criteria.where("list").all(1L,2L));
		//all 数据 [name=10, age=10, id=4, list=[1, 2]]
		//完全匹配返回数据
		return mongoTemplate.find(findAllMatching, User.class);
	}
	
	@Override
	public List<User> findSize(){
		Query findSize =  new Query(Criteria.where("list").size(2));
		//size 数据 [name=10, age=10, id=4, list=[1, 2]] 匹配数组内的元素数量的 list.size =2 返回 
		return mongoTemplate.find(findSize, User.class);
	}
	@Override
	public List<User> findExists(){
		Query findExists =  new Query(Criteria.where("list").exists(false));
		//exists 判断字段是否为空  等同于sql的  list is null  和 list  is not null
		return mongoTemplate.find(findExists, User.class);
	}
	@Override
	public List<User> findType(){
		Query findType =  new Query(Criteria.where("list").type(4));
		//匹配数据类型
		//Double				1	“double”	
		//String				2	“string”	
		//Object				3	“object”	
		//Array	  			    4	“array”	
		//Binary data			5	“binData”	
		//Undefined				6	“undefined”	Deprecated.
		//ObjectId				7	“objectId”	
		//Boolean				8	“bool”	
		//Date	    			9	“date”	
		//Null	  				10	“null”	
		//Regular Expression	11	“regex”	
		//DBPointer				12	“dbPointer”	Deprecated.
		//JavaScript			13	“javascript”	
		//Symbol				14	“symbol”	Deprecated.
		//JavaScript (with scope)	15	“javascriptWithScope”	
		//32-bit integer		16	“int”	
		//Timestamp				17	“timestamp”	
		//64-bit integer		18	“long”	
		//Decimal128			19	“decimal”	New in version 3.4.
		//Min key				-1	“minKey”	
		//Max key				127	“maxKey”
		return mongoTemplate.find(findType, User.class);
	}
	@Override
	public List<User> findNot() {
		Query findDie =  new Query(Criteria.where("age").not().mod(9, 1));
		//not取反
		return mongoTemplate.find(findDie, User.class);
	}
	@Override
	public long count() {
		Query count =  new Query(Criteria.where("age").not().mod(9, 1));
		//总条数
		return mongoTemplate.count(count, User.class);
		
	}
	@Override
	public List<?> distinct(){
		return mongoTemplate.getCollection("User").distinct("name");
	}
	@Override
	public List<?> sort(){
		Query sort =  new Query();
		//排序
		return mongoTemplate.find(sort.with(new Sort(Direction.DESC, "id")), User.class);
	}
	@Override
	public List<?> limit(){
		Query limit =  new Query();
		//分页
		//skip 设置起始数
		//limit 设置查询条数
		return mongoTemplate.find(limit.skip(1).limit(3), User.class);
	}

}

复杂分页查询 我的理解这里的query就是用来封装查询条件的 criteria.and().is() 在这里是构建了一个精准查询的条件,并且用 'and' 相连。

@Override
    public Page<AlarmLog> findAlarmLog(LogCondition logCondition) {
        log.info("进入查询告警接口,请求参数LogCondition={}",JSON.toJSONString(logCondition));
        int currentPage = logCondition.getCurrentPage();
        int pageSize = logCondition.getPageSize();
        Date startTime = logCondition.getStartTime();

        Query query = new Query();
        //设置起始数
        query.skip((currentPage - 1) * pageSize);
        //设置查询条数
        query.limit(pageSize);
        Criteria criteria = new Criteria();
        if (StringUtils.isNotEmpty(logCondition.getSystem())) {
            criteria.and("system").is(logCondition.getSystem());
        }
        if (StringUtils.isNotEmpty(logCondition.getLevel())) {
            criteria.and("level").is(logCondition.getLevel());
        }
        if (StringUtils.isNotEmpty(logCondition.getHostname())) {
            criteria.and("hostname").is(logCondition.getHostname());
        }
        if (StringUtils.isNotEmpty(logCondition.getMessage())) {
            criteria.and("message").is(logCondition.getMessage());
        }
        if (StringUtils.isNotEmpty(logCondition.getStatus())) {
            criteria.and("status").is(logCondition.getStatus());
        }
        if (StringUtils.isNotEmpty(logCondition.getStartTime()) && StringUtils.isNotEmpty(logCondition.getEndTime())) {
            criteria.and("createTime").gte(logCondition.getStartTime()).lte(logCondition.getEndTime());
        } else if (StringUtils.isNotEmpty(logCondition.getStartTime())) {
            criteria.and("createTime").gte(logCondition.getStartTime());
        } else if (StringUtils.isNotEmpty(logCondition.getEndTime())) {
            criteria.and("createTime").lte(logCondition.getEndTime());
        }
        query.addCriteria(criteria);
        query.with(new Sort(Sort.Direction.DESC, "createTime"));
        List<AlarmLog> alarmLogs = mongoOperations.find(query,AlarmLog.class);
        log.info("查询结果alarmLogs={}", JSON.toJSONString(alarmLogs));
        int count = (int) mongoOperations.count(query, AlarmLog.class);
        Page<AlarmLog> logPage = new Page<>();
        logPage.setRecords(alarmLogs);
        logPage.setTotal(count);
        logPage.setSize(logPage.setSize(count % pageSize == 0 ? count / pageSize : count / pageSize + 1););

这里解释一下,上面是最近做的一个查询,是对多个字段可能有可能没有的分页查询,所以我只能用If做判断处理,里面有个查询条件是个开始时间和截止时间内容。对一个字段做两次

criteria.and("createTime").gte()
criteria.and("createTime").lte()

会报错,所以使用了上面颇为丑陋的if else判断。写的时候查到了这种情况的处理方式,用到 andOperator 方法。试了一下可以。

//大于方法
Criteria gt = Criteria.where("createTime").gte("你的条件");
//小于方法
Criteria lt = Criteria.where("createTime").lte("你的条件");
/**
  * new Criteria().andOperator(gt,lt) 把上面的大于和小于放在一起,注意上面两个key一定是一样的
  * andOperator() 这个方法就是关键的方法了,把同key的多个条件组在一起
  * 但是 andOperator() 有个很坑的地方就是,在一个query中只能出现一次
  * 如果你有很固定很明确的入参,那还好,直接调用一次 andOperator()
  * 如果是多个key需要多个条件,而且条件数量还是动态的,怕不是魔鬼吧...
  */
criteria.andOperator(gte, lte);

按照惯例推荐一个软件:wox(解放你的桌面,和上次说的everything一起使用,爽到嗨。可以自己网上搜最新版)

链接:pan.baidu.com/s/1KDXwbQyk… 提取码:48b6