Spring Data cassandra 抛出:无法获取实体的 where 子句

Spring Data cassandra throws: Cannot obtain where clauses for entity

我正在尝试使用 Spring data cassandra 通过 REST api 与 cassandra/scyllaDB 通信。 我有实体

@Table
public class Transaction {

    @PrimaryKeyColumn(name = "id", ordinal = 0, type = PrimaryKeyType.PARTITIONED)
    private String id;

    @PrimaryKeyColumn(name = "timestamp", ordinal = 1, type = PrimaryKeyType.CLUSTERED)
    private Instant timestamp;

    private String currency;
}

有存储库

@Repository
public interface TransactionRepository extends CassandraRepository<Transaction, String> {
}

和在 REST 控制器中调用的服务

@Service
public class TransactionServiceImpl implements TransactionService{

    @Autowired
    private TransactionRepository transactionRepository;
    
    private Transaction getTransaction(String transactionId) {
        return transactionRepository.findById(transactionId)
                .orElseThrow(() -> new NotFoundException("Transaction with provided " + transactionId + " does not exist."));
    }
}

当我调用所需的 REST 端点时,我提供了正确的 transactionId,抛出异常。

Caused by: org.springframework.dao.InvalidDataAccessApiUsageException: Cannot obtain where clauses for 
entity [com.example.dao.entity.Transaction] using [id123456]

我做了一些调查,但实体 class 中提供的组合键应该是有效的。我做错了什么?

table 型号:

CREATE TABLE etl.transaction
(
    id text,
    timestamp timestamp,
    currency ascii,
    PRIMARY KEY (id, timestamp)
)
WITH CLUSTERING ORDER BY (timestamp ASC) AND
default_time_to_live = 157680000; // 5 years in seconds

我从未收到此错误消息。如果您有 setter/getter 方法或使用 Lombok 项目并在 @Table 旁边添加 @Data 注释,您会收到什么错误消息? (如果你使用IntelliJ,你必须安装Lombok插件!)

在我的回答中,如果有任何结果,请发表评论。

@Data
@Table
public class Transaction {

    @PrimaryKeyColumn(name = "id", ordinal = 0, type = PrimaryKeyType.PARTITIONED)
    private String id;

    @PrimaryKeyColumn(name = "timestamp", ordinal = 1, type = PrimaryKeyType.CLUSTERED)
    private Instant timestamp;

    private String currency;
}

更新

TL;DR:如果只有一个@PrimaryKeyColumn,只需在属性上方添加@Id。如果你使用更多的@PrimaryKeyColumn,你必须使用:

  • @PrimaryKey 和@PrimaryKeyClass(您可以使用 findById)
public interface TestRepository extends CassandraRepository<Test, KeyClass> {
}
@Data
@Table
public class Test {
    
    @PrimaryKey
    private KeyClass id;    

    @Data
    @PrimaryKeyClass
    public static class KeyClass { // You either give it a different name or move it in another file. Nevermind.
        @PrimaryKeyColumn(name = "id", type = PrimaryKeyType.PARTITIONED)
        private Integer id;
    
        @PrimaryKeyColumn(name = "data")
        private Integer data;
    }
}
  • 或下一个接口:
public interface TestRepository extends CassandraRepository<Test, Integer> {
    Optional<Test> findByIdAndData(Integer id, Integer data);
}
@Data
@Table
public class Test {
    
    @PrimaryKeyColumn(name = "id", type = PrimaryKeyType.PARTITIONED)
    private Integer id;
    
    @PrimaryKeyColumn(name = "data")
    private Integer data;
}

独奏@PrimaryKeyColumn

测试数据:

-- auto-generated definition
CREATE TABLE test
(
    id   int PRIMARY KEY,
    data int
)
    WITH CACHING = {'keys': 'ALL', 'rows_per_partition': 'NONE'}
     AND COMPACTION = {'max_threshold': '32', 'min_threshold': '4', 'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'}
     AND COMPRESSION = {'class': 'org.apache.cassandra.io.compress.LZ4Compressor', 'chunk_length_in_kb': '64'}
     AND DCLOCAL_READ_REPAIR_CHANCE = 0.1;
public interface TestRepository extends CassandraRepository<Test, Integer> {
}
@Data
@Table
public class Test {
    
    /*
     * You can only use "findById" or "findallById" with @Id annotation.
     * @PrimaryKey contains the @Id itself, but ofc you have to configuration @PrimaryKeyClass.
     */
    @PrimaryKeyColumn(name = "id", type = PrimaryKeyType.PARTITIONED)
    @Id // <-- add
    private Integer id;
    
    @Column("data")
    private Integer data;
}

测试:

@SpringBootTest
class ApplicationTests {
    
    @Autowired
    private TestRepository testRepository;
    
    @Test
    void contextLoads() {
        var x = testRepository.findById(1).orElse(null);
        
        if(x == null) {
            fail();
        }
    
        assertEquals((int) x.getData(), 5);
    }
}

结果:


带@PrimaryKey Table:

public interface TestRepository extends CassandraRepository<Test, KeyClass> {
}
@Data
@Table
public class Test {
    
    @PrimaryKey
    private KeyClass id;    

    @Data
    @PrimaryKeyClass
    public static class KeyClass { // You either give it a different name or move it in another file. Nevermind.
        @PrimaryKeyColumn(name = "id", type = PrimaryKeyType.PARTITIONED)
        private Integer id;
    
        @PrimaryKeyColumn(name = "data")
        private Integer data;
    }
}


使用 JPA API

第二种遗传类型(定义什么类型的id)无所谓,在这个解决方案上。


如果您只想按分区查询。 CLUSTERED 存在!

方案一:

第二种遗传类型(定义什么类型的id)无所谓,在这个解决方案上。

方案二:

id 重命名为其他名称并以这种方式配置 JPA。

第二种遗传类型(定义什么类型的id)无所谓,在这个解决方案上。

@PrimaryKeyClass 的解决方案

public interface TestRepository extends CassandraRepository<Test, KeyClass> {
    // findby[ID-1]_[ID-2](...)
    // ID-1 is mean: Test's "id" attribute
    // "_" is mean chain/access instance
    // ID-2 is mean: KeyClass's "id" attribute
    Optional<Test> findById_Id(Integer id);

    // Look this both:
    Optional<Test> findById_IdAndId_Data(Integer id, Integer data);
}