38 changed files with 11 additions and 1589 deletions
@ -1,77 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2023-2024 the original author or authors. |
|
||||||
* |
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||||
* you may not use this file except in compliance with the License. |
|
||||||
* You may obtain a copy of the License at |
|
||||||
* |
|
||||||
* https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
* |
|
||||||
* Unless required by applicable law or agreed to in writing, software |
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||||
* See the License for the specific language governing permissions and |
|
||||||
* limitations under the License. |
|
||||||
*/ |
|
||||||
package org.springframework.data.jdbc.core.convert; |
|
||||||
|
|
||||||
import org.springframework.data.convert.CustomConversions; |
|
||||||
import org.springframework.data.mapping.context.MappingContext; |
|
||||||
import org.springframework.data.relational.core.conversion.RelationalConverter; |
|
||||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext; |
|
||||||
import org.springframework.data.relational.core.sql.IdentifierProcessing; |
|
||||||
|
|
||||||
/** |
|
||||||
* {@link RelationalConverter} that uses a {@link MappingContext} to apply conversion of relational values to property |
|
||||||
* values. |
|
||||||
* <p> |
|
||||||
* Conversion is configurable by providing a customized {@link CustomConversions}. |
|
||||||
* |
|
||||||
* @author Mark Paluch |
|
||||||
* @since 1.1 |
|
||||||
* @deprecated since 3.2, use {@link MappingJdbcConverter} instead as the naming suggests a limited scope of |
|
||||||
* functionality. |
|
||||||
*/ |
|
||||||
@Deprecated(since = "3.2") |
|
||||||
public class BasicJdbcConverter extends MappingJdbcConverter { |
|
||||||
|
|
||||||
/** |
|
||||||
* Creates a new {@link BasicJdbcConverter} given {@link MappingContext} and a {@link JdbcTypeFactory#unsupported() |
|
||||||
* no-op type factory} throwing {@link UnsupportedOperationException} on type creation. Use |
|
||||||
* {@link #BasicJdbcConverter(RelationalMappingContext, RelationResolver, CustomConversions, JdbcTypeFactory, IdentifierProcessing)} |
|
||||||
* (MappingContext, RelationResolver, JdbcTypeFactory)} to convert arrays and large objects into JDBC-specific types. |
|
||||||
* |
|
||||||
* @param context must not be {@literal null}. |
|
||||||
* @param relationResolver used to fetch additional relations from the database. Must not be {@literal null}. |
|
||||||
*/ |
|
||||||
public BasicJdbcConverter(RelationalMappingContext context, RelationResolver relationResolver) { |
|
||||||
super(context, relationResolver); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Creates a new {@link BasicJdbcConverter} given {@link MappingContext}. |
|
||||||
* |
|
||||||
* @param context must not be {@literal null}. |
|
||||||
* @param relationResolver used to fetch additional relations from the database. Must not be {@literal null}. |
|
||||||
* @param typeFactory must not be {@literal null} |
|
||||||
* @since 3.2 |
|
||||||
*/ |
|
||||||
public BasicJdbcConverter(RelationalMappingContext context, RelationResolver relationResolver, |
|
||||||
CustomConversions conversions, JdbcTypeFactory typeFactory) { |
|
||||||
super(context, relationResolver, conversions, typeFactory); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Creates a new {@link BasicJdbcConverter} given {@link MappingContext}. |
|
||||||
* |
|
||||||
* @param context must not be {@literal null}. |
|
||||||
* @param relationResolver used to fetch additional relations from the database. Must not be {@literal null}. |
|
||||||
* @param typeFactory must not be {@literal null} |
|
||||||
* @param identifierProcessing must not be {@literal null} |
|
||||||
* @since 2.0 |
|
||||||
*/ |
|
||||||
public BasicJdbcConverter(RelationalMappingContext context, RelationResolver relationResolver, |
|
||||||
CustomConversions conversions, JdbcTypeFactory typeFactory, IdentifierProcessing identifierProcessing) { |
|
||||||
super(context, relationResolver, conversions, typeFactory); |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,79 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2022-2024 the original author or authors. |
|
||||||
* |
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||||
* you may not use this file except in compliance with the License. |
|
||||||
* You may obtain a copy of the License at |
|
||||||
* |
|
||||||
* https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
* |
|
||||||
* Unless required by applicable law or agreed to in writing, software |
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||||
* See the License for the specific language governing permissions and |
|
||||||
* limitations under the License. |
|
||||||
*/ |
|
||||||
package org.springframework.data.jdbc.core.convert; |
|
||||||
|
|
||||||
import org.springframework.jdbc.core.JdbcOperations; |
|
||||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; |
|
||||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; |
|
||||||
import org.springframework.jdbc.core.namedparam.SqlParameterSource; |
|
||||||
import org.springframework.jdbc.support.KeyHolder; |
|
||||||
import org.springframework.lang.Nullable; |
|
||||||
|
|
||||||
/** |
|
||||||
* Counterpart to {@link org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations} containing methods for |
|
||||||
* performing batch updates with generated keys. |
|
||||||
* |
|
||||||
* @author Chirag Tailor |
|
||||||
* @author Mark Paluch |
|
||||||
* @since 2.4 |
|
||||||
* @deprecated since 3.2. Use {@link org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations#batchUpdate} |
|
||||||
* methods instead. |
|
||||||
*/ |
|
||||||
@Deprecated(since = "3.2") |
|
||||||
public class BatchJdbcOperations { |
|
||||||
|
|
||||||
private final NamedParameterJdbcOperations jdbcOperations; |
|
||||||
|
|
||||||
public BatchJdbcOperations(JdbcOperations jdbcOperations) { |
|
||||||
this.jdbcOperations = new NamedParameterJdbcTemplate(jdbcOperations); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Execute a batch using the supplied SQL statement with the batch of supplied arguments, returning generated keys. |
|
||||||
* |
|
||||||
* @param sql the SQL statement to execute |
|
||||||
* @param batchArgs the array of {@link SqlParameterSource} containing the batch of arguments for the query |
|
||||||
* @param generatedKeyHolder a {@link KeyHolder} that will hold the generated keys |
|
||||||
* @return an array containing the numbers of rows affected by each update in the batch (may also contain special |
|
||||||
* JDBC-defined negative values for affected rows such as |
|
||||||
* {@link java.sql.Statement#SUCCESS_NO_INFO}/{@link java.sql.Statement#EXECUTE_FAILED}) |
|
||||||
* @throws org.springframework.dao.DataAccessException if there is any problem issuing the update |
|
||||||
* @see org.springframework.jdbc.support.GeneratedKeyHolder |
|
||||||
* @since 2.4 |
|
||||||
*/ |
|
||||||
int[] batchUpdate(String sql, SqlParameterSource[] batchArgs, KeyHolder generatedKeyHolder) { |
|
||||||
return jdbcOperations.batchUpdate(sql, batchArgs, generatedKeyHolder); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Execute a batch using the supplied SQL statement with the batch of supplied arguments, returning generated keys. |
|
||||||
* |
|
||||||
* @param sql the SQL statement to execute |
|
||||||
* @param batchArgs the array of {@link SqlParameterSource} containing the batch of arguments for the query |
|
||||||
* @param generatedKeyHolder a {@link KeyHolder} that will hold the generated keys |
|
||||||
* @param keyColumnNames names of the columns that will have keys generated for them |
|
||||||
* @return an array containing the numbers of rows affected by each update in the batch (may also contain special |
|
||||||
* JDBC-defined negative values for affected rows such as |
|
||||||
* {@link java.sql.Statement#SUCCESS_NO_INFO}/{@link java.sql.Statement#EXECUTE_FAILED}) |
|
||||||
* @throws org.springframework.dao.DataAccessException if there is any problem issuing the update |
|
||||||
* @see org.springframework.jdbc.support.GeneratedKeyHolder |
|
||||||
* @since 2.4 |
|
||||||
*/ |
|
||||||
int[] batchUpdate(String sql, SqlParameterSource[] batchArgs, KeyHolder generatedKeyHolder, |
|
||||||
@Nullable String[] keyColumnNames) { |
|
||||||
return jdbcOperations.batchUpdate(sql, batchArgs, generatedKeyHolder, keyColumnNames); |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,290 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2019-2024 the original author or authors. |
|
||||||
* |
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||||
* you may not use this file except in compliance with the License. |
|
||||||
* You may obtain a copy of the License at |
|
||||||
* |
|
||||||
* https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
* |
|
||||||
* Unless required by applicable law or agreed to in writing, software |
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||||
* See the License for the specific language governing permissions and |
|
||||||
* limitations under the License. |
|
||||||
*/ |
|
||||||
package org.springframework.data.jdbc.core; |
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.*; |
|
||||||
import static org.assertj.core.api.SoftAssertions.*; |
|
||||||
import static org.springframework.data.relational.core.sql.SqlIdentifier.*; |
|
||||||
|
|
||||||
import java.util.List; |
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test; |
|
||||||
import org.springframework.data.annotation.Id; |
|
||||||
import org.springframework.data.annotation.ReadOnlyProperty; |
|
||||||
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; |
|
||||||
import org.springframework.data.mapping.PersistentPropertyPath; |
|
||||||
import org.springframework.data.relational.core.mapping.Embedded; |
|
||||||
import org.springframework.data.relational.core.mapping.Embedded.OnEmpty; |
|
||||||
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension; |
|
||||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; |
|
||||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author Jens Schauder |
|
||||||
* @author Daniil Razorenov |
|
||||||
*/ |
|
||||||
public class PersistentPropertyPathExtensionUnitTests { |
|
||||||
|
|
||||||
JdbcMappingContext context = new JdbcMappingContext(); |
|
||||||
private RelationalPersistentEntity<?> entity = context.getRequiredPersistentEntity(DummyEntity.class); |
|
||||||
|
|
||||||
@Test // DATAJDBC-340
|
|
||||||
void isEmbedded() { |
|
||||||
|
|
||||||
assertSoftly(softly -> { |
|
||||||
|
|
||||||
softly.assertThat(extPath(entity).isEmbedded()).isFalse(); |
|
||||||
softly.assertThat(extPath("second").isEmbedded()).isFalse(); |
|
||||||
softly.assertThat(extPath("second.third2").isEmbedded()).isTrue(); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
@Test // DATAJDBC-340
|
|
||||||
void isMultiValued() { |
|
||||||
|
|
||||||
assertSoftly(softly -> { |
|
||||||
|
|
||||||
softly.assertThat(extPath(entity).isMultiValued()).isFalse(); |
|
||||||
softly.assertThat(extPath("second").isMultiValued()).isFalse(); |
|
||||||
softly.assertThat(extPath("second.third2").isMultiValued()).isFalse(); |
|
||||||
softly.assertThat(extPath("secondList.third2").isMultiValued()).isTrue(); |
|
||||||
softly.assertThat(extPath("secondList").isMultiValued()).isTrue(); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
@Test // DATAJDBC-340
|
|
||||||
void leafEntity() { |
|
||||||
|
|
||||||
RelationalPersistentEntity<?> second = context.getRequiredPersistentEntity(Second.class); |
|
||||||
RelationalPersistentEntity<?> third = context.getRequiredPersistentEntity(Third.class); |
|
||||||
|
|
||||||
assertSoftly(softly -> { |
|
||||||
|
|
||||||
softly.assertThat(extPath(entity).getLeafEntity()).isEqualTo(entity); |
|
||||||
softly.assertThat(extPath("second").getLeafEntity()).isEqualTo(second); |
|
||||||
softly.assertThat(extPath("second.third2").getLeafEntity()).isEqualTo(third); |
|
||||||
softly.assertThat(extPath("secondList.third2").getLeafEntity()).isEqualTo(third); |
|
||||||
softly.assertThat(extPath("secondList").getLeafEntity()).isEqualTo(second); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
@Test // DATAJDBC-340
|
|
||||||
void isEntity() { |
|
||||||
|
|
||||||
assertSoftly(softly -> { |
|
||||||
|
|
||||||
softly.assertThat(extPath(entity).isEntity()).isTrue(); |
|
||||||
String path = "second"; |
|
||||||
softly.assertThat(extPath(path).isEntity()).isTrue(); |
|
||||||
softly.assertThat(extPath("second.third2").isEntity()).isTrue(); |
|
||||||
softly.assertThat(extPath("second.third2.value").isEntity()).isFalse(); |
|
||||||
softly.assertThat(extPath("secondList.third2").isEntity()).isTrue(); |
|
||||||
softly.assertThat(extPath("secondList.third2.value").isEntity()).isFalse(); |
|
||||||
softly.assertThat(extPath("secondList").isEntity()).isTrue(); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
@Test // DATAJDBC-340
|
|
||||||
void getTableName() { |
|
||||||
|
|
||||||
assertSoftly(softly -> { |
|
||||||
|
|
||||||
softly.assertThat(extPath(entity).getQualifiedTableName()).isEqualTo(quoted("DUMMY_ENTITY")); |
|
||||||
softly.assertThat(extPath("second").getQualifiedTableName()).isEqualTo(quoted("SECOND")); |
|
||||||
softly.assertThat(extPath("second.third2").getQualifiedTableName()).isEqualTo(quoted("SECOND")); |
|
||||||
softly.assertThat(extPath("second.third2.value").getQualifiedTableName()).isEqualTo(quoted("SECOND")); |
|
||||||
softly.assertThat(extPath("secondList.third2").getQualifiedTableName()).isEqualTo(quoted("SECOND")); |
|
||||||
softly.assertThat(extPath("secondList.third2.value").getQualifiedTableName()).isEqualTo(quoted("SECOND")); |
|
||||||
softly.assertThat(extPath("secondList").getQualifiedTableName()).isEqualTo(quoted("SECOND")); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
@Test // DATAJDBC-340
|
|
||||||
void getTableAlias() { |
|
||||||
|
|
||||||
assertSoftly(softly -> { |
|
||||||
|
|
||||||
softly.assertThat(extPath(entity).getTableAlias()).isEqualTo(null); |
|
||||||
softly.assertThat(extPath("second").getTableAlias()).isEqualTo(quoted("second")); |
|
||||||
softly.assertThat(extPath("second.third2").getTableAlias()).isEqualTo(quoted("second")); |
|
||||||
softly.assertThat(extPath("second.third2.value").getTableAlias()).isEqualTo(quoted("second")); |
|
||||||
softly.assertThat(extPath("second.third").getTableAlias()).isEqualTo(quoted("second_third")); |
|
||||||
softly.assertThat(extPath("second.third.value").getTableAlias()).isEqualTo(quoted("second_third")); |
|
||||||
softly.assertThat(extPath("secondList.third2").getTableAlias()).isEqualTo(quoted("secondList")); |
|
||||||
softly.assertThat(extPath("secondList.third2.value").getTableAlias()).isEqualTo(quoted("secondList")); |
|
||||||
softly.assertThat(extPath("secondList.third").getTableAlias()).isEqualTo(quoted("secondList_third")); |
|
||||||
softly.assertThat(extPath("secondList.third.value").getTableAlias()).isEqualTo(quoted("secondList_third")); |
|
||||||
softly.assertThat(extPath("secondList").getTableAlias()).isEqualTo(quoted("secondList")); |
|
||||||
softly.assertThat(extPath("second2.third").getTableAlias()).isEqualTo(quoted("secthird")); |
|
||||||
softly.assertThat(extPath("second3.third").getTableAlias()).isEqualTo(quoted("third")); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
@Test // DATAJDBC-340
|
|
||||||
void getColumnName() { |
|
||||||
|
|
||||||
assertSoftly(softly -> { |
|
||||||
|
|
||||||
softly.assertThat(extPath("second.third2.value").getColumnName()).isEqualTo(quoted("THRDVALUE")); |
|
||||||
softly.assertThat(extPath("second.third.value").getColumnName()).isEqualTo(quoted("VALUE")); |
|
||||||
softly.assertThat(extPath("secondList.third2.value").getColumnName()).isEqualTo(quoted("THRDVALUE")); |
|
||||||
softly.assertThat(extPath("secondList.third.value").getColumnName()).isEqualTo(quoted("VALUE")); |
|
||||||
softly.assertThat(extPath("second2.third2.value").getColumnName()).isEqualTo(quoted("SECTHRDVALUE")); |
|
||||||
softly.assertThat(extPath("second2.third.value").getColumnName()).isEqualTo(quoted("VALUE")); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
@Test // DATAJDBC-359
|
|
||||||
void idDefiningPath() { |
|
||||||
|
|
||||||
assertSoftly(softly -> { |
|
||||||
|
|
||||||
softly.assertThat(extPath("second.third2.value").getIdDefiningParentPath().getLength()).isEqualTo(0); |
|
||||||
softly.assertThat(extPath("second.third.value").getIdDefiningParentPath().getLength()).isEqualTo(0); |
|
||||||
softly.assertThat(extPath("secondList.third2.value").getIdDefiningParentPath().getLength()).isEqualTo(0); |
|
||||||
softly.assertThat(extPath("secondList.third.value").getIdDefiningParentPath().getLength()).isEqualTo(0); |
|
||||||
softly.assertThat(extPath("second2.third2.value").getIdDefiningParentPath().getLength()).isEqualTo(0); |
|
||||||
softly.assertThat(extPath("second2.third.value").getIdDefiningParentPath().getLength()).isEqualTo(0); |
|
||||||
softly.assertThat(extPath("withId.second.third2.value").getIdDefiningParentPath().getLength()).isEqualTo(1); |
|
||||||
softly.assertThat(extPath("withId.second.third.value").getIdDefiningParentPath().getLength()).isEqualTo(1); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
@Test // DATAJDBC-359
|
|
||||||
void reverseColumnName() { |
|
||||||
|
|
||||||
assertSoftly(softly -> { |
|
||||||
|
|
||||||
softly.assertThat(extPath("second.third2").getReverseColumnName()).isEqualTo(quoted("DUMMY_ENTITY")); |
|
||||||
softly.assertThat(extPath("second.third").getReverseColumnName()).isEqualTo(quoted("DUMMY_ENTITY")); |
|
||||||
softly.assertThat(extPath("secondList.third2").getReverseColumnName()).isEqualTo(quoted("DUMMY_ENTITY")); |
|
||||||
softly.assertThat(extPath("secondList.third").getReverseColumnName()).isEqualTo(quoted("DUMMY_ENTITY")); |
|
||||||
softly.assertThat(extPath("second2.third2").getReverseColumnName()).isEqualTo(quoted("DUMMY_ENTITY")); |
|
||||||
softly.assertThat(extPath("second2.third").getReverseColumnName()).isEqualTo(quoted("DUMMY_ENTITY")); |
|
||||||
softly.assertThat(extPath("withId.second.third2.value").getReverseColumnName()).isEqualTo(quoted("WITH_ID")); |
|
||||||
softly.assertThat(extPath("withId.second.third").getReverseColumnName()).isEqualTo(quoted("WITH_ID")); |
|
||||||
softly.assertThat(extPath("withId.second2.third").getReverseColumnName()).isEqualTo(quoted("WITH_ID")); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
@Test // DATAJDBC-359
|
|
||||||
void getRequiredIdProperty() { |
|
||||||
|
|
||||||
assertSoftly(softly -> { |
|
||||||
|
|
||||||
softly.assertThat(extPath(entity).getRequiredIdProperty().getName()).isEqualTo("entityId"); |
|
||||||
softly.assertThat(extPath("withId").getRequiredIdProperty().getName()).isEqualTo("withIdId"); |
|
||||||
softly.assertThatThrownBy(() -> extPath("second").getRequiredIdProperty()) |
|
||||||
.isInstanceOf(IllegalStateException.class); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
@Test // DATAJDBC-359
|
|
||||||
void extendBy() { |
|
||||||
|
|
||||||
assertSoftly(softly -> { |
|
||||||
|
|
||||||
softly.assertThat(extPath(entity).extendBy(entity.getRequiredPersistentProperty("withId"))) |
|
||||||
.isEqualTo(extPath("withId")); |
|
||||||
softly.assertThat(extPath("withId").extendBy(extPath("withId").getRequiredIdProperty())) |
|
||||||
.isEqualTo(extPath("withId.withIdId")); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
@Test // GH-1164
|
|
||||||
void equalsWorks() { |
|
||||||
|
|
||||||
PersistentPropertyPathExtension root1 = extPath(entity); |
|
||||||
PersistentPropertyPathExtension root2 = extPath(entity); |
|
||||||
PersistentPropertyPathExtension path1 = extPath("withId"); |
|
||||||
PersistentPropertyPathExtension path2 = extPath("withId"); |
|
||||||
|
|
||||||
assertSoftly(softly -> { |
|
||||||
|
|
||||||
softly.assertThat(root1).describedAs("root is equal to self").isEqualTo(root1); |
|
||||||
softly.assertThat(root2).describedAs("root is equal to identical root").isEqualTo(root1); |
|
||||||
softly.assertThat(path1).describedAs("path is equal to self").isEqualTo(path1); |
|
||||||
softly.assertThat(path2).describedAs("path is equal to identical path").isEqualTo(path1); |
|
||||||
softly.assertThat(path1).describedAs("path is not equal to other path").isNotEqualTo(extPath("entityId")); |
|
||||||
softly.assertThat(root1).describedAs("root is not equal to path").isNotEqualTo(path1); |
|
||||||
softly.assertThat(path1).describedAs("path is not equal to root").isNotEqualTo(root1); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
@Test // GH-1249
|
|
||||||
void isWritable() { |
|
||||||
|
|
||||||
assertSoftly(softly -> { |
|
||||||
softly.assertThat(PersistentPropertyPathExtension.isWritable(createSimplePath("withId"))) |
|
||||||
.describedAs("simple path is writable").isTrue(); |
|
||||||
softly.assertThat(PersistentPropertyPathExtension.isWritable(createSimplePath("secondList.third2"))) |
|
||||||
.describedAs("long path is writable").isTrue(); |
|
||||||
softly.assertThat(PersistentPropertyPathExtension.isWritable(createSimplePath("second"))) |
|
||||||
.describedAs("simple read only path is not writable").isFalse(); |
|
||||||
softly.assertThat(PersistentPropertyPathExtension.isWritable(createSimplePath("second.third"))) |
|
||||||
.describedAs("long path containing read only element is not writable").isFalse(); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
@Test // GH-1525
|
|
||||||
void getAggregatePath() { |
|
||||||
assertThat(extPath("withId").getAggregatePath()).isNotNull(); |
|
||||||
} |
|
||||||
@Test // GH-1525
|
|
||||||
void getAggregatePathFromRoot() { |
|
||||||
assertThat(extPath(entity).getAggregatePath()).isNotNull(); |
|
||||||
} |
|
||||||
private PersistentPropertyPathExtension extPath(RelationalPersistentEntity<?> entity) { |
|
||||||
return new PersistentPropertyPathExtension(context, entity); |
|
||||||
} |
|
||||||
|
|
||||||
private PersistentPropertyPathExtension extPath(String path) { |
|
||||||
return new PersistentPropertyPathExtension(context, createSimplePath(path)); |
|
||||||
} |
|
||||||
|
|
||||||
PersistentPropertyPath<RelationalPersistentProperty> createSimplePath(String path) { |
|
||||||
return PersistentPropertyPathTestUtils.getPath(path, DummyEntity.class, context); |
|
||||||
} |
|
||||||
|
|
||||||
@SuppressWarnings("unused") |
|
||||||
static class DummyEntity { |
|
||||||
@Id Long entityId; |
|
||||||
@ReadOnlyProperty Second second; |
|
||||||
@Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "sec") Second second2; |
|
||||||
@Embedded(onEmpty = OnEmpty.USE_NULL) Second second3; |
|
||||||
List<Second> secondList; |
|
||||||
WithId withId; |
|
||||||
} |
|
||||||
|
|
||||||
@SuppressWarnings("unused") |
|
||||||
static class Second { |
|
||||||
Third third; |
|
||||||
@Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "thrd") Third third2; |
|
||||||
} |
|
||||||
|
|
||||||
@SuppressWarnings("unused") |
|
||||||
static class Third { |
|
||||||
String value; |
|
||||||
} |
|
||||||
|
|
||||||
@SuppressWarnings("unused") |
|
||||||
static class WithId { |
|
||||||
@Id Long withIdId; |
|
||||||
Second second; |
|
||||||
@Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "sec") Second second2; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,61 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2018-2024 the original author or authors. |
|
||||||
* |
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||||
* you may not use this file except in compliance with the License. |
|
||||||
* You may obtain a copy of the License at |
|
||||||
* |
|
||||||
* https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
* |
|
||||||
* Unless required by applicable law or agreed to in writing, software |
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||||
* See the License for the specific language governing permissions and |
|
||||||
* limitations under the License. |
|
||||||
*/ |
|
||||||
package org.springframework.data.relational.core.conversion; |
|
||||||
|
|
||||||
import org.springframework.data.convert.CustomConversions; |
|
||||||
import org.springframework.data.mapping.context.MappingContext; |
|
||||||
import org.springframework.data.mapping.model.SimpleTypeHolder; |
|
||||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext; |
|
||||||
|
|
||||||
/** |
|
||||||
* {@link RelationalConverter} that uses a {@link MappingContext} to apply basic conversion of relational values to |
|
||||||
* property values. |
|
||||||
* <p> |
|
||||||
* Conversion is configurable by providing a customized {@link CustomConversions}. |
|
||||||
* |
|
||||||
* @author Mark Paluch |
|
||||||
* @author Jens Schauder |
|
||||||
* @author Chirag Tailor |
|
||||||
* @author Vincent Galloy |
|
||||||
* @see MappingContext |
|
||||||
* @see SimpleTypeHolder |
|
||||||
* @see CustomConversions |
|
||||||
* @deprecated since 3.2, use {@link MappingRelationalConverter} instead as the naming suggests a limited scope of |
|
||||||
* functionality. |
|
||||||
*/ |
|
||||||
@Deprecated(since = "3.2") |
|
||||||
public class BasicRelationalConverter extends MappingRelationalConverter { |
|
||||||
|
|
||||||
/** |
|
||||||
* Creates a new {@link BasicRelationalConverter} given {@link MappingContext}. |
|
||||||
* |
|
||||||
* @param context must not be {@literal null}. |
|
||||||
*/ |
|
||||||
public BasicRelationalConverter(RelationalMappingContext context) { |
|
||||||
super(context); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Creates a new {@link BasicRelationalConverter} given {@link MappingContext} and {@link CustomConversions}. |
|
||||||
* |
|
||||||
* @param context must not be {@literal null}. |
|
||||||
* @param conversions must not be {@literal null}. |
|
||||||
*/ |
|
||||||
public BasicRelationalConverter(RelationalMappingContext context, CustomConversions conversions) { |
|
||||||
super(context, conversions); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,485 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2019-2024 the original author or authors. |
|
||||||
* |
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||||
* you may not use this file except in compliance with the License. |
|
||||||
* You may obtain a copy of the License at |
|
||||||
* |
|
||||||
* https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
* |
|
||||||
* Unless required by applicable law or agreed to in writing, software |
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||||
* See the License for the specific language governing permissions and |
|
||||||
* limitations under the License. |
|
||||||
*/ |
|
||||||
package org.springframework.data.relational.core.mapping; |
|
||||||
|
|
||||||
import java.util.Objects; |
|
||||||
|
|
||||||
import org.springframework.data.mapping.PersistentProperty; |
|
||||||
import org.springframework.data.mapping.PersistentPropertyPath; |
|
||||||
import org.springframework.data.mapping.context.MappingContext; |
|
||||||
import org.springframework.data.relational.core.sql.SqlIdentifier; |
|
||||||
import org.springframework.data.util.Lazy; |
|
||||||
import org.springframework.lang.Nullable; |
|
||||||
import org.springframework.util.Assert; |
|
||||||
import org.springframework.util.StringUtils; |
|
||||||
|
|
||||||
/** |
|
||||||
* A wrapper around a {@link org.springframework.data.mapping.PersistentPropertyPath} for making common operations |
|
||||||
* available used in SQL generation and conversion |
|
||||||
* |
|
||||||
* @author Jens Schauder |
|
||||||
* @author Daniil Razorenov |
|
||||||
* @author Kurt Niemi |
|
||||||
* @since 1.1 |
|
||||||
* @deprecated use {@link AggregatePath} instead |
|
||||||
*/ |
|
||||||
@Deprecated(since = "3.2", forRemoval = true) |
|
||||||
public class PersistentPropertyPathExtension { |
|
||||||
|
|
||||||
private final RelationalPersistentEntity<?> entity; |
|
||||||
private final @Nullable PersistentPropertyPath<? extends RelationalPersistentProperty> path; |
|
||||||
private final MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context; |
|
||||||
|
|
||||||
private final Lazy<SqlIdentifier> columnAlias = Lazy.of(() -> prefixWithTableAlias(getColumnName())); |
|
||||||
|
|
||||||
/** |
|
||||||
* Creates the empty path referencing the root itself. |
|
||||||
* |
|
||||||
* @param context Must not be {@literal null}. |
|
||||||
* @param entity Root entity of the path. Must not be {@literal null}. |
|
||||||
*/ |
|
||||||
public PersistentPropertyPathExtension( |
|
||||||
MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context, |
|
||||||
RelationalPersistentEntity<?> entity) { |
|
||||||
|
|
||||||
Assert.notNull(context, "Context must not be null"); |
|
||||||
Assert.notNull(entity, "Entity must not be null"); |
|
||||||
|
|
||||||
this.context = context; |
|
||||||
this.entity = entity; |
|
||||||
this.path = null; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Creates a non-empty path. |
|
||||||
* |
|
||||||
* @param context must not be {@literal null}. |
|
||||||
* @param path must not be {@literal null}. |
|
||||||
*/ |
|
||||||
public PersistentPropertyPathExtension( |
|
||||||
MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context, |
|
||||||
PersistentPropertyPath<? extends RelationalPersistentProperty> path) { |
|
||||||
|
|
||||||
Assert.notNull(context, "Context must not be null"); |
|
||||||
Assert.notNull(path, "Path must not be null"); |
|
||||||
Assert.notNull(path.getBaseProperty(), "Path must not be empty."); |
|
||||||
|
|
||||||
this.context = context; |
|
||||||
this.entity = path.getBaseProperty().getOwner(); |
|
||||||
this.path = path; |
|
||||||
} |
|
||||||
|
|
||||||
public static boolean isWritable(@Nullable PersistentPropertyPath<? extends RelationalPersistentProperty> path) { |
|
||||||
return path == null || path.getLeafProperty().isWritable() && isWritable(path.getParentPath()); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Returns {@literal true} exactly when the path is non-empty and the leaf property an embedded one. |
|
||||||
* |
|
||||||
* @return if the leaf property is embedded. |
|
||||||
*/ |
|
||||||
public boolean isEmbedded() { |
|
||||||
return path != null && path.getLeafProperty().isEmbedded(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Returns the path that has the same beginning but is one segment shorter than this path. |
|
||||||
* |
|
||||||
* @return the parent path. Guaranteed to be not {@literal null}. |
|
||||||
* @throws IllegalStateException when called on an empty path. |
|
||||||
*/ |
|
||||||
public PersistentPropertyPathExtension getParentPath() { |
|
||||||
|
|
||||||
if (path == null) { |
|
||||||
throw new IllegalStateException("The parent path of a root path is not defined."); |
|
||||||
} |
|
||||||
|
|
||||||
if (path.getLength() == 1) { |
|
||||||
return new PersistentPropertyPathExtension(context, entity); |
|
||||||
} |
|
||||||
|
|
||||||
return new PersistentPropertyPathExtension(context, path.getParentPath()); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Returns {@literal true} if there are multiple values for this path, i.e. if the path contains at least one element |
|
||||||
* that is a collection and array or a map. |
|
||||||
* |
|
||||||
* @return {@literal true} if the path contains a multivalued element. |
|
||||||
*/ |
|
||||||
public boolean isMultiValued() { |
|
||||||
|
|
||||||
return path != null && //
|
|
||||||
(path.getLeafProperty().isCollectionLike() //
|
|
||||||
|| path.getLeafProperty().isQualified() //
|
|
||||||
|| getParentPath().isMultiValued() //
|
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The {@link RelationalPersistentEntity} associated with the leaf of this path. |
|
||||||
* |
|
||||||
* @return Might return {@literal null} when called on a path that does not represent an entity. |
|
||||||
*/ |
|
||||||
@Nullable |
|
||||||
public RelationalPersistentEntity<?> getLeafEntity() { |
|
||||||
return path == null ? entity |
|
||||||
: context.getPersistentEntity(path.getLeafProperty().getTypeInformation().getActualType()); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The {@link RelationalPersistentEntity} associated with the leaf of this path or throw {@link IllegalStateException} |
|
||||||
* if the leaf cannot be resolved. |
|
||||||
* |
|
||||||
* @return the required {@link RelationalPersistentEntity} associated with the leaf of this path. |
|
||||||
* @since 3.0 |
|
||||||
* @throws IllegalStateException if the persistent entity cannot be resolved. |
|
||||||
*/ |
|
||||||
public RelationalPersistentEntity<?> getRequiredLeafEntity() { |
|
||||||
|
|
||||||
RelationalPersistentEntity<?> entity = getLeafEntity(); |
|
||||||
|
|
||||||
if (entity == null) { |
|
||||||
|
|
||||||
if (this.path == null) { |
|
||||||
throw new IllegalStateException("Couldn't resolve leaf PersistentEntity absent path"); |
|
||||||
} |
|
||||||
throw new IllegalStateException( |
|
||||||
String.format("Couldn't resolve leaf PersistentEntity for type %s", path.getLeafProperty().getActualType())); |
|
||||||
} |
|
||||||
|
|
||||||
return entity; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @return {@literal true} when this is an empty path or the path references an entity. |
|
||||||
*/ |
|
||||||
public boolean isEntity() { |
|
||||||
return path == null || path.getLeafProperty().isEntity(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @return {@literal true} when this is references a {@link java.util.List} or {@link java.util.Map}. |
|
||||||
*/ |
|
||||||
public boolean isQualified() { |
|
||||||
return path != null && path.getLeafProperty().isQualified(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @return {@literal true} when this is references a {@link java.util.Collection} or an array. |
|
||||||
*/ |
|
||||||
public boolean isCollectionLike() { |
|
||||||
return path != null && path.getLeafProperty().isCollectionLike(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The name of the column used to reference the id in the parent table. |
|
||||||
* |
|
||||||
* @throws IllegalStateException when called on an empty path. |
|
||||||
*/ |
|
||||||
public SqlIdentifier getReverseColumnName() { |
|
||||||
|
|
||||||
Assert.state(path != null, "Empty paths don't have a reverse column name"); |
|
||||||
return path.getLeafProperty().getReverseColumnName(this); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The alias used in select for the column used to reference the id in the parent table. |
|
||||||
* |
|
||||||
* @throws IllegalStateException when called on an empty path. |
|
||||||
*/ |
|
||||||
public SqlIdentifier getReverseColumnNameAlias() { |
|
||||||
|
|
||||||
return prefixWithTableAlias(getReverseColumnName()); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The name of the column used to represent this property in the database. |
|
||||||
* |
|
||||||
* @throws IllegalStateException when called on an empty path. |
|
||||||
*/ |
|
||||||
public SqlIdentifier getColumnName() { |
|
||||||
|
|
||||||
Assert.state(path != null, "Path is null"); |
|
||||||
|
|
||||||
return path.getLeafProperty().getColumnName(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The alias for the column used to represent this property in the database. |
|
||||||
* |
|
||||||
* @throws IllegalStateException when called on an empty path. |
|
||||||
*/ |
|
||||||
public SqlIdentifier getColumnAlias() { |
|
||||||
return columnAlias.get(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @return {@literal true} if this path represents an entity which has an Id attribute. |
|
||||||
*/ |
|
||||||
public boolean hasIdProperty() { |
|
||||||
|
|
||||||
RelationalPersistentEntity<?> leafEntity = getLeafEntity(); |
|
||||||
return leafEntity != null && leafEntity.hasIdProperty(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Returns the longest ancestor path that has an {@link org.springframework.data.annotation.Id} property. |
|
||||||
* |
|
||||||
* @return A path that starts just as this path but is shorter. Guaranteed to be not {@literal null}. |
|
||||||
*/ |
|
||||||
public PersistentPropertyPathExtension getIdDefiningParentPath() { |
|
||||||
|
|
||||||
PersistentPropertyPathExtension parent = getParentPath(); |
|
||||||
|
|
||||||
if (parent.path == null) { |
|
||||||
return parent; |
|
||||||
} |
|
||||||
|
|
||||||
if (!parent.hasIdProperty()) { |
|
||||||
return parent.getIdDefiningParentPath(); |
|
||||||
} |
|
||||||
|
|
||||||
return parent; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The fully qualified name of the table this path is tied to or of the longest ancestor path that is actually tied to |
|
||||||
* a table. |
|
||||||
* |
|
||||||
* @return the name of the table. Guaranteed to be not {@literal null}. |
|
||||||
* @since 3.0 |
|
||||||
*/ |
|
||||||
public SqlIdentifier getQualifiedTableName() { |
|
||||||
return getTableOwningAncestor().getRequiredLeafEntity().getQualifiedTableName(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The name of the table this path is tied to or of the longest ancestor path that is actually tied to a table. |
|
||||||
* |
|
||||||
* @return the name of the table. Guaranteed to be not {@literal null}. |
|
||||||
* @since 3.0 |
|
||||||
*/ |
|
||||||
public SqlIdentifier getTableName() { |
|
||||||
return getTableOwningAncestor().getRequiredLeafEntity().getTableName(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The alias used for the table on which this path is based. |
|
||||||
* |
|
||||||
* @return a table alias, {@literal null} if the table owning path is the empty path. |
|
||||||
*/ |
|
||||||
@Nullable |
|
||||||
public SqlIdentifier getTableAlias() { |
|
||||||
|
|
||||||
PersistentPropertyPathExtension tableOwner = getTableOwningAncestor(); |
|
||||||
|
|
||||||
return tableOwner.path == null ? null : tableOwner.assembleTableAlias(); |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The column name of the id column of the ancestor path that represents an actual table. |
|
||||||
*/ |
|
||||||
public SqlIdentifier getIdColumnName() { |
|
||||||
return getTableOwningAncestor().getRequiredLeafEntity().getIdColumn(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* If the table owning ancestor has an id the column name of that id property is returned. Otherwise the reverse |
|
||||||
* column is returned. |
|
||||||
*/ |
|
||||||
public SqlIdentifier getEffectiveIdColumnName() { |
|
||||||
|
|
||||||
PersistentPropertyPathExtension owner = getTableOwningAncestor(); |
|
||||||
return owner.path == null ? owner.getRequiredLeafEntity().getIdColumn() : owner.getReverseColumnName(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The length of the path. |
|
||||||
*/ |
|
||||||
public int getLength() { |
|
||||||
return path == null ? 0 : path.getLength(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Tests if {@code this} and the argument represent the same path. |
|
||||||
* |
|
||||||
* @param path to which this path gets compared. May be {@literal null}. |
|
||||||
* @return Whence the argument matches the path represented by this instance. |
|
||||||
*/ |
|
||||||
public boolean matches(PersistentPropertyPath<RelationalPersistentProperty> path) { |
|
||||||
return this.path == null ? path.isEmpty() : this.path.equals(path); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The id property of the final element of the path. |
|
||||||
* |
|
||||||
* @return Guaranteed to be not {@literal null}. |
|
||||||
* @throws IllegalStateException if no such property exists. |
|
||||||
*/ |
|
||||||
public RelationalPersistentProperty getRequiredIdProperty() { |
|
||||||
return this.path == null ? entity.getRequiredIdProperty() : getRequiredLeafEntity().getRequiredIdProperty(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The column name used for the list index or map key of the leaf property of this path. |
|
||||||
* |
|
||||||
* @return May be {@literal null}. |
|
||||||
*/ |
|
||||||
@Nullable |
|
||||||
public SqlIdentifier getQualifierColumn() { |
|
||||||
return path == null ? SqlIdentifier.EMPTY : path.getLeafProperty().getKeyColumn(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The type of the qualifier column of the leaf property of this path or {@literal null} if this is not applicable. |
|
||||||
* |
|
||||||
* @return may be {@literal null}. |
|
||||||
*/ |
|
||||||
@Nullable |
|
||||||
public Class<?> getQualifierColumnType() { |
|
||||||
return path == null ? null : path.getLeafProperty().getQualifierColumnType(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Creates a new path by extending the current path by the property passed as an argument. |
|
||||||
* |
|
||||||
* @param property must not be {@literal null}. |
|
||||||
* @return Guaranteed to be not {@literal null}. |
|
||||||
*/ |
|
||||||
public PersistentPropertyPathExtension extendBy(RelationalPersistentProperty property) { |
|
||||||
|
|
||||||
PersistentPropertyPath<? extends RelationalPersistentProperty> newPath = path == null //
|
|
||||||
? context.getPersistentPropertyPath(property.getName(), entity.getTypeInformation()) //
|
|
||||||
: context.getPersistentPropertyPath(path.toDotPath() + "." + property.getName(), entity.getTypeInformation()); |
|
||||||
|
|
||||||
return new PersistentPropertyPathExtension(context, newPath); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public String toString() { |
|
||||||
return String.format("PersistentPropertyPathExtension[%s, %s]", entity.getName(), |
|
||||||
path == null ? "-" : path.toDotPath()); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* For empty paths this is the type of the entity. For non empty paths this is the actual type of the leaf property. |
|
||||||
* |
|
||||||
* @return Guaranteed to be not {@literal null}. |
|
||||||
* @see PersistentProperty#getActualType() |
|
||||||
*/ |
|
||||||
public Class<?> getActualType() { |
|
||||||
|
|
||||||
return path == null //
|
|
||||||
? entity.getType() //
|
|
||||||
: path.getLeafProperty().getActualType(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @return {@literal true} if the leaf property of this path is a {@link java.util.Map}. |
|
||||||
* @see RelationalPersistentProperty#isMap() |
|
||||||
*/ |
|
||||||
public boolean isMap() { |
|
||||||
return path != null && path.getLeafProperty().isMap(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Converts this path to a non-null {@link PersistentPropertyPath}. |
|
||||||
* |
|
||||||
* @return Guaranteed to be not {@literal null}. |
|
||||||
* @throws IllegalStateException if this path is empty. |
|
||||||
*/ |
|
||||||
public PersistentPropertyPath<? extends RelationalPersistentProperty> getRequiredPersistentPropertyPath() { |
|
||||||
|
|
||||||
Assert.state(path != null, "No path."); |
|
||||||
|
|
||||||
return path; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Finds and returns the longest path with ich identical or an ancestor to the current path and maps directly to a |
|
||||||
* table. |
|
||||||
* |
|
||||||
* @return a path. Guaranteed to be not {@literal null}. |
|
||||||
*/ |
|
||||||
private PersistentPropertyPathExtension getTableOwningAncestor() { |
|
||||||
|
|
||||||
return isEntity() && !isEmbedded() ? this : getParentPath().getTableOwningAncestor(); |
|
||||||
} |
|
||||||
|
|
||||||
@Nullable |
|
||||||
private SqlIdentifier assembleTableAlias() { |
|
||||||
|
|
||||||
Assert.state(path != null, "Path is null"); |
|
||||||
|
|
||||||
RelationalPersistentProperty leafProperty = path.getLeafProperty(); |
|
||||||
String prefix; |
|
||||||
if (isEmbedded()) { |
|
||||||
prefix = leafProperty.getEmbeddedPrefix(); |
|
||||||
|
|
||||||
} else { |
|
||||||
prefix = leafProperty.getName(); |
|
||||||
} |
|
||||||
|
|
||||||
if (path.getLength() == 1) { |
|
||||||
Assert.notNull(prefix, "Prefix mus not be null"); |
|
||||||
return StringUtils.hasText(prefix) ? SqlIdentifier.quoted(prefix) : null; |
|
||||||
} |
|
||||||
|
|
||||||
PersistentPropertyPathExtension parentPath = getParentPath(); |
|
||||||
SqlIdentifier sqlIdentifier = parentPath.assembleTableAlias(); |
|
||||||
|
|
||||||
if (sqlIdentifier != null) { |
|
||||||
|
|
||||||
return parentPath.isEmbedded() ? sqlIdentifier.transform(name -> name.concat(prefix)) |
|
||||||
: sqlIdentifier.transform(name -> name + "_" + prefix); |
|
||||||
} |
|
||||||
return SqlIdentifier.quoted(prefix); |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
private SqlIdentifier prefixWithTableAlias(SqlIdentifier columnName) { |
|
||||||
|
|
||||||
SqlIdentifier tableAlias = getTableAlias(); |
|
||||||
return tableAlias == null ? columnName : columnName.transform(name -> tableAlias.getReference() + "_" + name); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public boolean equals(@Nullable Object o) { |
|
||||||
|
|
||||||
if (this == o) |
|
||||||
return true; |
|
||||||
if (o == null || getClass() != o.getClass()) |
|
||||||
return false; |
|
||||||
PersistentPropertyPathExtension that = (PersistentPropertyPathExtension) o; |
|
||||||
return entity.equals(that.entity) && Objects.equals(path, that.path); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public int hashCode() { |
|
||||||
return Objects.hash(entity, path); |
|
||||||
} |
|
||||||
|
|
||||||
public AggregatePath getAggregatePath() { |
|
||||||
if (path != null) { |
|
||||||
|
|
||||||
return ((RelationalMappingContext) context).getAggregatePath(path); |
|
||||||
} else { |
|
||||||
return ((RelationalMappingContext) context).getAggregatePath(entity); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,174 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2018-2024 the original author or authors. |
|
||||||
* |
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||||
* you may not use this file except in compliance with the License. |
|
||||||
* You may obtain a copy of the License at |
|
||||||
* |
|
||||||
* https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
* |
|
||||||
* Unless required by applicable law or agreed to in writing, software |
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||||
* See the License for the specific language governing permissions and |
|
||||||
* limitations under the License. |
|
||||||
*/ |
|
||||||
package org.springframework.data.relational.core.conversion; |
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.*; |
|
||||||
|
|
||||||
import java.util.ArrayList; |
|
||||||
import java.util.Arrays; |
|
||||||
import java.util.List; |
|
||||||
import java.util.function.Function; |
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeEach; |
|
||||||
import org.junit.jupiter.api.Test; |
|
||||||
import org.springframework.data.convert.ConverterBuilder; |
|
||||||
import org.springframework.data.convert.ConverterBuilder.ConverterAware; |
|
||||||
import org.springframework.data.convert.CustomConversions; |
|
||||||
import org.springframework.data.mapping.PersistentPropertyAccessor; |
|
||||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext; |
|
||||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; |
|
||||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; |
|
||||||
import org.springframework.data.util.TypeInformation; |
|
||||||
|
|
||||||
/** |
|
||||||
* Unit tests for {@link BasicRelationalConverter}. |
|
||||||
* |
|
||||||
* @author Mark Paluch |
|
||||||
* @author Chirag Tailor |
|
||||||
*/ |
|
||||||
class BasicRelationalConverterUnitTests { |
|
||||||
|
|
||||||
RelationalMappingContext context = new RelationalMappingContext(); |
|
||||||
RelationalConverter converter; |
|
||||||
|
|
||||||
@BeforeEach |
|
||||||
public void before() throws Exception { |
|
||||||
|
|
||||||
List<Object> converters = new ArrayList<>(); |
|
||||||
converters.addAll( |
|
||||||
ConverterBuilder.writing(MyValue.class, String.class, MyValue::foo).andReading(MyValue::new).getConverters()); |
|
||||||
|
|
||||||
ConverterAware converterAware = ConverterBuilder |
|
||||||
.writing(MySimpleEnum.class, MySimpleEnum.class, Function.identity()).andReading(mySimpleEnum -> mySimpleEnum); |
|
||||||
converters.addAll(converterAware.getConverters()); |
|
||||||
|
|
||||||
CustomConversions conversions = new CustomConversions(CustomConversions.StoreConversions.NONE, converters); |
|
||||||
context.setSimpleTypeHolder(conversions.getSimpleTypeHolder()); |
|
||||||
|
|
||||||
converter = new BasicRelationalConverter(context, conversions); |
|
||||||
} |
|
||||||
|
|
||||||
@Test // DATAJDBC-235
|
|
||||||
@SuppressWarnings("unchecked") |
|
||||||
void shouldUseConvertingPropertyAccessor() { |
|
||||||
|
|
||||||
RelationalPersistentEntity<MyEntity> entity = (RelationalPersistentEntity) context |
|
||||||
.getRequiredPersistentEntity(MyEntity.class); |
|
||||||
|
|
||||||
MyEntity instance = new MyEntity(); |
|
||||||
|
|
||||||
PersistentPropertyAccessor<MyEntity> accessor = converter.getPropertyAccessor(entity, instance); |
|
||||||
RelationalPersistentProperty property = entity.getRequiredPersistentProperty("flag"); |
|
||||||
accessor.setProperty(property, "1"); |
|
||||||
|
|
||||||
assertThat(instance.flag).isTrue(); |
|
||||||
} |
|
||||||
|
|
||||||
@Test // DATAJDBC-235
|
|
||||||
void shouldConvertEnumToString() { |
|
||||||
|
|
||||||
Object result = converter.writeValue(MyEnum.ON, TypeInformation.of(String.class)); |
|
||||||
|
|
||||||
assertThat(result).isEqualTo("ON"); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
void shouldConvertEnumArrayToStringArray() { |
|
||||||
|
|
||||||
Object result = converter.writeValue(new MyEnum[] { MyEnum.ON }, TypeInformation.OBJECT); |
|
||||||
|
|
||||||
assertThat(result).isEqualTo(new String[] { "ON" }); |
|
||||||
} |
|
||||||
|
|
||||||
@Test // GH-1593
|
|
||||||
void shouldRetainEnumArray() { |
|
||||||
|
|
||||||
Object result = converter.writeValue(new MySimpleEnum[] { MySimpleEnum.ON }, TypeInformation.OBJECT); |
|
||||||
|
|
||||||
assertThat(result).isEqualTo(new MySimpleEnum[] { MySimpleEnum.ON }); |
|
||||||
} |
|
||||||
|
|
||||||
@Test // GH-1593
|
|
||||||
void shouldConvertStringToEnum() { |
|
||||||
|
|
||||||
Object result = converter.readValue("OFF", TypeInformation.of(MyEnum.class)); |
|
||||||
|
|
||||||
assertThat(result).isEqualTo(MyEnum.OFF); |
|
||||||
} |
|
||||||
|
|
||||||
@Test // GH-1046
|
|
||||||
void shouldConvertArrayElementsToTargetElementType() throws NoSuchMethodException { |
|
||||||
|
|
||||||
TypeInformation<?> typeInformation = TypeInformation.fromReturnTypeOf(EntityWithArray.class.getMethod("floats")); |
|
||||||
Double[] value = { 1.2d, 1.3d, 1.4d }; |
|
||||||
Object result = converter.readValue(value, typeInformation); |
|
||||||
assertThat(result).isEqualTo(Arrays.asList(1.2f, 1.3f, 1.4f)); |
|
||||||
} |
|
||||||
|
|
||||||
@Test // DATAJDBC-235
|
|
||||||
@SuppressWarnings("unchecked") |
|
||||||
void shouldCreateInstance() { |
|
||||||
|
|
||||||
RelationalPersistentEntity<WithConstructorCreation> entity = (RelationalPersistentEntity) context |
|
||||||
.getRequiredPersistentEntity(WithConstructorCreation.class); |
|
||||||
|
|
||||||
WithConstructorCreation result = converter.createInstance(entity, it -> "bar"); |
|
||||||
|
|
||||||
assertThat(result.foo).isEqualTo("bar"); |
|
||||||
} |
|
||||||
|
|
||||||
@Test // DATAJDBC-516
|
|
||||||
void shouldConsiderWriteConverter() { |
|
||||||
|
|
||||||
Object result = converter.writeValue(new MyValue("hello-world"), TypeInformation.of(String.class)); |
|
||||||
|
|
||||||
assertThat(result).isEqualTo("hello-world"); |
|
||||||
} |
|
||||||
|
|
||||||
@Test // DATAJDBC-516
|
|
||||||
void shouldConsiderReadConverter() { |
|
||||||
|
|
||||||
Object result = converter.readValue("hello-world", TypeInformation.of(MyValue.class)); |
|
||||||
|
|
||||||
assertThat(result).isEqualTo(new MyValue("hello-world")); |
|
||||||
} |
|
||||||
|
|
||||||
record EntityWithArray(List<Float> floats) { |
|
||||||
} |
|
||||||
|
|
||||||
static class MyEntity { |
|
||||||
boolean flag; |
|
||||||
} |
|
||||||
|
|
||||||
record WithConstructorCreation(String foo) { |
|
||||||
} |
|
||||||
|
|
||||||
record MyValue(String foo) { |
|
||||||
} |
|
||||||
|
|
||||||
static class MyEntityWithConvertibleProperty { |
|
||||||
|
|
||||||
MyValue myValue; |
|
||||||
} |
|
||||||
|
|
||||||
enum MyEnum { |
|
||||||
ON, OFF; |
|
||||||
} |
|
||||||
|
|
||||||
enum MySimpleEnum { |
|
||||||
ON, OFF; |
|
||||||
} |
|
||||||
} |
|
||||||
Loading…
Reference in new issue