-
Notifications
You must be signed in to change notification settings - Fork 131
Description
I have such entity class:
@Table("persons")
class Person {
@Id
Long id;
@Column("firstName")
private String firstName;
@Column("lastName")
private String lastName;
}
Unfortunately Spring Boot 2.3.4 with Spring Data R2DBC 1.1.4.RELEASE and Spring Data Relational 2.0.4.RELEASE cant handle such entity.
First problem is org.springframework.data.relational.core.mapping.NamingStrategy.
Default method to get column name looks like that:
/**
* Defaults to return the given {@link RelationalPersistentProperty}'s name with the parts of a camel case name
* separated by '_';
*/
default String getColumnName(RelationalPersistentProperty property) {
Assert.notNull(property, "Property must not be null.");
return ParsingUtils.reconcatenateCamelCase(property.getName(), "_");
}
But it can be fixed by myself by creating my own NamingStrategy, which will override method like that:
@Override
public String getColumnName(RelationalPersistentProperty property) {
return property.getName();
}
It is not flexible, but it works. I think Spring should provide naming strategy to handle camel case columns in Postgresql.
Unfortunately just creating custom NamingStrategy is not enough. It helps with converting data read from database to entity, so we do not get
entity with nulls but inserts and read queries which uses camel case columns will still fail.
They will fail with io.r2dbc.postgresql.ExceptionFactory$PostgresqlBadGrammarException and message that reqeusted column do not exist.
For example 'column persons.firstname does not exists'. And it is true. Column 'firstname' does not exist. Column 'firstName' exist.
It means that by default Spring Data is not quoting columns names.
Fortunately from Spring Boot 2.3 in R2dbcMappingContext there is setForceQuote(boolean forceQuote) method provided which set forceQuote in org.springframework.data.relational.core.mapping.RelationalMappingContext
It helps with queries so queries do not throw exceptions that columns do not exist.
Unfortunately after enabling it we are back to square one with reading data. Now after read we got null in our entity.
What causes this are toString() methods in:
org.springframework.data.relational.core.sql.DefaultSqlIdentifier and
org.springframework.data.relational.core.mapping.DerivedSqlIdentifier
and this line in MappingR2dbcConverter
String identifier = prefix + property.getColumnName();
in Object readFrom(Row row, @Nullable RowMetadata metadata, RelationalPersistentProperty property, String prefix) method.
The problem is that when forceQuote is set to true, then DefaultSqlIdentifier toString() method returns column name quoted and DerivedSqlIdentifier toString() returns column name in uppercase and in such case object in this line:
Object value = row.get(identifier);
is equal to null, because column name in Row object is stored in lowercase for DerivedSqlIdentifier case and not quoted camel case for DefaultSqlIdentifier case.
To fix this issue I had to copy DefaultSqlIdentifier and DerivedSqlIdentifier classes to project and change toString() method to:
@Override
public String toString() {
return this.name;
}
Which is not clean solution.
I am not sure if I should post this issue here or in Spring Data Relational project jira because SqlIdentifier classes are from Spring Data Relational project but MappingR2dbcConverter is from Spring Data R2DBC project, so please direct me correctly. :)