跳至主要内容

Spring Data Mongo: bridge MongoDB and Spring


Spring Data Mongo: bridge MongoDB and Spring

MongoDB is one of the most popular NoSQL products, Spring Data Mongo(Maven archetype id is spring-data-mongodb) tries to provides a simple approach to access MongoDB.

Configuration

Add the following code fragments in your Spring configuration.
<!-- Mongo config -->
<mongo:db-factory id="mongoDbFactory" host="localhost"
 port="27017" dbname="conference-db" />

<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
 <constructor-arg ref="mongoDbFactory" />
</bean>

<mongo:repositories base-package="com.hantsylabs.example.spring.mongo" />
Firstly, declare a DbFactory which is responsible for connecting to the MongoDB server.
Then, register the MongoTemplate object which envelop the data operations on MongoDB.
Lastly add mongo:repositories with an essential base-packageattribute, Spring will discovery your defined Repositories in the packages.
Note: There is no transaction manager declared, Spring Data Mongo does not support Spring Transaction now.

Declare your Documents

Firstly, you have to create your domain classes,. As comparison with the JPA concept, you can consider one row of records in a table is aDocument in MongoDB, and a table which has many rows is a collection of Document.
@Document
public class Conference {

 @Id
 private String id;

 @Version
 private Integer version;

 @NotNull
 private String name;

 @NotNull
 private String description;

 @NotNull
 @DateTimeFormat(style = "M-")
 private Date startedDate;

 @NotNull
 @DateTimeFormat(style = "M-")
 private Date endedDate;

 @NotNull
 private String slug;
 
 private Address address;
 
 //getters and setters.
}

public class Address {

 private String addressLine1;
 
 private String addressLine2;
 
 private String zipCode;
 
 private String city;
 
 private String country
 
 //getters and setters
}

@Document
public class Signup {

 @Id
 private String id;

 @Version
 private Integer version;

 @DBRef
 private Conference conference;

 //other properites 
 //getters and setters
}
Conference and Signup are defined as @Document, and in Sginup, a@DBRef annotation is used to declare it is a reference of theConference document. There is no JPA @OneToMany like annotations to define the relations between documents for MongoDB.
Address is annotated with nothing, and it is an embedded object inConference document, in concept, it is very similar with the@Embedable class in JPA, and its lifecycle is fully controlled by its dependent Conference document.
In MongoDB, the data in the storage is presented as JSON like format.
The following is an example of Conference document.
{ 
 "_id" : ObjectId("51b422f066d41dc05f0292f0"), 
 "_class" : "com.hantsylabs.example.spring.model.Conference", 
 "version" : 0, 
 "name" : "Test JUD", 
 "description" : "JBoss User Developer Conference 2013 Boston", 
 "startedDate" : ISODate("2013-07-09T06:38:40.272Z"), 
 "endedDate" : ISODate("2013-07-16T06:38:40.272Z"), 
 "slug" : "test-jud", 
 "address" : { 
  "addressLine1" : "address line 1", 
  "addressLine2" : "address line 2", 
  "zipCode" : "510000", 
  "city" : "NY", 
  "country" : "US" 
 } 
}
The following is an example of Signup document.
{ 
 "_id" : ObjectId("51b422f066d41dc05f0292f1"), 
 "_class" : "com.hantsylabs.example.spring.model.Signup", 
 "version" : 0, 
 "firstName" : "Hantsy", 
 "lastName" : "Bai", 
 "email" : "test@test.com", 
 "phone" : "123 222 444", 
 "occupation" : "Developer", 
 "company" : "TestCompany", 
 "comment" : "test comments", 
 "createdDate" : ISODate("2013-06-09T06:38:40.288Z"), 
 "status" : "PENDING", 
 "conference" : DBRef("conference", ObjectId("51b422f066d41dc05f0292f0")) 
}
As you see, Address is embedded in the Conference document, and in Signup, there is a DBRef used to indicate it is a reference ofConference document.
@Id and @Version annotations are from the packageorg.springframework.data.annotation, which is similar with JPA, they are used to define the unique identification flag and version of a Document.
A String, BigInteger and Mongo specific ObjectId type property can be used as document id (annotated with the @Id annotation), Spring Data Mongo will convert it to Mongo internal id at runtime.
@Version is similar with the JPA specific @Version, Spring Data Mongo will fill this field automatically at runtime.
Spring Data Mongo also provides other annotations for the field mapping, such as @Field can be used to customize the filed name of a document in MongoDB, @Indexed is designated to create indies based on the fields at runtime.

