mongoTemplate query by DBRef
db.user.find()
{... , "department" : DBRef("department", ObjectId("51f941bb3580d5d8b1ae97f9"))}
Query q = new Query(where("department.$id").is(new ObjectId("51f941bb3580d5d8b1ae97f9")));
Handling DBRefs in MongoDB with Spring Data
Posted by
onin mongodb, nosql, Spring, Spring Data
Recently I was watching a
presentation titled Why I Chose MongoDB for guardian.co.uk by Mat Wall. I thought of giving a look to mongoDB. Those who dont know what is mongoDB, well, its a document oriented database with some advantages over other relational databases. For more info you can have a look at MongoDb website. While I was researching on how to access mongoDB easily and more efficiently I found out Spring Data. I also found some useful blogs explaining integration of both. But I was still not able to find a concrete working example which explains how to handle class relationship with MongoDB(DBRefs). So I thought of writing a complete working Spring MVC + Spring Data + MongoDB application handling DBRefs.
I have created a simple example with three domain classes User, Post and Comment. User can create Post and Post can have Comment(keeping it as simple as that). Below are the domain classes(of the example),
1 2 3 4 5 6 7 8 9 10 11 12 | import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @Document public class User { @Id private String id; private String userName; private String password; //getters & setters } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | import org.springframework.data.annotation.Id; @org .springframework.data.mongodb.core.mapping.Document public class Post { @Id private String id; private String subject; private String content; @org .springframework.data.mongodb.core.mapping.DBRef private User user; //getters and setters } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import org.springframework.data.annotation.Id; @org .springframework.data.mongodb.core.mapping.Document public class Comment { @Id private String id; private String content; @org .springframework.data.mongodb.core.mapping.DBRef private User user; @org .springframework.data.mongodb.core.mapping.DBRef private Post post; //getters and setters } |
You can see how the classes are linked with each other using @DBRef annotation. Now in order to access MongoDB and we need to create some DAO classes. Well thats a piece of cake with Spring Data's MongoRepository. Just extend MongoRepository<T, X> with you DAO where T is class name and X is class used for creating the key.
1 2 3 4 5 6 7 | import in.xebia.mongodb.blog.domain.User; import org.springframework.transaction.annotation.Transactional; @Transactional public interface UserDao extends org.springframework.data.mongodb.repository.MongoRepository<User, String> { } |
1 2 3 4 5 6 7 | import in.xebia.mongodb.blog.domain.Post; import org.springframework.transaction.annotation.Transactional; @Transactional public interface PostDao extends org.springframework.data.mongodb.repository.MongoRepository<Post, String> { } |
1 2 3 4 5 6 7 | import in.xebia.mongodb.blog.domain.Comment; import org.springframework.transaction.annotation.Transactional; @Transactional public interface CommentDao extends org.springframework.data.mongodb.repository.MongoRepository<Comment, String>{ } |
MongoRepository provides basic CRUD operations like findOne(),findALL(),save(),delete(), etc. but in order to handle DBRefs we need to make custom use of MongoTemplate as shown below in the CommentService class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | import static org.springframework.data.mongodb.core.query.Criteria.where; import in.xebia.mongodb.blog.api.CommentDao; import in.xebia.mongodb.blog.api.CommentService; import in.xebia.mongodb.blog.domain.Comment; import in.xebia.mongodb.blog.domain.Post; import in.xebia.mongodb.blog.domain.User; import java.util.List; import java.util.UUID; import javax.annotation.Resource; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.query.Query; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service ( "commentService" ) @Transactional public class CommentServiceImpl implements CommentService { @Resource private CommentDao commentDao; @Resource private MongoTemplate mongoTemplate; public List<Comment> getAll() { List<Comment> comments = commentDao.findAll(); return comments; } public Comment get(String id) { Comment comment = commentDao.findOne(id); return comment; } public Boolean add(Comment comment) { try { if (comment.getId()== null || comment.getId().length()== 0 ) comment.setId(UUID.randomUUID().toString()); commentDao.save(comment); return true ; } catch (Exception e) { e.printStackTrace(); return false ; } } public List<Comment> get(Post post) { Query query = new Query(where( "post.$id" ).is(post.getId())); List<Comment> comments = mongoTemplate.find(query, Comment. class ); return comments; } public List<Comment> get(User user) { Query query = new Query(where( "user.$id" ).is(user.getId())); List<Comment> comments = mongoTemplate.find(query, Comment. class ); return comments; } } |
Last but not the least here is the config file for mongo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <? xml version = "1.0" encoding = "UTF-8" ?> xsi:schemaLocation="http://www.springframework.org/schema/beans < mongo:repositories base-package = "in.xebia.mongodb.blog.api" /> < mongo:mongo host = "localhost" port = "27017" /> <!--mongoTemplate is required as it is used by mongorepository --> < bean id = "mongoTemplate" class = "org.springframework.data.mongodb.core.MongoTemplate" > < constructor-arg ref = "mongo" /> < constructor-arg name = "databaseName" value = "test" /> </ bean > < bean id = "populatorService" class = "in.xebia.mongodb.blog.impl.PopulatorService" init-method = "init" /> </ beans > |
Thats all I have for this blog you can download the complete RESTful web-app from the following git repository: HandlingDBRefsInMongoDBWithSpringData. In order to use the web app you will require some tools like soapUI or Poster(mozilla addon) as there is no UI for application. Just publish the app and start making json requests(see example in readme.txt)
Have a nice day.
출처 - http://xebee.xebia.in/index.php/2011/09/20/handling-dbrefs-in-mongodb-with-spring-data/
Spring Data MongoDB cascade save on DBRef objects
Spring Data MongoDB by default does not support cascade operations on referenced objects with @DBRefannotations as reference says:
The mapping framework does not handle cascading saves. If you change an Account object that is referenced by a Person object, you must save the Account object separately. Calling save on the Person object will not automatically save the Account objects in the property accounts.
That’s quite problematic because in order to achieve saving child objects you need to override save method in repository in parent or create additional “service” methods like it is presented in here.
In this article I will show you how it can be achieved for all documents using generic implementation ofAbstractMongoEventListener.
@CascadeSave annotation
Because we can’t change @DBRef annotation by adding cascade property lets create new annotation @CascadeSave that will be used to mark which fields should be saved when parent object is saved.
1 2 3 4 5 6 7 8 9 10 11 12 | package pl.maciejwalkowiak.springdata.mongodb; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention (RetentionPolicy.RUNTIME) @Target ({ ElementType.FIELD }) public @interface CascadeSave { } |
CascadingMongoEventListener
Next part is to implement handler for this annotation. We will use for that powerful Spring Application Event mechanism. In particular we will extend AbstractMongoEventListener to catch saved object before it is converted to Mongo’sDBOBject.
How does it work? When object MongoTemplate#save method is called, before object is actually saved it is being converted into DBObject from MongoDB api. CascadingMongoEventListener – implemented below – provides hook that catches object before its converted and:
- goes through all its fields to check if there are fields annotated with @DBRef and @CascadeSave at once.
- when field is found it checks if @Id annotation is present
- child object is being saved
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | package pl.maciejwalkowiak.springdata.mongodb; import org.bson.types.ObjectId; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.annotation.Id; import org.springframework.data.mapping.model.MappingException; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.mapping.DBRef; import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener; import org.springframework.stereotype.Component; import org.springframework.util.ReflectionUtils; import java.lang.reflect.Field; public class CascadingMongoEventListener extends AbstractMongoEventListener { @Autowired private MongoOperations mongoOperations; @Override public void onBeforeConvert( final Object source) { ReflectionUtils.doWithFields(source.getClass(), new ReflectionUtils.FieldCallback() { public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException { ReflectionUtils.makeAccessible(field); if (field.isAnnotationPresent(DBRef. class ) && field.isAnnotationPresent(CascadeSave. class )) { final Object fieldValue = field.get(source); DbRefFieldCallback callback = new DbRefFieldCallback(); ReflectionUtils.doWithFields(fieldValue.getClass(), callback); if (!callback.isIdFound()) { throw new MappingException( "Cannot perform cascade save on child object without id set" ); } mongoOperations.save(fieldValue); } } }); } private static class DbRefFieldCallback implements ReflectionUtils.FieldCallback { private boolean idFound; public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException { ReflectionUtils.makeAccessible(field); if (field.isAnnotationPresent(Id. class )) { idFound = true ; } } public boolean isIdFound() { return idFound; } } } |
Mapping requirements
As you can see in order to make thing work you need to follow some rules:
- parent’s class child property has to be mapped with @DBRef and @CascadeSave
- child class needs to have property annotated with @Id and if that id is supposed to be autogenerated it should by type of ObjectId
Usage
In order to use cascade saving in your project you need just to register CascadingMongoEventListener in Spring Context:
1 | < bean class = "pl.maciejwalkowiak.springdata.mongodb.CascadingMongoEventListener" /> |
Let’s test it
In order to show an example I made two document classes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | @Document public class User { @Id private ObjectId id; private String name; @DBRef @CascadeSave private Address address; public User(String name) { this .name = name; } // ... getters, setters, equals hashcode } |
1 2 3 4 5 6 7 8 9 10 11 12 | @Document public class Address { @Id private ObjectId id; private String city; public Address(String city) { this .city = city; } // ... getters, setters, equals hashcode } |
In test there is one user with address created and then user is saved. Test will cover only positive scenario and its just meant to show that it actually works (applcationContext-tests.xml
contains only default Spring Data MongoDB beans and CascadingMongoEventListener registered):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | @RunWith (SpringJUnit4ClassRunner. class ) @ContextConfiguration (locations = { "classpath:applcationContext-tests.xml" }) public class CascadingMongoEventListenerTest { @Autowired private MongoOperations mongoOperations; /** * Clean collections before tests are executed */ @Before public void cleanCollections() { mongoOperations.dropCollection(User. class ); mongoOperations.dropCollection(Address. class ); } @Test public void testCascadeSave() { // given User user = new User( "John Smith" ); user.setAddress( new Address( "London" )); // when mongoOperations.save(user); // then List<User> users = mongoOperations.findAll(User. class ); assertThat(users).hasSize( 1 ).containsOnly(user); User savedUser = users.get( 0 ); assertThat(savedUser.getAddress()).isNotNull().isEqualTo(user.getAddress()); List<Address> addresses = mongoOperations.findAll(Address. class ); assertThat(addresses).hasSize( 1 ).containsOnly(user.getAddress()); } } |
We can check that also in Mongo console:
> db.user.find()
{ "_id" : ObjectId("4f9d1bab1a8854250a5bf13e"), "_class" : "pl.maciejwalkowiak.springdata.mongodb.domain.User", "name" : "John Smith", "address" : { "$ref" : "address", "$id" : ObjectId("4f9d1ba41a8854250a5bf13d") } }
> db.address.find()
{ "_id" : ObjectId("4f9d1ba41a8854250a5bf13d"), "_class" : "pl.maciejwalkowiak.springdata.mongodb.domain.Address", "city" : "London" }
Summary
With this simple solution we can finally save child objects with one method call without implementing anything special for each document class.
I believe that we will find this functionality together with cascade delete as part Spring Data MongoDB release in the future. Solution presented here works but:
- it requires to use additional annotation
- uses reflection API to iterate through fields which is not the fastest way to do it (but feel free to implement caching if needed)
If that could be part of Spring Data MongoDB instead of additional annotation – @DBRef could have additional property “cascade”. Instead of reflection we could use MongoMappingContext together with MongoPersistentEntity. I’ve started already to prepare pull request with those changes. We will see if it will be accepted by Spring Source team.
Update: 2012-05-10
Idea presented here written in Spring way with added support for cascade delete has been proposed to Spring Data project in pull request
If you enjoyed this post, then make sure you subscribe to my RSS feed
출처 - http://maciejwalkowiak.pl/blog/2012/04/30/spring-data-mongodb-cascade-save-on-dbref-objects/
Spring MVC 3.1 - Implement CRUD with Spring Data MongoDB (Part 3)
Review
In the previous section, we have learned how to setup a MongoDB server in Windows and Ubuntu. In this section, we will discuss the project's structure, write the Java classes, and organize them in layers.Table of Contents
Part 1: Introduction and Functional SpecsPart 2: MongoDB setup
Part 3: Java classes
Part 4: XML configuration
Part 5: HTML Files (with AJAX)
Part 6: Running the Application
Project Structure
Our application is a Maven project and therefore follows Maven structure. As we create the classes, we've organized them in logical layers: domain, repository, service, and controller.Here's a preview of our project's structure:
The Layers
Domain Layer
This layer contains two classes, User and Role. They represent our database collections, user and rolerespectively. We're using Spring Data MongoDB to simplify MongoDB access. And to optimize these classes for the framework, we've added the @Document annotation. If you're familiar with JPA, this is similar to the @Entity annotation.Notice the User class has a reference to a Role property. In order to achieve that, we must annotate the field with @DBRef.
1234567891011121314151617181920212223 |
|
123456789101112131415 |
|
Mapping annotation overviewSource: http://static.springsource.org/spring-data/data-document/docs/current/reference/html/
- @Id - applied at the field level to mark the field used for identiy purpose.
- @Document - applied at the class level to indicate this class is a candidate for mapping to the database. You can specify the name of the collection where the database will be stored.
- @DBRef - applied at the field to indicate it is to be stored using a com.mongodb.DBRef.
Controller Layer
This layer contains two controllers, MediatorController and UserController- MediatorController is responsible for redirecting requests to appropriate pages. This isn't really required but it's here for organizational purposes.
- UserController is responsible for handling user-related requests such as adding and deleting of records
1234567891011121314 |
|
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889 |
|
Service Layer
This layer contains two services, UserService and InitMongoService- UserService is our CRUD service for the user collection. All data access is delegated to the repositories
- InitMongoService is used for initiliazing our database with sample data. Notice we're using theMongoTemplate itself instead of the Spring Data MongoDB-based repository. There is no difference between the two. In fact, it's simpler if we used Spring Data instead. But I have chosen MongoTemplate to show an alternative way of interacting with the database
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869 |
|
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253 |
|
Note
The mapping framework does not handle cascading saves. If you change an Account object that is referenced by a Person object, you must save the Account object separately. Calling save on the Person object will not automatically save the Account objects in the property accounts.
Source: Spring Data MongoDB Reference (7.3.3. Using DBRefs)
Repository Layer
This layer contains two repositories, UserRepository and RoleRepository. These are our data access objects (DAO). With the help of Spring Data MongoDB, Spring will automatically provide the actual implementation. Notice for custom methods we just have to add the method signature.What is Spring Data MongoDB?
Spring Data for MongoDB is part of the umbrella Spring Data project which aims to provide a familiar and consistent Spring-based programming model for for new datastores while retaining store-specific features and capabilities. The Spring Data MongoDB project provides integration with the MongoDB document database. Key functional areas of Spring Data MongoDB are a POJO centric model for interacting with a MongoDB DBCollection and easily writing a Repository style data access layer
Source: http://www.springsource.org/spring-data/mongodb
123456789 |
|
1234567 |
|
Utility classes
TraceInterceptor class is an AOP-based utility class to help us debug our application. This is a subclass of CustomizableTraceInterceptor (see Spring Data JPA FAQ)1234567891011121314151617181920212223242526272829 |
|
Next
In the next section, we will focus on the configuration files for enabling Spring MVC. Click here to proceed.site - http://krams915.blogspot.kr/2012/01/spring-mvc-31-implement-crud-with_7897.html
'Framework & Platform > Spring' 카테고리의 다른 글
spring - 이미지 파일 다운로드(image download) (0) | 2013.06.19 |
---|---|
spring - ContentNegotiatingViewResolver 확장자로 mediaType 설정 (0) | 2013.06.12 |
spring - @EnableTransactionManagement (0) | 2013.06.10 |
spring - @MVC에서 favicon.ico 처리 (0) | 2013.06.10 |
spring data - mongodb 도메인 객체에서 사용되는 annotation (0) | 2013.06.06 |