Why is Spring Data MongoDB unable to instantiate this nested type structure? - mongodb

My document structure is like:
{
_id: "A",
groups:[{
groupId: "someId",
groupName: "someName",
params: {
type1: ["a", "b"],
type2: ["c", d]
}
}],
config: {
person: {}
dataDetails: {
dataTypeDetails: {},
dataList: ["dt1", "dt2"]
}
}
}
My Spring Data MongoDB model types look like this:
// Imports etc.
#Document
public class Entity {
#Id
private String _id;
private List<Group> groups;
private Config config;
// setters and getters
public class Group {
private String groupId;
private String groupName;
private ParamData params;
// setter and getters
}
public class ParamData {
private List<String> type1;
private List<String> type2;
}
public class Config {
private Map person;
private DataConfig dataDetails;
}
public class DataConfig {
private Map dataTypeDetails;
private List<String> dataList;
}
}
Stacktrace:
org.springframework.data.mapping.model.MappingInstantiationException: Failed to instantiate com.****.common.models.Entity$ParamData using constructor public com.****.common.models.Entity$ParamData(com.****.common.models.Entity) with arguments com.****.common.models.Entity$Group#2eb61a7b
at org.springframework.data.convert.ReflectionEntityInstantiator.createInstance(ReflectionEntityInstantiator.java:78)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:257)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:237)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readValue(MappingMongoConverter.java:1136)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.access$100(MappingMongoConverter.java:78)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$MongoDbPropertyValueProvider.getPropertyValue(MappingMongoConverter.java:1085)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.getValueInternal(MappingMongoConverter.java:816)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$1.doWithPersistentProperty(MappingMongoConverter.java:270)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$1.doWithPersistentProperty(MappingMongoConverter.java:263)
at org.springframework.data.mapping.model.BasicPersistentEntity.doWithProperties(BasicPersistentEntity.java:261)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:263)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:237)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readCollectionOrArray(MappingMongoConverter.java:861)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readValue(MappingMongoConverter.java:1134)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.access$100(MappingMongoConverter.java:78)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$MongoDbPropertyValueProvider.getPropertyValue(MappingMongoConverter.java:1085)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.getValueInternal(MappingMongoConverter.java:816)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$1.doWithPersistentProperty(MappingMongoConverter.java:270)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$1.doWithPersistentProperty(MappingMongoConverter.java:263)
at org.springframework.data.mapping.model.BasicPersistentEntity.doWithProperties(BasicPersistentEntity.java:261)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:263)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:237)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:201)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:197)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:78)
at org.springframework.data.mongodb.core.MongoTemplate$ReadDbObjectCallback.doWith(MongoTemplate.java:2016)
at org.springframework.data.mongodb.core.MongoTemplate.executeFindMultiInternal(MongoTemplate.java:1700)
at org.springframework.data.mongodb.core.MongoTemplate.doFind(MongoTemplate.java:1523)
at org.springframework.data.mongodb.core.MongoTemplate.doFind(MongoTemplate.java:1507)
at org.springframework.data.mongodb.core.MongoTemplate.find(MongoTemplate.java:532)
at org.springframework.data.mongodb.core.MongoTemplate.findOne(MongoTemplate.java:497)
at org.springframework.data.mongodb.core.MongoTemplate.findOne(MongoTemplate.java:489)
My in DAO I am trying to fetch the document by identifier, but it is failing only for the values at dataDetails and params. If I comment out the ParamData param, I get error for DataConfig. The data was added using command line/node scripts and was not added using this code. But it is getting retrieved properly by node/mongoose as well as from command line.

This seems to be an issue with doubly nested inner classes and the synthetically generated constructors created by the compiler. I could reproduce that issue locally and see if we can provide a fix. In the meantime you have two options:
Turn the inner class into static ones as this will remove the synthetic constructors and instantiation will work correctly.
Nest the type declarations in the same way you nest the properties. I.e. move the ParamData class into the Group class, DataConfig into Config as that will cause the synthetic constructors created in a way they match instantiation order Spring Data currently relies on.
I'd suggest the former approach as it doesn't artificially bind the classes to instances of the outer class.

