
在NoSQL数据库领域,MongoDB无疑是文档型数据库的标杆之作。它是一个基于分布式文件存储的NoSQL数据库,旨在为Web应用提供可扩展的高性能数据存储解决方案,凭借灵活的存储模式、优异的性能和强大的扩展能力,成为互联网、大数据等场景下的首选数据库之一。很多开发者日常使用MongoDB进行数据存储、查询,但对其核心功能背后的架构设计、算法支撑却了解不深。今天这篇博客,就带大家从“是什么(功能特性)”到“为什么(架构算法)”,全面拆解MongoDB的核心逻辑,帮你深入了解这款数据库。
Learn and share.

在NoSQL数据库领域,MongoDB无疑是文档型数据库的标杆之作。它是一个基于分布式文件存储的NoSQL数据库,旨在为Web应用提供可扩展的高性能数据存储解决方案,凭借灵活的存储模式、优异的性能和强大的扩展能力,成为互联网、大数据等场景下的首选数据库之一。很多开发者日常使用MongoDB进行数据存储、查询,但对其核心功能背后的架构设计、算法支撑却了解不深。今天这篇博客,就带大家从“是什么(功能特性)”到“为什么(架构算法)”,全面拆解MongoDB的核心逻辑,帮你深入了解这款数据库。
第三种方式,是用XCONF文件通知eXistDB要对哪个collection中的哪些操作做触发,然后触发器指向一个JAVA类。
1、首先,编写触发器的java类,打成jar包,放到%existdb_home%\lib\user路径下
TriggerTest.java
package com.neohope.existdb.test;
import org.exist.collections.Collection;
import org.exist.collections.IndexInfo;
import org.exist.collections.triggers.DocumentTrigger;
import org.exist.collections.triggers.SAXTrigger;
import org.exist.collections.triggers.TriggerException;
import org.exist.dom.DocumentImpl;
import org.exist.dom.NodeSet;
import org.exist.security.PermissionDeniedException;
import org.exist.security.xacml.AccessContext;
import org.exist.storage.DBBroker;
import org.exist.storage.txn.Txn;
import org.exist.xmldb.XmldbURI;
import org.exist.xquery.CompiledXQuery;
import org.exist.xquery.XPathException;
import org.exist.xquery.XQueryContext;
import java.util.ArrayList;
import java.util.Map;
public class TriggerTest extends SAXTrigger implements DocumentTrigger {
private String logCollection = "xmldb:exist:///db/Triggers";
private String logFileName = "logj.xml";
private String logUri;
@Override
public void configure(DBBroker broker, Collection parent, Map parameters)
throws TriggerException {
super.configure(broker, parent, parameters);
ArrayList<String> objList = (ArrayList<String>)parameters.get("LogFileName");
if(objList!=null && objList.size()>0)
{
logFileName= objList.get(0);
}
logUri = logCollection+"/"+logFileName;
}
@Override
public void beforeCreateDocument(DBBroker broker, Txn transaction, XmldbURI uri) throws TriggerException {
LogEvent(broker,uri.toString(),"beforeCreateDocument");
}
@Override
public void afterCreateDocument(DBBroker broker, Txn transaction, DocumentImpl document) throws TriggerException {
LogEvent(broker, document.getDocumentURI(),"afterCreateDocument");
}
@Override
public void beforeUpdateDocument(DBBroker broker, Txn transaction, DocumentImpl document) throws TriggerException {
LogEvent(broker,document.getDocumentURI(), "beforeUpdateDocument");
}
@Override
public void afterUpdateDocument(DBBroker broker, Txn transaction, DocumentImpl document) throws TriggerException {
LogEvent(broker, document.getDocumentURI(),"afterUpdateDocument");
}
@Override
public void beforeMoveDocument(DBBroker broker, Txn transaction, DocumentImpl document, XmldbURI newUri) throws TriggerException {
LogEvent(broker, document.getDocumentURI(),"beforeMoveDocument");
}
@Override
public void afterMoveDocument(DBBroker broker, Txn transaction, DocumentImpl document, XmldbURI newUri) throws TriggerException {
LogEvent(broker, document.getDocumentURI(),"afterMoveDocument");
}
@Override
public void beforeCopyDocument(DBBroker broker, Txn transaction, DocumentImpl document, XmldbURI newUri) throws TriggerException {
LogEvent(broker, document.getDocumentURI(),"beforeCopyDocument");
}
@Override
public void afterCopyDocument(DBBroker broker, Txn transaction, DocumentImpl document, XmldbURI newUri) throws TriggerException {
LogEvent(broker, document.getDocumentURI(),"afterCopyDocument");
}
@Override
public void beforeDeleteDocument(DBBroker broker, Txn transaction, DocumentImpl document) throws TriggerException {
LogEvent(broker, document.getDocumentURI(),"beforeDeleteDocument");
}
@Override
public void afterDeleteDocument(DBBroker broker, Txn transaction, XmldbURI uri) throws TriggerException {
LogEvent(broker, uri.toString(),"afterDeleteDocument");
}
@Override
public void beforeUpdateDocumentMetadata(DBBroker broker, Txn txn, DocumentImpl document) throws TriggerException {
LogEvent(broker, document.getDocumentURI(),"beforeUpdateDocumentMetadata");
}
@Override
public void afterUpdateDocumentMetadata(DBBroker broker, Txn txn, DocumentImpl document) throws TriggerException {
LogEvent(broker, document.getDocumentURI(),"afterUpdateDocumentMetadata");
}
private void LogEvent(DBBroker broker,String uriFile, String logContent) throws TriggerException {
String xQuery = "update insert <trigger event=\""+logContent+"\" uri=\""+uriFile+"\" timestamp=\"{current-dateTime()}\"/> into doc(\""+logUri+"\")/TriggerLogs";
try {
XQueryContext context = broker.getXQueryService().newContext(AccessContext.TRIGGER);
CreateLogFile(broker,context);
CompiledXQuery compiled = broker.getXQueryService().compile(context,xQuery);
broker.getXQueryService().execute(compiled, NodeSet.EMPTY_SET);
} catch (XPathException e) {
e.printStackTrace();
} catch (PermissionDeniedException e) {
e.printStackTrace();
}
}
private void CreateLogFile(DBBroker broker,XQueryContext context)
{
String xQuery = "if (not(doc-available(\""+logUri+"\"))) then xmldb:store(\""+logCollection+"\", \""+logFileName+"\", <TriggerLogs/>) else ()";
try {
CompiledXQuery compiled = broker.getXQueryService().compile(context,xQuery);
broker.getXQueryService().execute(compiled, NodeSet.EMPTY_SET);
} catch (XPathException e) {
e.printStackTrace();
} catch (PermissionDeniedException e) {
e.printStackTrace();
}
}
}
第二种方式,是用XCONF文件通知eXistDB要对哪个collection中的哪些操作做触发,然后将XQuery语句包含在XCONF文件中。
1、在你需要触发的collection的对应配置collection中,增加一个xconf文件,文件名任意,官方推荐collection.xconf。配置collection与原collection的对应关系为,在/db/system/config/db下,建立/db下相同的collection。
比如,如果你希望监控/db/cda02路径,就需要在/db/system/config/db/cda02路径下,新增一个collection.xconf。
collection.xconf
<collection xmlns="http://exist-db.org/collection-config/1.0">
<triggers>
<trigger event="create" class="org.exist.collections.triggers.XQueryTrigger">
<parameter name="query" value="
xquery version '3.0';
module namespace trigger='http://exist-db.org/xquery/trigger';
declare namespace xmldb='http://exist-db.org/xquery/xmldb';
declare function trigger:before-create-document($uri as xs:anyURI)
{
local:log-event('before', 'create', 'document', $uri)
};
declare function trigger:after-create-document($uri as xs:anyURI)
{
local:log-event('after', 'create', 'document', $uri)
};
declare function trigger:before-delete-document($uri as xs:anyURI)
{
local:log-event('before', 'delete', 'document', $uri)
};
declare function trigger:after-delete-document($uri as xs:anyURI)
{
local:log-event('after', 'delete', 'document', $uri)
};
declare function local:log-event($type as xs:string, $event as xs:string, $object-type as xs:string, $uri as xs:string)
{
let $log-collection := '/db/Triggers'
let $log := 'log02.xml'
let $log-uri := concat($log-collection, '/', $log)
return
(
(: util:log does not work at all
util:log('warn', 'trigger fired'),
:)
(: create the log file if it does not exist :)
if (not(doc-available($log-uri))) then
xmldb:store($log-collection, $log, <triggers/>)
else ()
,
(: log the trigger details to the log file :)
update insert <trigger event='{string-join(($type, $event, $object-type), '-')}' uri='{$uri}' timestamp='{current-dateTime()}'/> into doc($log-uri)/triggers ) };"/>
</trigger>
</triggers>
</collection>
第一种方式,是用XCONF文件通知eXistDB要对哪个collection中的哪些操作做触发,然后触发器指向一个XQM的文件。
1、首先,编写触发器的xqm文件,比如我的保存路径为/db/Triggers/TriggerTest01.xqm
TriggerTest01.xqm
xquery version "3.0";
module namespace trigger="http://exist-db.org/xquery/trigger";
declare namespace xmldb="http://exist-db.org/xquery/xmldb";
declare function trigger:before-create-collection($uri as xs:anyURI)
{
local:log-event("before", "create", "collection", $uri)
};
declare function trigger:after-create-collection($uri as xs:anyURI)
{
local:log-event("after", "create", "collection", $uri)
};
declare function trigger:before-copy-collection($uri as xs:anyURI, $new-uri as xs:anyURI)
{
local:log-event("before", "copy", "collection", concat("from: ", $uri, " to:", $new-uri))
};
declare function trigger:after-copy-collection($new-uri as xs:anyURI, $uri as xs:anyURI)
{
local:log-event("after", "copy", "collection", concat("from: ", $uri, " to:", $new-uri))
};
declare function trigger:before-move-collection($uri as xs:anyURI, $new-uri as xs:anyURI)
{
local:log-event("before", "move", "collection", concat("from: ", $uri, " to:", $new-uri))
};
declare function trigger:after-move-collection($new-uri as xs:anyURI, $uri as xs:anyURI)
{
local:log-event("after", "move", "collection", concat("from: ", $uri, " to:", $new-uri))
};
declare function trigger:before-delete-collection($uri as xs:anyURI)
{
local:log-event("before", "delete", "collection", $uri)
};
declare function trigger:after-delete-collection($uri as xs:anyURI)
{
local:log-event("after", "delete", "collection", $uri)
};
declare function trigger:before-create-document($uri as xs:anyURI)
{
local:log-event("before", "create", "document", $uri)
};
declare function trigger:after-create-document($uri as xs:anyURI)
{
local:log-event("after", "create", "document", $uri)
};
declare function trigger:before-update-document($uri as xs:anyURI)
{
local:log-event("before", "update", "document", $uri)
};
declare function trigger:after-update-document($uri as xs:anyURI)
{
local:log-event("after", "update", "document", $uri)
};
declare function trigger:before-copy-document($uri as xs:anyURI, $new-uri as xs:anyURI)
{
local:log-event("before", "copy", "document", concat("from: ", $uri, " to: ", $new-uri))
};
declare function trigger:after-copy-document($new-uri as xs:anyURI, $uri as xs:anyURI)
{
local:log-event("after", "copy", "document", concat("from: ", $uri, " to: ", $new-uri))
};
declare function trigger:before-move-document($uri as xs:anyURI, $new-uri as xs:anyURI)
{
local:log-event("before", "move", "document", concat("from: ", $uri, " to: ", $new-uri))
};
declare function trigger:after-move-document($new-uri as xs:anyURI, $uri as xs:anyURI)
{
local:log-event("after", "move", "document", concat("from: ", $uri, " to: ", $new-uri))
};
declare function trigger:before-delete-document($uri as xs:anyURI)
{
local:log-event("before", "delete", "document", $uri)
};
declare function trigger:after-delete-document($uri as xs:anyURI)
{
local:log-event("after", "delete", "document", $uri)
};
declare function local:log-event($type as xs:string, $event as xs:string, $object-type as xs:string, $uri as xs:string)
{
let $log-collection := "/db/Triggers"
let $log := "log01.xml"
let $log-uri := concat($log-collection, "/", $log)
return
(
(: create the log file if it does not exist :)
if (not(doc-available($log-uri))) then
xmldb:store($log-collection, $log, <triggers/>)
else ()
,
(: log the trigger details to the log file :)
update insert <trigger event="{string-join(($type, $event, $object-type), '-')}" uri="{$uri}" timestamp="{current-dateTime()}"/> into doc($log-uri)/triggers
)
};
1、计算阶乘的函数
xquery version "3.0";
declare function local:fact($n as xs:integer) {
if ($n eq 1) then
$n
else
$n * local:fact($n - 1)
,
util:log('warn','n is ' || $n)
};
local:fact(5)
1、QueryFileSOAP.java
package com.neohope.existdb.test;
import org.exist.soap.Query;
import org.exist.soap.QueryResponse;
import org.exist.soap.QueryService;
import org.exist.soap.QueryServiceLocator;
import java.net.URL;
import java.nio.charset.Charset;
public class QueryFileSOAP {
public static void QueryXML(String xquery, String user, String pwd) throws Exception {
QueryService service = new QueryServiceLocator();
Query query = service.getQuery(new URL("http://localhost:8080/exist/services/Query"));
String sessionId = query.connect("neotest", "neotest");
byte[] queryData = xquery.getBytes(Charset.forName("UTF-8"));
QueryResponse resp = query.xquery( sessionId, queryData );
System.out.println( "found: " + resp.getHits() );
if(resp.getHits() == 0) {
return;
}
else {
//get 10 results
byte[][] hits = query.retrieveData(sessionId, 1, 10,
true, false, "elements").getElements();
for (int i = 0; i < hits.length; i++) {
System.out.println(new String(hits[i], "UTF-8"));
}
}
query.disconnect(sessionId);
}
public static void main(String args[]) throws Exception {
String user = "neotest";
String pwd = "neotest";
String query ="for $name in collection('/db/CDA')/ClinicalDocument/recordTarget/patientRole/patient/name \n" +
"return \n" +
"<name>{$name}</name> ";
QueryXML(query, user, pwd);
}
}
1、GetFileSOAP.java
package com.neohope.existdb.test;
import org.exist.soap.Query;
import org.exist.soap.QueryService;
import org.exist.soap.QueryServiceLocator;
import java.net.URL;
public class GetFileSOAP {
public static void GetXML(String fileId, String user, String pwd) throws Exception {
QueryService service = new QueryServiceLocator();
Query query = service.getQuery(new URL("http://localhost:8080/exist/services/Query"));
String session = query.connect(user, pwd);
byte[] data = query.getResourceData(session,
"/db/CDA/"+fileId,
true, false, false);
System.out.println(new String(data, "UTF-8"));
query.disconnect(session);
}
public static void main(String args[]) throws Exception {
String user = "neotest";
String pwd = "neotest";
GetXML("入院患者护理评估单01.xml",user,pwd);
}
}
1、SaveFileSOAP.java
package com.neohope.existdb.test;
import org.exist.soap.*;
import java.io.BufferedReader;
import java.io.FileReader;
import java.net.URL;
import java.nio.charset.Charset;
public class SaveFileSOAP {
public static void SaveXML(String xmlFilePath, String user, String pwd) throws Exception {
AdminService adminService = new AdminServiceLocator();
Admin admin = adminService.getAdmin(new URL("http://localhost:8080/exist/services/Admin"));
String session = admin.connect("neotest", "neotest");
BufferedReader f = new BufferedReader(new FileReader(xmlFilePath));
String line;
StringBuffer xml = new StringBuffer();
while ((line = f.readLine()) != null)
xml.append(line);
f.close();
admin.store(session, xml.toString().getBytes(Charset.forName("UTF-8")), "UTF-8", "/db/CDA/入院患者护理评估单02.xml", true);
admin.disconnect(session);
}
public static void main( String[] args ) throws Exception {
String user = "neotest";
String pwd = "neotest";
SaveXML("PATH_TO_FILE\\入院患者护理评估单02.xml", user, pwd);
}
}
1、QueryFileHTTP.java
package com.neohope.existdb.test;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.Charset;
public class QueryFileHTTP {
public static void QueryXML(String query) throws IOException {
URL url = new URL("http://localhost:8080/exist/rest/db/CDA");
HttpURLConnection connect = (HttpURLConnection) url.openConnection();
connect.setRequestProperty("Content-Type", "application/xml");
connect.setRequestMethod("POST");
connect.setDoOutput(true);
OutputStream os = connect.getOutputStream();
os.write(query.getBytes(Charset.forName("UTF-8")));
connect.connect();
BufferedReader is = new BufferedReader(new InputStreamReader(connect.getInputStream()));
String line;
while((line = is.readLine()) != null)
System.out.println(line);
}
public static void main(String[] args) throws IOException {
String query ="<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<query xmlns=\"http://exist.sourceforge.net/NS/exist\" start=\"1\" max=\"10\" cache=\"no\">\n";
query +="<text><![CDATA[\n" +
"for $name01 in /ClinicalDocument/recordTarget/patientRole/patient/name \n" +
"return \n" +
"<name>{$name01}</name> \n" +
"]]></text> \n";
query +="<properties> \n";
query +="<property name=\"indent\" value=\"yes\"/> \n";
query +="<property name=\"encoding\" value=\"UTF-8\"/> \n";
query +="</properties> \n";
query +="</query>";
System.out.println(query);
QueryXML(query);
}
}
1、GetFileHTTP.java
package com.neohope.existdb.test;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
public class GetFileHTTP {
public static void GetXML(String fileId) throws IOException {
URL url = new URL("http://localhost:8080/exist/rest/db/CDA/" + fileId);
System.out.println("GET file from " + url.toString());
HttpURLConnection connect = (HttpURLConnection) url.openConnection();
connect.setRequestMethod("GET");
connect.connect();
System.out.println("Result:");
BufferedReader bis = new BufferedReader(new InputStreamReader(connect.getInputStream()));
String line;
while ((line = bis.readLine()) != null) {
System.out.println(line);
}
}
public static void main(String[] args) throws IOException {
GetXML("入院患者护理评估单01.xml");
}
}