Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@CassandraType is ignored in the presence of CustomConversions [DATACASS-557] #702

Closed
spring-projects-issues opened this issue May 29, 2018 · 5 comments
Assignees
Labels
type: bug A general bug

Comments

@spring-projects-issues
Copy link

spring-projects-issues commented May 29, 2018

M. Justin opened DATACASS-557 and commented

Perhaps this is intentional, but if there are any CustomConversions matching the type of an entity's property, there does not appear to be a way to override the type using @CassandraType.

Per org.springframework.data.cassandra.core.convert.MappingCassandraConverter#getTargetType, the annotation is only checked if there are any custom conversions.

I ran across this issue in a Spring Boot app, as this automatically wires in org.springframework.data.cassandra.core.convert.CassandraCustomConversions. This app is connecting to a Cassandra database, which has a timestamp field which is being used as a date (the time fields are zeroed out).  Ideally I'd update the column to be a Cassandra date, but this is not the only application using that database, so changing it does not make sense at this point in tie.

If I make the property a LocalDate in my entity, the app correctly reads in entities of the type, but cannot save them (it throws "InvalidQueryException: Expected 8 or 0 byte long for date (4)"). The Cassandra driver needs this to be a java.util.Date as it's a timestamp in the database, but the custom converters are converting it to a com.datastax.driver.core.LocalDate when saving it. Annotating the field as @Column("timestamp_value") does nothing (per above). What does make this work is replacing the CassandraConverter Spring bean with one that does not have all the conversions in CassandraCustomConversions; it is then converted to a Date value:

@Bean
public CassandraConverter cassandraConverter(CassandraMappingContext mapping) {
    MappingCassandraConverter converter = new MappingCassandraConverter(mapping);
    converter.setCustomConversions(
            new CustomConversions(CustomConversions.StoreConversions.of(CassandraSimpleTypeHolder.HOLDER),
                    Collections.emptyList()));
    return converter;
}

Example

CREATE TABLE test_table (id TimeUUID PRIMARY KEY, timestamp_value timestamp);
@Table("test_table")
public class TestTable {
    @PrimaryKey
    private UUID id;

    @CassandraType(type = DataType.Name.TIMESTAMP)
    @Column("timestamp_value")
    private LocalDate timestampValue;
    
    //Getters, setters, and constructors
}
@Repository
public interface TestTableRepository extends CassandraRepository<TestTable, String> {
}
@RunWith(SpringRunner.class)
@TestExecutionListeners(mergeMode = MergeMode.MERGE_WITH_DEFAULTS, listeners = {
        CassandraUnitDependencyInjectionTestExecutionListener.class})
@SpringBootTest
@CassandraDataSet(keyspace = "test_keyspace", value = "setup.cql")
@EmbeddedCassandra(timeout = 60000)
public class SampleCassandraApplicationTests {
    @Autowired
    private TestTableRepository repository;

    @Test
    public void testInsert() {
        LocalDate now = LocalDate.now();
        repository.insert(new TestTable(UUIDs.timeBased(), now)); //This fails

        assertEquals(1, repository.count());
        assertEquals(now, repository.findAll().get(0).getTimestampValue());
    }
}

Affects: 2.0.7 (Kay SR7)

Issue Links:

  • DATACASS-470 Move Cassandra type resolution into MappingCassandraConverter
    ("is superseded by")
@spring-projects-issues
Copy link
Author

spring-projects-issues commented May 30, 2018

Mark Paluch commented

Type information is derived either from (in the order of precedence):

  1. A custom converter, see see https://docs.spring.io/spring-data/cassandra/docs/current/reference/html/#mapping-conversion for the target type mapping
  2. Explicit type annotation using @CassandraType
  3. Column type (implicit mapping), see https://docs.spring.io/spring-data/cassandra/docs/current/reference/html/#mapping-conversion

@CassandraType has no effect when applying a custom conversion.

You can provide your own @WritingConverter between LocalDate and java.util.Date to use the appropriate data type for writing values.

	@Bean
	public CassandraCustomConversions cassandraCustomConversions() {

		return new CassandraCustomConversions(Arrays.asList(LocalDateToDateConverter.INSTANCE));
	}

	@WritingConverter
	enum LocalDateToDateConverter implements Converter<LocalDate, Date> {
		INSTANCE;

		@Override
		public Date convert(LocalDate source) {
			return Date.from(source.atStartOfDay(systemDefault()).toInstant());
		}
	}

@spring-projects-issues
Copy link
Author

spring-projects-issues commented May 30, 2018

M. Justin commented

Thanks, Mark, I confirmed that this worked for my situation.

That said, it's a bit magical in an unfortunate sense that @CassandraType will be ignored for certain types (that you generally won't know until trial and error), especially in Boot-land where CassandraCustomConversions is autowired in by default, applying converters to only some of the things you'll be using

@spring-projects-issues
Copy link
Author

spring-projects-issues commented May 30, 2018

M. Justin commented

Would this solution still work if a different entity also wanted its LocalDate mapped to a Cassandra LocalDate?

@spring-projects-issues
Copy link
Author

spring-projects-issues commented Jun 11, 2018

Mark Paluch commented

If you register a writing converter with a source type of LocalDate, then LocalDate is always converted to the target type.

After reconsidering the mapping, it might be still a good exercise to investigate on raising the priority of @CassandraType on property level even if a custom converter is registered. We could derive the target type (implied by the default type mapping) and use the target type to check for registered converters

@spring-projects-issues
Copy link
Author

spring-projects-issues commented Apr 23, 2020

Mark Paluch commented

This issue was addressed with DATACASS-470 where we revised how data types are determined. CassandraType has now precedence over converters

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: bug A general bug
Projects
None yet
Development

No branches or pull requests

2 participants