Failed to instantiate ... using constructor public ... ReflectionEntityInstantiator
says it cannot create the objects using reflection.
Do you have getters and setters for all the fields in all your classes? Your code above does not have them for ParamData, Config and DataConfig.
Also, if you happen to have non-default constructors in any of your classes make sure you have an empty argument constructor, else reflection will fail.

Related

Custom annotation processing while coding

The purpose of the project I'm working on is to handle annotation at compile time, it is not focused on what exactly I'm developing.
I took a simple subject for this and I'm writing a custom collection that will store elements and provide methods to manage them.
What I wanna do is to create an annotation #Contains, for example, to generate itemsContains method that could be processed while coding (instead of writing code manually).
public class Array {
private List<String> items;
public Array() {
items = Arrays.asList("abc", "def", "xyz");
}
public boolean itemsContains(String expected) {
return items.contains(expected);
}
}
Generally, I want my class to look something like:
public class Array {
#Contains
private List<String> items;
public Array() {
items = Arrays.asList("abc", "def", "111");
}
}
The important thing I want to reach is to have itemsContains method show up once the annotation is applied to a field. This is how it should look like:
expected result
Alternate existing examples are Lombok's #Getter/#Setter.
So what functionality or configurations should I implement to get the expected result?
Would be grateful for some real implementations or guides how to perform it step by step.
Annotation processing does not change the source file yet it generates a new file,
Lombok on the other hand does a trick to modify the source file itself, meaning that you need to call the generated class somewhere in your code.
One way to do this is to generate a class that extends the main class
#Generated
public class ArrayProxy extends Array {
public boolean itemsContains(String expected) {
return items.contains(expected);
}
}
and in your main class you need to do two things:
first you need to make items protected
you can add factory method to actually create the generated class
public class Array {
#Contains
protected List<String> items;
public static ArrayProxy create(){
return new ArrayProxy();
}
private Array() {
items = Arrays.asList("abc", "def", "111");
}
}
And of course you need to use it like this
ArrayProxy array = Array.create();
array.itemsContains("expected");

Kotlin inheritance and JPA