Query

Before you do some query operations, you have to create aRepository class as the steps in before post.
@Repository
public interface ConferenceRepository extends MongoRepository<Conference, String>{
}
MongoRepository is a Mongo specific Repository provided by Spring Data Mongo, it is dirven from PagingAndSortingRepository in Spring Data Commons.
Now you can use all the methods defined in thePagingAndSortingRepository and CrudRepository.
For example,
@Autowired
ConferenceRepository conferenceRepository;


conferenceRepository.save();
conferenceRepository.delete();
//etc
Firstly inject the ConferenceRepository in your classes, then use it as you expected.
The convention based methods are also supported by default in Spring Data Mongo, you have to research the Spring Data Mongo reference to get all available legal expression of the methods.
For example,
public Conference findBySlug(String slug);
It is used to find Conference by the slug property.

Custom Query

Like the Spring Data JPA, Spring Data Mongo also provides a@Query annotation(in the packageorg.springframework.data.mongodb.repository) to define and execute custom query.
For example,
@Query("{description:{ $regex: '*'+?0+'*', $options: 'i'}}")
public List<Conference> searchByDescriptionLike(String like);
The value attribute of the @Query annotation can accept a Mongo aggregation expression, please refer to the official document for theAggregation.
You can also create a Custom interface and your implementation class to execute the custom query.
The steps are similar with ones in the Spring Data JPA post.
  1. Firstly create an interface make sure the class name is endedCustom.
 public interface ConferenceRepositoryCustom {
 public List<Conference> searchByDescription(String like);
 
 public void updateConferenceDescription(String description, String id );
 }
 
  1. Modify the ConferenceRepository and addConferenceRepositoryCustom interface to be extended.
 @Repository
 public interface ConferenceRepository extends ConferenceRepositoryCustom, MongoRepository{
 }
 
  1. Create your imeplementation class to implement the methods deifned in ConferenceRepositoryCustom interface.
 public class ConferenceRepositoryImpl implements ConferenceRepositoryCustom {
 private static final Logger log = LoggerFactory
   .getLogger(ConferenceRepositoryImpl.class);

 @Autowired
 MongoTemplate mongoTemplate;

 @Override
 public List<Conference> searchByDescription(String d) {
  return mongoTemplate.find(
    Query.query(Criteria.where("description").regex(
      "[\\w]*" + d + "[\\w]*", "i")), Conference.class);
 }

 @Override
 public void updateConferenceDescription(String description, String id) {
  WriteResult result = mongoTemplate.updateMulti(
    Query.query(Criteria.where("id").is(id)),
    Update.update("description", description), Conference.class);
  
  log.debug("result @"+result.getField("description"));
 }

 }
 
Unlike the Spring Data JPA, there is no a Mongo SQL like language support in the Mongo Java Driver and Spring Data Mongo. In this implementation class, MongoTemplate is used to perform data operations.
Some other solutions I motioned before, such as DataNucleusprovides standard JPA and JDO APIs for NoSQL, so using JPA APIs on Mongo is possible when use DataNucleus.

MongoTemplate

MongoTemplate simplified the document collection based operations, please refer to the Mongo official document about Collection methods.
Criteria is a fluent API to build the query condition, it try to simplify the Mongo aggregation expression.
Query is used to combine the Criteria and other query condition, such as Pageable capability.
Update is the encapsulation of the Mongo core update operation, seeMongo core operations for more details.
In fact, you can use MongoTemplate freely in the classes outside of the Repository API.
@Repository
public class AnyBean {
 @Autowired
 MongoTemplate mongoTemplate;
}
And declare context:component-scan in your configuration to discover your beans.
<context:component-scan
 base-package="com.hantsylabs.example.spring.mongo">
</context:component-scan>

QueryDSL integration

QueryDSL supports MongoDB officially, but the Metamodel generation and data operations is dependent on a third party project named Morphia which provides JPA like APIs for Mongo operations.
But unluckily, Spring did not adopt it in Spring Data Mongo project.
  1. Extend the Spring specific QueryDslPredicateExecutor, it is from Spring Data Commons, the same interface we used in before post.
 @Repository
 public interface ConferenceRepository extends ConferenceRepositoryCustom, 
 MongoRepository<Conference, String>,
 QueryDslPredicateExecutor<Conference> 
 {
 }
 
  1. Generate the Metamodels.
