spring-data-mongodb Query.fields().slice() on #DBRef field throws MappingException - spring

I have a problem with sliced access to some #DBRef field in my model. I use spring-data-mongodb-1.8.0.M1.jar
The model is like:
class Model {
....
#DBRef
List<OtherModel> members;
...
}
and I need sliced access to the members variable.
I use this query:
ObjectId objectId = new ObjectId("55c36f44f359d8a455a21f68");
Query query = new Query(Criteria.where("_id").is(objectId));
query.fields().slice("members", pageable.getOffset(), pageable.getPageSize());
List<Model> models = mongoTemplate.findOne(query, Model.class);
But I get this exception:
org.springframework.data.mapping.model.MappingException: No id property found on class class [Ljava.lang.Integer;
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.createDBRef(MappingMongoConverter.java:842)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.toDBRef(MappingMongoConverter.java:329)
at org.springframework.data.mongodb.core.convert.QueryMapper.createDbRefFor(QueryMapper.java:460)
at org.springframework.data.mongodb.core.convert.QueryMapper.convertAssociation(QueryMapper.java:417)
at org.springframework.data.mongodb.core.convert.QueryMapper.convertAssociation(QueryMapper.java:378)
at org.springframework.data.mongodb.core.convert.QueryMapper.getMappedKeyword(QueryMapper.java:257)
at org.springframework.data.mongodb.core.convert.QueryMapper.getMappedObjectForField(QueryMapper.java:200)
at org.springframework.data.mongodb.core.convert.QueryMapper.getMappedObject(QueryMapper.java:123)
at org.springframework.data.mongodb.core.MongoTemplate.doFindOne(MongoTemplate.java:1647)
at org.springframework.data.mongodb.core.MongoTemplate.findOne(MongoTemplate.java:563)
at org.springframework.data.mongodb.core.MongoTemplate.findOne(MongoTemplate.java:558)
where a field
boolean needsAssociationConversion = property.isAssociation() && !keyword.isExists();
is set. It checks against isExists, but not against something like isSliced (which does not yet exist) and therefore is evaluated to true and, as a cause, tries to convert the non-existing association which is, in this case, just the slice-directive (an integer array). When I set the variable needsAssociationConversion to false while debugging, as if a kind of keyword.isSlice() check was done, everything works fine.
Is this a bug?
Executable project is here
https://github.com/zhsyourai/sliceDemo

Related

MapStruct Spring Page to custom object conversion includes check

I am using MapStruct to convert a Page object to a custom object of my application. I am using this mapping in order to convert the content field of the Page object to a list of custom objects found in my data model:
#Mapping(target = "journeys", source = "content")
While this works OK and does convert the elements when content is present, this does not work correctly in case of no Page content. Taking a look at the code seems to show that the following check is added in the generated mapper class:
if ( page.hasContent() ) {
List<JourneyDateViewResponseDto> list = page.getContent();
journeyDateViewPageResponseDto.setJourneys( new ArrayList<JourneyDateViewResponseDto>( list ) );
}
When this is added the mapping action of the inner objects is omitted, meaning that I end up with a null list. I am not really sure as to why and how this check is added but I would like to find a way of disabling it and simply end up with an empty list of elements. Is there a way this can be done using MapStruct?
MapStruct has the concept of presence checkers (methods that have the pattern hasXXX). This is used to decide if a source property needs to be mapped.
In case you want to have a default value in your object I would suggest making sure that your object is instantiated with an empty collection or provide an #ObjectFactory for your object in which you are going to set the empty collection.
e.g.
Default value in class
public class JourneyDateViewPageResponseDto {
protected List<JourneyDateViewResponseDto> journeys = new ArrayList<>();
//...
}
Using #ObjectFactory
#Mapper
public interface MyMapper {
JourneyDateViewPageResponseDto map(Page< JourneyDateViewResponseDto> page);
#ObjectFactory
default JourneyDateViewPageResponseDto createDto() {
JourneyDateViewPageResponseDto dto = new JourneyDateViewPageResponseDto();
dto.setJourneys(new ArrayList<>());
return dto;
}
}
#Mapping(target = "journeys", source = "content", defaultExpression = "java(java.util.List.of())")

Sorting a custom JPA query with pageable

So, I've already done this using the standard Spring Data JPA interface which extends PagingAndSortingRepository in order to achieve pagination and sorting for a REST API. The thing is, now I want to achieve the very same thing but now using just vanilla JPA and so far so good I managed to get my API to paginate but the sorting doesn't work at all. Every time I try to set the parameter (from a pageable object using pageable.getSort()) it ends with a query error (either if I just send a string as parameter like "name" or just send the sort object, it shows errors).
Here's some code:
My repo implementation:
#Override
public List<Project> findByAll(Pageable pageable) {
Query query = em.createQuery("SELECT project FROM Project project ORDER BY :sort");
query.setParameter("sort", pageable.getSort());
query.setMaxResults(pageable.getPageSize());
query.setFirstResult(pageable.getPageSize() * pageable.getPageNumber());
return query.getResultList();
}
My service:
#Override
public Page<Project> findAll(Pageable pageable) {
objects = Lists.newArrayList(repository.findByAll(pageable));
PageImpl<Project> pages= new PageImpl<Project>(objects, pageable, repository.count());
return pages;
}
To be clear, I'm filling the Pageable object via URI and from the console I can say it's actually getting the data, so I assume the problem is with the repo.
Edit: This is the error I get when I replace the setParameter("sort", ...) for a hardcoded string aka query.setParameter("sort", "name"):
java.lang.NumberFormatException: For input string: "name"
And I think this method should stand for strings as well. If I use query.setParameter("sort", pageable.getSort()), the error is the same.
The order by cannot be set as a query parameter. Also, the Pageable.getSort().toString() likely won't return a string suitable for use in an order by clause as it will result in a String that represents the Order as property: ORDER, note the colon.
Here are some modifications that will work, assuming Java 8...
String order = StringUtils.collectionToCommaDelimitedString(
StreamSupport.stream(sort.spliterator(), false)
.map(o -> o.getProperty() + " " + o.getDirection())
.collect(Collectors.toList()));
Query query = em.createQuery(
String.format("SELECT project FROM Project project ORDER BY %s", order));

Spring Repository method complains about field not found

I'm using Spring Data Neo4j in my project and I'm having issues with naming conventions for repositories.
This is a simple class containing only one field and the getters/setters
#RelationshipEntity
public class ScoredRelationship
{
protected Float score;
}
and the class below extends it with other kind of fields
#RelationshipEntity(
type = RecommenderRelTypes.GOV_CONSUMER_TO_GOV_CONSUMER_SIMILARITY)
public class GovConsumerToGovConsumerSimilarity extends ScoredRelationship
{ // Other fields}
To access the relationship, I'm using the usual repository class
public interface GovConsumerToGovConsumerSimilarityRepository extends
GraphRepository<GovConsumerToGovConsumerSimilarity>
{
public Set<GovConsumerToGovConsumerSimilarity> findByScoreGreaterThan(Float value);
public Set<GovConsumerToGovConsumerSimilarity>
findByScoreGreaterThanOrderByScoreDesc(Float value);
public Set<GovConsumerToGovConsumerSimilarity>
findTopXByScoreGreaterThanOrderByScoreDesc(int limit, Float score);
}
This code compiles well. However, whenever I'm trying to use one of the methods, Spring return a series of exception or doesn't act as intended.
F.e. #findByScoreGreaterThan(0.3f) always returns an empty set. However, invoking a findAll() and printing all the scores it actually has a lot of objects with a score greater than 0.3.
In the second and third case, it always throws an exception saying
Caused by: Unknown identifier `score`.
at org.neo4j.cypher.internal.symbols.SymbolTable.evaluateType(SymbolTable.scala:60)
at org.neo4j.cypher.internal.commands.expressions.Identifier.evaluateType(Identifier.scala:51)
at org.neo4j.cypher.internal.commands.expressions.Expression.assertTypes(Expression.scala:53)
at org.neo4j.cypher.internal.pipes.SortPipe$$anonfun$assertTypes$1.apply(SortPipe.scala:34)
at org.neo4j.cypher.internal.pipes.SortPipe$$anonfun$assertTypes$1.apply(SortPipe.scala:33)
at scala.collection.LinearSeqOptimized$class.foreach(LinearSeqOptimized.scala:59)
at scala.collection.immutable.List.foreach(List.scala:45)
at org.neo4j.cypher.internal.pipes.SortPipe.assertTypes(SortPipe.scala:33)
at org.neo4j.cypher.internal.pipes.PipeWithSource.<init>(PipeWithSource.scala:27)
at org.neo4j.cypher.internal.pipes.SortPipe.<init>(SortPipe.scala:29)
at org.neo4j.cypher.internal.executionplan.builders.SortBuilder.apply(SortBuilder.scala:33)
at org.neo4j.cypher.internal.executionplan.ExecutionPlanImpl.prepareExecutionPlan(ExecutionPlanImpl.scala:49)
at org.neo4j.cypher.internal.executionplan.ExecutionPlanImpl.<init>(ExecutionPlanImpl.scala:33)
at org.neo4j.cypher.ExecutionEngine$$anonfun$prepare$1.apply(ExecutionEngine.scala:67)
at org.neo4j.cypher.ExecutionEngine$$anonfun$prepare$1.apply(ExecutionEngine.scala:67)
at org.neo4j.cypher.internal.LRUCache.getOrElseUpdate(LRUCache.scala:37)
at org.neo4j.cypher.ExecutionEngine.prepare(ExecutionEngine.scala:67)
at org.neo4j.cypher.ExecutionEngine.execute(ExecutionEngine.scala:59)
at org.neo4j.cypher.ExecutionEngine.execute(ExecutionEngine.scala:63)
at org.neo4j.cypher.javacompat.ExecutionEngine.execute(ExecutionEngine.java:79)
at org.springframework.data.neo4j.support.query.CypherQueryEngine.parseAndExecuteQuery(CypherQueryEngine.java:61)
How could be possible? I mean, the class obviously has the score field. Also, executing the simple #findByScoreGreaterThan(float value) doesn't throw any exception, but at the same the latter method always returns an empty set.
EDIT:
These are the queries used by Spring. Actually, they seems right
Executing cypher query: START `govConsumerToGovConsumerSimilarity`=node:__types__(className="it.cerict.recommender.persistence.neo4j.GovConsumerToGovConsumerSimilarity") WHERE `govConsumerToGovConsumerSimilarity`.`score`! > {0} RETURN `govConsumerToGovConsumerSimilarity` params {0=0.3}
Executing cypher query: START `govConsumerToGovConsumerSimilarity`=node:__types__(className="it.cerict.recommender.persistence.neo4j.GovConsumerToGovConsumerSimilarity") WHERE `govConsumerToGovConsumerSimilarity`.`score`! > {0} RETURN `govConsumerToGovConsumerSimilarity` ORDER BY score DESC params {0=0.3}
EDIT2: I've also tried to change the score type from Float to float with no further improvements.
This seems to be a bug related to Spring Data Neo4j. Looking to the query executed, it is clear that it searches for nodes, while it should search for relationships.
I changed the #findByScoreGreaterThanOrderByScoreDesc() method using the #Query annotation that specifies the following Cypher query
START `govConsumerToGovConsumerSimilarity`=rel:__rel_types__(className="it.cerict.recommender.persistence.neo4j.GovConsumerToGovConsumerSimilarity") WHERE `govConsumerToGovConsumerSimilarity`.`score`! > 0.3 RETURN `govConsumerToGovConsumerSimilarity` ORDER BY `govConsumerToGovConsumerSimilarity`.`score` DESC;

Cannot map raw SQL query to DataRow

I am trying to get IEnumerable from linq query below. What am I doing wrong?
IEnumerable<DataRow> results =
context.Database.SqlQuery<DataRow>("SELECT * FROM Customer").AsEnumerable();
DataRow class does not have default (parameterless) constructor, so you can't use it as query parameter type. There is no generic constraints on type parameter, and nothing mentioned on MSDN(!), but column map factory will throw exception if parameter type does not have default constructor:
The result type 'System.Data.DataRow' may not be abstract and must
include a default constructor.
Here is a code which throws this exception:
internal static CollectionColumnMap CreateColumnMapFromReaderAndClrType(
DbDataReader reader, Type type, MetadataWorkspace workspace)
{
BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
ConstructorInfo constructor = type.GetConstructor(flags, (Binder) null, Type.EmptyTypes, (ParameterModifier[]) null);
if (type.IsAbstract || (ConstructorInfo) null == constructor && !type.IsValueType)
throw EntityUtil.InvalidOperation(InvalidTypeForStoreQuery((object) type));
// ...
}
BTW Mapping to DataRow makes no sense, even if it would have default public constructor. Because it is not simple primitive type and it does not have properties which match the names of columns returned from the query (yes, mapping uses properties only).
Correct usage of Linq will be
IEnumerable<Customer> results = context.Customers;
That will generate SELECT * FROM Customer query, and map query results to customer entities. If you really want to use raw SQL:
IEnumerable<Customer> results =
context.Database.SqlQuery<Customer>("SELECT * FROM Customers");
I think we were trying to solve the same problem (Google led me here, anyway). I am executing a raw SQL command through SqlQuery<TElement>(string sql, params object[] parameters and wanted to assert individual properties of the results returned from the query in a unit test.
I called the method:
var result = (db.SqlQuery<Customer>("select * from customers").First();
and verified the data it returned:
Assert.AreEqual("John", result.FirstName);
I defined a private class Customer inside my test class (unfortunately, I'm not using Entity Framework):
private class Customer
{
public string FirstName { get; set; }
}
The properties of Customer must match the column names returned in the SQL query, and they must be properties (not just variables of the Customer class. You don't have to create properties for all of the columns returned from the query.

Entity Framework Code-First: "The ObjectStateManager cannot track multiple objects with the same key."

I'm running into an issue with Entity Framework code-first in MVC3. I'm hitting this exception:
An object with the same key already exists in the ObjectStateManager.
The ObjectStateManager cannot track multiple objects with the same
key.
This is addressed many times on SO, but I'm having trouble utilizing any of the suggested solutions in my situation.
Here is a code sample:
FestORM.SaleMethod method = new FestORM.SaleMethod
{
Id = 2,
Name = "Test Sale Method"
};
FestContext context = new FestContext();
//everything works without this line:
string thisQueryWillMessThingsUp =
context.SaleMethods.Where(m => m.Id == 2).Single().Name;
context.Entry(method).State = System.Data.EntityState.Modified;
context.SaveChanges();
EDITED to clarify: I am attempting to update an object that already exists in the database.
Everything works fine without the query noted in the code. In my application, my controller is instantiating the context, and that same context is passed to several repositories that are used by the controller--so I am not able to simply use a different context for the initial query operation. I've tried to remove the entity from being tracked in the ObjectStateManager, but I can't seem to get anywhere with that either. I'm trying to figure out a solution that will work for both conditions: sometimes I will be updating an object that is tracked by the ObjectStateManager, and sometimes it will happen to have not been tracked yet.
FWIW, my real repository functions look like this, just like the code above:
public void Update(T entity)
{
//works ONLY when entity is not tracked by ObjectStateManager
_context.Entry(entity).State = System.Data.EntityState.Modified;
}
public void SaveChanges()
{
_context.SaveChanges();
}
Any ideas? I've been fighting this for too long...
The problem is that this query
string thisQueryWillMessThingsUp =
context.SaleMethods.Where(m => m.Id == 2).Single().Name;
brings one instance of the SaleMethod entity into the context and then this code
context.Entry(method).State = System.Data.EntityState.Modified;
attaches a different instance to the context. Both instances have the same primary key, so EF thinks that you are trying to attach two different entities with the same key to the context. It doesn't know that they are both supposed to be the same entity.
If for some reason you just need to query for the name, but don't want to actually bring the full entity into the context, then you can do this:
string thisQueryWillMessThingsUp =
context.SaleMethods.Where(m => m.Id == 2).AsNoTracking().Single().Name;
If what you are tying to do is update an existing entity and you have values for all mapped properties of that entity, then the simplest thing to do is to not run the query and just use:
context.Entry(method).State = System.Data.EntityState.Modified;
If you don't want to update all properties, possibly because you don't have values for all properties, then querying for the entity and setting properties on it before calling SaveChanges is an acceptable approach. There are several ways to do this depending on your exact requirements. One way is to use the Property method, something like so:
var salesMethod = context.SaleMethods.Find(2); // Basically equivalent to your query
context.Entry(salesMethod).Property(e => e.Name).CurrentValue = newName;
context.Entry(salesMethod).Property(e => e.SomeOtherProp).CurrentValue = newOtherValue;
context.SaveChanges();
These blog posts contain some additional information that might be helpful:
http://blogs.msdn.com/b/adonet/archive/2011/01/29/using-dbcontext-in-ef-feature-ctp5-part-4-add-attach-and-entity-states.aspx
http://blogs.msdn.com/b/adonet/archive/2011/01/30/using-dbcontext-in-ef-feature-ctp5-part-5-working-with-property-values.aspx
The obvious answer would be that your not actually saving the method object to the database before you call:
//everything works without this line:
string thisQueryWillMessThingsUp = context.SaleMethods.Where(m => m.Id == 2).Single().Name;
However, I think perhaps this is just a bit a code you left out.
What if you make your entities inherit from an abstract class ie.
public abstract class BaseClass
{
public int Id { get; set; }
}
Then update your Repository to
public class Repository<T> where T : BaseClass
{
.....
public void Update(T entity)
{
_context.Entry(entity).State = entity.Id == 0 ? System.Data.EntityState.Added : System.Data.EntityState.Modified;
}
}
Also you might want to not set the ID of your SaleMethod and let it be generated by the database. Problem could also be because SaleMethod Object in the database has Id of 2 and then you try to add another SaleMethod object with Id 2.
The error you see stems from trying to add another SaleMethod object with ID of 2 to the ObjectStateManager.

Resources