Add camelCase to snake_case fallback for native query projections.
Closes #3462 Original pull request: #3472
This commit is contained in:
committed by
Mark Paluch
parent
966b43c008
commit
3a2e433af0
@@ -47,6 +47,7 @@ import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.ResultProcessor;
|
||||
import org.springframework.data.repository.query.ReturnedType;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.jdbc.support.JdbcUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -62,6 +63,7 @@ import org.springframework.util.Assert;
|
||||
* @author Сергей Цыпанов
|
||||
* @author Wonchul Heo
|
||||
* @author Julia Lee
|
||||
* @author Yanming Zhou
|
||||
*/
|
||||
public abstract class AbstractJpaQuery implements RepositoryQuery {
|
||||
|
||||
@@ -149,7 +151,7 @@ public abstract class AbstractJpaQuery implements RepositoryQuery {
|
||||
Object result = execution.execute(this, accessor);
|
||||
|
||||
ResultProcessor withDynamicProjection = method.getResultProcessor().withDynamicProjection(accessor);
|
||||
return withDynamicProjection.processResult(result, new TupleConverter(withDynamicProjection.getReturnedType()));
|
||||
return withDynamicProjection.processResult(result, new TupleConverter(withDynamicProjection.getReturnedType(), method.isNativeQuery()));
|
||||
}
|
||||
|
||||
private JpaParametersParameterAccessor obtainParameterAccessor(Object[] values) {
|
||||
@@ -304,6 +306,8 @@ public abstract class AbstractJpaQuery implements RepositoryQuery {
|
||||
|
||||
private final ReturnedType type;
|
||||
|
||||
private final boolean nativeQuery;
|
||||
|
||||
/**
|
||||
* Creates a new {@link TupleConverter} for the given {@link ReturnedType}.
|
||||
*
|
||||
@@ -311,9 +315,21 @@ public abstract class AbstractJpaQuery implements RepositoryQuery {
|
||||
*/
|
||||
public TupleConverter(ReturnedType type) {
|
||||
|
||||
this(type, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link TupleConverter} for the given {@link ReturnedType}.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param nativeQuery is this converter for native query?
|
||||
*/
|
||||
public TupleConverter(ReturnedType type, boolean nativeQuery) {
|
||||
|
||||
Assert.notNull(type, "Returned type must not be null");
|
||||
|
||||
this.type = type;
|
||||
this.nativeQuery = nativeQuery;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -334,7 +350,7 @@ public abstract class AbstractJpaQuery implements RepositoryQuery {
|
||||
}
|
||||
}
|
||||
|
||||
return new TupleBackedMap(tuple);
|
||||
return new TupleBackedMap(nativeQuery ? new NativeTupleWrapper(tuple) : tuple);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -451,4 +467,76 @@ public abstract class AbstractJpaQuery implements RepositoryQuery {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class NativeTupleWrapper implements Tuple {
|
||||
|
||||
private final Tuple underlying;
|
||||
|
||||
NativeTupleWrapper(Tuple underlying) {
|
||||
this.underlying = underlying;
|
||||
}
|
||||
|
||||
String fallback(String alias) {
|
||||
return JdbcUtils.convertPropertyNameToUnderscoreName(alias);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <X> X get(TupleElement<X> tupleElement) {
|
||||
try {
|
||||
return underlying.get(tupleElement);
|
||||
} catch (IllegalArgumentException original) {
|
||||
try {
|
||||
return underlying.get(fallback(tupleElement.getAlias()), tupleElement.getJavaType());
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
throw original;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <X> X get(String s, Class<X> aClass) {
|
||||
try {
|
||||
return underlying.get(s, aClass);
|
||||
} catch (IllegalArgumentException original) {
|
||||
try {
|
||||
return underlying.get(fallback(s), aClass);
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
throw original;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get(String s) {
|
||||
try {
|
||||
return underlying.get(s);
|
||||
} catch (IllegalArgumentException original) {
|
||||
try {
|
||||
return underlying.get(fallback(s));
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
throw original;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <X> X get(int i, Class<X> aClass) {
|
||||
return underlying.get(i, aClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get(int i) {
|
||||
return underlying.get(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
return underlying.toArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TupleElement<?>> getElements() {
|
||||
return underlying.getElements();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import java.util.Set;
|
||||
* @author Jeff Sheets
|
||||
* @author JyotirmoyVS
|
||||
* @author Greg Turnquist
|
||||
* @author Yanming Zhou
|
||||
*/
|
||||
@Entity
|
||||
@NamedEntityGraphs({ @NamedEntityGraph(name = "User.overview", attributeNodes = { @NamedAttributeNode("roles") }),
|
||||
@@ -101,6 +102,8 @@ public class User {
|
||||
|
||||
@Column(nullable = false, unique = true) private String emailAddress;
|
||||
|
||||
@Column(name = "secondary_email_address") private String secondaryEmailAddress;
|
||||
|
||||
@ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE }) private Set<User> colleagues;
|
||||
|
||||
@ManyToMany private Set<Role> roles;
|
||||
@@ -173,6 +176,14 @@ public class User {
|
||||
this.emailAddress = emailAddress;
|
||||
}
|
||||
|
||||
public String getSecondaryEmailAddress() {
|
||||
return secondaryEmailAddress;
|
||||
}
|
||||
|
||||
public void setSecondaryEmailAddress(String secondaryEmailAddress) {
|
||||
this.secondaryEmailAddress = secondaryEmailAddress;
|
||||
}
|
||||
|
||||
public void setActive(boolean active) {
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
@@ -95,6 +95,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
* @author Simon Paradies
|
||||
* @author Geoffrey Deremetz
|
||||
* @author Krzysztof Krason
|
||||
* @author Yanming Zhou
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration("classpath:application-context.xml")
|
||||
@@ -2970,6 +2971,24 @@ class UserRepositoryTests {
|
||||
.isNotNull();
|
||||
}
|
||||
|
||||
@Test // DATAJPA-3462
|
||||
void supportsProjectionsWithNativeQueriesAndUnderscoresColumnNameToCamelCaseProperty() {
|
||||
|
||||
User user = new User();
|
||||
user.setEmailAddress("primary@something");
|
||||
user.setSecondaryEmailAddress("secondary@something");
|
||||
em.persist(user);
|
||||
|
||||
UserRepository.EmailOnly result = repository.findEmailOnlyByNativeQuery(user.getId());
|
||||
|
||||
String secondaryEmailAddress = result.getSecondaryEmailAddress();
|
||||
|
||||
assertThat(secondaryEmailAddress) //
|
||||
.isEqualTo(user.getSecondaryEmailAddress()) //
|
||||
.as("ensuring secondary email is actually not null") //
|
||||
.isNotNull();
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1235
|
||||
void handlesColonsFollowedByIntegerInStringLiteral() {
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
* @author Simon Paradies
|
||||
* @author Diego Krupitza
|
||||
* @author Geoffrey Deremetz
|
||||
* @author Yanming Zhou
|
||||
*/
|
||||
public interface UserRepository extends JpaRepository<User, Integer>, JpaSpecificationExecutor<User>,
|
||||
UserRepositoryCustom, ListQuerydslPredicateExecutor<User> {
|
||||
@@ -555,7 +556,7 @@ public interface UserRepository extends JpaRepository<User, Integer>, JpaSpecifi
|
||||
NameOnly findByNativeQuery(Integer id);
|
||||
|
||||
// DATAJPA-1248
|
||||
@Query(value = "SELECT emailaddress FROM SD_User WHERE id = ?1", nativeQuery = true)
|
||||
@Query(value = "SELECT emailaddress, secondary_email_address FROM SD_User WHERE id = ?1", nativeQuery = true)
|
||||
EmailOnly findEmailOnlyByNativeQuery(Integer id);
|
||||
|
||||
// DATAJPA-1235
|
||||
@@ -721,6 +722,8 @@ public interface UserRepository extends JpaRepository<User, Integer>, JpaSpecifi
|
||||
|
||||
interface EmailOnly {
|
||||
String getEmailAddress();
|
||||
|
||||
String getSecondaryEmailAddress();
|
||||
}
|
||||
|
||||
interface IdOnly {
|
||||
|
||||
Reference in New Issue
Block a user