javascript
Spring Boot开发MongoDB应用实践
本文繼續(xù)上一篇定時(shí)任務(wù)中提到的郵件服務(wù),簡(jiǎn)單講解Spring Boot中如何使用MongoDB進(jìn)行應(yīng)用開(kāi)發(fā)。
上文中提到的這個(gè)簡(jiǎn)易郵件系統(tǒng)大致設(shè)計(jì)思路如下:
1、發(fā)送郵件支持同步和異步發(fā)送兩種
2、郵件使用MongDB進(jìn)行持久化保存
3、異步發(fā)送,直接將郵件批量保存在MongoDB中,然后通過(guò)后臺(tái)定時(shí)任務(wù)發(fā)送
4、同步發(fā)送,先調(diào)用Spring的發(fā)送郵件功能,接著將郵件批量保存至MongDB
5、不論同步還是異步,郵件發(fā)送失敗,定時(shí)任務(wù)可配置為進(jìn)行N次重試
一、MongoDB
MongoDB現(xiàn)在已經(jīng)是應(yīng)用比較廣泛的文檔型NoSQL產(chǎn)品,有不少公司都拿MongoDB來(lái)開(kāi)發(fā)日志系統(tǒng)。隨著MongoDB的不斷迭代更新,據(jù)說(shuō)最新版已經(jīng)支持ACID和事務(wù)了。不過(guò)鑒于歷史上MongoDB應(yīng)用的一些問(wèn)題,以及考慮到數(shù)據(jù)持久化和運(yùn)維的要求,核心業(yè)務(wù)系統(tǒng)存儲(chǔ)的技術(shù)選型要非常慎重。
1、什么是MongoDB
MongoDB是由C++語(yǔ)言編寫(xiě)的一個(gè)基于分布式文件存儲(chǔ)的開(kāi)源數(shù)據(jù)庫(kù)系統(tǒng)。MongoDB將數(shù)據(jù)存儲(chǔ)為一個(gè)文檔,數(shù)據(jù)結(jié)構(gòu)由鍵值(key=>value)對(duì)組成。MongoDB 文檔類(lèi)似于 JSON 對(duì)象(也就是BSON,10gen開(kāi)發(fā)的一個(gè)數(shù)據(jù)格式),字段值可以包含其他文檔,數(shù)組及文檔數(shù)組。主要優(yōu)點(diǎn)可以概括如下:
(1)、SchemaLess,結(jié)構(gòu)靈活,表結(jié)構(gòu)更改非常自由,不用每次修改的時(shí)候都付出代價(jià)(想想RDBMS修改表結(jié)構(gòu)要注意什么),適合業(yè)務(wù)快速迭代表結(jié)構(gòu)非常不確定的場(chǎng)景,而且json和大多數(shù)的語(yǔ)言有天然的契合,還支持?jǐn)?shù)組,嵌套文檔等數(shù)據(jù)類(lèi)型
(2)、自帶高可用,自動(dòng)主從切換(副本集)
(3)、自帶水平分片(分片),內(nèi)置了路由,配置管理,應(yīng)用只要連接路由,對(duì)應(yīng)用來(lái)說(shuō)是透明的
2、添加依賴(lài)
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId></dependency> mongodb3、添加配置
配置MongoDB連接串:
spring.data.mongodb.uri=mongodb://name:pass@ip:port/database?maxPoolSize=256如果使用多臺(tái)MongoDB數(shù)據(jù)庫(kù)服務(wù)器,參考配置如下:
spring.data.mongodb.uri=mongodb://user:pwd@ip1:port1,ip2:port2/database?maxPoolSize=512連接串的一般配置,可以參考:猛擊這里
環(huán)境搭建好了,下面就是著手編碼了。
通常我們會(huì)有各種語(yǔ)言的MongoDB客戶端,直接引入調(diào)用API。在Spring Boot中,直接使用MongoTemplate即可。
4、定義DAO接口
package com.power.demo.mongodb;import com.power.demo.domain.MailDO;import java.util.Date; import java.util.List;public interface MailDao {/*** 批量創(chuàng)建對(duì)象** @param entList*/void batchInsert(List<MailDO> entList);/*** 創(chuàng)建對(duì)象** @param ent*/void insert(MailDO ent);/*** 根據(jù)ID查詢對(duì)象** @param mailId* @return*/MailDO findByMailId(Long mailId);/*** 查詢一段時(shí)間范圍內(nèi)待發(fā)送的郵件** @param startTime 開(kāi)始時(shí)間* @param endTime 結(jié)束時(shí)間* @return*/List<MailDO> findToSendList(Date startTime, Date endTime);/*** 更新** @param ent*/void update(MailDO ent);/*** 刪除** @param mailId*/void delete(Long mailId);} MailDao5、實(shí)現(xiàn)DAO
package com.power.demo.mongodb;import com.power.demo.common.AppConst; import com.power.demo.common.SendStatusType; import com.power.demo.domain.MailDO; import com.power.demo.util.CollectionHelper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.MongoTemplate; 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.Component;import java.util.Date; import java.util.List;@Component public class MailDaoImpl implements MailDao {@Autowiredprivate MongoTemplate mongoTemplate;public void batchInsert(List<MailDO> entList) {//分組批量多次插入 每次2000條List<List<MailDO>> groupList = CollectionHelper.spliceArrays(entList, AppConst.BATCH_RECORD_COUNT);for (List<MailDO> list : groupList) {mongoTemplate.insert(list, MailDO.class);}}public void insert(MailDO ent) {mongoTemplate.save(ent);}public MailDO findByMailId(Long mailId) {Query query = new Query(Criteria.where("mailId").is(mailId));MailDO ent = mongoTemplate.findOne(query, MailDO.class);return ent;}/*** 查詢一段時(shí)間范圍內(nèi)待發(fā)送的郵件** @param startTime 開(kāi)始時(shí)間* @param endTime 結(jié)束時(shí)間* @return*/public List<MailDO> findToSendList(Date startTime, Date endTime) {Query query = new Query(Criteria.where("create_time").gte(startTime).lt(endTime).and("has_delete").is(Boolean.FALSE).and("send_status").ne(SendStatusType.SendSuccess.toString()).and("retry_count").lt(AppConst.MAX_RETRY_COUNT)) //重試次數(shù)小于3的記錄.limit(AppConst.RECORD_COUNT); //每次取20條 List<MailDO> entList = mongoTemplate.find(query, MailDO.class);return entList;}public void update(MailDO ent) {Query query = new Query(Criteria.where("_id").is(ent.getMailId()));Update update = new Update().set("send_status", ent.getSendStatus()).set("retry_count", ent.getRetryCount()).set("remark", ent.getRemark()).set("modify_time", ent.getModifyTime()).set("modify_user", ent.getModifyUser());//更新查詢返回結(jié)果集的第一條mongoTemplate.updateFirst(query, update, MailDO.class);}public void delete(Long mailId) {Query query = new Query(Criteria.where("_id").is(mailId));mongoTemplate.remove(query, MailDO.class);} } MailDaoImpl6、數(shù)據(jù)訪問(wèn)對(duì)象實(shí)體
實(shí)體MailDO這里只列舉了我在實(shí)際開(kāi)發(fā)應(yīng)用中經(jīng)常用到的字段,這個(gè)實(shí)體抽象如下:
package com.power.demo.domain;import lombok.Data; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Field;import java.io.Serializable; import java.util.Date;@Data @Document(collection = "mailinfo") public class MailDO implements Serializable {private static final long serialVersionUID = 1L;//唯一主鍵 @Id@Field("mail_id")private String mailId;@Field("mail_no")private Long mailNo;//郵件類(lèi)型 如:Text表示純文本、HTML等@Field("mail_type")private String mailType;//郵件發(fā)送人@Field("from_address")private String fromAddress;//郵件接收人@Field("to_address")private String toAddress;//CC郵件接收人@Field("cc_address")private String ccAddress;//BC郵件接收人@Field("bcc_address")private String bccAddress;//郵件標(biāo)題@Field("subject")private String subject;//郵件內(nèi)容@Field("mail_body")private String mailBody;//發(fā)送優(yōu)先級(jí) 如:Normal表示普通@Field("send_priority")private String sendPriority;//處理狀態(tài) 如:SendWait表示等待發(fā)送@Field("send_status")private String sendStatus;//是否有附件@Field("has_attachment")private boolean hasAttatchment;//附件保存的絕對(duì)地址,如fastdfs返回的url@Field("attatchment_urls")private String[] attatchmentUrls;//客戶端應(yīng)用編號(hào)或名稱(chēng) 如:CRM、訂單、財(cái)務(wù)、運(yùn)營(yíng)等@Field("client_appid")private String clientAppId;//是否刪除@Field("has_delete")private boolean hasDelete;//發(fā)送次數(shù)@Field("retry_count")private int retryCount;//創(chuàng)建時(shí)間@Field("create_time")private Date createTime;//創(chuàng)建人@Field("create_user")private String createUser;//更新時(shí)間@Field("modify_time")private Date modifyTime;//更新人@Field("modify_user")private String modifyUser;//備注@Field("remark")private String remark;//擴(kuò)展信息@Field("extend_info")private String extendInfo;public String getMailId() {return mailId;}public void setMailId(String mailId) {this.mailId = mailId;}public Long getMailNo() {return mailNo;}public void setMailNo(Long mailNo) {this.mailNo = mailNo;}public String getMailType() {return mailType;}public void setMailType(String mailType) {this.mailType = mailType;}public String getFromAddress() {return fromAddress;}public void setFromAddress(String fromAddress) {this.fromAddress = fromAddress;}public String getToAddress() {return toAddress;}public void setToAddress(String toAddress) {this.toAddress = toAddress;}public String getCcAddress() {return ccAddress;}public void setCcAddress(String ccAddress) {this.ccAddress = ccAddress;}public String getBccAddress() {return bccAddress;}public void setBccAddress(String bccAddress) {this.bccAddress = bccAddress;}public String getSubject() {return subject;}public void setSubject(String subject) {this.subject = subject;}public String getMailBody() {return mailBody;}public void setMailBody(String mailBody) {this.mailBody = mailBody;}public String getSendPriority() {return sendPriority;}public void setSendPriority(String sendPriority) {this.sendPriority = sendPriority;}public String getSendStatus() {return sendStatus;}public void setSendStatus(String sendStatus) {this.sendStatus = sendStatus;}public boolean isHasAttatchment() {return hasAttatchment;}public void setHasAttatchment(boolean hasAttatchment) {this.hasAttatchment = hasAttatchment;}public String[] getAttatchmentUrls() {return attatchmentUrls;}public void setAttatchmentUrls(String[] attatchmentUrls) {this.attatchmentUrls = attatchmentUrls;}public String getClientAppId() {return clientAppId;}public void setClientAppId(String clientAppId) {this.clientAppId = clientAppId;}public boolean isHasDelete() {return hasDelete;}public void setHasDelete(boolean hasDelete) {this.hasDelete = hasDelete;}public int getRetryCount() {return retryCount;}public void setRetryCount(int retryCount) {this.retryCount = retryCount;}public Date getCreateTime() {return createTime;}public void setCreateTime(Date createTime) {this.createTime = createTime;}public String getCreateUser() {return createUser;}public void setCreateUser(String createUser) {this.createUser = createUser;}public Date getModifyTime() {return modifyTime;}public void setModifyTime(Date modifyTime) {this.modifyTime = modifyTime;}public String getModifyUser() {return modifyUser;}public void setModifyUser(String modifyUser) {this.modifyUser = modifyUser;}public String getRemark() {return remark;}public void setRemark(String remark) {this.remark = remark;}public String getExtendInfo() {return extendInfo;}public void setExtendInfo(String extendInfo) {this.extendInfo = extendInfo;} } MailDO請(qǐng)大家注意實(shí)體上的注解,@Document(collection = "mailinfo")將會(huì)在文檔數(shù)據(jù)庫(kù)中創(chuàng)建一個(gè)mailinfo的表,@Id表示指定該字段為主鍵, @Field("mail_no")表示實(shí)體字段mailNo存在MongoDB中的字段名稱(chēng)為mail_no。
根據(jù)MongoDB官方文檔介紹,如果在插入數(shù)據(jù)時(shí)沒(méi)有指定主鍵,MongoDB會(huì)自動(dòng)給插入行自動(dòng)加上一個(gè)主鍵_id,MongoDB客戶端把這個(gè)id類(lèi)型稱(chēng)為ObjectId,看上去就是一個(gè)UUID。我們可以通過(guò)注解自己設(shè)置主鍵類(lèi)型,但是根據(jù)實(shí)踐,_id名稱(chēng)是無(wú)法改變的。@Id和 @Field("mail_id")表面看上去是我想創(chuàng)建一個(gè)mail_id為主鍵的表,但是實(shí)際主鍵只有_id而沒(méi)有mail_id。當(dāng)然,主鍵的類(lèi)型不一定非要是UUID,可以是你自己根據(jù)業(yè)務(wù)生成的唯一流水號(hào)等等。
同時(shí),還需要注意attatchment_urls這個(gè)字段,看上去數(shù)組也可以直接存進(jìn)MongoDB中,畢竟SchemaLess曾經(jīng)是MongoDB宣傳過(guò)的比RDBMS最明顯的優(yōu)勢(shì)之一。
7、郵件接口
package com.power.demo.apiservice.impl;import com.google.common.collect.Lists; import com.power.demo.apientity.request.BatchSendEmailRequest; import com.power.demo.apientity.response.BatchSendEmailResponse; import com.power.demo.apiservice.contract.MailApiService; import com.power.demo.common.*; import com.power.demo.domain.MailDO; import com.power.demo.entity.vo.MailVO; import com.power.demo.mongodb.MailDao; import com.power.demo.service.contract.MailService; import com.power.demo.util.ConfigUtil; import com.power.demo.util.FastMapperUtil; import com.power.demo.util.SerialNumberUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils;import java.util.Arrays; import java.util.Date; import java.util.List;@Component public class MailApiServiceImpl implements MailApiService {@Autowiredprivate MailService mailService;@Autowiredprivate MailDao mailDao;/*** 發(fā)送郵件** @param request 請(qǐng)求* @return 發(fā)送失敗的郵件**/public BatchSendEmailResponse batchSendEmail(BatchSendEmailRequest request) {BatchSendEmailResponse response = new BatchSendEmailResponse();response.setSuccess("");if (request == null) {response.setFail("請(qǐng)求為空");} else if (request.getMailList() == null || request.getMailList().size() == 0) {response.setFail("待發(fā)送郵件為空");}if (response.getIsOK() == false) {return response;}List<MailVO> failedMails = Lists.newArrayList();//沒(méi)有處理成功的郵件//構(gòu)造郵件對(duì)象List<MailVO> allMails = generateMails(request);failedMails = processSendMail(allMails);response.setFailMailList(failedMails);response.setSuccess(String.format("發(fā)送郵件提交成功,發(fā)送失敗的記錄為:%d", failedMails.size()));return response;}/*** 構(gòu)造待發(fā)送郵件 特殊字段賦值** @param request 請(qǐng)求* @return 發(fā)送失敗的郵件**/private List<MailVO> generateMails(BatchSendEmailRequest request) {List<MailVO> allMails = Lists.newArrayList();for (MailVO mail : request.getMailList()) {if (mail == null) {continue;}//默認(rèn)字段賦值mail.setCreateTime(new Date());mail.setModifyTime(new Date());mail.setRetryCount(0);mail.setHasDelete(false);mail.setMailNo(SerialNumberUtil.create());if (StringUtils.isEmpty(mail.getMailType())) {mail.setMailType(MailType.TEXT.toString());} else if (Arrays.stream(MailType.values()).filter(x -> x.toString().equalsIgnoreCase(mail.getMailType())).count() == 0) {mail.setMailType(MailType.TEXT.toString());}if (StringUtils.isEmpty(mail.getSendStatus())) {mail.setSendStatus(SendStatusType.SendWait.toString());} else if (Arrays.stream(SendStatusType.values()).filter(x -> x.toString().equalsIgnoreCase(mail.getSendStatus())).count() == 0) {mail.setSendStatus(SendStatusType.SendWait.toString());}if (StringUtils.isEmpty(mail.getSendPriority())) {mail.setSendPriority(SendPriorityType.Normal.toString());} else if (Arrays.stream(SendPriorityType.values()).filter(x -> x.toString().equalsIgnoreCase(mail.getSendPriority())).count() == 0) {mail.setSendPriority(SendPriorityType.Normal.toString());}if (StringUtils.isEmpty(mail.getMailId())) {mail.setMailId(String.valueOf(SerialNumberUtil.create()));}if (StringUtils.isEmpty(mail.getFromAddress())) {String fromAddr = ConfigUtil.getConfigVal(AppField.MAIL_SENDER_ADDR);mail.setFromAddress(fromAddr);}allMails.add(mail);}return allMails;}/*** 處理郵件** @param allMails 所有郵件* @return 發(fā)送失敗的郵件**/private List<MailVO> processSendMail(List<MailVO> allMails) {List<MailVO> failedMails = Lists.newArrayList();//沒(méi)有處理成功的郵件 List<MailVO> asyncMails = Lists.newArrayList();//待異步處理的郵件for (MailVO mail : allMails) {if (mail.isSync() == false) { //異步處理continue;}//同步調(diào)用BizResult<String> bizResult = safeSendMail(mail);//發(fā)送郵件成功if (bizResult.getIsOK() == true) {mail.setSendStatus(SendStatusType.SendSuccess.toString());mail.setRemark("同步發(fā)送郵件成功");} else {mail.setSendStatus(SendStatusType.SendFail.toString());mail.setRemark(String.format("同步發(fā)送郵件失敗:%s", bizResult.getMessage()));failedMails.add(mail);}}//批量保存郵件至MongoDB safeStoreMailList(allMails);return failedMails;}/*** 發(fā)送郵件** @param ent 郵件信息* @return**/private BizResult<String> safeSendMail(MailVO ent) {BizResult<String> bizSendResult = null;if (MailType.TEXT.toString().equalsIgnoreCase(ent.getMailType())) {bizSendResult = mailService.sendSimpleMail(ent);} else if (MailType.HTML.toString().equalsIgnoreCase(ent.getMailType())) {bizSendResult = mailService.sendHtmlMail(ent);}if (bizSendResult == null) {bizSendResult = new BizResult<>(false, AppConst.SUCCESS, "不支持的郵件類(lèi)型");}return bizSendResult;}/*** 批量保存郵件** @param entList 郵件信息列表* @return**/private boolean safeStoreMailList(List<MailVO> entList) {boolean isOK = storeMailList(entList);if (isOK == true) {return isOK;}for (int i = 1; i <= AppConst.MAX_RETRY_COUNT; i++) {try {Thread.sleep(100 * i);} catch (Exception te) {te.printStackTrace();}isOK = storeMailList(entList);if (isOK == true) {break;}}return isOK;}/*** 存儲(chǔ)郵件** @param entList 郵件信息列表* @return**/private boolean storeMailList(List<MailVO> entList) {boolean isOK = false;try {List<MailDO> dbEntList = Lists.newArrayList();entList.forEach(x -> {MailDO dbEnt = FastMapperUtil.cloneObject(x, MailDO.class);dbEntList.add(dbEnt);});mailDao.batchInsert(dbEntList);isOK = true;} catch (Exception e) {e.printStackTrace();}return isOK;}} MailApiServiceImpl到這里,MongoDB的主要存儲(chǔ)和查詢就搞定了。
二、郵件
在上面的郵件接口API實(shí)現(xiàn)中,我們定義了郵件發(fā)送服務(wù)MailService,在Spring Boot中發(fā)送郵件也非常簡(jiǎn)單。
1、郵件配置
## 郵件配置 spring.mail.host=smtp.xxx.com //郵箱服務(wù)器地址 spring.mail.username=abc@xxx.com //用戶名 spring.mail.password=123456 //密碼 spring.mail.default-encoding=UTF-8 mail.sender.addr=abc@company.com //發(fā)送者郵箱 mailsetting2、簡(jiǎn)單郵件
通過(guò)Spring的JavaMailSender對(duì)象,可以輕松實(shí)現(xiàn)郵件發(fā)送。
發(fā)送簡(jiǎn)單郵件代碼:
/*** 發(fā)送簡(jiǎn)單文本郵件** @param ent 郵件信息**/public BizResult<String> sendSimpleMail(MailVO ent) {BizResult<String> bizResult = new BizResult<>(true, AppConst.SUCCESS);try {if (ent == null) {bizResult.setFail("郵件信息為空");return bizResult;}if (StringUtils.isEmpty(ent.getToAddress())) {bizResult.setFail("簡(jiǎn)單郵件,接收人郵箱為空");return bizResult;}//默認(rèn)發(fā)件人設(shè)置if (StringUtils.isEmpty(ent.getFromAddress())) {ent.setFromAddress(senderAddr);}SimpleMailMessage message = new SimpleMailMessage();message.setFrom(ent.getFromAddress());message.setTo(ent.getToAddress());message.setCc(ent.getCcAddress());message.setBcc(ent.getBccAddress());message.setSubject(ent.getSubject());message.setText(ent.getMailBody());message.setSentDate(new Date());mailSender.send(message);bizResult.setSuccess("簡(jiǎn)單郵件已經(jīng)發(fā)送");} catch (Exception e) {e.printStackTrace();PowerLogger.error(String.format("發(fā)送簡(jiǎn)單郵件時(shí)發(fā)生異常:%s", e));bizResult.setFail(String.format("發(fā)送簡(jiǎn)單郵件時(shí)發(fā)生異常:%s", e));} finally {PowerLogger.info(String.format("簡(jiǎn)單郵件,發(fā)送結(jié)果:%s", SerializeUtil.Serialize(bizResult)));}return bizResult;} sendSimpleMail3、HTML郵件
同理,我們經(jīng)常要發(fā)送帶格式的HTML郵件,發(fā)送代碼可以參考如下:
/*** 發(fā)送HTML郵件** @param ent 郵件信息**/public BizResult<String> sendHtmlMail(MailVO ent) {BizResult<String> bizResult = new BizResult<>(true, AppConst.SUCCESS);try {if (ent == null) {bizResult.setFail("郵件信息為空");return bizResult;}if (StringUtils.isEmpty(ent.getToAddress())) {bizResult.setFail("HTML郵件,接收人郵箱為空");return bizResult;}//默認(rèn)發(fā)件人設(shè)置if (StringUtils.isEmpty(ent.getFromAddress())) {ent.setFromAddress(senderAddr);}MimeMessage message = mailSender.createMimeMessage();//true表示需要?jiǎng)?chuàng)建一個(gè)multipart messageMimeMessageHelper helper = new MimeMessageHelper(message, true);helper.setFrom(ent.getFromAddress());helper.setTo(ent.getToAddress());helper.setCc(ent.getCcAddress());helper.setBcc(ent.getBccAddress());helper.setSubject(ent.getSubject());helper.setText(ent.getMailBody(), true);//true表示是html郵件helper.setSentDate(new Date());//判斷有無(wú)附件 循環(huán)添加附件if (ent.isHasAttatchment() && ent.getAttatchmentUrls() != null) {for (String filePath : ent.getAttatchmentUrls()) {FileSystemResource file = new FileSystemResource(new File(filePath));String fileName = filePath.substring(filePath.lastIndexOf(File.separator));helper.addAttachment(fileName, file);}}mailSender.send(message);bizResult.setSuccess("HTML郵件已經(jīng)發(fā)送");} catch (Exception e) {e.printStackTrace();PowerLogger.error(String.format("發(fā)送HTML郵件時(shí)發(fā)生異常:%s", e));bizResult.setFail(String.format("發(fā)送HTML郵件時(shí)發(fā)生異常:%s", e));} finally {PowerLogger.info(String.format("HTML郵件,發(fā)送結(jié)果:%s", SerializeUtil.Serialize(bizResult)));}return bizResult;} sendHtmlMail郵件附件的處理,本文僅僅是簡(jiǎn)單示例,實(shí)際情況是通常都免不了要上傳分布式文件系統(tǒng),如FastDFS等,有空我會(huì)繼續(xù)寫(xiě)一下Spring Boot和分布式文件系統(tǒng)的應(yīng)用實(shí)踐。
還記得上一篇文章里的定時(shí)任務(wù)發(fā)送郵件嗎?貼一下MailServiceImpl下的補(bǔ)償發(fā)送實(shí)現(xiàn):
/*** 自動(dòng)查詢并發(fā)送郵件** @param startTime 開(kāi)始時(shí)間* @param endTime 結(jié)束時(shí)間* @return**/public void autoSend(Date startTime, Date endTime) {StopWatch watch = DateTimeUtil.StartNew();List<MailDO> mailDOList = mailDao.findToSendList(startTime, endTime);for (MailDO dbEnt : mailDOList) {MailVO ent = FastMapperUtil.cloneObject(dbEnt, MailVO.class);BizResult<String> bizSendResult = null;if (MailType.TEXT.toString().equalsIgnoreCase(ent.getMailType())) {bizSendResult = sendSimpleMail(ent);} else if (MailType.HTML.toString().equalsIgnoreCase(ent.getMailType())) {bizSendResult = sendHtmlMail(ent);}if (bizSendResult == null) {bizSendResult = new BizResult<>(false, AppConst.SUCCESS, "不支持的郵件類(lèi)型");}if (bizSendResult.getIsOK() == true) {dbEnt.setSendStatus(SendStatusType.SendSuccess.toString());} else {dbEnt.setSendStatus(SendStatusType.SendFail.toString());}dbEnt.setRetryCount(dbEnt.getRetryCount() + 1);//重試次數(shù)+1 dbEnt.setRemark(SerializeUtil.Serialize(bizSendResult));dbEnt.setModifyTime(new Date());dbEnt.setModifyUser("QuartMailTask");mailDao.update(dbEnt);}watch.stop();PowerLogger.info(String.format("本次共處理記錄數(shù):%s,總耗時(shí):%s", mailDOList.size(), watch.getTotalTimeMillis()));} 自動(dòng)查詢并發(fā)送郵件這里貼出來(lái)的示例代碼是線性的一個(gè)一個(gè)發(fā)送郵件,我們完全可以改造成多線程的并行處理方式來(lái)提升郵件發(fā)送處理能力。
三、MongoDB注意事項(xiàng)
1、常見(jiàn)參數(shù)設(shè)置問(wèn)題
MongoDB的默認(rèn)最大連接數(shù)是100,不同的客戶端有不同的實(shí)現(xiàn),對(duì)于讀多寫(xiě)多的應(yīng)用,最大連接數(shù)可能成為瓶頸。
不過(guò)設(shè)置最大連接數(shù)也要注意內(nèi)存開(kāi)銷(xiāo),合理配置連接池maxPoolSize。
其中,生產(chǎn)環(huán)境為了保證高可用,通常會(huì)配置副本集連接字符串格式mongodb://username:password@host1:port1,host2:port2[,...,hostN:portN]/database?options
options 是連接配置中的可選項(xiàng),replicaSet 是其中的一個(gè)子項(xiàng)。
最終的配置連接串可能形如:mongodb://username:password@host1:port1,host2:port2[,...,hostN:portN]/database?replicaSet=yourreplset&maxPoolSize=512
批量插入可以減少數(shù)據(jù)向服務(wù)器提交次數(shù),提高性能,但是批量提交的BSON不能超過(guò)48M,不注意這個(gè)細(xì)節(jié)很容易造成數(shù)據(jù)丟失。
關(guān)于常用連接參數(shù),可以參考這里。
2、MongoDB事務(wù)性
早期版本的MongoDB已經(jīng)支持行級(jí)的事務(wù),支持簡(jiǎn)單的行級(jí)操作原子性,單行的操作要么全部成功,要么全部失敗。
MongoDB的WiredTiger引擎本身支持事務(wù),官方在最新版本中,號(hào)稱(chēng)完全支持ACID和事務(wù)。
3、MongoDB如何提升查詢速度
可以選取合適字段創(chuàng)建索引,和RDBMS一樣,MongoDB的索引也有很多種,如:單字段索引、復(fù)合索引、多Key索引、哈希索引等。
在常見(jiàn)的查詢字段上合理添加索引,或者定期歸檔數(shù)據(jù),減少查詢數(shù)據(jù)量,這些手段都可以有效提高查詢速度。
還有一種非常常見(jiàn)的手段就是Sharding,也就是數(shù)據(jù)庫(kù)分片技術(shù)。當(dāng)數(shù)據(jù)量比較大的時(shí)候,我們需要把數(shù)據(jù)分片運(yùn)行在不同的機(jī)器中,以降低CPU、內(nèi)存和IO的壓力。MongoDB分片技術(shù)類(lèi)似MySQL的水平切分和垂直切分,主要由兩種方式做Sharding:垂直擴(kuò)展和橫向切分。垂直擴(kuò)展的方式就是進(jìn)行集群擴(kuò)展,添加更多的CPU,內(nèi)存,磁盤(pán)空間等。橫向切分則是通過(guò)數(shù)據(jù)分片的方式,通過(guò)集群統(tǒng)一提供服務(wù)。
4、MongoDB的高可用方案
高可用是絕大多數(shù)數(shù)據(jù)庫(kù)管理系統(tǒng)的核心目標(biāo)之一。真正的高可用系統(tǒng),很少有單實(shí)例的應(yīng)用形態(tài)存在。
MongoDB支持主從方式、雙機(jī)雙工方式(互備互援)和集群工作方式(多服務(wù)器互備方式),減少單點(diǎn)出故障的可能。
如果要想生產(chǎn)數(shù)據(jù)在發(fā)生故障后依然可用,就需要確保為生產(chǎn)數(shù)據(jù)庫(kù)多部署一臺(tái)服務(wù)器。MongoDB副本集提供了數(shù)據(jù)的保護(hù)、高可用和災(zāi)難恢復(fù)的機(jī)制。在MongoDB 中,有兩種數(shù)據(jù)冗余方式,一種是 Master-Slave 模式(主從復(fù)制),一種是 Replica Sets 模式(副本集)。主從復(fù)制和副本集使用了相同的復(fù)制機(jī)制,但是副本集額外增加了自動(dòng)化災(zāi)備機(jī)制:如果主節(jié)點(diǎn)宕機(jī),其中一個(gè)從節(jié)點(diǎn)會(huì)自動(dòng)提升為從節(jié)點(diǎn)。除此之外,副本集還提供了其他改進(jìn),比如更易于恢復(fù)和更復(fù)雜地部署拓?fù)渚W(wǎng)絡(luò)。集群中沒(méi)有特定的主庫(kù),主庫(kù)是選舉產(chǎn)生,如果主庫(kù) down 了,會(huì)再選舉出一臺(tái)主庫(kù)。
?
參考:
<<MongoDB權(quán)威指南>>
https://docs.mongodb.com/
http://www.runoob.com/mongodb/mongodb-tutorial.html
https://www.cnblogs.com/binyue/p/5901328.html
https://yq.aliyun.com/articles/33726
https://yq.aliyun.com/articles/66623
http://www.cnblogs.com/l1pe1/p/7871790.html
總結(jié)
以上是生活随笔為你收集整理的Spring Boot开发MongoDB应用实践的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: ENVI/IDL编程:相对辐射校正-直方
- 下一篇: 解决JS浮点数(小数)计算加减乘除的BU