概览
- 因AI要使用到向量存储,JanusGraph也使用到Cassandra
卸载先前版本
docker stop cassandra && docker remove cassandra && rm -rf cassandra/
运行Cassandra容器
docker run \
--name cassandra \
--hostname cassandra \
-p 9042:9042 \
--privileged=true \
--net network-common \
-v /elf/cassandra:/var/lib/cassandra \
-itd cassandra:5.0.0
连接到Cassanra
# docker logs --tail 100 cassandra
# 等完全启动后
# docker exec -it cassandra /bin/bash
# 查询集群状态,用于应用程序连接等,详情参考Troubleshooting章节
# /opt/cassandra/bin/nodetool status
# show version
[cqlsh 6.2.0 | Cassandra 5.0.0 | CQL spec 3.4.7 | Native protocol v5]
# cqlsh 127.0.0.1 9042 -u cassandra -p cassandra
# describe keyspaces;
system system_distributed system_traces system_virtual_schema
system_auth system_schema system_views
# /opt/cassandra/bin/nodetool status
Datacenter: datacenter1
=======================
Status=Up/Down
|/ State=Normal/Leaving/Joining/Moving
-- Address Load Tokens Owns (effective) Host ID Rack
UN 172.18.0.2 136.3 KiB 16 100.0% 63c1c77a-fa0b-4630-9fd0-9568133bd1df rack1
Dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-cassandra</artifactId>
</dependency>
Product
import java.util.UUID;
import org.springframework.data.cassandra.core.mapping.Column;
import org.springframework.data.cassandra.core.mapping.PrimaryKey;
import org.springframework.data.cassandra.core.mapping.Table;
@Table
public class Product {
@PrimaryKey
@Column(value = "product_id")
private UUID productId;
@Column(value = "product_name")
private String productName;
@Column(value = "product_memo")
private String productMemo;
@Column(value = "product_state")
private boolean productState;
public Product() {}
public Product(UUID productId, String productName,
String productMemo, boolean productState) {
super();
this.productId = productId;
this.productName = productName;
this.productMemo = productMemo;
this.productState = productState;
}
@Override
public String toString() {
return "Product [productId=" + productId
+ ", productName=" + productName + ", productMemo="
+ productMemo + ", productState=" + productState + "]";
}
public UUID getProductId() {
return productId;
}
public void setProductId(UUID productId) {
this.productId = productId;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public String getProductMemo() {
return productMemo;
}
public void setProductMemo(String productMemo) {
this.productMemo = productMemo;
}
public boolean isProductState() {
return productState;
}
public void setProductState(boolean productState) {
this.productState = productState;
}
}
ProductRepository
import java.util.List;
import java.util.UUID;
import io.os.cassandra.model.Product;
import org.springframework.data.cassandra.repository.AllowFiltering;
import org.springframework.data.cassandra.repository.CassandraRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface ProductRepository extends CassandraRepository<Product,UUID> {
//@AllowFiltering:允许方法对服务器端筛选,等效于
//select * from product where product_state = [true/false];
@AllowFiltering
List<Product> findByProductState(boolean productState);
List<Product> findByProductMemo(String productName);
}
ProductController
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import io.os.cassandra.model.Product;
import io.os.cassandra.repository.ProductRepository;
@RestController
@RequestMapping("/api/product")
public class ProductController {
@Autowired
private ProductRepository productRepository;
@GetMapping("/getAllProductProduct")
public ResponseEntity<List<Product>> getAllProductProduct(
@RequestParam(required = false,value = "productMemo") String productMemo) {
try {
List<Product> productList = new ArrayList<Product>();
if (productMemo == null) {
productRepository.findAll().forEach(productList::add);
} else {
productRepository.findByProductMemo(productMemo).forEach(productList::add);
}
if (productList.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} else {
return new ResponseEntity<>(productList, HttpStatus.OK);
}
} catch (Exception e) {
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@GetMapping("/getProductById/{id}")
public ResponseEntity<Product> getProductById(
@PathVariable("id") String id) {
var product = productRepository.findById(UUID.fromString(id));
if (product.isPresent()) {
return new ResponseEntity<>(product.get(), HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
@PostMapping("/createProduct")
public ResponseEntity<Product> createProduct(
@RequestBody Product product) {
var requestProduct = new Product(
UUID.randomUUID(),product.getProductName(),
product.getProductMemo(),product.isProductState());
try {
Product p = productRepository.save(requestProduct);
return new ResponseEntity<>(p,HttpStatus.CREATED);
} catch (Exception e) {
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@PutMapping("/updateProduct/{id}")
public ResponseEntity<Product> updateProduct(
@PathVariable("id") String id,@RequestBody Product product) {
var updateProduct = productRepository.findById(UUID.fromString(id));
if (updateProduct.isPresent()) {
var p = updateProduct.get();
p.setProductName(product.getProductName());
p.setProductMemo(product.getProductMemo());
p.setProductState(product.isProductState());
return new ResponseEntity<>(productRepository.save(p), HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
@DeleteMapping("/deleteProductById/{id}")
public ResponseEntity<HttpStatus> deleteProductById(
@PathVariable("id") String id) {
try {
productRepository.deleteById(UUID.fromString(id));
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@DeleteMapping("/deleteAllProduct")
public ResponseEntity<HttpStatus> deleteAllProduct() {
try {
productRepository.deleteAll();
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@GetMapping("/getProductByState")
public ResponseEntity<List<Product>> findByProductState() {
try {
var productList = productRepository.findByProductState(true);
if (productList.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} else {
return new ResponseEntity<>(productList, HttpStatus.OK);
}
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
application.yaml
- application.yaml和CassandraConfig.java,二选一
server:
port: 8080
servlet:
context-path: /
spring:
data:
cassandra:
repositories:
type: imperative
cassandra:
keyspace-name: elf
port: 9042
contact-points:
- 192.168.0.123:9042
schema-action: create-if-not-exists
password: cassandra
username: cassandra
# /opt/cassandra/bin/nodetool status命令查询
local-datacenter: datacenter1
import java.net.InetSocketAddress;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.cassandra.repository.config.EnableCassandraRepositories;
import com.datastax.oss.driver.api.core.CqlSession;
@Configuration
@EnableCassandraRepositories
public class CassandraConfig {
@Bean
CqlSession session() {
return CqlSession.builder().withKeyspace("elf")
.withAuthCredentials("cassandra", "cassandra")
.withLocalDatacenter("datacenter1")
.addContactPoint(InetSocketAddress.createUnresolved("192.168.0.123",9042))
.build();
}
}
CassandraEntry
import java.lang.invoke.MethodHandles;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
@SpringBootApplication
public class CassandraEntry {
public static void main(String[] sa) {
var cls = MethodHandles.lookup().lookupClass();
SpringApplication.run(cls,sa);
}
}
Environment
# cassandra为Cassandra容器名
docker start cassandra
# docker exec -it cassandra /bin/bash
# 查询集群状态,用于应用程序连接等,详情参考Troubleshooting章节
# /opt/cassandra/bin/nodetool status
# cqlsh 127.0.0.1 9042 -u cassandra -p cassandra
# describe keyspaces;
Product.cql
create keyspace if not exists elf with replication={'class':'SimpleStrategy','replication_factor':1};
use elf;
create table if not exists elf.product(
product_id uuid primary key,
product_name varchar,
product_memo text,
product_state boolean
);
select * from elf.product;
truncate table elf.product;
# 不要换行,不要随意添加空格,否则cql语法可能无法通过;
# UUID:Java UUID.randomUUID()形式,Cassandra uuid()形式;
insert into elf.product(product_id,product_name,product_memo,product_state)values(uuid(),'JanusGraph','Graph,Open Source',true);
insert into elf.product(product_id,product_name,product_memo,product_state)values(uuid(),'Cassandra','Wide Column,Vector,Multi Model',true);
insert into elf.product(product_id,product_name,product_memo,product_state)values(uuid(),'Presto','Relational,Open Source,Big Data',false);
insert into elf.product(product_id,product_name,product_memo,product_state)values(uuid(),'Trino','Relational,Document,Spatial,TS,KV',true);
insert into elf.product(product_id,product_name,product_memo,product_state)values(uuid(),'Weaviate','Vector,AI,Real Time',true);
insert into elf.product(product_id,product_name,product_memo,product_state)values(uuid(),'Milvus','Vector,Similarity Search,ML',true);
insert into elf.product(product_id,product_name,product_memo,product_state)values(uuid(),'Qdrant','Vector,NN,Semantic Matching',true);
insert into elf.product(product_id,product_name,product_memo,product_state)values(uuid(),'ElasticSearch','Search,Document,Spatial,Vector',true);
+--------------------------------------+-----------------------------------+---------------+--------------+
| product_id | product_memo | product_name | product_state|
|--------------------------------------+-----------------------------------+---------------+--------------|
| 29af0738-4a17-4571-90cd-700ab8995db7 | Vector,Similarity Search,ML | Milvus | True|
| 3a3415aa-8dfa-42fa-9723-0142960b687a | Relational,Open Source,Big Data | Presto | False|
| 741e5240-8a7b-40fa-b07c-e88b3638bf36 | Graph,Open Source | JanusGraph | True|
| 63608ee0-eaf1-41cf-970e-04238a556103 | Vector,AI,Real Time | Weaviate | True|
| 6d580a60-daba-46cc-b3d9-6061aa48f0ff | Search,Document,Spatial,Vector | ElasticSearch | True|
| d4760f62-9b76-4c40-8a56-8c1e7214dfdd | Wide Column,Vector,Multi Model | Cassandra | True|
| ddfe8f96-c49f-4ad0-899a-9b3b484419d8 | Vector,NN,Semantic Matching | Qdrant | True|
| cb99a803-2443-48ec-accb-5262ef9bc429 | Relational,Document,Spatial,TS,KV | Trino | True|
+--------------------------------------+-----------------------------------+---------------+--------------+
Visualization Tool
-
安装DbVisualizer V24.1.4;
-
Tool ☞ Driver Manager ☞ 搜索Cassandra
☞ 右键选择Create User Driver from Template
-
Name:Custom Cassandra
-
Url:jdbc:cassandra://192.168.0.123:9042
-
Driver Class:com.simba.cassandra.jdbc42.Driver
-
点击右侧地球仪:Download/Update driver for remote artifact,
下载完成后关闭窗口; -
点击左上角加号 ☞ Create Database Connection:
- SQL Commander ☞ New SQL Commander ☞ select * from elf.product;
Create Product
-
http://localhost:8080/api/product/createProduct
-
请求方法:POST,请求对象和响应对象分别为:
{
"productName":"Cassandra",
"productMemo":"Wide Column,Vector,Multi Model",
"productState":true
}
{
"productId": "24672630-4ca4-424b-8fc5-c20f1400074b",
"productName": "Cassandra",
"productMemo": "Wide Column,Vector,Multi Model",
"productState": true
}
Update Product
-
http://localhost:8080/api/product/updateProduct/productId
http://localhost:8080/api/product/updateProduct/24672630-4ca4-424b-8fc5-c20f1400074b -
请求方法:PUT,请求对象和响应对象分别为:
{
"productName":"Cassandra-Update",
"productMemo":"Wide Column,Vector,Multi Model",
"productState":true
}
{
"productId": "24672630-4ca4-424b-8fc5-c20f1400074b",
"productName": "Cassandra-Update",
"productMemo": "Wide Column,Vector,Multi Model",
"productState": true
}
Retrieve By Id
-
请求方法:GET;
-
http://localhost:8080/api/product/getProductById/productId
http://localhost:8080/api/product/getProductById/29af0738-4a17-4571-90cd-700ab8995db7
{
"productId": "29af0738-4a17-4571-90cd-700ab8995db7",
"productName": "Milvus",
"productMemo": "Vector,Similarity Search,ML",
"productState": true
}
Retrieve All Product
-
请求方法:GET;
-
http://localhost:8080/api/product/getAllProductProduct
[
{
"productId": "29af0738-4a17-4571-90cd-700ab8995db7",
"productName": "Milvus",
"productMemo": "Vector,Similarity Search,ML",
"productState": true
},
{
"productId": "3a3415aa-8dfa-42fa-9723-0142960b687a",
"productName": "Presto",
"productMemo": "Relational,Open Source,Big Data",
"productState": false
},
{
"productId": "741e5240-8a7b-40fa-b07c-e88b3638bf36",
"productName": "JanusGraph",
"productMemo": "Graph,Open Source",
"productState": true
},
{
"productId": "63608ee0-eaf1-41cf-970e-04238a556103",
"productName": "Weaviate",
"productMemo": "Vector,AI,Real Time",
"productState": true
},
{
"productId": "6d580a60-daba-46cc-b3d9-6061aa48f0ff",
"productName": "ElasticSearch",
"productMemo": "Search,Document,Spatial,Vector",
"productState": true
},
{
"productId": "d4760f62-9b76-4c40-8a56-8c1e7214dfdd",
"productName": "Cassandra",
"productMemo": "Wide Column,Vector,Multi Model",
"productState": true
},
{
"productId": "ddfe8f96-c49f-4ad0-899a-9b3b484419d8",
"productName": "Qdrant",
"productMemo": "Vector,NN,Semantic Matching",
"productState": true
},
{
"productId": "cb99a803-2443-48ec-accb-5262ef9bc429",
"productName": "Trino",
"productMemo": "Relational,Document,Spatial,TS,KV",
"productState": true
}
]
-
http://localhost:8080/api/product/getAllProductProduct?productMemo=vector
-
关于自定义索引,后续章节说明,此处不再赘述;
select * from elf.product where product_memo like '%vector%';
----
[source,sql]
----
InvalidRequest: Error from server: code=2200 [Invalid query]
msg="like restriction only support on properly indexed column
product_memo LIKE '%vector%' is not valid."
Delete Product
-
请求方法:DELETE;
-
http://localhost:8080/api/deleteProductById/productId
http://localhost:8080/api/deleteProductById/6d580a60-daba-46cc-b3d9-6061aa48f0ff
Delete All Product
-
请求方法:DELETE;
-
http://localhost:8080/api/product/deleteAllProduct
Retrieve By State
-
请求方法:GET;
-
http://localhost:8080/api/product/getProductByState
[
{
"productId": "29af0738-4a17-4571-90cd-700ab8995db7",
"productName": "Milvus",
"productMemo": "Vector,Similarity Search,ML",
"productState": true
},
{
"productId": "741e5240-8a7b-40fa-b07c-e88b3638bf36",
"productName": "JanusGraph",
"productMemo": "Graph,Open Source",
"productState": true
},
{
"productId": "63608ee0-eaf1-41cf-970e-04238a556103",
"productName": "Weaviate",
"productMemo": "Vector,AI,Real Time",
"productState": true
},
{
"productId": "d4760f62-9b76-4c40-8a56-8c1e7214dfdd",
"productName": "Cassandra",
"productMemo": "Wide Column,Vector,Multi Model",
"productState": true
},
{
"productId": "ddfe8f96-c49f-4ad0-899a-9b3b484419d8",
"productName": "Qdrant",
"productMemo": "Vector,NN,Semantic Matching",
"productState": true
},
{
"productId": "cb99a803-2443-48ec-accb-5262ef9bc429",
"productName": "Trino",
"productMemo": "Relational,Document,Spatial,TS,KV",
"productState": true
}
]