I'm trying to implement inheritance with Kotlin and JPA. My abstract base class (annotated with #Entity) holds the ID (annotated with #Id and #GeneratedValue) and other metadata, like createDate, etc. I'm getting several errors from Hibernate, one for each field except the ID:
org.hibernate.tuple.entity.PojoEntityTuplizer - HHH000112: Getters of lazy classes cannot be final: com.example.BaseEntity.createDate
As I've read I need to include the open keyword for each property.
I have 3 questions regarding this:
Why do I have to do that in the superclass, and don't need in subclass? I'm not overriding those properties.
Why isn't it complaining about the ID?
It seems to work without the open keyword, then why is the error logged?
Edit:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
abstract class BaseEntity(
#Id #GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long = 0,
val createdAt: Instant = Instant.now()
)
#Entity
class SubClass(
val someProperty: String = ""
) : BaseEntity()
I'm using the JPA plugin for Gradle, which I believe creates the noarg constructor, that's why I don't have to specify everything nullable.
Thank you!
The logged error has to do with lazy loading.
Hibernate extends entities at runtime to enable it. It is done by intercepting an access to properties when an entity is loaded lazily.
Kotlin has flipped the rules and all classes are final by default there. It is the reason why we're advised to add an open keyword.
If a property is not open hibernate cannot intercept access to it because final methods cannot be overridden. Hence the error.
Why isn't it complaining about the ID?
Because #Id is always loaded. There is no need to intercept access to it.
It seems to work without the open keyword, then why is the error logged?
The key word here is seems. It may introduce subtle bugs.
Consider the following #Entity:
#Entity
public class Book {
#Id
private Long id;
private String title;
public final Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public final String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
And the #Test:
#Test
public void test() {
EntityManager entityManager = entityManagerFactory.createEntityManager();
entityManager.getTransaction().begin();
// signal here
Book book = new Book();
book.setId(1L);
book.setTitle("myTitle");
entityManager.persist(book);
// noise
entityManager.getTransaction().commit();
entityManager.close();
entityManager = entityManagerFactory.createEntityManager();
entityManager.getTransaction().begin();
// signal
Book reference = entityManager.getReference(Book.class, 1L);
String title = reference.getTitle();
assertNull(title); // passes
entityManager.getTransaction().commit();
entityManager.close();
}
This test passes but it should not (and fails if getTitle is not final).
This would be hard to notice
Why do I have to do that in the superclass, and don't need in subclass? I'm not overriding those properties.
Looks like Hibernate gives up when it sees final #Entity.
Add open to SubClass and you will the precious:
2019-05-02 23:27:27.500 ERROR 5609 --- [ main] o.h.tuple.entity.PojoEntityTuplizer : HHH000112: Getters of lazy classes cannot be final: com.caco3.hibernateanswer.SubClass.someProperty
See also:
final methods on entity silently breaks lazy proxy loading
How to avoid initializing HibernateProxy when invoking toString() on it? - my old question (note that Hibernate uses Byte Buddy these days).
PS
Did you forget to include #MappedSuperclass on BaseEntity?
Without the annotation it should fail with something like:
org.hibernate.AnnotationException: No identifier specified for entity: com.caco3.hibernateanswer.SubClass

How do I pass an array to a MongoRepository #Query?

Using Spring Data MongoDB. And I have defined this query on my MongoRepository:
#Query("{'type':?0, 'tags.type':?1, 'tags.softSkill.name': {$all: ?2}}")
List<Foo> findByTypeAndTags_TypeAndTags_SoftSkills(
FooType type, TagType tagType, String[] softSkills);
As I want to use $all operator I can't build the query through the method-naming way so I use the #Query annotation.
The issue is that it doesn't like the String[] that I try to pass for ?2 argument. I've tried with a List but the the error remains.
value expected to be of type java.util.List but is class java.lang.String toString:fooStr
On a mongo-shell would be like: $all: ['foo1','foo2']
Foo abstract entity is like:
#Data
#Document(collection="foes")
public abstract class Foo {
/*...*/
protected List<Tag> tags = new ArrayList<>();
}
Tag asbtract pojo:
#Data
public abstract class Tag {
protected final TagType type;
protected TagLevel level;
}
TagSoftSkill pojo:
#Data
#EqualsAndHashCode(callSuper=true)
public class TagSoftSkill extends Tag{
private SoftSkill softSkill; //another pojo that has a "name" field
public TagSoftSkill() {
super(TagType.SOFT_SKILL);
}
}
I already debugged and softSkills prints [foo]. I modified the value to be like ['foo'] but there are no changes.
I didn't mention that I'm using fongo. Switched to real mongo and everything works as it should. So it's a fongo issue.

Cannot create self link for class X. No persistent entity found

Getting the error in the title when using Spring Data REST. How to resolve?
Party.java:
#Entity
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
#JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, property="#class")
#JsonSubTypes({ #JsonSubTypes.Type(value=Individual.class, name="Individual") })
public abstract class Party {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
protected Long id;
protected String name;
#Override
public String toString() {
return id + " " + name;
}
...getters, setters...
}
Individual.java:
#Entity
public class Individual extends Party {
private String gender;
#Override
public String toString() {
return gender + " " + super.toString();
}
...getters, setters...
}
PartyRepository.java:
public interface PartyRepository extends JpaRepository<Party,Long> {
}
If I POST, it saves to the db correctly:
POST /parties {"#class":"com.example.Individual", "name":"Neil", "gender":"MALE"}
But returns a 400 error:
{"cause":null,"message":"Cannot create self link for class com.example.Individual! No persistent entity found!"}
It looks like it's an Individual after retrieving from repository:
System.out.println(partyRepository.findOne(1L));
//output is MALE 1 Neil
Looks like Jackson can figure out that it's an individual:
System.out.println( new ObjectMapper().writeValueAsString( partyRepository.findOne(1L) ) );
//output is {"#class":"com.example.Individual", "id":1, "name":"Neil", "gender":"MALE"}
Why can SDR not figure it out?
How to fix? Preferably with XML config.
Versions:
SDR 2.2.0.RELEASE
SD JPA 1.7.0.RELEASE
Hibernate 4.3.6.Final
SDR repositories expect a non-abstract entity, in your case it would be Individual. You can google or search here for explaination on why SDR expects a non-abstract entity.
I tried your code myself and SDR won't even work for POST and I see below error message.
{
"cause": {
"cause": null,
"message": "Can not construct instance of com.spring.data.rest.test.Party, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information\n at [Source: org.apache.catalina.connector.CoyoteInputStream#30217e25; line: 1, column: 1]"
},
"message": "Could not read JSON: Can not construct instance of com.spring.data.rest.test.Party, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information\n at [Source: org.apache.catalina.connector.CoyoteInputStream#30217e25; line: 1, column: 1]; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of com.spring.data.rest.test.Party, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information\n at [Source: org.apache.catalina.connector.CoyoteInputStream#30217e25; line: 1, column: 1]"
}
I suggest you change respository from PartyRepository to IndividualRepository
public interface IndividualRepository extends JpaRepository<Individual,Long> {
}
You are seeing that error since SDR could not find a repository refering Individual while constructing links. Simply adding Individual respository and not exporting it would solve your problem.
#RepositoryRestResource(exported = false)
public interface IndividualRepository extends JpaRepository<Individual,Long> {
}

Persist derived objects using Mongo C# driver

I have the following class hierarchy
[BsonKnownTypes(typeof(MoveCommand))]
public abstract class Command : ICommand
{
public abstract string Name
{
get;
}
public abstract ICommandResult Execute();
}
public class MoveCommand : Command
{
public MoveCommand()
{
this.Id = ObjectId.GenerateNewId().ToString();
}
[BsonId]
public string Id { get; set; }
public override string Name
{
get { return "Move Command"; }
}
public override ICommandResult Execute()
{
return new CommandResult { Status = ExecutionStatus.InProgress };
}
}
if I save the command like so:
Command c = new MoveCommand();
MongoDataBaseInstance.GetCollection<Command>("Commands").Save(c);
and then query the DB, I don't see the derived properties persisted.
{ "_id" : "4df43312c4c2ac12a8f987e4", "_t" : "MoveCommand" }
I would expect a Name property as a key in the document.
What am I doing wrong?
Also, is there a way to avoid having a BsonKnowTypes attribute on the base class for persisting derived instances? I don't see the why a base class needs to know about derived classes. This is bad OO design and is being forced on my class hierarchy by the BSON library. Am I missing something here?
1.Name property was not saved into database because it haven't setter. Serializers not serialize properties that's haven't setters (because if serializer serialize such property it will not able deserialize it back). So if you want serialize Name property then just add fake setter(into ICommand need to add it also):
public override string Name
{
get { return "Move Command"; }
set{}
}
2.If you don't want use BsonKnownTypes attribute there is another way to notify serializer about know types it might encounter during deserialization. Just Register maps once, on app start event:
BsonClassMap.RegisterClassMap<MoveCommand>();
//all other inherited from ICommand classes need register here also
So you should use or KnownTypes attribute or register BsonClassMap for each polymorphic class, otherwise you will get 'unknown descriminator' error during deserializtion:
var commands = col.FindAllAs<ICommand>().ToList();
3 You said:
This is bad OO design and is being
forced on my class hierarchy by the
BSON library.
In any way even without KnownTypes atribute your code using Bson lib through BsonId attribute.
If you want avoid it you can:
BsonClassMap.RegisterClassMap<MoveCommand>(cm => {
cm.AutoMap();
cm.SetIdMember(cm.GetMemberMap(c => c.Id));
});
So now you can remove reference to Mongodb.Bson lib from your domain code lib.