转自: http://www.blogjava.net/watchzerg/archive/2012/09/21/388291.html
最近研究mongoDB的各种pojo-mapping框架,中意的就两个:morphia和spring-data-mongodb。
本来想着spring-data-mongodb与spring的结合更紧密些,但悲剧的是其要求spring3.0.x以上版本,与生产环境不符。查了查stackoverflow,大家评价morphia更老牌更稳定一些,于是就用这个了。
研究了一番,果然与spring整合起来很麻烦。
首先看stackoverflow上的帖子,提问者跟我的想法完全一样:在spring里,我没有现成的办法调用ensureIndexes()这样的方法啊,肿么办?
http://stackoverflow.com/questions/5365315/using-morphia-with-spring
回答者给出的两个链接我也看了,真心没啥收获。
后来又搜到一篇帖子:
http://topmanopensource.iteye.com/blog/1449889
很粗略的看了一下,还不错,总之都得自己实现那些工厂类,完成与spring的集成。
看来网上这方面的需求还不少,甚至在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/
本文基本上就是根据上面两个连接的思路写的,自己总结一下而已,不做过多解释了,代码里有注释。
首先我们需要一个生成和配置mongodb的工厂类:
1
public
class MongoFactoryBean
extends AbstractFactoryBean<Mongo> {
2
3
//
表示服务器列表(主从复制或者分片)的字符串数组
4
private String[] serverStrings;
5
//
mongoDB配置对象
6
private MongoOptions mongoOptions;
7
//
是否主从分离(读取从库),默认读写都在主库
8
private
boolean readSecondary =
false ;
9
//
设定写策略(出错时是否抛异常),默认采用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
//
设定主从分离
22
if (readSecondary) {
23 mongo.setReadPreference(ReadPreference.secondaryPreferred());
24 }
25
26
//
设定写策略
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
//
根据条件创建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
* 根据服务器字符串列表,解析出服务器对象列表
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 }
其次我们需要一个产生和配置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的原始定义,默认设为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 }
最后我们还需要一个产生和配置Datastore的工厂类:
1
public
class DatastoreFactoryBean
extends AbstractFactoryBean<Datastore> {
2
3
private Morphia morphia;
//
morphia实例,最好是单例
4
private Mongo mongo;
//
mongo实例,最好是单例
5
private String dbName;
//
数据库名
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专门服务的DAO,这里继承morphia里的BasicDAO:
1
public
class HotelDAO
extends BasicDAO<Hotel, ObjectId> {
2
3
protected HotelDAO(Datastore ds) {
4
super (ds);
5 }
6
7
/*
----------------以下是自定义的数据查询方法(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
<!--
服务器是否自动重连,默认为false
-->
13
<
property
name
="autoConnectRetry"
value
="false"
/>
14
<!--
对同一个服务器尝试重连的时间(毫秒),设为0时默认使用15秒
-->
15
<
property
name
="maxAutoConnectRetryTime"
value
="0"
/>
16
<!--
与每个主机的连接数,默认为10
-->
17
<
property
name
="connectionsPerHost"
value
="10"
/>
18
<!--
连接超时时间(毫秒),默认为10000
-->
19
<
property
name
="connectTimeout"
value
="10000"
/>
20
<!--
是否创建一个finalize方法,以便在客户端没有关闭DBCursor的实例时,清理掉它。默认为true
-->
21
<
property
name
="cursorFinalizerEnabled"
value
="true"
/>
22
<!--
线程等待连接可用的最大时间(毫秒),默认为120000
-->
23
<
property
name
="maxWaitTime"
value
="120000"
/>
24
<!--
可等待线程倍数,默认为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
<!--
对应全局的WriteConcern.SAFE,默认为false
-->
31
<
property
name
="safe"
value
="true"
/>
32
<!--
对应全局的WriteConcern中的w,默认为0
-->
33
<
property
name
="w"
value
="0"
/>
34
<!--
对应全局的WriteConcern中的wtimeout,默认为0
-->
35
<
property
name
="wtimeout"
value
="0"
/>
36
<!--
对应全局的WriteConcern.FSYNC_SAFE,如果为真,每次写入要等待写入磁盘,默认为false
-->
37
<
property
name
="fsync"
value
="false"
/>
38
<!--
对应全局的WriteConcern.JOURNAL_SAFE,如果为真,每次写入要等待日志文件写入磁盘,默认为false
-->
39
<
property
name
="j"
value
="false"
/>
40
</
bean
>
41
42
<!--
使用工厂创建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
<!--
设定写策略,默认为WriteConcern.SAFE,优先级高于mongoOptions中的safe
-->
51
<
property
name
="writeConcern"
value
="SAFE"
/>
52
53
<!--
设定服务器列表,默认为localhost:27017
-->
54
<
property
name
="serverStrings"
>
55
<
array
>
56
<
value
> ${mongoDB.server}
</
value
>
57
</
array
>
58
</
property
>
59
</
bean
>
60
61
62
<!--
使用工厂创建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
<!--
使用工厂创建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的实现=====================
-->
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
* 生成指定个数的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插入数据库
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("更新时发生错误");
105 }
106
if (ur.getUpdatedExisting()) {
107 System.out.println("更新成功,更新条数为[" + ur.getUpdatedCount()
108 + "],插入条数为[" + 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
//
统计star大于等于9的数目
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 }
大功告成~