Mapping persisted entities to mongo using spring - spring

In spring documentation talking about Customizing type mapping
Reference
Using #TypeAlias and TypeInformationMapper .. but I cannot find any practical example
can someone please reference?
When defining MappingMongoConverter it is related the entities persistence?
Thanks

When you persist an entity using spring-data then mongo document created will have an _class attribute which stores the fully qualified name of the class. The #TypeAlias is to customize the value saved in the _class attribute.
This example from the spring reference shows how the _class attribute is added to the mongo document. If you attach a #TypeAlias("sample) then the _class attribute will have the value "sample" instead of the fully qualified name.
public class Sample {
Contact value;
}
public abstract class Contact { … }
public class Person extends Contact { … }
Sample sample = new Sample();
sample.value = new Person();
mongoTemplate.save(sample);
{ "_class" : "com.acme.Sample",
"value" : { "_class" : "com.acme.Person" }
}

Related

Spring's couchbase JPA repository with abstract class fails to find entity

We are developing a project in Springboot that uses a Couchbase, I have following classes:
public abstract class Content {
...
}
public class Film extends Content {
...
}
public class Serie extends Content {
...
}
Then I have following JPA repository:
public interface ContentJpaRepository extends ReactiveCouchbaseSortingRepository<Content> {
}
Then, when I save a content (film or serie) the content is successfully saved, however, the _class field gets the simple class name (instead of the full package name).
Then, when doing:
repository.findById(id);
The repository fails as it can't deserialize the json document to the expected entity. How could I achieve that?
Thank you very much
Using a generic repository is currently not supported for Couchbase Spring Data, as the _class attribute will refer to the abstract class instead of its implementations.

How to get #TypeAlias working when reading document after upgrading to Spring Boot 2.1.3 when it was working in 1.4.5

I am currently using spring data mongodb and the Configuration file extends AbstractMongoConfiguration:
#Configuration
#EnableMongoRepositories(basePackages = "com.mycompany")
#EnableMongoAuditing
public class MongoConfig extends AbstractMongoConfiguration
{
I override the getMappingBasePackage() method to set the package to scan like this:
#Override
protected String getMappingBasePackage()
{
return "com.mycompany";
}
I have been debugging through the code and noticed some interesting things:
There are two place where I get a java.lang.InstantiationError. Both cases occur when I am trying to read in document from mongo that has a reference to an abstract class (ParentClass). It is trying to instantiate the abstract class instead of finding the #TypeAlias annotation which I have added to the child classes.
This is what my ParentClass looks like:
#Document
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.EXISTING_PROPERTY, visible=true, property="type")
#JsonSubTypes({
#Type(value=Child1.class, name="JSON_TYPE_CHILD1"),
#Type(value=Child2.class, name="JSON_TYPE_CHILD2"),
#Type(value=Child3.class, name="JSON_TYPE_CHILD3")
})
public abstract class ParentClass
{
...
My child classes look like this:
#Document
#JsonTypeName("JSON_TYPE_CHILD1")
#TypeAlias("ALIAS_TYPE_CHILD1")
public class Child1 extends ParentClass
{
...
This is what the json looks like (simplified) that I am trying to read in:
{
"_id" : ObjectId("5c86d31388f13344f4098c64"),
"listOfWrapperClass" : [
{
"parentClass" : {
"type" : "JSON_TYPE_CHILD1",
"prop1" : 50.0,
"prop2" : 50.0,
"_class" : "ALIAS_TYPE_CHILD1"
},
"isReportOutOfDate" : false,
}
],
"_class" : "com.mycompany.domain.job.Job"
}
When I debug through spring data the problem occurs in DefaultTypeMapper:
private TypeInformation<?> getFromCacheOrCreate(Alias alias) {
Optional<TypeInformation<?>> typeInformation = typeCache.get(alias);
if (typeInformation == null) {
typeInformation = typeCache.computeIfAbsent(alias, getAlias);
}
return typeInformation.orElse(null);
}
It load the wrapper class fine, but when it gets to child class the alias is set to "ALIAS_TYPE_CHILD1" as it should but the following values are in the typeCache:
{
NONE=Optional.empty,
ALIAS_TYPE_CHILD1=Optional.empty,
com.mycompany.domain.job.Job=Optional[com.mycompany.domain.job.Job]
}
Because the key "ALIAS_TYPE_CHILD1" has an Optional.empty as a value the code doesn't get the correct target type to load and it therefore uses the rawType which is the ParentClass. Which blows up because it can't instantiate an abstract class. Here is the stacktrace:
Caused by: java.lang.InstantiationError: com.mycompany.domain.job.base.ParentClass
at com.mycompany.domain.job.base.ParentClass_Instantiator_q3kytg.newInstance(Unknown Source)
at org.springframework.data.convert.ClassGeneratingEntityInstantiator$EntityInstantiatorAdapter.createInstance(ClassGeneratingEntityInstantiator.java:226)
at org.springframework.data.convert.ClassGeneratingEntityInstantiator.createInstance(ClassGeneratingEntityInstantiator.java:84)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:272)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:245)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readValue(MappingMongoConverter.java:1491)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$MongoDbPropertyValueProvider.getPropertyValue(MappingMongoConverter.java:1389)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readProperties(MappingMongoConverter.java:378)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.populateProperties(MappingMongoConverter.java:295)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:275)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:245)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readCollectionOrArray(MappingMongoConverter.java:1038)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readValue(MappingMongoConverter.java:1489)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$MongoDbPropertyValueProvider.getPropertyValue(MappingMongoConverter.java:1389)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readProperties(MappingMongoConverter.java:378)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.populateProperties(MappingMongoConverter.java:295)
at ...
The weird thing is if I insert a new document that has #TypeAlias("ALIAS_TYPE_CHILD1") first, the typeCache mentioned above is populated correctly like so:
{
NONE=Optional.empty,
ALIAS_TYPE_CHILD1=Optional[com.mycompany.domain.job.base.Child1],
com.mycompany.domain.job.Job=Optional[com.mycompany.domain.job.Job]
}
When I do findOne right after the insert, I can read in the document without error because it uses Child1 to instantiate the pojo instead of the ParentClass. If I try to read first then it doesn't matter if I insert after that or not because the typeCace gets the wrong value in there and it uses that until you restart the server.
My guess is there was a change in configuration or in a default setting. I was able to work through all the other upgrade issues, but this one has me baffled. I would be shocked if there is an actual issue in spring data because I am sure somebody would have run into this issue by now because I can't be the only one trying to use #TypeAlias with spring-data-mongodb. Not to mention this all works great with the previous version of spring boot that I used (1.4.5 which uses spring-data-mongodb 1.9.8.RELEASE).
Any thoughts or advice on what to try next are welcome. I am simply at a loss of what to do next.
The problem was the fact that the typeCache wasn't getting populated in the first place on server startup. This is because protected String getMappingBasePackage() is now deprecated. You should use protected Collection<String> getMappingBasePackages() instead and then everything works great.
Overriding this method solves the issue:
#Override
protected Collection<String> getMappingBasePackages()
{
return Arrays.asList("com.mycompany");
}

