随笔 - 22, 文章 - 0, 评论 - 1, 引用 - 0
数据加载中……

morphia与spring的整合

最近研究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 }

大功告成~

posted on 2012-09-21 18:09 王星游 阅读(8430) 评论(0)  编辑  收藏 所属分类: java


只有注册用户登录后才能发表评论。


网站导航: