Spring data #Transient in abstract class - java

I have a strategy pattern implemented with an abstract class called presentation
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({
#Type( value=PiePresentation.class, name="PIE"),
#Type( value=BarPresentation.class, name="BAR")})
public abstract class Presentation {
String id;
List<? extends DetailResponse> data = new ArrayList();
String[] variables;
Map<String, Object> configurations;
#Transient
protected ExecutionState state;
}
And two sub classes, here's one of them
public class PiePresentation extends Presentation{
#Transient
private List<Segment> response;
}
This classes are fields of another class that is stored into a mongo collection.
The problem is that spring data is storing the state field of the Presentation class, is like in inheritance the #Transient annotation is being ignored.

Your code looks good to me.
Are you sure that you are using the correct #Transient annotation?
It must be imported from the package org.springframework.data.annotation.
Don't use the javax persistence one.
Moreover be sure to use MappingMongoConverter. Annotation based mapping only works if you're using the MappingMongoConverter as backing converter for MongoTemplate. If you're not configuring the converter a SimpleMongoConverter will be used by default that simply serializes objects into Mongo without taking a look at any meta information whatsoever.

Related

How to persist some fields in Generic class in Spring Data JPA?

I have an entity class as follows. What I need is, I need to use generics, and by using it I need to pass some classes that extend the SomeInfo class and persist a common field inside the SubClass table and when returning data I can populate T parameter and send it easily. But I can't extend SomeInfo class directly because it contains fields that I don't need to be created in the SubClass table and even I don't need to use the Inheritance Single Table solution. I have tried using #AttributeOverride but it threw an error and I used #Type annotation and the error was gone, but the field info_name isn't created.
#Entity
public class SubClass<T extends SomeInfo> extends ParentClass {
#Type(type = "com.package.SomeInfo")
#AttributeOverrides({
#AttributeOverride(name = "name", column = #Column(name = "info_name", nullable = false))
})
private T info;
}
#MappedSuperClass
public abstract class SomeInfo extends ParentClass {
// some fields
}
Is there a way to achieve what I need? Thanks in advance.

How can I use polymorphism in Spring Boot Services?

I am currently working on a Spring Boot project and I would like to speed up process of writing the service/data layer boilerplate code (one service and one repository (CrudRepository) for every entity, every one having mostly the same methods).
As of now I am using TABLE_PER_CLASS inheritance in several entities (e.g.: Warehouse and Office are subclasses of Location (an abstract class defining common attributes for all locations).
I would like to define 1 repository and 1 service to manage both Location and its subtypes so I can do something like in my control layer:
#Autowired
LocationsService locationsService;
Warehouse cityWarehouse = new Warehouse();
Office centralOffice = new Office();
locationsService.addNewLocation(cityWarehouse);
locationsService.addNewLocation(centralOffice);
I know I can just use method overloading but I would really like to avoid repeating the same code in situations like this one.
I've also tried using parametric polymorphism:
#Service
public class LocationsService {
#Autowired
LocationsRepository locationsRepository;
public void addNewLocation(Location location) {
locationsRepository.save(location);
}
}
Unfortunately this won't work as Spring can't tell if I want to save a Location or a Warehouse object:
nested exception is org.springframework.orm.jpa.JpaObjectRetrievalFailureException:
Unable to find com.test.springboot.entities.locations.Location with id 55db6993-8a58-4e3a-a6ab-d60d93ab6182; nested exception is javax.persistence.EntityNotFoundException: Unable to find com.test.springboot.entities.locations.Location with id 55db6993-8a58-4e3a-a6ab-d60d93ab6182
I need to use concrete Location objects so using #MappedSuperclass is not an option.
Is there something I am missing? Is it even posible to achieve what I want?
Please note that I am fairly new to Spring Boot so maybe there's something obvious I don't know about yet.
I got it working thanks to some of the comments and after briefly reading the JPA specification.
Because I wanted to use my superclasses as entities I ended up using SINGLE_TABLE inheritance.
For example, this is the Location entity:
#Entity
#Data
#Accessors(chain = true)
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "location_type")
public class Location {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
// Skipped
}
The key here is to use #DiscriminatorColumn with SINGLE_TABLE inheritance in the parent class and add #DiscriminatorValue in the correspondent subclasses:
#Entity
#Data #Accessors(chain = true)
#DiscriminatorValue("warehouse_location")
public class Warehouse extends Location {
#JoinColumn(name = "INTERNAL_ROUTE_ID")
#OneToOne(orphanRemoval = true)
private Route internalRoute;
#JoinColumn(name = "EXTERNAL_WAREHOUSE_ID")
#OneToMany(orphanRemoval = true, fetch = FetchType.EAGER)
private List<Warehouse> externalWarehouses;
}
This way, I can define LocationsRepository as:
public interface LocationsRepository extends CrudRepository<Location, Long> {
Warehouse findByCityIgnoreCase(String city);
}
Also note that subclass-specific methods can be defined here as long as its return type is explicitly specified (otherwise the method would return ALL Locations, not just the Warehouses).
Finally, in the service layer I can make the relevant methods return any entity just by downcasting the result of the repository call into the appropriate subclass

