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

背景:

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

因为以前没使用过springData,摸索的过程还是挺复杂的,不过springData官网和github上这方面的资料还是很齐全的,把我的小小经验记载下来…
Spring data简介
spring Data 项目的目的是为了简化构建基于 Spring 框架应用的数据访问计数,包括非关系数据库、Map-Reduce 框架、云数据服务等等;另外也包含对关系数据库的访问支持。不需要使用SQL语句,用关键子可以直接操作数据库(见后), 对于复杂(如多表,嵌套子查询等)的SQL语句, Spring data还提供了@Query()注解的形式在repository的方法上标注;
应用:
项目pom文件

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

[html] view plain copy

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

配置文件: spring-dao.xml

[html] view plain copy

<?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和PagingAndSortingRepository这两个接口
自定义接口后可以直接使用接口中封装的方法,比如简单的CRUD, 源码如下:
[java] view plain copy

public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {  
<S extends T> S save(S entity);<span style="white-space:pre;">        </span>//保存实体  
<S extends T> Iterable<S> save(Iterable<S> entities);<span style="white-space:pre;">  </span>//保存多个实体  
T findOne(ID id);<span style="white-space:pre;">    </span>//安装主键_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();  

[java] view plain copy

}  

PagingAndSortingRepository继承了CrudRepository,添加了分页和排序的两个查询
[java] view plain copy

public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {  
    Iterable<T> findAll(Sort sort);<span style="white-space:pre;">        </span>//排序查询  
    Page<T> findAll(Pageable pageable);<span style="white-space:pre;">    </span>//分页查询(Pageable中包含了排序)  
}  

使用接口XXXXRepository继承CrudRepository接口,PagingAndSortingRepository接口; 例如:
[java] view plain copy

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,最新状态,状态集合(记录每次状态更新的时间和改变后的状态),最后一次更新状态的时间

[java] view plain copy

@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;  

[java] view plain copy

public class MessageStatus {  

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

    /**消息更新时间*/  
    private Date time;  

Message文档结构如下图:

重要部分:自定义的Repository
[java] view plain copy

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 返回指定设备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具体实现见下
[java] view plain copy

/** 
 * 分页获取设备最新的离线消息列表 
 *  
 * @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();  

[java] view plain copy

[java] view plain copy

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语句查询操作了, 具体如何操作我没有试过..

猜你喜欢

转载自blog.csdn.net/qq_35337467/article/details/80729273