背景:
最近项目使用缓存存储离线设备下发的消息. 技术支持是使用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语句查询操作了, 具体如何操作我没有试过..