Java :: JacksonXmlProperty :: Multiple fields with same name

I'm extending code from an existing Java class that serializes to and from XML. The existing class is somewhat like this:
#Getter
#JacksonXmlRootElement("element")
public class Element {
#JacksonXmlProperty(localName = "type", isAttribute = true)
private String type;
}
The type field has a finite set of possible values so I created an enum Type with all possible values and (to avoid breaking existing functionality) added a new field to the class, like so:
#Getter
#JacksonXmlRootElement("element")
public class Element {
#JacksonXmlProperty(localName = "type", isAttribute = true)
private String type;
#JacksonXmlProperty(localName = "type", isAttribute = true)
#JsonDeserialize(using = TypeDeserializer.class)
private Type typeEnum;
}
This gives me the following error:
Multiple fields representing property "type": Element#type vs Element#typeEnum
I understand why this is a problem cuz when Jackson would try to serialize the class, two fields in my class map onto the same field in the output XML.
I tried adding a #JsonIgnore on one of the fields and it gets rid of the error but has the side effect of not populating the ignored field either. Is there a way to annotate that a field should be deserialized (while reading XML) but not serialized (while writing XML)?
I really need to keep both fields in the class to not disturb any legacy code that might be using the first field, but at the same time allow newer code to leverage the second field.
Thank you!

How to learn Jackson to cast inheritors of abstract class?

I have a class:
#EqualsAndHashCode(callSuper = true)
#Data
public class AppealTemplateDto extends AbstractDto {
private List<AbstractFieldDto> fields;
}
This class contains list of AbstractFieldDto inheritors, e.g.:
#EqualsAndHashCode(callSuper = true)
#Data
#NoArgsConstructor
public class InputFieldDto extends AbstractFieldDto {
private String fieldType = FieldType.INPUT.name();
private String text;
}
Totally, there are near 6-7 inheritors, & AbstractTemplateDto may contain any set of them.
Controller:
#PostMapping
public ResponseEntity<AppealTemplateDto> create(#RequestBody AppealTemplateDto dto) {
return ResponseEntity.ok(service.save(dto));
}
When Jackson trying to parse AppealTemplateDto, it crashes with exception:
Caused by:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot
construct instance of
ru.appeal.template.dto.field.AbstractFieldDto
(no Creators, like default construct, exist): abstract types either
need to be mapped to concrete types, have custom deserializer, or
contain additional type information
As I understand, Jackson can't define, how to cast incoming AbstractFieldDto. Please, advice me, what to do?
The Annotation your are needing are:
#JsonTypeInfo
#JsonSubType
#JsonTypeName
Some explanation: if you have many implementation of your abstract type, Jackson can't guess which type is your json, you need to add a type name in json, for example as a new property (this is one of the strategies):
//tell to jackson where to find the type name
#JsonTypeInfo( use = JsonTypeInfo.Id.NAME, include = As.PROPERTY, property = "type")
// tell to jackson the implementations to scan
#JsonSubTypes({
#JsonSubTypes.Type(value = InputFieldDto.class, name = "input")
//, ...
})
public class AbstractFieldDto {
}
//tell to jackson what is the type name in json
#JsonTypeName("input")
public class InputFieldDto extends AbstractFieldDto {
private String fieldType = FieldType.INPUT.name();
private String text;
}