Spring Data Mongo provides a custom APT processor to generate the Metamodels instead of the one provided in QueryDSL, it will scan the Spring specific @Document instead of the Morphia specific annotations.
 <plugin>
 <groupId>com.mysema.maven</groupId>
 <artifactId>apt-maven-plugin</artifactId>
 <version>1.0.9</version>
 <executions>
  <execution>
   <goals>
    <goal>process</goal>
   </goals>
   <configuration>
    <outputDirectory>target/generated-sources/java</outputDirectory>
    <processor>org.springframework.data.mongodb.repository.support.MongoAnnotationProcessor</processor>
   </configuration>
  </execution>
 </executions>
 <dependencies>
  <dependency>
   <groupId>com.mysema.querydsl</groupId>
   <artifactId>querydsl-apt</artifactId>
   <version>${querydsl.version}</version>
  </dependency>
  <dependency>
   <groupId>com.mysema.querydsl</groupId>
   <artifactId>querydsl-mongodb</artifactId>
   <classifier>apt</classifier>
   <version>${querydsl.version}</version>
  </dependency>
 </dependencies> 
 </plugin>
 
Run mvn compile to generate the Metamodels. Open theQConference class, you will find there is no difference from the early version generated for JPA entities in last post.
  1. Now you can use the APIs freely like the Spring Data JPA.
 QConference qconf = QConference.conference;
 List<Conference> conferences = (List<Conference>) conferenceRepository
    .findAll(qconf.slug.eq("test-jud"));


 List<Conference> conferences2 = (List<Conference>) conferenceRepository
    .findAll(QConference.conference.address.country.eq("CN"));
 
All work as expected.
Try another one on Signup document collection.
 List<Signup> signups = (List<Signup>) signupRepository
   .findAll(QSignup.signup.conference.eq(conf));
 
Unfortunately, this query does not work as expected, you can find some issues have been filed about this problem on Spring JIRA. Currently, QueryDSL integration in Spring Data Mongo does not support reference(the fields marked with the @DBRef annotation) in query path. In this example, the conference caused the problem.
Spring Data Mongo also provides Geo, GridFS support abstraction which are not motioned here.

Summary

Spring Data Mongo provides simple data operations on MongoDB, due to there is no query language like JPQL for Mongo, all features provided in Spring Data Mongo based on the concept of Mongo client tools and shell interaction.
The most regrettable is it can not provide consistent experience as JPA support when adopting QueryDSL in projects.
发表评论

此博客中的热门博文

JPA 2.1: Attribute Converter

JPA 2.1: Attribute Converter If you are using Hibernate, and want a customized type is supported in your Entity class, you could have to write a custom Hibernate Type. JPA 2.1 brings a new feature named attribute converter, which can help you convert your custom class type to JPA supported type. Create an Entity Reuse thePostentity class as example. @Entity @Table(name="POSTS") public class Post implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name="ID") private Long id; @Column(name="TITLE") private String title; @Column(name="BODY") private String body; @Temporal(javax.persistence.TemporalType.DATE) @Column(name="CREATED") private Date created; @Column(name="TAGS") private List<String> tags=new ArrayList<>(); } Create an attribute converter In this example…

Auditing with Hibernate Envers

Auditing with Hibernate Envers The approaches provided in JPA lifecyle hook and Spring Data auditing only track the creation and last modification info of an Entity, but all the modification history are not tracked. Hibernate Envers fills the blank table. Since Hibernate 3.5, Envers is part of Hibernate core project. Configuration Configure Hibernate Envers in your project is very simple, just need to addhibernate-enversas project dependency. <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-envers</artifactId> </dependency> Done. No need extra Event listeners configuration as the early version. Basic Usage Hibernate Envers provides a simple@Auditedannotation, you can place it on an Entity class or property of an Entity. @Audited private String description; If@Auditedannotation is placed on a property, this property can be tracked.

Create a restful application with AngularJS and Zend 2 framework

Create a restful application with AngularJS and Zend 2 framework This example application uses AngularJS/Bootstrap as frontend and Zend2 Framework as REST API producer. The backend code This backend code reuses the database scheme and codes of the official Zend Tutorial, and REST API support is also from the Zend community. Getting Started with Zend Framework 2Getting Started with REST and Zend Framework 2 Zend2 provides aAbstractRestfulControllerfor RESR API producing. class AlbumController extends AbstractRestfulController { public function getList() { $results = $this->getAlbumTable()->fetchAll(); $data = array(); foreach ($results as $result) { $data[] = $result; } return new JsonModel(array( 'data' => $data) ); } public function get($id) { $album = $this->getAlbumTable()->getAlbum($id); return new JsonModel(array("data" => $album)); } …