MongoDB的存储是基于文档的,区别于以往的关系型数据库,它的数据模式可以更加宽松灵活,特别适合日益多变的互联网应用。在最开始接触MongoDB的时候就被它JavaScript式的操作所吸引,更被JSON式的数据存储所震撼。当时就有一种直觉,这将极大简化应用接口的开
MongoDB的存储是基于文档的,区别于以往的关系型数据库,它的数据模式可以更加宽松灵活,特别适合日益多变的互联网应用。在最开始接触MongoDB的时候就被它Javascript式的操作所吸引,更被JSON式的数据存储所震撼。当时就有一种直觉,这将极大简化应用接口的开发工作,使得数据交互变得异常轻松。下面我将以示例代码的方式介绍我的使用方法。
本示例代码使用了如下开发包:
MongoDB的官方Driver包:mongo-2.7.3.jar
基于MongoDB的ORM产品:morphia-0.99.jar
性能与功能都很棒的Jackson JSON:jackson-all-1.9.3.jar
另外还要辅助一些其他的依赖包:
morphia-logging-slf4j-0.99.jar
slf4j-api-1.6.1.jar
slf4j-nop-1.6.1.jar
上面的两篇文章是本人总结的一些经验之谈。作为参考,朋友们可以在遇到问题的时候参阅上面两篇文章。
下面进入正题:
我的思路分为两方面,即数据的输入和数据的输出。
输入源是一个文本文件,其中的数据来自SQL
Server中的Northwind示例数据库。具体来说是从其中的订单信息中抽离出相关联的部分,经过加工整理而成。(数据已经上传到CSDN资源频道,下载地址:http://download.csdn.net/detail/chaijunkun/4067684,不要资源分哦!)通过按行读取文件中的内容模拟Web应用各客户端发来的数据,然后将它们存储到MongoDB中;
输出部分将实现一个按条件查询,从MongoDB中得到的查询结果将通过控制台输出。这样就能模拟Web应用服务端响应请求,将数据推送到客户端页面的场景了。
(特别注意:本文中设计到的所有源代码都是UTF-8格式编码的)
先实现一个连接属性的配置
MongoDBConnConfig.java
package net.csdn.blog.chaijunkun;
public class MongoDBConnConfig {
//MongoDB主机名称或IP
public static final String SERVER= "localhost";
//MongoDB端口
public static final int PORT= 27017;
//使用的数据库名称
public static final String ORDERS_DATABASE= "orders";
}
再实现一个Morphia框架与MongoDB的集成方法
MongoDBDataStore.java
package net.csdn.blog.chaijunkun;
import java.net.UnknownHostException;
import com.google.code.morphia.Datastore;
import com.google.code.morphia.Morphia;
import com.
mongodb.Mongo;
import com.mongodb.MongoException;
/**
* MongoDB联合Morphia框架生成DataStore的类
* @author chaijunkun
*
*/
public class MongoDBDataStore {
/**
* 生成Orders数据库Datastore对象
* @return 返回Datastore对象 发生异常返回为null
*/
public static Datastore getOrdersInstance(){
Mongo connection = null;
try {
connection = new Mongo(MongoDBConnConfig.SERVER, MongoDBConnConfig.PORT);
} catch (UnknownHostException e) {
return null;
} catch (MongoException e) {
return null;
}
Morphia morphia= new Morphia();
return morphia.createDatastore(connection, MongoDBConnConfig.ORDERS_DATABASE);
}
}
我们还需要一个Java对象到MongoDB文档和Java对象到输出JSON对象的POJO实体:
OrderInfo.java
package net.csdn.blog.chaijunkun.entities;
import java.util.Date;
import org.bson.types.ObjectId;
import org.codehaus.jackson.annotate.JsonIgnore;
import org.codehaus.jackson.map.annotate.JsonDeserialize;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import com.google.code.morphia.annotations.Entity;
import com.google.code.morphia.annotations.Id;
import com.google.code.morphia.annotations.PostPersist;
import com.google.code.morphia.annotations.PrePersist;
//morphia中的注解 标明该对象存入orderInfo集合中 并且不存储类名
@Entity(value="orderInfo", noClassnameStored= true)
public class OrderInfo {
//morphia中的注解 标明该key为标识字段(MongoDB中特殊的ObjectId字段)
@Id
//Jackson中的注解 标明在序列化与反序列化过程中不使用该key
@JsonIgnore(value= true)
private ObjectId id;
private Long orderId;
private String productName;
private Integer quantity;
private Float unitPrice;
//Jackson中的注解 标明该字段使用自定义的DateSerializer类实现序列化
@JsonSerialize(using= DateSerializer.class)
//Jackson中的注解 标明该字段使用自定义的DateDeserializer类实现反序列化
@JsonDeserialize(using= DateDeserializer.class)
private Date orderDate;
private String contactName;
private String address;
private String phone;
@SuppressWarnings("unused")
//morphia中的注解 指示在存入MongoDB之前的操作
@PrePersist
private void beforeSaving(){
System.out.println("即将保存对象:"+ this.toString());
}
@SuppressWarnings("unused")
//morphia中的注解 指示在存入MongoDB之后的操作
@PostPersist
private void afterSaving(){
System.out.println("对象保存完毕:"+ this.toString());
}
//以下就是Getters和Setters了
public ObjectId getId() {
return id;
}
public void setId(ObjectId id) {
this.id = id;
}
public Long getOrderId() {
return orderId;
}
public void setOrderId(Long orderId) {
this.orderId = orderId;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public Integer getQuantity() {
return quantity;
}
public void setQuantity(Integer quantity) {
this.quantity = quantity;
}
public Float getUnitPrice() {
return unitPrice;
}
public void setUnitPrice(Float unitPrice) {
this.unitPrice = unitPrice;
}
public Date getOrderDate() {
return orderDate;
}
public void setOrderDate(Date orderDate) {
this.orderDate = orderDate;
}
public String getContactName() {
return contactName;
}
public void setContactName(String contactName) {
this.contactName = contactName;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
我们注意到上面的POJO实体中包含Date(日期)类型的数据。这类数据特别指定了它的序列化与反序列化方法,下面我们来具体看一下:
序列化方法:DateSerializer.java
package net.csdn.blog.chaijunkun.entities;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.map.JsonSerializer;
import org.codehaus.jackson.map.SerializerProvider;
public class DateSerializer extends JsonSerializer
{
@Override
public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
throws IOException, JsonProcessingException {
SimpleDateFormat sdf= new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
String formatedDate= sdf.format(date);
jsonGenerator.writeString(formatedDate);
}
}
这其实是Jackson中的一个很实用的功能,通过自定义序列化器,可以实现Java对象到JSON的数据格式自定义。例如Date的格式我们可以任意改写。上面的泛型类被具象为Date类型。重写了serialize方法。输入的date参数就是将要被序列化的数据,jsonGenerator就是序列化的上下文。当数据内容被整理好后将内容写入序列化的上下文中就完成了自定义过程。
类似地,我们来看一下反序列化方法DateDeserializer.java
package net.csdn.blog.chaijunkun.entities;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.map.DeserializationContext;
import org.codehaus.jackson.map.JsonDeserializer;
public class DateDeserializer extends JsonDeserializer{
@Override
public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException, JsonProcessingException {
String unformatedDate= jsonParser.getText();
SimpleDateFormat sdf= new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
Date retVal;
try {
retVal = sdf.parse(unformatedDate);
} catch (ParseException e) {
return null;
}
return retVal;
}
}
泛型的JSON反序列化类被我们具象成了Date类型的,并且重写了deserialize方法。传入的jsonParser是反序列化过程中的上下文,通过它我们可以找到当前处理的JSON字段内容。然后将内容按照特定的要求来生成对应的数据类型(Date),然后作为返回值将其返回。此时POJO中相应的字段就顺利完成了反序列化。另外这样做还能保证类型安全。
2012年12月17日补充:
最近有一个需求,需要在序列化与反序列化对象的时候对数据进行修改,当发现数据源值为空时需要让生成的JSON显示改字段为“游客”。可是我无论如何指定序列化器与反序列化器都无效。程序根本走不到指定的代码中去。后来我得出结论,Jackson
JSON在反序列化对象的时候,若JSON数据中对应属性为null,则不会走自定义的反序列化器;同样地,当你设置对象的某个属性值为null时,在将其序列化成JSON时,也不会走自定义的序列化器。因此若有类似的需求,请在序列化与反序列化之前通过硬代码形式判断和修改,千万不要什么事都指望着序列化器与反序列化器。
再来看如何将数据输入MongoDB:
JSONTransformData.java
package net.csdn.blog.chaijunkun;
import java.net.UnknownHostException;
import net.csdn.blog.chaijunkun.entities.OrderInfo;
import com.google.code.morphia.Datastore;
import com.google.code.morphia.Key;
import com.mongodb.MongoException;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.map.ObjectMapper;
public class JSONTransformData {
public static void main(String[] args) throws UnknownHostException, MongoException{
//创建Jackson全局的objectMapper 它既可以用于序列化 也可以用于反序列化
ObjectMapper objectMapper= new ObjectMapper();
//得到JSON处理的工厂对象
JsonFactory jsOnFactory= objectMapper.getJsonFactory();
//得到Morphia框架的Datastore对象用于数据库操作
Datastore ds= MongoDBDataStore.getOrdersInstance();
//为保证每次本程序都得到相同结果 需要将之前的过期数据库删除
ds.getMongo().dropDatabase(MongoDBConnConfig.ORDERS_DATABASE);
//进入读文件阶段
File dataFile= new File("jsonData.txt");
FileReader fr;
Integer idx=1;
try {
fr = new FileReader(dataFile);
BufferedReader br=new BufferedReader(fr);
String currentJsOnStr= null;
try {
//按行读取
while((currentJsOnStr= br.readLine())!=null){
currentJsOnStr= new String(currentJsonStr.getBytes(), "UTF-8");
if (currentJsonStr.trim().equals("")){
continue;
}
//进入反序列化阶段
//通过JSON处理工厂对象创建JSON分析器
JsonParser jsOnParser= jsonFactory.createJsonParser(currentJsonStr);
//反序列化的关键
OrderInfo orderInfo= jsonParser.readValueAs(OrderInfo.class);
if (orderInfo!=null){
//将对象存入MongoDB
Key key= ds.save(orderInfo);
if (key!= null){
System.out.println("已存入:"+ key.getId() + ",元素序列:"+ idx);
System.out.println(currentJsonStr);
}else{
System.out.println("元素序列"+ idx+ "发生错误,JSON:");
System.out.println(currentJsonStr);
break;
}
}
idx++;
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
通过代码中的注释,我们不难理解各行代码的含义。并且整个代码也很简洁。
以下是运行结果片段:
即将保存对象:net.csdn.blog.chaijunkun.entities.OrderInfo@1f8d244
对象保存完毕:net.csdn.blog.chaijunkun.entities.OrderInfo@1f8d244
已存入:4f3ca1785a27520bbb93c4c5,元素序列:2152
{"orderId":11077, "productName":"Louisiana Hot Spiced Okra", "quantity":1, "unitPrice":17.00, "orderDate":"1998-05-06 00:00:00", "contactName":"Paula Wilson", "address":"2817 Milton Dr.", "phone":"(505) 555-5939"}
即将保存对象:net.csdn.blog.chaijunkun.entities.OrderInfo@b48b11
对象保存完毕:net.csdn.blog.chaijunkun.entities.OrderInfo@b48b11
已存入:4f3ca1785a27520bbb93c4c6,元素序列:2153
{"orderId":11077, "productName":"Rd Kaviar", "quantity":2, "unitPrice":15.00, "orderDate":"1998-05-06 00:00:00", "contactName":"Paula Wilson", "address":"2817 Milton Dr.", "phone":"(505) 555-5939"}
最后我们再来看一下查询的结果如何进行反序列化:
JSONQueryDemo.java
package net.csdn.blog.chaijunkun;
import java.io.IOException;
import java.io.StringWriter;
import java.net.UnknownHostException;
import java.util.Iterator;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.map.ObjectMapper;
import net.csdn.blog.chaijunkun.entities.OrderInfo;
import com.google.code.morphia.Datastore;
import com.google.code.morphia.query.Query;
import com.mongodb.MongoException;
public class JSONQueryDemo {
public static void main(String[] args) throws UnknownHostException, MongoException {
//创建Jackson全局的objectMapper 它既可以用于序列化 也可以用于反序列化
ObjectMapper objectMapper= new ObjectMapper();
//得到Morphia框架的Datastore对象用于数据库操作
Datastore ds= MongoDBDataStore.getOrdersInstance();
//按条件进行查询 这里的条件是orderId等于10875的所有信息
//这里特别注意key和条件关系式"="之间要有空格
Query queryResult= ds.find(OrderInfo.class, "orderId =", 10875);
//创建查询结果的迭代器
Iterator it= queryResult.iterator();
//遍历查询结果
while(it.hasNext()){
OrderInfo orderInfo= it.next();
System.out.println("================================================");
//由于Jackson写出JSON内容时统一采用流式写出
//为了以字符串形式能够展示生成的JSON 这里特别用到了StringWriter
StringWriter sw= new StringWriter();
JsonGenerator jsOnGenerator= null;
try {
//从全局的objectMapper建立JSON处理工厂,随即建立JSON生成器
jsonGenerator = objectMapper.getJsonFactory().createJsonGenerator(sw);
//流式向StringWriter中写入JSON
jsonGenerator.writeObject(orderInfo);
jsonGenerator.flush();
jsonGenerator.close();
//输出的JSON字符串
System.out.println(sw.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("查询完成");
}
}
下面是查询结果: ================================================
{"orderId":10875,"productName":"Teatime Chocolate Biscuits","quantity":25,"unitPrice":9.2,"orderDate":"1998-02-06 12:00:00","contactName":"Christina Berglund","address":"Berguvsvgen 8","phone":"0921-12 34 65"}
================================================
{"orderId":10875,"productName":"Zaanse koeken","quantity":21,"unitPrice":9.5,"orderDate":"1998-02-06 12:00:00","contactName":"Christina Berglund","address":"Berguvsvgen 8","phone":"0921-12 34 65"}
================================================
{"orderId":10875,"productName":"Maxilaku","quantity":15,"unitPrice":20.0,"orderDate":"1998-02-06 12:00:00","contactName":"Christina Berglund","address":"Berguvsvgen 8","phone":"0921-12 34 65"}
查询完成
我们看到,查询出的结果已经按照我们的约定方式进行显示了。
本文总结了一套完整的利用MongoDB与Jackson
JSON框架结合实现快速开发应用接口的例子。在实际生产环境中,如果需求有所变动,我们完全可以通过更改POJO实体的方法来随意扩展。希望以上例子能给朋友们一点启发。也欢迎关注此技术的朋友一起探讨。