Reputation: 35
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
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:
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;
}
With solo @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;
}
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);
}
}
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;
}
}
With JPA API
The second genetic type (what defined type of id) doesn’t matter what, on this solution.
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.
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.
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