morphia与spring的整合

转自: 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  }

大功告成~

你可能感兴趣的:(Morphia)