I'm using Spring Data for MongoDB -1.4.1.RELEASE and MongoDB 2.4.9
I'm using the mongoOperations.save() in order to update entities.
On previouse version, 1.2.0.RELEASE (due to a bug: DATAMONGO-571 ) I used to exclude some of the properties by setting their value to be null.
Now, This bug was fixed, and I'm not able to do so, but I still need to exclude some properties from the save operation (in case of update).
Does someone have an idea how this can be achieved?
I wrote abstract repository, that do partial update for single object. It expect that object contain 'id' field.
package xxx;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
import org.springframework.data.mongodb.repository.support.MongoRepositoryFactory;
import org.springframework.data.mongodb.repository.support.SimpleMongoRepository;
import java.io.Serializable;
import java.util.Map;
public abstract class ExtendedMongoRepository<T, ID extends Serializable> extends SimpleMongoRepository<T, ID> {
private Class<T> clazz;
//hidden constructor
private ExtendedMongoRepository(MongoEntityInformation metadata, MongoOperations mongoOperations) {
super(metadata, mongoOperations);
}
public ExtendedMongoRepository(MongoRepositoryFactory mongoRepositoryFactory, MongoOperations mongoOperations, Class<T> clazz) {
this(mongoRepositoryFactory.<T, ID>getEntityInformation(clazz), mongoOperations);
this.clazz = clazz;
}
protected Query getByIdQuery(ID id) {
return new Query(Criteria.where("_id").is(id));
}
/**
* Partial object update
* #param entity
* #param id
* #return
*/
public T updatePartial(T entity, ID id) {
ObjectMapper objectMapper = new ObjectMapper();
//exclude null
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
Map<String, Object> objectAsMap = objectMapper.convertValue(entity, Map.class);
//exclude id
objectAsMap.remove("id");
//add fields to update
Update u = new Update();
for (Map.Entry<String, Object> e : objectAsMap.entrySet()) {
u.set(e.getKey(), e.getValue());
}
getMongoOperations().updateFirst(getByIdQuery(id), u, clazz);
return getMongoOperations().findById(id, clazz);
}
}
And example how to use it.
public class GroupRepository extends ExtendedMongoRepository<Group, String> {
#Autowired
public GroupRepository(MongoRepositoryFactory mongoRepositoryFactory, MongoOperations mongoOperations) {
super(mongoRepositoryFactory, mongoOperations, Group.class);
}
}
MongoTemplate has dedicated methods to perform partial updates like:
updateFirst(…) - updates the first document matching a given criteria.
updateMulti(…) - updates all documents matching a given criteria.
Related
I am using spring data to fetch data for my application.
The repository class uses a mongo entity class which is being added as an upstream dependency to my project which means I don't have any control to change the source code of the class. As a result of this, I cannot use #Document annotation from org.springframework.data.mongodb.core.mapping to my mongo entity class.
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.repository.MongoRepository;
public interface DummyRepository extends MongoRepository<Dummy, String> {
Page<Dummy> findAll(Pageable pageable);
}
Here, I don't have any control over source code of Dummy class so I can't add #Document to specify collection name for this class
How can I specify the collection name while using DummyRepository to query mongo collection?
One way would be to use #EnableMongoRepositories#repositoryFactoryBeanClass with your own flavor of MongoRepsoitoryFactoryBean overriding getEntityInformation(Class).
Unfortunately there's a bug (DATAMONGO-2297) in the code and for the time being you also need to customize getTargetRepsoitory(RepositoryInformation) as shown in the snippet below.
#Configuration
#EnableMongoRepositories(repositoryFactoryBeanClass = CustomRepoFactory.class)
class config extends AbstractMongoConfiguration {
// ...
}
class CustomRepoFactory extends MongoRepositoryFactoryBean {
public CustomRepoFactory(Class repositoryInterface) {
super(repositoryInterface);
}
#Override
protected RepositoryFactorySupport getFactoryInstance(MongoOperations operations) {
return new MongoRepositoryFactory(operations) {
#Override
public <T, ID> MongoEntityInformation<T, ID> getEntityInformation(Class<T> domainClass) {
return new MappingMongoEntityInformation(
operations.getConverter().getMappingContext().getPersistentEntity(domainClass)) {
#Override
public String getCollectionName() {
return "customize-as-you-wish";
}
};
}
#Override // you should not need this when DATAMONGO-2297 is resolved
protected Object getTargetRepository(RepositoryInformation information) {
MongoEntityInformation<?, Serializable> entityInformation = getEntityInformation(information.getDomainType());
return getTargetRepositoryViaReflection(information, entityInformation, operations);
}
};
}
}
I have this Bean that works. I would like to implement a method in order to receive parameters from a JSF search form, perform the search and return the results.
So, my questions are:
- What would be a method signature to Wrap my search parameters?
- Is there a way to config the Hibernate/JPA behavior to make a single SELECT that returns all registries that fits my search parameter instead of my actual behavior (1 query excuted per row returned)?
import java.util.List;
import javax.ejb.Stateful;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.persistence.TypedQuery;
import my.package.entity.Address;
#Stateful
public class AddressEJB {
#PersistenceContext(unitName = "RentACar-persistence-unit",type=PersistenceContextType.EXTENDED)
private EntityManager entityManager;
public List<Address> findAll() {
TypedQuery<Address> query = entityManager.createNamedQuery(
"Address.findAll", Address.class);
return query.getResultList();
}
/*This is the method I want to write*/
public List<Address> findByParameter() {
TypedQuery<Address> query = entityManager.createNamedQuery(
"Address.findAll", Address.class);
return query.getResultList();
}
public Address addNew(Address address) {
entityManager.persist(address);
return address;
}
public Address update(Address address) {
entityManager.merge(address);
return address;
}
public void delete(Address country) {
entityManager.remove(country);
}
}
This is my code
import javax.persistence.Query;
import play.db.jpa.*;
import play.db.jpa.JPA;
import java.util.*;
#play.db.jpa.Transactional
public class GetS {
public static List get()
{
Query query = JPA.em().createNativeQuery("select * from product limit 1");
return query.getResultList();
}
}
I have already added Transactional but still error remain same.
You need to add the #Transactional annotation on controller method.
class S extends Controller {
...
#Transactional
public static Result index() {
...
}
...
}
Here more info: http://www.playframework.com/documentation/2.2.x/JavaJPA
I've inherited a web project that a contractor started. I and my coworkers are unfamiliar with the technology used, and have a number of questions. From what we can tell, this appears to be some sort of RESTful Java server code, but my understanding is there are lots of different types of Java RESTful services. Which one is this? Specific questions:
1) Where can we read more (particularly introductory information) about this specific service?
2) The code creates and returns a JSON through some kind of "magic"... I merely return a model class (code below) that has getter and setter methods for its fields, and it's automagically converted into a JSON. I'd like to learn more about how this is done automagically.
3) We already have some code that creates a JSON. We need to return this using this framework. If I already have a JSON, how do I return that? I tried something like this:
String testJSON = "{\"menu\": {\"id\": \"file\", \"value\": \"Hello there\"}}";
return testJSON;
instead of returning a model object with getters/setters, but this returns a literal text string, not a JSON. Is there a way to return an actual JSON that's already a JSON string, and have it be sent as a JSON?
You don't have to be able to answer all of the questions above. Any/all pointers in a helpful direction appreciated!
CODE
First, the view controller that returns the JSON:
package com.aimcloud.server;
import com.aimcloud.util.MySqlConnection;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.FormParam;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import com.aimcloud.models.SubscriptionTierModel;
#Path("subscription_tier")
public class SubscriptionTierController
{
// this method will return a list of subscription_tier table entries that are currently active
#GET
#Produces({ MediaType.APPLICATION_JSON })
public String/*ArrayList<SubscriptionTierModel>*/ getSubscriptionTiers(#QueryParam("includeActiveOnly") Boolean includeActiveOnly)
{
MySqlConnection mysql = MySqlConnection.getConnection();
ArrayList<SubscriptionTierModel> subscriptionTierArray = new ArrayList<SubscriptionTierModel>();
String queryString;
if (includeActiveOnly)
queryString = "SELECT * FROM subscription_tier WHERE active=1";
else
queryString = "SELECT * FROM subscription_tier";
List<Map<String, Object>> resultList = mysql.query(queryString, null);
for (Map<String, Object> subscriptionRow : resultList)
subscriptionTierArray.add( new SubscriptionTierModel(subscriptionRow) );
// String testJSON = "{\"menu\": {\"id\": \"file\", \"value\": \"Hello there\"}}";
// return testJSON;
return subscriptionTierArray;
}
}
Next, the model the code above returns:
package com.aimcloud.models;
// NOTE this does NOT import Globals
import java.sql.Types;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.json.JSONObject;
import com.aimcloud.util.LoggingUtils;
public class SubscriptionTierModel extends ModelPrototype
{
private String name;
private Integer num_studies;
private Integer cost_viewing;
private Integer cost_processing;
private Integer active;
protected void setupFields()
{
this.fields.add("name");
this.fields.add("num_studies");
this.fields.add("cost_viewing");
this.fields.add("cost_processing");
this.fields.add("active");
}
public SubscriptionTierModel()
{
super("subscription");
this.setupFields();
}
public SubscriptionTierModel(Map<String, Object> map)
{
super("subscription");
this.setupFields();
this.initFromMap(map);
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setNum_Studies(Integer num_studies) {
this.num_studies = num_studies;
}
public Integer getNum_studies() {
return this.num_studies;
}
public void setCost_viewing(Integer cost_viewing) {
this.cost_viewing = cost_viewing;
}
public Integer getCost_viewing() {
return this.cost_viewing;
}
public void setCost_processing(Integer cost_processing) {
this.cost_processing = cost_processing;
}
public Integer getCost_processing() {
return this.cost_processing;
}
public void setActive(Integer active) {
this.active = active;
}
public Integer getActive() {
return this.active;
}
}
public abstract class ModelPrototype {
protected MySqlConnection mysql;
protected ArrayList<String> fields;
protected String table;
protected Integer id = null;
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
abstract protected void setupFields();
public ModelPrototype() {
mysql = MySqlConnection.getConnection();
this.fields = new ArrayList<String>();
this.fields.add("id");
}
public void initFromDbResult(List<Map<String, Object>> result) {
if (result.size() >= 1)
{
Map<String, Object> userRow = result.get(0);
this.initFromMap(userRow);
if (result.size() > 1)
{
Thread.dumpStack();
}
}
else
{
throw new WebApplicationException(ServerUtils.generateResponse(Response.Status.NOT_FOUND, "resource not found"));
}
}
protected void initFromMap(Map<String, Object> map) {
for (Map.Entry<String, Object> entry : map.entrySet()) {
Object value = entry.getValue();
// LoggingUtils.log(entry.getKey() + " " + entry.getValue().toString());
if (value != null && this.fields.contains(entry.getKey())) {
this.setField(entry.getKey(), value);
}
}
}
....
1) Where can we read more (particularly introductory information)
about this specific service?
This is a RESTful service that uses basic jax-rs annotations to build the service. I suggest looking at a tutorial like "REST using jersey" or "REST using CXF".
2) The code creates and returns a JSON through some kind of "magic"...
The restful framework used usually takes care of this. #Produces({ MediaType.APPLICATION_JSON }) annotation indicates the framework to do this conversion.This will be defined somewhere in the configuration. Check the spring config files if you are using spring to define the beans. Usually a mapper or a provider will be defined that converts the object to json.
3) We already have some code that creates a JSON. We need to return this using this framework. If I already have a JSON, how do I return that? I tried something like this:
If you already have a json just return that json from the method. Remember to still have the #Produces({ MediaType.APPLICATION_JSON }) annotation on the method.
but this returns a literal text string, not a JSON
A json is a string. That is what you will see in the response, unless you deserialize it back to an object.
I suggest you read up on JAX-RS, the Java specification for RESTful web services. All of the "javax.ws.rs.*" classes/annotations come from JAX-RS
As JAX-RS, is just a specification, there needs to be something that implements the spec. There is probably a third-party, JAX-RS component that is used to run this service. Jersey in one popular implementation. Apache CXF is another.
Now back to JAX-RS. When you read up on this, you will see that the annotations on your class determine the REST characteristics of your service. For example,
#Path("subscription_tier")
defines your class as the resource with URI BASE_PATH/subscription_tier, where BASE_PATH is propbably defined in a configuration file for your web service framework.
As for how the objects are "automagically" converted into a JSON response: that is the role of the web service framework as well. It probably uses some kind of standard object-to-JSON mapping to accomplish this. (I have worked with CXF and XML resources. In that case JAXB was the mapping mechanism). This is a good thing, as the web service developer does not have to worry about this mapping, and can focus on coding just the implementation of service itself.
Use Case
I am trying to use Adding custom behaviour to all repositories functionality of Spring Data MongoDB.
The documentation unhelpfully describes how to connect using JPA. Anyways got the config setup with Mongo equivalent.
I want to add a findByCategoryName(String categoryName) method to all entities as all my entities will have a Category . Category is a DBRef object so have to use custom query.
Below is relevant part of the config
<!-- Activate Spring Data MongoDB repository support -->
<mongo:repositories base-package="com.domain.*.repo" repository-impl-postfix="CustomImpl"
factory-class="com.domain.commonrepo.CommonMongoRepoFactoryBean"/>
<bean id="mappingContext" class="org.springframework.data.mongodb.core.mapping.MongoMappingContext" />
<mongo:mapping-converter mapping-context-ref="mappingContext">
<mongo:custom-converters base-package="com.domain.mongo.converter" />
</mongo:mapping-converter>
<bean id="entityInformationCreator" class="org.springframework.data.mongodb.repository.support.DefaultEntityInformationCreator">
<constructor-arg name="mappingContext" ref="mappingContext" />
</bean>
.
.
The FactoryBean
#NoRepositoryBean
public class CommonMongoRepoFactoryBean<T extends MongoRepository<?,?>, ID extends
Serializable> extends MongoRepositoryFactoryBean{
#Autowired
private static MongoTemplate mongoTemplate;
protected MongoRepositoryFactory getRepositoryFactory(Class<T> clazz) {
return new CommonMongoRepoFactory(clazz);
}
private static class CommonMongoRepoFactory extends MongoRepositoryFactory {
private Class clazz;
public CommonMongoRepoFactory(Class clazz) {
super(mongoTemplate);
this.clazz = clazz;
}
public CommonMongoRepoImpl getTargetRepository() {
return new CommonMongoRepoImpl(clazz);
}
public Class<?> getRepositoryClass() {
return CommonMongoRepoImpl.class;
}
}
I know it's a bit of a hack but with no documentation it is a pain. If anyone knows better PLEASE give me a github link :-)
Common Repo interface
#NoRepositoryBean
public interface CommonMongoRepo<T, ID extends Serializable> extends MongoRepository<T,ID> {
public List<T> findByCategoryName(String categoryName);
Implementation
#NoRepositoryBean
public class CommonMongoRepoImpl<T, ID extends Serializable> extends SimpleMongoRepository<T,
ID> implements CommonMongoRepo<T, ID> {
private Class<T> type;
#Autowired
private static MongoTemplate mongoOperations;
#Autowired
private static EntityInformationCreator entityInformationCreator;
#Autowired
private CategoryRepo categoryRepo;
public CommonMongoRepoImpl(Class<T> type) {
super((MongoEntityInformation<T, ID>) entityInformationCreator.getEntityInformation(type), mongoOperations);
}
#Override
public List<T> findByCategoryName(String categoryName) {
Category category = categoryRepo.findByName(categoryName);
return mongoOperations.find(query(where("categories.$id").is(category.getId())), type);
}
PROBLEM
Now when I am trying to use the common method I get an exception
No Property category found in "Entity". Which is I guess when mongo repo is trying to auto implement the method. This is inspite of me declaring the bean as #NoRepositoryBean
PLEASE HELP!!! Dont want to add the same custom method to all the entities
Here is the best solution!
Step One:
Add a custom method to interface!
增加一个自定义的方法
#custom interface
/**
* Basic Repository for common custom methods
* #author liangping
*/
import java.io.Serializable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.PagingAndSortingRepository;
#NoRepositoryBean
public interface WootideRepositoryCustom <T, ID extends Serializable>
extends PagingAndSortingRepository<T, ID>, MongoRepository<T, ID> {
public Page<T> search(Query query, Pageable pageable);
}
Implementation
Step Two:
Add implement for your custom method!
实现你的自定义方法
/**
* implement for wootide basic repository
* #author liangping
*/
import java.io.Serializable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
import org.springframework.data.mongodb.repository.support.SimpleMongoRepository;
public class WootideRepositoryImpl<T, ID extends Serializable> extends
SimpleMongoRepository<T, ID> implements WootideRepositoryCustom<T, ID> {
public WootideRepositoryImpl(MongoEntityInformation<T, ID> metadata,
MongoOperations mongoOperations) {
super(metadata, mongoOperations);
}
#Override
public Page<T> search(Query query, Pageable pageable) {
long total = this.getMongoOperations().count(query, this.getEntityInformation().getJavaType() );
return new PageImpl<T>(this.getMongoOperations().find(query.with(pageable), this.getEntityInformation().getJavaType()), pageable, total);
}
}
Create a new factory for custom repository
/**
* Repository Factory for all Subrepository
* #author liangping
*/
import java.io.Serializable;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
import org.springframework.data.mongodb.repository.support.MappingMongoEntityInformation;
import org.springframework.data.mongodb.repository.support.MongoRepositoryFactory;
import org.springframework.data.mongodb.repository.support.MongoRepositoryFactoryBean;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
public class WootideRepositoryFactoryBean<R extends MongoRepository<T, I>, T, I extends Serializable>
extends MongoRepositoryFactoryBean<R, T, I> {
#Override
protected RepositoryFactorySupport getFactoryInstance(
MongoOperations operations) {
return new WootideMongoRepositoryFactory<T,I>( operations );
}
private static class WootideMongoRepositoryFactory<T, ID extends Serializable>
extends MongoRepositoryFactory {
private MongoOperations mongo;
public WootideMongoRepositoryFactory(MongoOperations mongoOperations) {
super(mongoOperations);
this.mongo = mongoOperations;
}
#SuppressWarnings("unchecked")
protected Object getTargetRepository(RepositoryMetadata metadata) {
TypeInformation<T> information = ClassTypeInformation.from((Class<T>)metadata.getDomainType());
MongoPersistentEntity<T> pe = new BasicMongoPersistentEntity<T>(information);
MongoEntityInformation<T,ID> mongometa = new MappingMongoEntityInformation<T, ID>(pe);
return new WootideRepositoryImpl<T, ID>( mongometa, mongo);
}
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return WootideRepositoryCustom.class;
}
}
}
Make it works
<mongo:repositories base-package="com.***.mongodb"
factory-class="com.***.mongodb.custom.WootideRepositoryFactoryBean"/>
Good Luck! 祝你好运!
Somewhat delayed but here is sample code that does this for a Spring web app project. The salient points are:
Interface used in Controller
Implementation done in a separate class that inherits from a base
The base implementation provides common methods that any other Controller can use with just a quick inheritance