Faunadb和google spanner都属于云分布式数据库天然支持分片(无需做分表分库操作,一库搞定,当然价格另说),国内的也有比如TiDB Oceanbase等
本文使用java语言,其他语言可以跳过;有想直接使用的可以参考(无法访问外网,可以搞个vpn吧!!!,有时会遇到网络问题):GitHub - fauna/faunadb-jvm: Scala and Java driver for FaunaDB v4
此文旨在想了解的小伙伴看看(免费使用30天)
本文演示使用的jdk版本为jdk21
目录
1.登录账号
2.了解一下FQL
3. 创建数据库,创建集合
4.点击搜索框中的dashbord进入到控制台然后到控制台创建集合
5. 生成数据库秘钥
6.springboot整合项目
7.实体
8.service及接口
9.属性文件配置属性
10.controller
11.启动后postman试试
1.登录账号
使用github账号或者注册一个
Welcome to Fauna docs - Fauna Documentation
2.了解一下FQL
建议按照图看下去
3. 创建数据库,创建集合
最好是跟着官网文档走
4.点击搜索框中的dashbord进入到控制台然后到控制台创建集合
(参考:使用 Spring Boot 使用 Fauna 和 Java 开始构建_rxjava_云O生-云原生)
最新的不一样,使用下面语法
Collection.create({
name: 'todos'
})
5. 生成数据库秘钥
记住不要到account哪里去申请
6.springboot整合项目
依赖
<dependency>
<groupId>com.faunadb</groupId>
<artifactId>faunadb-java</artifactId>
<version>4.4.0</version>
<scope>compile</scope>
</dependency>
实体\service\controller均参考博文:使用 Spring Boot 使用 Fauna 和 Java 开始构建_rxjava_云O生-云原生
7.实体
参考这个也行:GitHub - fauna/faunadb-jvm: Scala and Java driver for FaunaDB v4
import lombok.Data;
@Data
public abstract class Entity {
protected String id;
}
import java.util.List;
import java.util.Optional;
public class Page <T> {
private List<T> data;
private Optional<String> before;
private Optional<String> after;
public Page(List<T> data, Optional<String> before, Optional<String> after) {
this.data = data;
this.before = before;
this.after = after;
}
public List<T> getData() {
return data;
}
public void setData(List<T> data) {
this.data = data;
}
public Optional<String> getBefore() {
return before;
}
public void setBefore(Optional<String> before) {
this.before = before;
}
public Optional<String> getAfter() {
return after;
}
public void setAfter(Optional<String> after) {
this.after = after;
}
}
package com.rulecheck.entity;
import java.util.Optional;
public class PaginationOptions {
private Optional<Integer> size;
private Optional<String> before;
private Optional<String> after;
public PaginationOptions(Optional<Integer> size, Optional<String> before, Optional<String> after) {
this.size = size;
this.before = before;
this.after = after;
}
public Optional<Integer> getSize() {
return size;
}
public void setSize(Optional<Integer> size) {
this.size = size;
}
public Optional<String> getBefore() {
return before;
}
public void setBefore(Optional<String> before) {
this.before = before;
}
public Optional<String> getAfter() {
return after;
}
public void setAfter(Optional<String> after) {
this.after = after;
}
}
import com.faunadb.client.types.FaunaConstructor;
import com.faunadb.client.types.FaunaField;
public class TodoEntity extends Entity {
@FaunaField
private String title;
@FaunaField
private String description;
@FaunaConstructor
public TodoEntity(@FaunaField("id") String id,
@FaunaField("title") String title,
@FaunaField("description") String description) {
this.id = id;
this.title = title;
this.description = description;
}
public void setTitle(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
package com.rulecheck.entity;
public class CreateOrUpdateTodoData {
private String title;
private String description;
public CreateOrUpdateTodoData(String title, String description) {
this.title = title;
this.description = description;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
8.service及接口
package com.rulecheck.service;
import com.faunadb.client.FaunaClient;
import com.faunadb.client.errors.NotFoundException;
import com.faunadb.client.query.Expr;
import com.faunadb.client.query.Pagination;
import com.faunadb.client.types.Value;
import com.rulecheck.entity.Entity;
import com.rulecheck.entity.Page;
import com.rulecheck.entity.PaginationOptions;
import jakarta.annotation.Resource;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.stream.Collectors;
import static com.faunadb.client.query.Language.*;
import java.lang.Class;
public abstract class FaunaRepository<T extends Entity > implements Repository<T>, IdentityFactory {
@Resource
private FaunaClient faunaClient;
protected final Class<T> entityType;
protected final String collectionName;
protected final String collectionIndexName;
protected FaunaRepository(Class<T> entityType, String collectionName, String collectionIndexName) {
this.entityType = entityType;
this.collectionName = collectionName;
this.collectionIndexName = collectionIndexName;
}
// This method returns a unique valid Id leveraging Fauna's NewId function.
@Override
public CompletableFuture<String> nextId() {
CompletableFuture<String> result =
faunaClient.query(
NewId()
)
.thenApply(value -> value.to(String.class).get());
return result;
}
// This method saves an entity to the database using the saveQuery method below. It also returns the result of the saved entity.
@Override
public CompletableFuture<T> save(T entity) {
CompletableFuture<T> result =
faunaClient.query(
saveQuery(Value(entity.getId()), Value(entity))
)
.thenApply(this::toEntity);
return result;
}
// This method deletes from the data an entity(document) with the specified Id.
@Override
public CompletableFuture<Optional<T>> remove(String id) {
CompletableFuture<T> result =
faunaClient.query(
Select(
Value("data"),
Delete(Ref(Collection(collectionName), Value(id)))
)
)
.thenApply(this::toEntity);
CompletableFuture<Optional<T>> optionalResult = toOptionalResult(result);
return optionalResult;
}
// This method finds an entity by its Id and returns the entity result.
@Override
public CompletableFuture<Optional<T>> find(String id) {
CompletableFuture<T> result =
faunaClient.query(
Select(
Value("data"),
Get(Ref(Collection(collectionName), Value(id)))
)
)
.thenApply(this::toEntity);
CompletableFuture<Optional<T>> optionalResult = toOptionalResult(result);
return optionalResult;
}
// This method returns all entities(documents) in the database collection using the paginationOptions parameters.
@Override
public CompletableFuture<Page<T>> findAll(PaginationOptions po) {
Pagination paginationQuery = Paginate(Match(Index(Value(collectionIndexName))));
po.getSize().ifPresent(size -> paginationQuery.size(size));
po.getAfter().ifPresent(after -> paginationQuery.after(Ref(Collection(collectionName), Value(after))));
po.getBefore().ifPresent(before -> paginationQuery.before(Ref(Collection(collectionName), Value(before))));
CompletableFuture<Page<T>> result =
faunaClient.query(
Map(
paginationQuery,
Lambda(Value("nextRef"), Select(Value("data"), Get(Var("nextRef"))))
)
).thenApply(this::toPage);
return result;
}
// This is the saveQuery expression method used by the save method to persist the database.
protected Expr saveQuery(Expr id, Expr data) {
Expr query =
Select(
Value("data"),
If(
Exists(Ref(Collection(collectionName), id)),
Replace(Ref(Collection(collectionName), id), Obj("data", data)),
Create(Ref(Collection(collectionName), id), Obj("data", data))
)
);
return query;
}
// This method converts a FaunaDB Value into an Entity.
protected T toEntity(Value value) {
return value.to(entityType).get();
}
// This method returns an optionalResult from a CompletableFuture<T> result.
protected CompletableFuture<Optional<T>> toOptionalResult(CompletableFuture<T> result) {
CompletableFuture<Optional<T>> optionalResult =
result.handle((v, t) -> {
CompletableFuture<Optional<T>> r = new CompletableFuture<>();
if(v != null) r.complete(Optional.of(v));
else if(t != null && t.getCause() instanceof NotFoundException) r.complete(Optional.empty());
else r.completeExceptionally(t);
return r;
}).thenCompose(Function.identity());
return optionalResult;
}
// This method converts a FaunaDB Value into a Page with the Entity type.
protected Page<T> toPage(Value value) {
Optional<String> after = value.at("after").asCollectionOf(Value.RefV.class).map(c -> c.iterator().next().getId()).getOptional();
Optional<String> before = value.at("before").asCollectionOf(Value.RefV.class).map(c -> c.iterator().next().getId()).getOptional();
List<T> data = value.at("data").collect(entityType).stream().collect(Collectors.toList());
Page<T> page = new Page(data, before, after);
return page;
}
}
import java.util.concurrent.CompletableFuture;
public interface IdentityFactory {
CompletableFuture<String> nextId();
}
import com.rulecheck.entity.Entity;
import com.rulecheck.entity.Page;
import com.rulecheck.entity.PaginationOptions;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
public interface Repository<T extends Entity> {
// This method saves the given Entity into the Repository.
CompletableFuture<T> save(T entity);
// This method finds an Entity for the given Id
CompletableFuture<Optional<T>> find(String id);
// This method retrieves a Page of TodoEntity entities for the given PaginationOptions
CompletableFuture<Page<T>> findAll(PaginationOptions po);
// This method finds the Entity for the given Id and removes it. If no Entity can be found for the given Id an empty result is returned.
CompletableFuture<Optional<T>> remove(String id);
}
import com.rulecheck.entity.TodoEntity;
import org.springframework.stereotype.Repository;
@Repository
public class TodoRepository extends FaunaRepository<TodoEntity> {
public TodoRepository(){
super(TodoEntity.class, "todos", "all_todos");
}
//-- Custom repository operations specific to the TodoEntity will go below --//
}
import com.rulecheck.entity.CreateOrUpdateTodoData;
import com.rulecheck.entity.Page;
import com.rulecheck.entity.PaginationOptions;
import com.rulecheck.entity.TodoEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@Service
public class TodoService {
@Autowired
private TodoRepository todoRepository;
public CompletableFuture<TodoEntity> createTodo(CreateOrUpdateTodoData data) {
CompletableFuture<TodoEntity> result =
todoRepository.nextId()
.thenApply(id -> new TodoEntity(id, data.getTitle(), data.getDescription()))
.thenCompose(todoEntity -> todoRepository.save(todoEntity));
return result;
}
public CompletableFuture<Optional<TodoEntity>> getTodo(String id) {
return todoRepository.find(id);
}
public CompletableFuture<Optional<TodoEntity>> updateTodo(String id, CreateOrUpdateTodoData data) {
CompletableFuture<Optional<TodoEntity>> result =
todoRepository.find(id)
.thenCompose(optionalTodoEntity ->
optionalTodoEntity
.map(todoEntity -> todoRepository.save(new TodoEntity(id, data.getTitle(), data.getDescription())).thenApply(Optional::of))
.orElseGet(() -> CompletableFuture.completedFuture(Optional.empty())));
return result;
}
public CompletableFuture<Optional<TodoEntity>> deleteTodo(String id) {
return todoRepository.remove(id);
}
public CompletableFuture<Page<TodoEntity>> getAllTodos(PaginationOptions po) {
return todoRepository.findAll(po);
}
}
import com.faunadb.client.FaunaClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
/**
* description:
*/
@Slf4j
@SpringBootApplication
public class RuleCheckApplication {
@Value("${fauna-db.secret}")
private String serverKey;
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
public FaunaClient faunaConfiguration() {
log.info("serverKey:{}", serverKey);
FaunaClient faunaClient = FaunaClient.builder()
.withSecret(serverKey)
.build();
return faunaClient;
}
public static void main(String[] args) {
SpringApplication.run(RuleCheckApplication.class, args);
}
}
9.属性文件配置属性
fauna-db.secret=fnAFN6K4SxAAQWm......... 自己生成数据库秘钥,非账户秘钥或者密码
10.controller
package com.rulecheck.controller;
import com.rulecheck.entity.CreateOrUpdateTodoData;
import com.rulecheck.entity.Page;
import com.rulecheck.entity.PaginationOptions;
import com.rulecheck.entity.TodoEntity;
import com.rulecheck.service.TodoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@RestController
public class TodoRestController {
@Autowired
private TodoService todoService;
@PostMapping("/todos")
public CompletableFuture<ResponseEntity> createTodo(@RequestBody CreateOrUpdateTodoData data) {
return todoService.createTodo(data)
.thenApply(todoEntity -> new ResponseEntity(todoEntity, HttpStatus.CREATED));
}
@GetMapping("/todos/{id}")
public CompletableFuture<ResponseEntity> getTodo(@PathVariable("id") String id) {
CompletableFuture<ResponseEntity> result =
todoService.getTodo(id)
.thenApply(optionalTodoEntity ->
optionalTodoEntity
.map(todoEntity -> new ResponseEntity(todoEntity, HttpStatus.OK))
.orElseGet(() -> new ResponseEntity(HttpStatus.NOT_FOUND))
);
return result;
}
@PutMapping("/todos/{id}")
public CompletableFuture<ResponseEntity> updateTodo(@PathVariable("id") String id, @RequestBody CreateOrUpdateTodoData data) {
CompletableFuture<ResponseEntity> result =
todoService.updateTodo(id, data)
.thenApply(optionalTodoEntity ->
optionalTodoEntity
.map(todoEntity -> new ResponseEntity(todoEntity, HttpStatus.OK))
.orElseGet(() -> new ResponseEntity(HttpStatus.NOT_FOUND)
)
);
return result;
}
@DeleteMapping(value = "/todos/{id}")
public CompletableFuture<ResponseEntity> deletePost(@PathVariable("id")String id) {
CompletableFuture<ResponseEntity> result =
todoService.deleteTodo(id)
.thenApply(optionalTodoEntity ->
optionalTodoEntity
.map(todo -> new ResponseEntity(todo, HttpStatus.OK))
.orElseGet(() -> new ResponseEntity(HttpStatus.NOT_FOUND)
)
);
return result;
}
@GetMapping("/todos")
public CompletableFuture<Page<TodoEntity>> getAllTodos(
@RequestParam("size") Optional<Integer> size,
@RequestParam("before") Optional<String> before,
@RequestParam("after") Optional<String> after) {
PaginationOptions po = new PaginationOptions(size, before, after);
CompletableFuture<Page<TodoEntity>> result = todoService.getAllTodos(po);
return result;
}
}
11. 参考pom依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.3</version>
<relativePath></relativePath>
</parent>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<mybatis-plus-boot-starter>3.5.3.1</mybatis-plus-boot-starter>
<mysql-connector-java>8.0.28</mysql-connector-java>
<commons-io>2.11.0</commons-io>
</properties>
<!-- <packaging>jar</packaging>-->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus-boot-starter}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector-java}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>${mybatis-plus-boot-starter}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons-io}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.faunadb</groupId>
<artifactId>faunadb-java</artifactId>
<version>4.4.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
11.启动后postman试试
http://localhost:8080/todos
{
"title":"create a job",
"description":"this post request is todos"
}