您的位置:首页 > 数据库 > Mongodb

springData+mongodb 条件查询+条件分页排序

2017-03-28 17:38 585 查看

背景:

最近项目使用缓存存储离线设备下发的消息. 技术支持是使用springmvc+springData,文档类型数据的存储就没使用mybatis.

因为以前没使用过springData,摸索的过程还是挺复杂的,不过springData官网和github上这方面的资料还是很齐全的,把我的小小经验记载下来...

Spring data简介

spring Data 项目的目的是为了简化构建基于 Spring 框架应用的数据访问计数,包括非关系数据库、Map-Reduce
框架、云数据服务等等;另外也包含对关系数据库的访问支持。不需要使用SQL语句,用关键子可以直接操作数据库(见后), 对于复杂(如多表,嵌套子查询等)的SQL语句, Spring data还提供了@Query()注解的形式在repository的方法上标注;

应用:

项目pom文件

使用springData+mongodb, 开始使用couchbase存储,但在使用过程中发现存储用时过长,一条很简单的消息缓存达到秒级别

<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>1.7.2.RELEASE</version>
</dependency>


配置文件: spring-dao.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:mongo="http://www.springframework.org/schema/data/mongo"
xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/data/mongo http://www.springframework.org/schema/data/mongo/spring-mongo-1.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd"> 
<!-- 引入参数配置文件 -->
<context:property-placeholder location="classpath:system.properties" />

<!-- 配置数据源 Mongo DB -->
<!-- Default bean name is 'mongo' -->
<!-- <mongo:mongo id="mongo" host="127.0.0.1" port="27017" /> -->
<mongo:mongo id="mongo" host="172.23.xx.xx" port="27017" />

<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg ref="mongo" />
<constructor-arg name="databaseName" value="msgCache" />
</bean>

<mongo:repositories base-package="com.xxxx.repository"></mongo:repositories>

</beans>


简单介绍@Document

使用Spring data时,实体分为两种: entity和对应mongodb中的映射entity; entity是普通的javaBean,不需要加注解,提供给调用缓存接口的消费者使用; mapper entity中必须加上@Document ,主键标识注解@Id, 其他属性可以不加注解,不添加时默认为@Field.

实体类加上@Document作为文档存储在mongodb中(实例见后)

还要注意的是:要自定义一个类型转换类(ClassUtil),将entity和映射entity进行转换, 查询时从mongodb中取出来的是文档类映射entity, 接口返回给消费者时应该是普通的entity,所以要进行转换;

Repository接口

Repositry接口
**基础的 Repository提供了最基本的数据访问功能,其几个子接口则扩展了一些功能。它们的继承关系如下: 
Repository:仅仅是一个标识,表明任何继承它的均为仓库接口类
CrudRepository:继承Repository,实现了一组CRUD相关的方法 
PagingAndSortingRepository:继承CrudRepository,实现了一组分页排序相关的方法 
JpaRepository:继承PagingAndSortingRepository,实现一组JPA规范相关的方法 
自定义的XxxxRepository需要继承 JpaRepository,这样的XxxxRepository接口就具备了通用的数据访问控制层的能力。
JpaSpecificationExecutor:不属于Repository体系,实现一组JPACriteria查询相关的方法 

这里只简单介绍下CrudRepository和[b]PagingAndSortingRepository[/b]这两个接口
自定义接口后可以直接使用接口中封装的方法,比如简单的CRUD, 源码如下:
public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {
<S extends T> S save(S entity);		//保存实体
<S extends T> Iterable<S> save(Iterable<S> entities);	//保存多个实体
T findOne(ID id);	//安装主键_id查找实体
boolean exists(ID id);
Iterable<T> findAll();
Iterable<T> findAll(Iterable<ID> ids);
long count();
void delete(ID id);
void delete(T entity);
void delete(Iterable<? extends T> entities);
void deleteAll();
}


PagingAndSortingRepository继承了CrudRepository,添加了分页和排序的两个查询
public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {
Iterable<T> findAll(Sort sort);		//排序查询
Page<T> findAll(Pageable pageable);	//分页查询(Pageable中包含了排序)
}


使用接口XXXXRepository继承CrudRepository接口,PagingAndSortingRepository接口;
例如:
public interface MessageRepository extends PagingAndSortingRepository<Message, String> {}


这个XXXXRepository就相当于 DAO,只是这个接口不用你去实现.Spring Data JPA为我们封装了,只要按照Spring Data JPA的规范来定义接口的方法名,就会自动为我们生成一个默认的代理实现类。

接口中定义方法的关键字

在实际应用中,以上spring data JPA提供的方法并不能满足我们的需求, 譬如 条件查询did下所有结果集合,结果你发现官方提供的方法都是使用主键_id查询的, 这时就要使用关键字进行自定义方法了,直接放上Spring Data文档中对方法名关键字的介绍, 这些关键字和SQL语句基本相同,简单查看就能懂spring-data



简单的了解知识就介绍到这, 详细文档见spring-data

应用

记载消息的处理过程,设计了消息文档,包含id,消息体,设备did,最新状态,状态集合(记录每次状态更新的时间和改变后的状态),最后一次更新状态的时间

