Tomas
Tomas

Reputation: 35

Spring Data cassandra throws: Cannot obtain where clauses for entity

iam trying to use Spring data cassandra to communicate with cassandra/scyllaDB via REST api. I have entity

@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;
}

with repository

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

and service which is called in REST controller

@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."));
    }
}

When i call required REST endpoint, where i provide correct transactionId, Exception is thrown.

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

I have made some investigation, but composite key provided in entity class should be valid. What ma i doing wrong?

Model of the 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

Upvotes: 1

Views: 3201

Answers (1)

Numichi
Numichi

Reputation: 1092

I never get this error message. What will you get error message if you have setter/getter methods or use Lombok project and add @Data annotation next to @Table? (If you use IntelliJ, you have to install Lombok plugin!)

In my answer, write comment if there is any result.

@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;
}

UPDATE

TL;DR: Just add @Id above the attribute if you have one only @PrimaryKeyColumn. If you use more @PrimaryKeyColumn, you have to use:

  • @PrimaryKey and @PrimaryKeyClass (and you can use 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;
    }
}
  • or next interface:
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;
}

With solo @PrimaryKeyColumn

Test data: enter image description here

-- 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;
}

Test:

@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);
    }
}

Result: enter image description here


With @PrimaryKey Table: enter image description here

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;
    }
}

enter image description here


With JPA API

The second genetic type (what defined type of id) doesn’t matter what, on this solution.

enter image description here

enter image description here

enter image description here


If you want just query by PARTITION. CLUSTERED is exist!

Solution 1:

The second genetic type (what defined type of id) doesn’t matter what, on this solution.

enter image description here

Solution 2:

Rename id to something else and configure JPA that way.

The second genetic type (what defined type of id) doesn’t matter what, on this solution.

enter image description here

enter image description here

enter image description here

Solution with @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);
}

Upvotes: 3

Related Questions