Spring/JPA/persistence entity attribute field cannot be final?

I have a Spring MVC project using JPA which I have worked on for some time in the past without this issue. But now for some reason (likely an environmental issue as I have switch to a new laptop since I last worked on it) I am getting this weird error.
The project is essentially a tool for creating and performing surveys which are just a set of questions. There are multiple types of question such as "auto complete question", "multiple choice question", "integer question", etc which collect different types of data. Each of this question types is modeled by a subclass which extends an abstract class called DdmQuestion which looks something like this:
#Entity
#Table(name = "ddm_question")
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(discriminatorType = DiscriminatorType.STRING, name = "question_type")
#JsonIgnoreProperties({"dataType"})
#JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS, include = JsonTypeInfo.As.PROPERTY, property = "#question_type")
#JsonSubTypes(value = { #Type(DdmTextQuestion.class),#Type(DdmDateQuestion.class),#Type(DdmTimeQuestion.class),#Type(DdmNumberIntegerQuestion.class),#Type(DdmChoiceMultiQuestion.class),#Type(DdmAutoCompleteQuestion.class) })
public abstract class DdmQuestion {
#Id
#GeneratedValue
#Column(name = "question_id")
private int questionId;
#Column(name = "name")
private String name;
public int getQuestionId() {
return questionId;
}
public void setQuestionId(int questionId) {
this.questionId = questionId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#JsonIgnore
public abstract String getDataType();
}
Note the getDataType() method.
Then, for each question type, I have a subclass extending this which looks something like this:
#Entity
#DiscriminatorValue("ddm_question_date")
public class DdmDateQuestion extends DdmQuestion {
final private String DATA_TYPE = "Long"; // this is the line with the error
#Override
public String getDataType() {
return DATA_TYPE;
}
}
Now, I've never encountered this error before (that I can recall) but Eclipse is throwing up an error here that says:
"The Java field for attribute "DATA_TYPE" is final". That's all it
says.
If I remove the #Entity annotation from the class, this error disappears so evidently something in JPA doesn't like something about this but I never had this error before so I'm thinking something changed in a newer version. My POM is not particularly explicit with dependency versions so this would not be surprising.
Can anyone explain to me why this is happening and what the correct resolution is? I could just remove the "final" from the field declaration but this seems wrong to me as it is definitely a final value...
Thanks in advance.
If it is a field that should not be persisted in the database you usually should take advantage of the transient annotation which would tell the persistence provider to ommit that field in its processing.:
#Transient
final private String DATA_TYPE = "Long";
If Eclipse is smart enough, it should stop highlighting the error altogether.
in this linkshttp://docs.oracle.com/javaee/5/tutorial/doc/bnbqa.html#Entities;
An entity class must follow these requirements:
The class must be annotated with the javax.persistence.Entity annotation.
The class must have a public or protected, no-argument constructor. The class may have other constructors.
The class must not be declared final. No methods or persistent instance variables must be declared final.
If an entity instance be passed by value as a detached object, such as through a session bean’s remote business interface, the class must implement the Serializable interface.
Entities may extend both entity and non-entity classes, and non-entity classes may extend entity classes.
Persistent instance variables must be declared private, protected, or package-private, and can only be accessed directly by the entity class’s methods. Clients must access the entity’s state through accessor or business methods.

Categories

Resources