I'm using Spring data rest with Kotlin and if I use data classes the associations via uri stops working with the error no String-argument constructor/factory method to deserialize from String value
Data class
#Entity
data class Comment(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long = 0,
var author: String? = null,
var content: String? = null,
#ManyToOne
var post: Post? = null) {
}
If I use a simple class instead the association works fine.
#Entity
class Comment {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY) var id: Long = 0
var author: String? = null
var content: String? = null
#ManyToOne
var post: Post? = null
}
The association is done via a POST request {"author":"John Doe","content":"Dummy Content", "post":"http://localhost:8080/post/33"}
Any ideia why I have this error when I use a data class and what can I do to use the association creation via uri and keep using data classes?
I did some investigation, and turns out Spring Data Rest uses a custom Jackson module to deserialize JSON into JPA entities: it uses PersistentEntityJackson2Module class and using the inner class UriStringDeserializer to resolve the concrete entities from entity URI references, http://localhost:8080/post/33 in your example.
Problem is, this custom deserialization only kicks in when the "standard deserialization" of Jackson is triggered: The one that uses empty constructor, then using setters to resolve & set the fields. At that moment, UriStringDeserializer converts the string into the concrete entity - Post instance of your example.
When you use a data class, the class neither has an empty constructor nor setters, therefore in BeanDeserializer#deserializeFromObject method of Jackson, it branches into if (_nonStandardCreation) being true, from there the call goes into BeanDeserializerBase#deserializeFromObjectUsingNonDefault , but not handed over to PersistentEntityJackson2Module anymore, and directly failing due to type mismatch between the constructor argument and the json value.
It seems you need to create a feature request for it to be implemented. If you decide to implement yourself, providing a _delegateDeserializer to the BeanDeserializer might be a start (not sure).
However, I don't know how JPA itself plays with data classes in the first place - after all it tracks the entity state changes, but a data class cannot have state changes. So, it might not be possible to use data classes after all - better to keep in mind.
Note: You probably cannot simply extend/override PersistentEntityJackson2Module because it is registered to multiple beans in RepositoryRestMvcConfiguration
Related
I try to use Mongodb with Spring Data.
I wanted to use UUID instead of ObjectId. I have followed this tutorial: https://www.baeldung.com/java-mongodb-uuid (some differences might exist because I use Kotlin). I took path 2 where I added Entity callback. When I create a new entity it is saved to the database with UUID as I wanted. If I use mongo console I can type:
db.home.find({_id: UUID("18aafcf9-0c5a-46f3-84ff-1c25b00dd1ab")})
And I will find my entity by id.
However, when I try to do it by code it doesn't work as it should. It will always throw here DataOperationException(NOT_FOUND) because findById returns null.
fun findHomeById(id: String): Home {
val home = homeRepository.findById(id)
return home.unwrap() ?: throw DataOperationException(NOT_FOUND)
}
Here is repository
#Repository
interface HomeRepository: MongoRepository<Home, String>
Abstraction with id.
abstract class UuidIdentifiedEntity {
#Id
var id: UUID? = null // I tried use UUID type and String with the same result
}
And my home class
class Home(
var address: String,
var rooms: Int,
): UuidIdentifiedEntity()
Not sure if this will help, but see if changing your #Id annotation to #MongoId fixes this. Under certain circumstances, Mongo needs to know more about a field being used for an id. #MongoId should give you more control on how the field is stored too.
What is use of #MongoId in Spring Data MongoDB over #Id?
I followed this tutorial to create a basic web app in Kotlin using Spring Boot. However, I fail to POST new entities with a many-to-one relationship to an existing resource.
My code:
#Entity
class Song(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null,
var title: String,
#ManyToOne(fetch = FetchType.EAGER)
var addedBy: User)
#Entity
class User(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null,
var email: String,
var displayName: String)
#RestController
#RequestMapping("/api/songs")
class SongController(private val repository: SongRepository) {
#PostMapping("/")
fun add(#RequestBody song: Song) =
repository.save(song)
This answer and others point out that you can reference another resource using its URI, but sending the following request:
{
"title": "Some title",
"addedBy": "http://localhost:8080/api/users/1"
}
gets me an errors with stack trace org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of 'com.example.springboot.User' (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('http://localhost:8080/api/users/1'); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of 'com.example.springboot.User' (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('http://localhost:8080/api/users/1')\n at [Source: (PushbackInputStream); line: 6, column: 13] (through reference chain: com.example.springboot.Song[\"addedBy\"])
I got out of this that somewhere between Jackson/Hibernate/Spring Data it fails to convert the User resource URI into a User entity, but I'm in the dark where this magic should happen.
It seems to be an issue that occurs with Kotlin specifically. All the suggestions here on SO do not solve this specific error and the tutorial itself stops just short of dealing with relationships. If it's not the right approach at all to handle relationships this way I'd be eager to know what the preferred practice would be.
The tutorial is using HATEOAS. See the request body where they are referencing the corresponding child entity by using
"books" : { "href" : "http://localhost:8080/authors/1/books" }
Meaning you should also apply this pattern to your request. Otherwise this will not work. HATEOAS allows you to directly reference the related child entities by their corresponding resource path but you need to keep the necessary structure which your posted request body is missing. Further you must support HATEOAS in your WebService / WebApi / Spring Boot Project.
What you could do:
{
"title": "Some title",
"addedByUserId": "1"
}
Then
#PostMapping("/")
fun add(#RequestBody song: Song) =
val userEntity = userRepository.findById(song.getAddedByUserId())
Song newSong = new SongEntity();
// map props
newSong.setUser(userEntity)
repository.save(song)
That code does not work but I hope you get the idea.
Further
In your code you are treating the Request Body as an Entity. Please consider to separate your incoming Class and your Entity class. This would make several things easier.
I think you're missing jackson's kotlin module, it's exactly what it was created for.
https://github.com/FasterXML/jackson-module-kotlin
Just adding this dependency in your project will cause spring to autoconfigure your object mapper with this new module. If you have a Bean with your own created objectMapper then you need to configure it manually, there's a section about this in module's README.md
I'm quite new to Spring Data JPA technology and currently facing one task I can't deal with. I am seeking best practice for such cases.
In my Postgres database I have a two tables connected with one-to-many relation. Table 'account' has a field 'type_id' which is foreign key references to field 'id' of table 'account_type':
So the 'account_type' table only plays a role of dictionary. Accordingly to that I've created to JPA entities (Kotlin code):
#Entity
class Account(
#Id #GeneratedValue var id: Long? = null,
var amount: Int,
#ManyToOne var accountType: AccountType
)
#Entity
class AccountType(
#Id #GeneratedValue var id: Long? = null,
var type: String
)
In my Spring Boot application I'd like to have a RestConroller which will be responsible for giving all accounts in JSON format. To do that I made entities classes serializable and wrote a simple restcontroller:
#GetMapping("/getAllAccounts", produces = [APPLICATION_JSON_VALUE])
fun getAccountsData(): String {
val accountsList = accountRepository.findAll().toMutableList()
return json.stringify(Account.serializer().list, accountsList)
}
where accountRepository is just an interface which extends CrudRepository<Account, Long>.
And now if I go to :8080/getAllAccounts, I'll get the Json of the following format (sorry for formatting):
[
{"id":1,
"amount":0,
"accountType":{
"id":1,
"type":"DBT"
}
},
{"id":2,
"amount":0,
"accountType":{
"id":2,
"type":"CRD"
}
}
]
But what I really want from that controller is just
[
{"id":1,
"amount":0,
"type":"DBT"
},
{"id":2,
"amount":0,
"type":"CRD"
}
]
Of course I can create new serializable class for accounts which will have String field instead of AccountType field and can map JPA Account class to that class extracting account type string from AccountType field. But for me it looks like unnecessary overhead and I believe that there could be a better pattern for such cases.
For example what I have in my head is that probably somehow I can create one JPA entity class (with String field representing account type) which will be based on two database tables and unnecessary complexity of having inner object will be reduced automagically each time I call repository methods :) Moreover I will be able to use this entity class in my business logic without any additional 'wrappers'.
P.s. I read about #SecondaryTable annotation but it looks like it can only work in cases where there is one-to-one relation between two tables which is not my case.
There are a couple of options whic allow clean separation without a DTO.
Firstly, you could look at using a projection which is kind of like a DTO mentioned in other answers but without many of the drawbacks:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections
#Projection(
name = "accountSummary",
types = { Account.class })
public Interface AccountSummaryProjection{
Long getId();
Integer getAmount();
#Value("#{target.accountType.type}")
String getType();
}
You then simply need to update your controller to call either query method with a List return type or write a method which takes a the proection class as an arg.
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projection.dynamic
#GetMapping("/getAllAccounts", produces = [APPLICATION_JSON_VALUE])
#ResponseBody
fun getAccountsData(): List<AccountSummaryProjection>{
return accountRepository.findAllAsSummary();
}
An alternative approach is to use the Jackson annotations. I note in your question you are manually tranforming the result to a JSON String and returning a String from your controller. You don't need to do that if the Jackson Json library is on the classpath. See my controller above.
So if you leave the serialization to Jackson you can separate the view from the entity using a couple of annotations. Note that I would apply these using a Jackson mixin rather than having to pollute the Entity model with Json processing instructions however you can look that up:
#Entity
class Account(
//in real life I would apply these using a Jacksin mix
//to prevent polluting the domain model with view concerns.
#JsonDeserializer(converter = StringToAccountTypeConverter.class)
#JsonSerializer(converter = AccountTypeToStringConverter.class
#Id #GeneratedValue var id: Long? = null,
var amount: Int,
#ManyToOne var accountType: AccountType
)
You then simply create the necessary converters:
public class StringToAccountTypeConverter extends StdConverter<String, CountryType>
implements org.springframework.core.convert.converter.Converter<String, AccountType> {
#Autowired
private AccountTypeRepository repo;
#Override
public AccountType convert(String value) {
//look up in repo and return
}
}
and vice versa:
public class AccountTypeToStringConverter extends StdConverter<String, CountryType>
implements org.springframework.core.convert.converter.Converter<AccountType, String> {
#Override
public String convert(AccountType value) {
return value.getName();
}
}
One of the least complicated ways to achieve what you are aiming for - from the external clients' point of view, at least - has to do with custom serialisation, what you seem to be aware of and what #YoManTaMero has extended upon.
Obtaining the desired class structure might not be possible. The closest I've managed to find is related to the #SecondaryTable annotation but the caveat is this only works for #OneToOne relationships.
In general, I'd pinpoint your problem to the issue of DTOs and Entities. The idea behind JPA is to map the schema and content of your database to code in an accessible but accurate way. It takes away the heavy-lifting of managing SQL queries, but it is designed mostly to reflect your DB's structure, not to map it to a different set of domains.
If the organisation of your DB schema does not exactly match the needs of your system's I/O communication, this might be a sign that:
Your DB has not been designed correctly;
Your DB is fine, but the manageable entities (tables) in it simply do not match directly to the business entities (models) in your external communication.
Should second be the case, Entities should be mapped to DTOs which can then be passed around. Single Entity may map to a few different DTOs. Single DTO might take more than one (related!) entities to be created. This is a good practice for medium-to-large systems in the first place - handing out references to the object that's the direct access point to your database is a risk.
Mind that simply because the id of the accountType is not taking part in your external communication does not mean it will never be a part of your business logic.
To sum up: JPA is designed with ease of database access in mind, not for smoothing out external communication. For that, other tools - such as e.g. Jackson serializer - are used, or certain design patterns - like DTO - are being employed.
One approach to solve this is to #JsonIgnore accountType and create getType method like
#JsonProperty("type")
var getType() {
return accountType.getType();
}
I am trying to migrate our project to use Room, which, by the way, I think is an awesome step forward.
I have the following structure:
public class Entity extends BaseObservable {
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "_id", typeAffinity = ColumnInfo.INTEGER)
private long mId;
#ColumnInfo(name = "is_dirty")
#TypeConverters(BooleanTypeConverter.class)
private boolean mIsDirty;
// default constructor and accessors omitted for brevity
}
#Entity(tableName = "some_entities")
public class SomeEntity extends Entity {
#ColumnInfo(name = "type", typeAffinity = ColumnInfo.TEXT)
private String mType;
#ColumnInfo(name = "timestamp", typeAffinity = ColumnInfo.INTEGER)
private long mTimestamp;
// constructor, accessors
}
When I try to compile my project, it fails with no specific error.
If I try to compile it with a flat entity hierarchy, all is well.
So, my main question is:
Does Room support entity inheritance? Will it be able to get the column definitions from the parent Entity class?
I would also like to know if extending BaseObservable (which I need to get the Data Binding working) can cause problems with Room? BaseObservable has one private transient field, so maybe this is causing some issues with the code generation.
Are there any recommended patterns to deal with this, or will I just have to flatten my entity hierarchy?
After further investigation it turns out that Room Entities should not extend the BaseObservable class. It contains fields that can't be marked with #Ignore and break the code generation.
Room works well with inheritance. The annotations are processed as expected and the DB operations behave normally. You can extend from both an Entity and a POJO.
In my project I use an enum in some entities. The enum is to be stored in the database as an integer. To achieve this I use EclipseLink's ObjectTypeConverter.
I'd like to use annotations since I use Spring to omit the persistence.xml. The annotation to configure the ObjectTypeConverter must be specified on an entity. I don't feel the need to specify the annotation on all classes that use this enum as this is redundant and not maintainable. Specifying it once on some entity would work but this doesn't make sense in an OOP design (or any design for that mater). A solution would be to annotate the enum with #ObjectTypeConverter, but this doesn't work since the enum isn't an entity.
Example that isn't working but would be ideal:
#Entity
public class ExampleEntity
{
#Id
private Long id;
#Convert("exampleenum")
private ExampleEnum ee;
}
#ObjectTypeConverter(name = "exampleenum", objectType = ExampleEnum.class, dataType = Integer.class,
conversionValues =
{
#ConversionValue(objectValue = "A", dataValue = "100"),
#ConversionValue(objectValue = "B", dataValue = "200"),
#ConversionValue(objectValue = "C", dataValue = "300")
})
public enum ExampleEnum
{
A, B, C;
}
Example results in the following exception:
Exception Description: The converter with name [exampleenum] used with the element [field ee] in the class [class com.example.ExampleEntity] was not found within the persistence unit. Please ensure you have provided the correct converter name.
Since I'm using Spring, JPA and EclipseLink I accept any answer using these frameworks.
Upon reading the documentation (which I should have done more carefully in the first place) I noticed:
An ObjectTypeConverter must be be uniquely identified by name and can be defined at the class, field and property level and can be specified within an Entity, MappedSuperclass and Embeddable class.
I couldn't annotate the enum with #Entity (as this requires a table) or #MappedSuperclass (as this doesn't make sense), but #Embeddable would make sense in a way. Marking the enum with #Embeddable did the trick:
#Embeddable
#ObjectTypeConverter(...)
public enum ExampleEnum
...