how to retrieve objects when using inheritance in spring Data

Say I have a class structure as follows, it is pretty basic inheritance:
Manager extends Person {
private String name;
Manager() {
}
}
Clerk extends Person {
private String salary;
}
In spring Data if I store these in Mongo, is it possible to configure it to map the correct class when I do a getById. I assume i will have to store some class info?
What i dont want to do is the need to create seperate repository classes if i can avoid it, also i dont know what the object will be when i do a getById
If you are using spring-data-mongodb MongoRepository to write data in your database according to your entity model, a _class field will be added to document roots and to complex property types (see this section). This fields store the fully qualified name of the Java class and it allows disambiguation when mapping from MongoDb Document to Spring data model.
However, if you only use MongoRepository to read from your database, you need to tell Spring-data how to map your entities explicitly. You will need to Override Mapping with Explicit Converters.
PersonReadConverter.class
public class PersonReadConverter implements Converter<Document, Person> {
#Override
public Contact convert(Document source) {
if (source.get("attribute_specific_to_Clerk") != null) {
Clerk clerk = new Clerk();
//Set attributes using setters or defined constructor
return clerk;
}
else {
Manager manager = new Manager()
//Set attribute using setters or defined constructor
return manager;
}
}
}
Then, you have to Register Spring Converters with the MongoConverter.
You can find an example of my own at: Spring Data Mongo - How to map inherited POJO entities?

Spring Mongo Repository Polymorphism

How do I define Repository interface for polymorphic classes
Ex.
abstract class Source { public String name }
class InternalSource extends Source { public int internalId }
class ExternalSource extends Source { public String contact }
Now I know I cannot define a repository interface like
interface SourceRepo extends Repository<? extends Source, String>{....}
or
interface SourceRepo extends Repository<Source, String> { ....}
Is defining simple plain interface and have an implmentation class is the only way?
Well letting spring to associate mongo document to java class mapping through '_class' attribute would work fine.
Mongo document would like some like this
{_id : "xxx", name : "abc", internalId : 123, _class = "...InternalSource" }
{_id : "xxx", name : "abc", contact: "John doe", _class = "...ExternalSource"}

Convert IQueryable<T> to IQueryable<Y>

I'm try to do this : I'm using EF code first to map an old existing database. There's many fields with the wrong type (ex: char(1) used as boolean) so I've created a wrapper class for my db context that maps perfectly to the database table. Now, I want to expose an IQueryable of my Entity type on my repository. See my example :
public class MyContext:DbContext
{
public DbSet<WrappedEntity> WrapperEntities;
}
public class Repository
{
private MyContext _context;
//contructors omitted
public IQueryable<Entity> GetEntities()
{
return _context.WrapperEntities; //doesn't compile, I need some convertion here
}
}
I already have my convertion routine, the only thing missing is a way to query my DbContext thought my repository without exposing the WrappedEntity class, Is it possible and how ?
Thanks.
Usually you project with Queryable.Select to change the type of your query...
public IQueryable<Entity> GetEntities()
{
return _context.WrapperEntities.Select(x => new Entity(){...});
}

Resources