最近研究mongoDB的各種pojo-mapping框架,中意的就兩個:morphia和spring-data-mongodb。
本來想著spring-data-mongodb與spring的結(jié)合更緊密些,但悲劇的是其要求spring3.0.x以上版本,與生產(chǎn)環(huán)境不符。查了查stackoverflow,大家評價morphia更老牌更穩(wěn)定一些,于是就用這個了。
研究了一番,果然與spring整合起來很麻煩。
首先看stackoverflow上的帖子,提問者跟我的想法完全一樣:在spring里,我沒有現(xiàn)成的辦法調(diào)用ensureIndexes()這樣的方法啊,腫么辦?
http://stackoverflow.com/questions/5365315/using-morphia-with-spring
回答者給出的兩個鏈接我也看了,真心沒啥收獲。
后來又搜到一篇帖子:
http://topmanopensource.iteye.com/blog/1449889
很粗略的看了一下,還不錯,總之都得自己實現(xiàn)那些工廠類,完成與spring的集成。
看來網(wǎng)上這方面的需求還不少,甚至在google-code上找到一個項目叫“spring-morphia”,專門來解決這個問題:
http://code.google.com/p/spring-morphia/
貌似荒廢已久,沒有完成的可供下載的jar包,但是在其svn上,可以看到一些可供我們參考的類:
http://code.google.com/p/spring-morphia/source/browse/trunk/spring-morphia/src/main/java/com/so/smorphia/
本文基本上就是根據(jù)上面兩個連接的思路寫的,自己總結(jié)一下而已,不做過多解釋了,代碼里有注釋。
首先我們需要一個生成和配置mongodb的工廠類:
1 public class MongoFactoryBean extends AbstractFactoryBean<Mongo> {
2
3 // 表示服務(wù)器列表(主從復制或者分片)的字符串數(shù)組
4 private String[] serverStrings;
5 // mongoDB配置對象
6 private MongoOptions mongoOptions;
7 // 是否主從分離(讀取從庫),默認讀寫都在主庫
8 private boolean readSecondary = false;
9 // 設(shè)定寫策略(出錯時是否拋異常),默認采用SAFE模式(需要拋異常)
10 private WriteConcern writeConcern = WriteConcern.SAFE;
11
12 @Override
13 public Class<?> getObjectType() {
14 return Mongo.class;
15 }
16
17 @Override
18 protected Mongo createInstance() throws Exception {
19 Mongo mongo = initMongo();
20
21 // 設(shè)定主從分離
22 if (readSecondary) {
23 mongo.setReadPreference(ReadPreference.secondaryPreferred());
24 }
25
26 // 設(shè)定寫策略
27 mongo.setWriteConcern(writeConcern);
28 return mongo;
29 }
30
31 /**
32 * 初始化mongo實例
33 * @return
34 * @throws Exception
35 */
36 private Mongo initMongo() throws Exception {
37 // 根據(jù)條件創(chuàng)建Mongo實例
38 Mongo mongo = null;
39 List<ServerAddress> serverList = getServerList();
40
41 if (serverList.size() == 0) {
42 mongo = new Mongo();
43 }else if(serverList.size() == 1){
44 if (mongoOptions != null) {
45 mongo = new Mongo(serverList.get(0), mongoOptions);
46 }else{
47 mongo = new Mongo(serverList.get(0));
48 }
49 }else{
50 if (mongoOptions != null) {
51 mongo = new Mongo(serverList, mongoOptions);
52 }else{
53 mongo = new Mongo(serverList);
54 }
55 }
56 return mongo;
57 }
58
59
60 /**
61 * 根據(jù)服務(wù)器字符串列表,解析出服務(wù)器對象列表
62 * <p>
63 *
64 * @Title: getServerList
65 * </p>
66 *
67 * @return
68 * @throws Exception
69 */
70 private List<ServerAddress> getServerList() throws Exception {
71 List<ServerAddress> serverList = new ArrayList<ServerAddress>();
72 try {
73 for (String serverString : serverStrings) {
74 String[] temp = serverString.split(":");
75 String host = temp[0];
76 if (temp.length > 2) {
77 throw new IllegalArgumentException(
78 "Invalid server address string: " + serverString);
79 }
80 if (temp.length == 2) {
81 serverList.add(new ServerAddress(host, Integer
82 .parseInt(temp[1])));
83 } else {
84 serverList.add(new ServerAddress(host));
85 }
86 }
87 return serverList;
88 } catch (Exception e) {
89 throw new Exception(
90 "Error while converting serverString to ServerAddressList",
91 e);
92 }
93 }
94
95 /* ------------------- setters --------------------- */
96 }
其次我們需要一個產(chǎn)生和配置morphia對象的工廠類:
1 public class MorphiaFactoryBean extends AbstractFactoryBean<Morphia> {
2 /**
3 * 要掃描并映射的包
4 */
5 private String[] mapPackages;
6
7 /**
8 * 要映射的類
9 */
10 private String[] mapClasses;
11
12 /**
13 * 掃描包時,是否忽略不映射的類
14 * 這里按照Morphia的原始定義,默認設(shè)為false
15 */
16 private boolean ignoreInvalidClasses;
17
18 @Override
19 protected Morphia createInstance() throws Exception {
20 Morphia m = new Morphia();
21 if (mapPackages != null) {
22 for (String packageName : mapPackages) {
23 m.mapPackage(packageName, ignoreInvalidClasses);
24 }
25 }
26 if (mapClasses != null) {
27 for (String entityClass : mapClasses) {
28 m.map(Class.forName(entityClass));
29 }
30 }
31 return m;
32 }
33
34 @Override
35 public Class<?> getObjectType() {
36 return Morphia.class;
37 }
38
39 /*----------------------setters-----------------------*/
40 }
最后我們還需要一個產(chǎn)生和配置Datastore的工廠類:
1 public class DatastoreFactoryBean extends AbstractFactoryBean<Datastore> {
2
3 private Morphia morphia; //morphia實例,最好是單例
4 private Mongo mongo; //mongo實例,最好是單例
5 private String dbName; //數(shù)據(jù)庫名
6 private String username; //用戶名,可為空
7 private String password; //密碼,可為空
8 private boolean toEnsureIndexes=false; //是否確認索引存在,默認false
9 private boolean toEnsureCaps=false; //是否確認caps存在,默認false
10
11
12 @Override
13 protected Datastore createInstance() throws Exception {
14 //這里的username和password可以為null,morphia對象會去處理
15 Datastore ds = morphia.createDatastore(mongo, dbName, username,
16 password==null?null:password.toCharArray());
17 if(toEnsureIndexes){
18 ds.ensureIndexes();
19 }
20 if(toEnsureCaps){
21 ds.ensureCaps();
22 }
23 return ds;
24 }
25
26 @Override
27 public Class<?> getObjectType() {
28 return Datastore.class;
29 }
30
31 @Override
32 public void afterPropertiesSet() throws Exception {
33 super.afterPropertiesSet();
34 if (mongo == null) {
35 throw new IllegalStateException("mongo is not set");
36 }
37 if (morphia == null) {
38 throw new IllegalStateException("morphia is not set");
39 }
40 }
41
42 /*----------------------setters-----------------------*/
43 }
我們來仿照morphia文檔,寫兩個測試的POJO:
1 @Entity
2 public class Hotel {
3 @Id private ObjectId id;
4
5 private String name;
6 private int stars;
7
8 @Embedded
9 private Address address;
10
11 /*-----------gettters & setters----------*/
12 }
1 @Embedded
2 public class Address {
3 private String street;
4 private String city;
5 private String postCode;
6 private String country;
7 /*-----------gettters & setters----------*/
8 }
還需要一個為測試POJO專門服務(wù)的DAO,這里繼承morphia里的BasicDAO:
1 public class HotelDAO extends BasicDAO<Hotel, ObjectId> {
2
3 protected HotelDAO(Datastore ds) {
4 super(ds);
5 }
6
7 /* ----------------以下是自定義的數(shù)據(jù)查詢方法(finder)----------------- */
8 }
最后是spring的XML文件:
1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"
4 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
5 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
6
7 <!-- 配置文件 -->
8 <context:property-placeholder location="classpath:config.properties" />
9
10 <!-- mongoDB的配置對象 -->
11 <bean id="mongoOptions" class="com.mongodb.MongoOptions">
12 <!-- 服務(wù)器是否自動重連,默認為false -->
13 <property name="autoConnectRetry" value="false" />
14 <!-- 對同一個服務(wù)器嘗試重連的時間(毫秒),設(shè)為0時默認使用15秒 -->
15 <property name="maxAutoConnectRetryTime" value="0" />
16 <!-- 與每個主機的連接數(shù),默認為10 -->
17 <property name="connectionsPerHost" value="10" />
18 <!-- 連接超時時間(毫秒),默認為10000 -->
19 <property name="connectTimeout" value="10000" />
20 <!-- 是否創(chuàng)建一個finalize方法,以便在客戶端沒有關(guān)閉DBCursor的實例時,清理掉它。默認為true -->
21 <property name="cursorFinalizerEnabled" value="true" />
22 <!-- 線程等待連接可用的最大時間(毫秒),默認為120000 -->
23 <property name="maxWaitTime" value="120000" />
24 <!-- 可等待線程倍數(shù),默認為5.例如connectionsPerHost最大允許10個連接,則10*5=50個線程可以等待,更多的線程將直接拋異常 -->
25 <property name="threadsAllowedToBlockForConnectionMultiplier" value="5" />
26 <!-- socket讀寫時超時時間(毫秒),默認為0,不超時 -->
27 <property name="socketTimeout" value="0" />
28 <!-- 是socket連接在防火墻上保持活動的特性,默認為false -->
29 <property name="socketKeepAlive" value="false" />
30 <!-- 對應(yīng)全局的WriteConcern.SAFE,默認為false -->
31 <property name="safe" value="true" />
32 <!-- 對應(yīng)全局的WriteConcern中的w,默認為0 -->
33 <property name="w" value="0" />
34 <!-- 對應(yīng)全局的WriteConcern中的wtimeout,默認為0 -->
35 <property name="wtimeout" value="0" />
36 <!-- 對應(yīng)全局的WriteConcern.FSYNC_SAFE,如果為真,每次寫入要等待寫入磁盤,默認為false -->
37 <property name="fsync" value="false" />
38 <!-- 對應(yīng)全局的WriteConcern.JOURNAL_SAFE,如果為真,每次寫入要等待日志文件寫入磁盤,默認為false -->
39 <property name="j" value="false" />
40 </bean>
41
42 <!-- 使用工廠創(chuàng)建mongo實例 -->
43 <bean id="mongo" class="me.watchzerg.test.morphia.spring.MongoFactoryBean">
44 <!-- mongoDB的配置對象 -->
45 <property name="mongoOptions" ref="mongoOptions"/>
46
47 <!-- 是否主從分離(讀取從庫),默認為false,讀寫都在主庫 -->
48 <property name="readSecondary" value="false"/>
49
50 <!-- 設(shè)定寫策略,默認為WriteConcern.SAFE,優(yōu)先級高于mongoOptions中的safe -->
51 <property name="writeConcern" value="SAFE"/>
52
53 <!-- 設(shè)定服務(wù)器列表,默認為localhost:27017 -->
54 <property name="serverStrings">
55 <array>
56 <value>${mongoDB.server}</value>
57 </array>
58 </property>
59 </bean>
60
61
62 <!-- 使用工廠創(chuàng)建morphia實例,同時完成類映射操作 -->
63 <bean id="morphia" class="me.watchzerg.test.morphia.spring.MorphiaFactoryBean" >
64 <!-- 指定要掃描的POJO包路徑 -->
65 <property name="mapPackages">
66 <array>
67 <value>me.watchzerg.test.morphia.pojo</value>
68 </array>
69 </property>
70
71 <!-- 指定要映射的類 -->
72 <!-- <property name="mapClasses">
73 <array>
74 <value>me.watchzerg.test.morphia.pojo.Hotel</value>
75 <value>me.watchzerg.test.morphia.pojo.Address</value>
76 </array>
77 </property> -->
78
79 <!-- 掃描包時是否忽略不可用的類,默認為false -->
80 <!-- <property name="ignoreInvalidClasses" value="false"/> -->
81 </bean>
82
83 <!-- 使用工廠創(chuàng)建datastore,同時完成index和caps的確認操作 -->
84 <bean id="datastore" class="me.watchzerg.test.morphia.spring.DatastoreFactoryBean" >
85 <property name="morphia" ref="morphia"/>
86 <property name="mongo" ref="mongo"/>
87
88 <!-- collection的名稱 -->
89 <property name="dbName" value="${mongoDB.dbName}"/>
90
91 <!-- 用戶名和密碼可以為空 -->
92 <!-- <property name="username" value="my_username"/>
93 <property name="password" value="my_password"/> -->
94
95 <!-- 是否進行index和caps的確認操作,默認為flase -->
96 <property name="toEnsureIndexes" value="true"/>
97 <property name="toEnsureCaps" value="true"/>
98 </bean>
99
100 <!-- ===============以下是具體DAO的實現(xiàn)===================== -->
101
102 <bean id="hotelDAO" class="me.watchzerg.test.morphia.dao.impl.HotelDAO">
103 <constructor-arg ref="datastore"/>
104 </bean>
105
106 </beans>
最后寫一個測試類看看我們的成果:
1 public class MorphiaTest {
2 private static HotelDAO hotelDAO;
3
4 /**
5 * 測試Morphia的DAO層
6 *
7 * @param args
8 * @throws Exception
9 */
10 public static void main(String[] args) throws Exception {
11 // 初始化DAO
12 initDAO();
13
14 // 插入測試
15 saveTest();
16
17 // 更新測試
18 // updateTest();
19
20 // 刪除測試
21 // deleteTest();
22
23 // 查詢測試
24 // queryHotel();
25
26 System.out.println("done!");
27 }
28
29 /**
30 * 初始化DAO
31 * <p>
32 * @Title: initDAO
33 * </p>
34 */
35 private static void initDAO() {
36 ApplicationContext context = new ClassPathXmlApplicationContext(
37 "config.xml");
38 hotelDAO = (HotelDAO) context.getBean("hotelDAO");
39 }
40
41 /**
42 * 生成指定個數(shù)的hotelList
43 * <p>
44 * @Title: getHotelList
45 * </p>
46 *
47 * @param num
48 * @return
49 */
50 private static List<Hotel> getHotelList(int num) {
51 List<Hotel> list = new ArrayList<Hotel>();
52 for (int i = 0; i < num; i++) {
53 Hotel hotel = new Hotel();
54 hotel.setName("編號為[" + i + "]的旅店");
55 hotel.setStars(i % 10);
56 Address address = new Address();
57 address.setCountry("中國");
58 address.setCity("北京");
59 address.setStreet("上帝南路");
60 address.setPostCode("10000" + (i % 10));
61 hotel.setAddress(address);
62 list.add(hotel);
63 }
64 return list;
65 }
66
67 /**
68 * 將hotelList插入數(shù)據(jù)庫
69 * <p>
70 * @Title: saveHotelList
71 * </p>
72 *
73 * @param hotelDAO
74 * @param hotelList
75 */
76 private static void saveTest() {
77 List<Hotel> hotelList = getHotelList(100);
78 for (Hotel hotel : hotelList) {
79 // Key<Hotel> key=hotelDAO.save(hotel,WriteConcern.SAFE);
80 Key<Hotel> key = hotelDAO.save(hotel);
81 System.out.println("id為[" + key.getId() + "]的記錄已被插入");
82 }
83 }
84
85 /**
86 * 更新操作測試
87 * <p>
88 * @Title: updateTest
89 * </p>
90 *
91 * @throws Exception
92 */
93 private static void updateTest() throws Exception {
94 //生成查詢條件
95 Query<Hotel> q = hotelDAO.createQuery().field("stars")
96 .greaterThanOrEq(9);
97 //生成更新操作
98 UpdateOperations<Hotel> ops = hotelDAO.createUpdateOperations()
99 .set("address.city", "shanghai").inc("stars");
100 // UpdateResults<Hotel> ur=hotelDAO.update(q, ops);
101 UpdateResults<Hotel> ur = hotelDAO.updateFirst(q, ops);
102 if (ur.getHadError()) {
103 System.out.println(ur.getError());
104 throw new Exception("更新時發(fā)生錯誤");
105 }
106 if (ur.getUpdatedExisting()) {
107 System.out.println("更新成功,更新條數(shù)為[" + ur.getUpdatedCount()
108 + "],插入條數(shù)為[" + ur.getInsertedCount() + "]");
109 } else {
110 System.out.println("沒有記錄符合更新條件");
111 }
112 }
113
114 /**
115 * 刪除操作測試
116 * <p>
117 * @Title: deleteTest
118 * </p>
119 */
120 private static void deleteTest() {
121 ObjectId id = hotelDAO.findIds().get(0);
122 hotelDAO.deleteById(id);
123
124 Query<Hotel> q = hotelDAO.createQuery().field("stars")
125 .greaterThanOrEq(100);
126 hotelDAO.deleteByQuery(q);
127 }
128
129 /**
130 * 查詢測試
131 * <p>
132 * @Title: queryHotel
133 * </p>
134 */
135 private static void queryHotel() {
136 // 顯示所有記錄
137 System.out.println("\nhotelDAO.find()=");
138 for (Hotel hotel : hotelDAO.find()) {
139 System.out.println(hotel);
140 }
141
142 // 統(tǒng)計star大于等于9的數(shù)目
143 System.out
144 .println("\nhotelDAO.count(hotelDAO.createQuery().field(\"stars\").greaterThanOrEq(9))="
145 + hotelDAO.count(hotelDAO.createQuery().field("stars")
146 .greaterThanOrEq(9)));
147
148 // 顯示符合條件的記錄ID
149 List<ObjectId> ids = hotelDAO.findIds("stars", 8);
150 System.out.println("\nhotelDAO.findIds(\"stars\", 8)=");
151 for (ObjectId id : ids) {
152 System.out.println(id);
153 }
154 }
155
156 }
大功告成~