Spring Data for MongoDB 作為 Spring 大家族中的一員,為MongoDB定制了類似于關(guān)系型數(shù)據(jù)庫的ORM框架。
與hibernate mybatis 等ORM框架類似,都需要一個pojo的bean。所不同的是,關(guān)系型數(shù)據(jù)庫對應(yīng)的是table,而此處對應(yīng)到MongoDB中的collection。
由于 MongoDB 本身并沒有事務(wù)支持,所以spring 也無法維護(hù)事務(wù)。但是在mongoDB的操作手冊提供了這樣一個方式來維護(hù)多文檔操作的事務(wù)。
原文是這樣說的:
Because only single-document operations are atomic with MongoDB, two-phase commits can
only offer transaction-like semantics. It is possible for applications to return intermediate data at
intermediate points during the two-phase commit or rollback.
只有單文檔操作是原子性的,兩階段提交可以提供這樣一個類似事務(wù)的模式。這樣來為應(yīng)用程序可以返回一個再兩階段提交或者回滾的中間點(diǎn)。直接略去我的翻譯,大致的意思,就是MongoDB僅僅支持單文檔的原子性操作,但是提供了一個兩階段方式來維護(hù)事務(wù)。也就是說在多個文檔操作時,MongoDB提供了一個兩階段提交模式來維護(hù)事務(wù)。
具體的操作方式,具體參考一下
https://docs.mongodb.com/manual/tutorial/perform-two-phase-commits/
對于這個MongoDB事務(wù),個人的觀點(diǎn)是不建議使用事務(wù),因?yàn)樵跀?shù)據(jù)庫選型的時候,如果希望事務(wù)比較強(qiáng),那么建議選用關(guān)系型數(shù)據(jù)庫來存儲數(shù)據(jù)。對于MongoDB的應(yīng)用,一般是用來查詢,或者是存儲日志等對事務(wù)要求比較弱的應(yīng)用場景。
spring.data.mongodb.1.7.2.RELEASE.jar
mongo-java-driver-2.13.3.jar
<?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/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.springframework.org/schema/data/mongo http://www.springframework.org/schema/data/mongo/spring-mongo-1.0.xsd"> <!-- 配置mongoTemplate --> <mongo:mongo host="${mongo.host}" port="${mongo.port}"></mongo:mongo> <bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate"> <constructor-arg ref="mongo" /> <constructor-arg name="databaseName" value="${mongo.dbname}" /> </bean></beans>
MongoTemplate 是spring data MongoDB 中的的操作類,繼承了MongoOperations 與ApplicationContextAware ,這個MongoOperations里面封裝了基礎(chǔ)CRUD操作,有興趣直接閱讀以下源碼很快能看懂。
package edu.yingding.core.mongo;import java.io.Serializable;import java.util.List;import java.util.Map;import org.springframework.data.mongodb.core.query.Criteria;import org.springframework.data.mongodb.core.query.Query;import edu.yingding.core.entity.PagerDTO;//mongo操作接口public interface IBaseMongo { public List selectPage(Map<String, Object> param, Class entityClass, PagerDTO pagerDto) throws Exception; public List selectPage(String queryStr, Class entityClass, PagerDTO pagerDto) throws Exception; List find( Query query,Class entityClass); public List selectPage(Query query,Class entityClass,PagerDTO pagerDto)throws Exception; /** * 分組取數(shù)據(jù) * @param collectionName mongo表名 * @param fieldName * @param param * @return * @throws Exception */ public List distinct(String collectionName, String fieldName, Map<String, Object> param) throws Exception; /** * 得到一個實(shí)體 * @param param * @param entityClass * @return * @throws Exception */ public Object findOne(Map<String, Object> param, Class entityClass) throws Exception; /** * 保存結(jié)果集 * @param entity * @return * @throws Exception */ public boolean insert(Object entity) throws Exception; /** * 按id更新數(shù)據(jù) * @param id * @param param * @return * @throws Exception */ public boolean updateById(String id, Map<String, Object> param, Class entityClass) throws Exception; List selectPage(String queryStr, Criteria criteria, Class entityClass, PagerDTO pagerDto) throws Exception;}
package edu.yingding.core.mongo;import java.util.Collections;import java.util.Iterator;import java.util.List;import java.util.Map;import org.apache.commons.lang.StringUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.domain.Sort;import org.springframework.data.domain.Sort.Direction;import org.springframework.data.mongodb.core.MongoTemplate;import org.springframework.data.mongodb.core.query.BasicQuery;import org.springframework.data.mongodb.core.query.Criteria;import org.springframework.data.mongodb.core.query.Query;import org.springframework.data.mongodb.core.query.Update;import org.springframework.stereotype.Service;import com.mongodb.WriteResult;import edu.yingding.core.entity.PagerDTO;import net.sf.json.JSONObject;/** * @Author:chenfanglin 【chenfanglincfl@163.com】 * @Description: * @Date: 15:53 2017/1/4 * @Version 1.0.0 */@Servicepublic class BaseMongoImpl implements IBaseMongo { @Autowired protected MongoTemplate mongoTemplate; /** * @param param 查詢參數(shù)【map類型】 * @param entityClass 反射類 * @param pagerDto 分頁實(shí)體 * @Author:chenfanglin 【chenfanglincfl@163.com】 * @Description: 分頁方法【支持map類型參數(shù)】 * @Date: 15:52 2017/1/4 * @Version 1.0.0 */ @Override public List selectPage(Map<String, Object> param, Class entityClass, PagerDTO pagerDto) throws Exception { Query query = parseParams(param); long total = mongoTemplate.count(query, entityClass); List results = null; if (total > 0) { pagerDto.init(total); query.skip((pagerDto.getPageNum() - 1) * pagerDto.getPageSize()); query.limit(pagerDto.getPageSize()); if (StringUtils.isNotEmpty(pagerDto.getOrderBy())) { JSONObject orderObj = JSONObject.fromObject(pagerDto.getOrderBy()); if (orderObj != null) { Iterator it = orderObj.keys(); while (it.hasNext()) { String key = (String) it.next(); int value = orderObj.getInt(key); if (value == 1) { query.with(new Sort(Direction.ASC, key)); } else { query.with(new Sort(Direction.DESC, key)); } } } } System.out.println(query.toString()); results = mongoTemplate.find(query, entityClass); pagerDto.setResult(results); } else { pagerDto.setResult(Collections.emptyList()); } return results; } /** * @param queryStr 查詢字符串 * @param entityClass 反射類 * @param pagerDto 分頁實(shí)體 * @Author:chenfanglin 【chenfanglincfl@163.com】 * @Description: 分頁方法 【支持查詢字符串】 * @Date: 15:54 2017/1/4 * @Version 1.0.0 */ @Override public List selectPage(String queryStr, Class entityClass, PagerDTO pagerDto) throws Exception { if (queryStr == null) { queryStr = ""; } BasicQuery query = new BasicQuery(queryStr); long total = mongoTemplate.count(query, entityClass); List results = null; if (total > 0) { pagerDto.init(total); query.skip((pagerDto.getPageNum() - 1) * pagerDto.getPageSize()); query.limit(pagerDto.getPageSize()); if (StringUtils.isNotEmpty(pagerDto.getOrderBy())) { JSONObject orderObj = JSONObject.fromObject(pagerDto.getOrderBy()); if (orderObj != null) { Iterator it = orderObj.keys(); while (it.hasNext()) { String key = (String) it.next(); int value = orderObj.getInt(key); if (value == 1) { query.with(new Sort(Direction.ASC, key)); } else { query.with(new Sort(Direction.DESC, key)); } } } } results = mongoTemplate.find(query, entityClass); pagerDto.setResult(results); } else { pagerDto.setResult(Collections.emptyList()); } return results; } /** * @param queryStr * @param criteria * @param entityClass * @param pagerDto * @Author:chenfanglin 【chenfanglincfl@163.com】 * @Description: 分頁方法 【支持查詢字符串與Criteria對象】 * @Param: * @Date: 15:55 2017/1/4 * @Version 1.0.0 */ @Override public List selectPage(String queryStr, Criteria criteria, Class entityClass, PagerDTO pagerDto) throws Exception { if (queryStr == null) { queryStr = ""; } BasicQuery query = new BasicQuery(queryStr); query.addCriteria(criteria); long total = mongoTemplate.count(query, entityClass); List results = null; if (total > 0) { pagerDto.init(total); query.skip((pagerDto.getPageNum() - 1) * pagerDto.getPageSize()); query.limit(pagerDto.getPageSize()); if (StringUtils.isNotEmpty(pagerDto.getOrderBy())) { JSONObject orderObj = JSONObject.fromObject(pagerDto.getOrderBy()); if (orderObj != null) { Iterator it = orderObj.keys(); while (it.hasNext()) { String key = (String) it.next(); int value = orderObj.getInt(key); if (value == 1) { query.with(new Sort(Direction.ASC, key)); } else { query.with(new Sort(Direction.DESC, key)); } } } } results = mongoTemplate.find(query, entityClass); pagerDto.setResult(results); } else { pagerDto.setResult(Collections.emptyList()); } return results; } /** * @Author:chenfanglin 【chenfanglincfl@163.com】 * @Description: 查詢實(shí)體list * @Param: query 查詢對象 * @Param: entityClass 反射類 * @Date: 15:57 2017/1/4 * @Version 1.0.0 */ @Override public List find(Query query, Class entityClass) { return mongoTemplate.find(query, entityClass); } /** * @Author:chenfanglin 【chenfanglincfl@163.com】 * @Description: 查詢分頁 【支持query對象】 * @Param: * @param query 查詢對象 * @Param: * @param entityClass 反射類 * @Param: * @param pagerDto 分頁實(shí)體 * @Date: 15:58 2017/1/4 * @Version 1.0.0 */ @Override public List selectPage(Query query, Class entityClass, PagerDTO pagerDto) throws Exception { List results = null; if (query != null) { long count = mongoTemplate.count(query, entityClass); if (count > 0) { pagerDto.init(count); query.skip((pagerDto.getPageNum() - 1) * pagerDto.getPageSize()); query.limit(pagerDto.getPageSize()); if (StringUtils.isNotEmpty(pagerDto.getOrderBy())) { JSONObject orderObj = JSONObject.fromObject(pagerDto.getOrderBy()); if (orderObj != null) { Iterator it = orderObj.keys(); while (it.hasNext()) { String key = (String) it.next(); int value = orderObj.getInt(key); if (value == 1) { query.with(new Sort(Direction.ASC, key)); } else { query.with(new Sort(Direction.DESC, key)); } } } } System.out.println(query.toString()); results = mongoTemplate.find(query, entityClass); pagerDto.setResult(results); } else { pagerDto.setResult(Collections.emptyList()); } } else { pagerDto.setResult(Collections.EMPTY_LIST); } return results; } /** * @Author:chenfanglin 【chenfanglincfl@163.com】 * @Description: 查詢?nèi)ブ貙?shí)體集合 * @Param: * @param collectionName 集合名稱 * @Param: * @param fieldName 去重字段名 * @Param: * @param param 查詢條件 * @Date: 15:59 2017/1/4 * @Version 1.0.0 */ @Override public List distinct(String collectionName, String fieldName, Map<String, Object> param) throws Exception { if (StringUtils.isEmpty(collectionName) || StringUtils.isEmpty(fieldName)) { return Collections.emptyList(); } Query query = parseParams(param); return mongoTemplate.getCollection(collectionName).distinct(fieldName, query.getQueryObject()); } /** * @Author:chenfanglin 【chenfanglincfl@163.com】 * @Description: 格式化查詢條件 * @Param: * @param param 查詢條件 * @Date: 16:00 2017/1/4 * @Version 1.0.0 */ private Query parseParams(Map<String, Object> param) { Query query = new Query(); if (param != null && !param.isEmpty()) { Iterator<Map.Entry<String, Object>> it = param.entrySet().iterator(); while (it.hasNext()) { Map.Entry<String, Object> entry = it.next(); String key = entry.getKey(); if (StringUtils.isEmpty(key)) { continue; } if (key.endsWith("Ignore")) { continue; } if (key.startsWith("$")) { } else if (key.endsWith("Contains")) { query.addCriteria(Criteria.where(key.replaceFirst("Contains", "")).regex((String) entry.getValue())); } else { query.addCriteria(Criteria.where(key).is(entry.getValue())); } } } return query; } @Override public Object findOne(Map<String, Object> param, Class entityClass) throws Exception { Query query = parseParams(param); return mongoTemplate.findOne(query, entityClass); } @Override public boolean insert(Object entity) throws Exception { try { mongoTemplate.insert(entity); } catch (Exception e) { e.printStackTrace(); return false; } return true; } @Override public boolean updateById(String id, Map<String, Object> param, Class entityClass) throws Exception { Query query = new Query(); query.addCriteria(Criteria.where("id").is(id)); Update update = parseUpdate(param); WriteResult result = mongoTemplate.updateFirst(query, update, entityClass); return result.getN() > 0 ? true : false; } private Update parseUpdate(Map<String, Object> param) { Update update = new Update(); if (param != null && !param.isEmpty()) { Iterator<Map.Entry<String, Object>> it = param.entrySet().iterator(); while (it.hasNext()) { Map.Entry<String, Object> entry = it.next(); String key = entry.getKey(); if (StringUtils.isEmpty(key)) { continue; } if (key.endsWith("Ignore")) { continue; } if (key.startsWith("$")) { } else { update.set(key, entry.getValue()); } } } return update; }}
package edu.yingding.core.entity;import java.io.UnsupportedEncodingException;import java.net.URLDecoder;import java.net.URLEncoder;import java.util.ArrayList;import java.util.List;import org.apache.commons.lang.StringUtils;/*** @Author:chenfanglin 【chenfanglincfl@163.com】* @Description: 分頁實(shí)體* @Param: * @param null* @Date: 16:01 2017/1/4* @Version 1.0.0*/public class PagerDTO { //當(dāng)前第幾頁 private int pageNum=1; //總共多少頁 private long pageCount; //每頁顯示幾條數(shù)據(jù) private int pageSize = 10; //總共多少條 private long total; /* 排序方式 */ private String orderBy; /* 查詢字符串 */ private String queryStr; /* 表單防止重復(fù)提交碼 */ private String formToken; /* 必須有參數(shù)才能做分頁查詢 */ private boolean argsCanSearch; /* 如果為真,并且pageCount > 0 ,則在數(shù)據(jù)庫中不進(jìn)行查詢 */ private boolean userPageCount; //分頁返回的數(shù)據(jù) private Object result; public boolean isUserPageCount() { return userPageCount; } public void setUserPageCount(boolean userPageCount) { this.userPageCount = userPageCount; } public boolean isArgsCanSearch() { return argsCanSearch; } public void setArgsCanSearch(boolean argsCanSearch) { this.argsCanSearch = argsCanSearch; } public String getFormToken() { return formToken; } public void setFormToken(String formToken) { this.formToken = formToken; } public void init(long total) { this.setTotal(total); boolean flag = (total%this.getPageSize() == 0) ? false : true; long iPageSize = flag ? (total/this.getPageSize()+1) : (total/this.getPageSize()); if(this.getPageNum() > iPageSize) { this.setPageNum(1); } this.setPageCount(iPageSize); } public int getPageNum() { return pageNum; } public void setPageNum(int pageNum) { this.pageNum = pageNum; } public long getPageCount() { return pageCount; } public void setPageCount(long pageCount) { this.pageCount = pageCount; } public int getPageSize() { return pageSize; } public void setPerPage(int pageSize) { if(pageSize > 100) { this.pageSize = 100; } else { this.pageSize = pageSize; } } public long getTotal() { return total; } public void setTotal(long total) { this.total = total; } public String getOrderBy() { return orderBy; } public void setOrderBy(String orderBy) { this.orderBy = orderBy; } public String getQueryStr() { return queryStr; } public Object getResult() { return result; } public void setResult(Object result) { this.result = result; } public String getEncodeQueryStr() { if(queryStr != null && !"".equals(queryStr)) { try { return URLEncoder.encode(queryStr, "UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } return ""; } public String getDecodeQueryStr() { if(queryStr != null && !"".equals(queryStr)) { try { return URLDecoder.decode(queryStr, "UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } return ""; } public void setQueryStr(String queryStr) { this.queryStr = queryStr; } public String getCacheString() { StringBuffer cacheKey = new StringBuffer(); cacheKey.append(this.getPageNum()).append("#"); cacheKey.append(this.getPageSize()).append("#"); if(StringUtils.isNotEmpty(this.getOrderBy())) { cacheKey.append(this.getOrderBy()).append("#"); } List<String> args = new ArrayList<String>(); setCacheList(args); if(args != null && !args.isEmpty()) { for(String arg : args) { if(StringUtils.isNotEmpty(arg)) { cacheKey.append(arg).append("#"); } } } return cacheKey.toString(); } /** * 設(shè)置緩存KEY * @param cacheKeys */ public void setCacheList(List<String> args) { }}
總的來說,Spring Data MongoDB 整體架構(gòu)方式還是類似于hibernate mybatis,只是相應(yīng)的會有一些概念的變動。就操作性來說,對于開發(fā)人員只是熟悉相關(guān)API以及相關(guān)的概念,觸類旁通。使用來說還是很方便的。
擴(kuò)展:
諸如類似Spring Data MongoDB ORM 框架還有諸如 morphia
https://github.com/mongodb/morphia。