ตัวอย่างการใช้ Database Client เพื่อเขียน Native SQL Query ข้อมูลสำหรับ Spring-boot Reactive R2DBC (The Reactive Relational Database Connectivity)
- R2DBC (The Reactive Relational Database Connectivity) เป็น Library/Dependency ฝั่งภาษา Java สำหรับการเขียน Code เพื่อเชื่อมต่อไปยัง Database แบบ Reactive (Non-Block I/O)
- มี Spring-data รองรับ เพื่อให้สามารถเขียน CRUD และเขียน Query อื่น ๆ ได้ง่ายขึ้น
- DatabaseClient เป็น Class/Component นึงของ R2DBC เพื่อใช้สำหรับ Query ข้อมูลจาก Database เองแบบ Manual
เว็บไซต์
- เตรียมฐานข้อมูล PostgreSQL ให้พร้อม
docker run -d -p 5432:5432 --name postgres -e POSTGRES_PASSWORD=password postgres
- สร้าง schema
app
- สร้าง table
user
ที่ schemaapp
โดยใช้ SQL นี้
CREATE SCHEMA "app";
CREATE TABLE "app"."user" (
"id" UUID NOT NULL,
"username" varchar(50) NOT NULL,
"first_name" varchar(50) NOT NULL,
"last_name" varchar(50) NOT NULL,
PRIMARY KEY ("id")
);
pom.xml
...
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.1</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<!-- Database ****************************************************** -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>r2dbc-postgresql</artifactId>
<version>1.0.4.RELEASE</version>
</dependency>
<!-- Database ****************************************************** -->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<id>build-info</id>
<goals>
<goal>build-info</goal>
</goals>
<configuration>
<additionalProperties>
<java.version>${java.version}</java.version>
</additionalProperties>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
คำอธิบาย
r2dbc-postgresql
เป็น dependency r2dbc สำหรับ postgresqlspring-boot-starter-data-r2dbc
เป็น dependency สำหรับใช้ spring-data ร่วมกับ r2dbc
@SpringBootApplication
public class AppStarter {
public static void main(String[] args) {
SpringApplication.run(AppStarter.class, args);
}
}
classpath:application.properties
#---------------------------------- Logging ------------------------------------
logging.level.me.jittagornp=DEBUG
logging.level.org.springframework.data.r2dbc=DEBUG
#---------------------------------- R2dbc --------------------------------------
spring.r2dbc.url=r2dbc:postgresql://localhost/postgres?schema=public
spring.r2dbc.username=postgres
spring.r2dbc.password=password
@Data
@Builder
public class UserEntity {
private UUID id;
private String username;
private String firstName;
private String lastName;
}
ในตัวอย่างนี้ เราจะ Manual Repository เอง
ประกาศ interface
public interface UserRepository {
Flux<UserEntity> findAll();
Mono<UserEntity> findById(final UUID id);
Mono<UserEntity> create(final UserEntity entity);
Mono<UserEntity> update(final UserEntity entity);
Mono<Void> deleteAll();
Mono<Void> deleteById(final UUID id);
}
implement interface
@Repository
@RequiredArgsConstructor
public class UserRepositoryImpl implements UserRepository {
private final DatabaseClient databaseClient;
@Override
public Flux<UserEntity> findAll() {
return databaseClient.sql("SELECT * FROM app.user")
.map(this::convert)
.all();
}
@Override
public Mono<UserEntity> findById(final UUID id) {
return databaseClient.sql("SELECT * FROM app.user WHERE id = :id")
.bind("id", id)
.map(this::convert)
.one()
.switchIfEmpty(Mono.error(new NotFoundException("User id \"" + id.toString() + "\"not found")));
}
@Override
public Mono<UserEntity> create(final UserEntity entity) {
entity.setId(UUID.randomUUID());
return databaseClient.sql(
"INSERT INTO app.user (id, username, first_name, last_name) " +
"VALUES (:id, :username, :first_name, :last_name)"
)
.bind("id", entity.getId())
.bind("username", entity.getUsername())
.bind("first_name", entity.getFirstName())
.bind("last_name", entity.getLastName())
.then()
.thenReturn(entity);
}
...
}
หมายเหตุ
- จากตัวอย่างด้านบน จะเห็นว่าเราใช้
DatabaseClient
Manual Native SQL เองทั้งหมดเลย - บนหัว implmentation (class) แปะด้วย
@Repository
เพื่อบอกว่าอันนี้เป็น repository น่ะ
@RestController
@RequestMapping("/users")
@RequiredArgsConstructor
public class UserController {
private final UserRepository userRepository;
@GetMapping
public Flux<UserEntity> findAll() {
return userRepository.findAll();
}
@GetMapping("/{id}")
public Mono<UserEntity> findById(@PathVariable("id") final UUID id) {
return userRepository.findById(id);
}
@ResponseStatus(HttpStatus.CREATED)
@PostMapping
public Mono<UserEntity> create(@RequestBody final UserEntity entity) {
return userRepository.create(entity);
}
@PutMapping("/{id}")
public Mono<UserEntity> update(@PathVariable("id") final UUID id, @RequestBody final UserEntity entity) {
entity.setId(id);
return userRepository.update(entity);
}
@ResponseStatus(HttpStatus.NO_CONTENT)
@DeleteMapping("/{id}")
public Mono<Void> deleteById(@PathVariable("id") final UUID id) {
return userRepository.deleteById(id);
}
}
cd ไปที่ root ของ project จากนั้น
$ mvn clean package
$ mvn spring-boot:run
Create User
Get all Users
Select Users from table