Cannot write to table with UDT, getting "Cannot resolve UserDefinedType for [devices]"

84 views Asked by At

These are the dependencies I have installed in my spring project

//cassandra
implementation 'org.springframework.data:spring-data-cassandra:3.1.2'
implementation 'io.projectreactor:reactor-core'
implementation 'com.datastax.cassandra:cassandra-driver-core:3.10.2'
implementation 'com.datastax.oss:java-driver-mapper-runtime:4.13.0'
implementation 'com.datastax.oss:java-driver-core:4.13.0'
implementation 'com.datastax.cassandra:cassandra-driver-mapping:3.6.0'
java --version
openjdk 11.0.18 2023-01-17 LTS
OpenJDK Runtime Environment Corretto-11.0.18.10.1 (build 11.0.18+10-LTS)
OpenJDK 64-Bit Server VM Corretto-11.0.18.10.1 (build 11.0.18+10-LTS, mixed mode)

For my entity class

import com.datastax.driver.core.DataType;
import com.xxx.pai.gm.ws.core.model.Widgets;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.cassandra.core.cql.Ordering;
import org.springframework.data.cassandra.core.cql.PrimaryKeyType;
import org.springframework.data.cassandra.core.mapping.CassandraType;
import org.springframework.data.cassandra.core.mapping.Column;
import org.springframework.data.cassandra.core.mapping.Frozen;
import org.springframework.data.cassandra.core.mapping.PrimaryKeyColumn;
import org.springframework.data.cassandra.core.mapping.Table;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Table("shortcut_widgets")
public class ShortcutWidgetEntity {
    @PrimaryKeyColumn(ordinal = 0, type = PrimaryKeyType.PARTITIONED, ordering = Ordering.ASCENDING)
    @Column("customerId")
    private String customerId;

    @Frozen
    @Column("device_list")
    @CassandraType(type = CassandraType.Name.LIST, userTypeName = "device_list", typeArguments = CassandraType.Name.UDT)
    List<Devices> deviceList;
}

And UDT

import com.datastax.driver.mapping.annotations.Field;
import com.datastax.driver.mapping.annotations.UDT;
import com.xxx.pai.gm.ws.core.controller.request.ShortcutWidgets;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.cassandra.core.mapping.UserDefinedType;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@UserDefinedType("device_list")
public class Devices {
    private Long bannerId;
    private String uniqueId;
    private String type;
    private String imageUrl;
    private String darkImageUrl;
    private String url;
    private String title;
    private Integer metaDataSourceId;
}

I am unable to write data into the tables, and UDT, which are as follows:

create TYPE device_list( bannerId bigint, uniqueId text, type text, 
        imageUrl text, darkImageUrl text, url text, title text, 
        metaDataSourceId int );

CREATE TABLE shortcut_widgets (
    customerId text PRIMARY KEY,
    device_list frozen<list<device_list>>
);

The repository where I am trying to write data is

@Repository
public class ShortcutWidgetsRepository {
    private static final String KEYSPACE_NAME = "shortcuts";
    private final MeterRegistry meterRegistry;
    private final ReactiveCassandraTemplate template; 

    public ShortcutWidgetsRepository(
    @Qualifier("cassandraTemplate1") ReactiveCassandraTemplate template,
    MeterRegistry meterRegistry) {
        this.template = template;
        this.meterRegistry = meterRegistry;
    }

    public Mono<ShortcutWidgetEntity> saveWidget(ShortcutWidgetEntity shortcutWidgetEntity) {
        return template.insert(shortcutWidgetEntity);
    }
}

The error I am facing is "Cannot resolve UserDefinedType for \[device_list\]"

I have tried changing versions of dependencies, tried following this blog https://www.linkedin.com/pulse/guide-cassandra-object-mapper-spring-nikunj-pandya/, but there were issues injecting Session bean using the dependencies I have now. Can anyone help me, How to solve this error?

2

There are 2 answers

0
Deep Kiran On BEST ANSWER

I have solved this issue by using SimpleUserTypeResolver. You can see it here

@Primary
  @Bean
  public CassandraMappingContext mappingContext() {
    CassandraMappingContext mappingContext = new CassandraMappingContext();
    mappingContext.setUserTypeResolver(
        new SimpleUserTypeResolver(Objects.requireNonNull(cassandraSession().getObject())));
    return mappingContext;
  }

  @Bean
  @Primary
  public CassandraConverter converter() {
    return new MappingCassandraConverter(mappingContext());
  }

  @Bean
  @Override
  public ReactiveCassandraTemplate reactiveCassandraTemplate() {
    return new ReactiveCassandraTemplate(reactiveCassandraSession(), converter());
  }
0
Erick Ramirez On

I noted that you are using Spring Data Cassandra v3.1.2:

implementation 'org.springframework.data:spring-data-cassandra:3.1.2'

You need to review the dependencies in your code because according to the official Spring Data Reference Documentation and the Maven compile dependencies, spring-data-cassandra v3.1.2 requires Cassandra Java driver 4.0 or later.

Also, your UDT class already uses the @UserDefinedType annotation:

@UserDefinedType("device_list")
public class Devices {
    ...
}

so you should NOT use the @CassandraType annotation in your entity class:

public class ShortcutWidgetEntity {
    ...

    @Frozen
    @Column("device_list")
    @CassandraType(type = CassandraType.Name.LIST, userTypeName = "device_list", typeArguments = CassandraType.Name.UDT)
    List<Devices> deviceList;
}

For what it's worth, Aaron Ploetz has a working example for an eCommerce app with the full source code on GitHub. In it you'll see how he has implemented the mapper for the UDT.

Having said all that, I strongly recommend only using UDTs as a last resort. Wherever possible, you should use native CQL types and model your data in regular columns/rows. Unless you have a very specific need to use UDTs, I suggest modelling your data with something like:

CREATE TABLE widgets_by_customer_id (
    customer_id text,
    device_id text,
    ...
    PRIMARY KEY(customer_id, device_id)
)

With this schema, each customer will have one or more rows of devices. This means that (1) you can page through all the devices, (2) you can retrieve a single device without having to retrieve the whole list collection, and (3) it's a lot easier to update the schema (add/drop columns) in the future. Cheers!