@Document
public class Message {

@Id
private String id;

/**消息ID*/
private String msgId;

/**消息内容*/
private String content;

/**消息状态(最后修改后的状态)*/
private int status;

/**设备ID*/
private String did;

/**消息创建时间*/
private Date createTime;

/**消息最后更新时间*/
private Date updateTime;

/**消息的历史状态列表*/
private List<MessageStatus> messageStatus;

public class MessageStatus {

/**消息状态*/
private int status;

/**消息更新时间*/
private Date time;
Message文档结构如下图:



重要部分:自定义的Repository
public interface MessageRepository extends PagingAndSortingRepository<Message, String> {

/**
* 按照设备ID查询分页缓存消息
* @param did 设备ID
* @param pageable Pageable分页对象
* @return 返回缓存消息列表
*/
Page<Message> findByDid(String did, Pageable pageable);

/**
* 根据设备ID和消息状态获取缓存消息列表
* @param did 设备ID
* @param status 消息状态码
* @return 返回指定消息码和设备ID的缓存消息列表
*/
List<Message> findByDidAndStatus(String did, int status);

/**
* 根据设备ID按照时间升序获取消息列表
* @param did 设备ID
* @return 返回指定
a1b1
设备ID的离线消息列表
*/
List<Message> findByDidOrderByUpdateTimeAsc(String did);

/**
* 根据设备ID, 消息状态, 按照时间升序获取缓存消息列表
* @param did 设备ID
* @param status 消息状态码
* @return 返回指定设备和消息状态的缓存消息列表
*/
List<Message> findByDidAndStatusOrderByUpdateTimeAsc(String did, int status);

/**
* 根据设备ID, 消息状态, 获取缓存消息的数目
* @param did 设备ID
* @param status 消息状态码
* @return 返回指定设备和消息状态的缓存消息数目
*/
int countByDidAndStatus(String did, int status);

/**
* 按照设备ID, 消息状态, 分页查询缓存消息
* @param did 设备ID
* @param status 消息状态码
* @param pageable Pageable分页对象
* @return 返回缓存消息列表
*/
Page<Message> findByDidAndStatus(String did, int status, Pageable pageable);

/**
* 按照设备ID, 多个消息状态, 分页查询缓存消息
* @param did 设备ID
* @param status 消息状态码
* @param pageable Pageable分页对象
* @return 返回缓存消息列表
*/
Page<Message> findByDidAndStatusIn(String did, Collection<Integer> status, Pageable pageable);

/**
* 根据设备ID, 消息状态集, 获取缓存消息的数目
* @param did 设备ID
* @param status 消息状态码
* @return 返回指定设备和消息状态的缓存消息数目
*/
int countByDidAndStatusIn(String did, Collection<Integer> status);
}


上面自定义的Repository都是使用关键字进行定义方法的,包含了稍微复杂点的排序分页

serviceImpl具体实现见下
/**
* 分页获取设备最新的离线消息列表
*
* @param count
*            返回个数
* @param pageNo
*            页码, 用于分页查询
* @param did
*            设备ID
* @param status
*            状态拼接字符串,状态之间逗号隔开
*
* @return 分页获取设备最新的离线消息列表
*/
public List<Message> getMsgListByPage(int count,
int pageNo,
String did,
String status) {

if (logger.isInfoEnabled()) {

logger.info(
"调用接口:[getMsgListByPage],参数:[count = {}, pageNo = {}, did = {}, status = {}]",
count, pageNo, did, status);
}

if (StringUtils.isBlank(status)) {
return getMsgListByPage(count, pageNo, did);
}

String[] splittedStatus = status.split(",");
List<Integer> statusToQuery = new ArrayList<Integer>(splittedStatus.length);
for (String s : splittedStatus) {
statusToQuery.add(Integer.valueOf(s));
}

Sort sort = new Sort(Direction.ASC, "updateTime");
Pageable pageable = new PageRequest(pageNo - 1, count, sort);
Page<Message> pageMessages = messageRepository.findByDidAndStatusIn(did, statusToQuery,
pageable);

List<Message> messageMapperList = pageMessages.getContent();
if (CollectionUtils.isNotEmpty(messageMapperList)) {
int size = messageMapperList.size();
List<Message> result = new ArrayList<Message>(size);
for (Message messageMapper : messageMapperList) {
result.add(ClassUtils.swapBack(messageMapper));
}
return result;
}
return Collections.<Message> emptyList();
}
ClassUtils mongodb中取出对象和需求中返回的对象有几个属性不对应,所有我直接做了个工具类转换封装, 这里命名重复了不是很科学,最好不要重复


 /**

     * 实现类中entity和api中entity类转化

     * @param messageMapper 实现类entity

     * @return 返回api中entity

     */

    public static Message swapBack(messageMapper) {

        Message message = new Message();

        message.setMsgId(messageMapper.getMsgId());

        message.setDid(messageMapper.getDid());

        message.setContent(messageMapper.getContent());

        message.setStatus(messageMapper.getStatus());

        message.setCreateTime(messageMapper.getCreateTime());

        message.setUpdateTime(messageMapper.getUpdateTime());

        return message;

    }




进一步复杂的查询,多表,嵌套等,就要在自定义的Repository的自定义方法上添加注解@Query("") 进行SQL语句查询操作了, 具体如何操作我没有试过..
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  mongodb Spring Data