spring-projects / spring-data-jpa

Simplifies the development of creating a JPA-based data access layer.
https://spring.io/projects/spring-data-jpa/
Apache License 2.0
2.93k stars 1.39k forks source link

Add `camelCase` to `snake_case` fallback for native query projections #3462

Closed cjacky475 closed 1 month ago

cjacky475 commented 1 month ago

Hello, I have my Friend repository class from friend Modulith module:

@Repository
interface FriendRepository: JpaRepository<Friend, Long> {

For testing purposes I will create simple function to get users within Friend repository:

 @Query(value = "SELECT * FROM users WHERE users.id = 558978864945139712 ", nativeQuery = true)
 fun getUsers(): Optional<UserProjection>

The interface for the User entity is:

interface UserProjection {
    fun getId(): Long
    fun getUsername(): String
    fun getEmail(): String
    fun getFullName(): String
}

The User entity has these fields (others emitted):

val email: String = "",
var fullName: String = "",

However, the REST API returns null on fullName field.

How does that work exactly? Why my projection fails for camel case fields when projection class (in this instance UserProjection is in different module?

--------------------- EDIT ---------------------

Okay, so I finally understood what was happening. I had to create two projection classes, one for functions that use native query and one for those who do not.

For functions that use native query, interface projection for camel case fields must be specified as:

 @JsonProperty("fullName")
 fun getFull_name(): String

for projection that do not use native queries, I can use:

fun getFullName(): String

I still do not understand why is that. Is there a more appropriate solution for using only single projection so it would satisfy both native and non native queries? Cheers.

quaff commented 1 month ago

By default, Spring Boot configures the physical naming strategy with CamelCaseToUnderscoresNamingStrategy.

The column name is full_name, you could use select full_name as fullName as workaround or switch to another NamingStrategy like org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl.

cjacky475 commented 1 month ago

Hi, @quaff, thanks for the answer.

The column name is full_name, you could use select full_name as fullName as workaround

This would become very inefficient and unmaintainable to write out all individual fields for selection, wouldn't it? SELECT id, full_name as fullName, etc...

switch to another NamingStrategy like org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

But this is not going to change the fact that when using native and non-native queries the return value is interpreted differently? I use PostgreSQL and return values are snake like full_name, also when submitting they have to be snake case.

Cheers.

quaff commented 1 month ago

But this is not going to change the fact that when using native and non-native queries the return value is interpreted differently? I use PostgreSQL and return values are snake like full_name, also when submitting they have to be snake case.

I agree, It would be great if Spring Data JPA handle it like BeanPropertyRowMapper does in Spring Framework.

https://github.com/spring-projects/spring-framework/blob/19792f9670943687e2497fa90084437c201b3ae8/spring-jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java#L277-L295

mp911de commented 1 month ago

Please provide a minimal yet complete sample that reproduces the problem. You can share it with us by pushing it to a separate repository on GitHub or by zipping it up and attaching it to this issue.

quaff commented 1 month ago

Please provide a minimal yet complete sample that reproduces the problem. You can share it with us by pushing it to a separate repository on GitHub or by zipping it up and attaching it to this issue.

You can reproduce it by running the added test in (https://github.com/spring-projects/spring-data-jpa/pull/3472)

mp911de commented 1 month ago

There's an easier approach by using @Value("#{target.full_name}") if you want to rename fields.