morphia与spring的整合

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 }

大功告成~

你可能感兴趣的:(morphia与spring的整合)