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

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> {
}

Related

Mapstruct - Multiple parameters to inner class

I'm trying to map to a inner class but it's not working.
I have the following Pojos:
public record Author(UUID id, String name) {}
public record Book(Author author) {}
And this is the mapper:
#Mapper
public interface BookMapper {
#Mapping(target="author", source=".");
Book map(UUID id, String name);
Author map(UUID id, String name);
}
But I get this error when compiling:
BookMapperImpl is not abstract and does not override abstract method map(UUID,String)
Any help is appreciated.
Thanks
BookMapperImpl is not abstract and does not override abstract method map(UUID,String)
That is a strange error, it means that MapStruct generated some partial implementation due to something else, but it didn't report an addition error. This is most likely a bug in MapStruct.
Nevertheless, using source = "." doesn't work with multiple source parameters and even without that it won't be able to pass the mapping to the Author mapping.
I would suggest using a custom method for mapping this e.g.
#Mapper
public interface BookMapper {
#Mapping(target="author", source=".");
default Book map(UUID id, String name) {
Author author = map(id, name);
return author != null ? new Book(author) : null;
}
Author map(UUID id, String name);
}

java.lang.IllegalArgumentException: You have provided an instance of an incorrect PK class for this find operation.

I'm trying to create a URI and returns an object looking for NIF. (One custom filter)
I have tried to replicate the search by id but does not work, the truth, I'm not sure what I do. I have two classes with these functions
ClientesAbstractFacade.java
public T findNif(Object nif) {
return getEntityManager().find(entityClass, nif);
}
lientesFacadeREST.java
#GET
#Path("nif/{nif}")
#Produces({MediaType.APPLICATION_JSON})
public Clientes findNif(#PathParam("nif") String nif) {
return super.findNif(nif);
}
And here POJO
#Entity
#Table(name = "clientes")
public class Clientes implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Integer id;
As you can see, I'm trying to do custom searches, something easy, and then implement a login.
But I can not even filter by nif, these return error 500
java.lang.IllegalArgumentException: You have provided an instance of an incorrect PK class for this find operation. Class expected : class java.lang.Integer, Class received : class java.lang.String.
The exception says it all:
IllegalArgumentException: You have provided an instance of an incorrect PK class for this find operation. Class expected : class java.lang.Integer, Class received : class java.lang.String
The getEntityManager().find(entityClass, nif) is working on the Primary Key column of your Table. This is, as the exception states, an Integer.
I guess you want to use your NamedQueries and have thus to use createNamedQuery-methods of the EntityManager.
So your find-method should look something like that:
public T findNif(String nif) {
return (T) getEntityManager().createNamedQuery("Clientes.findByNif", Clientes.class)
.setParameter("nif", nif).getSingleResult();
}

how to disambiguate using #DocumentField in spring data mongoDB

While trying to save Object into mongo DB using Spring-Data MongoDB, I am getting this exception.
Ambiguous field mapping detected! Both <child class field> and <parent class field> map to the same field name <field name>! Disambiguate using #DocumentField annotation!
I am hiding a field in child class and this is causing the issue. But this is essential for me to hide the field declared in super class. I cannot find #DocumentField annotation anywhere. How can I proceed here? Is there any other solution?
For those of you (like me) who arrived here due to another form of Ambiguous field mapping detected! error that is not quite the same as OP, consider that your child class may not even need a field at all and the answer provided by #sparm could be changed to:
public class Parent {
private String myField;
public getMyField() {
return myField;
}
}
#Document(collection = "children")
public class Child extends Parent {
public getMyField() {
super.getMyField();
}
}
I believe its an error in the exception. You need to use #Field instead and specify a different name for the parameter so:
public class Parent {
private String myField;
}
#Document(collection = "children")
public class Child extends Parent {
#Field("childField")
private String myField;
}

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

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.

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.