ตัวอย่างการใช้ R2dbcEntityTemplate เพื่อ 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
- R2dbcEntityTemplate เป็น Level Up ของ DatabaseClient ทำให้การ Manual Query ทำได้ง่ายขึ้นไปอีก
เว็บไซต์
- เตรียมฐานข้อมูล PostgreSQL ให้พร้อม
docker run -d -p 5432:5432 --name postgres -e POSTGRES_PASSWORD=password postgres
- สร้าง schema
app
- สร้าง table
user
ที่ schemaapp
โดยใช้ SQL นี้
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=app
spring.r2dbc.username=postgres
spring.r2dbc.password=password
เพื่อให้สามารถใช้ R2dbcEntityTemplate
ได้
@Slf4j
@Configuration
public class R2dbcConfig {
@Bean
public R2dbcEntityTemplate r2dbcEntityTemplate(final DatabaseClient databaseClient){
return new R2dbcEntityTemplate(databaseClient.getConnectionFactory());
}
}
Entity จะเป็นตัว Map ไปยัง Table
app.user
@Data
@Builder
@Table("app.user")
public class UserEntity {
//Primary Key
@Id
private UUID id;
private String username;
@Column("first_name")
private String firstName;
@Column("last_name")
private String lastName;
}
คำอธิบาย
- Annotation ต่าง ๆ ที่ใช้ ไม่ได้เป็นของ
javax.persistence.*
แต่เป็น Annotation ของ Spring-data เอง - ความสามารถของ Annotation จะไม่เท่ากับใน
javax.persistence.*
คือ ไม่สามารถกำหนด length, nullable ไม่สามาถทำ Join ต่าง ๆ ได้ ทำได้อย่างเดียวคือ Mapping Table/Column และกำหนด Primary Key ได้เท่านั้น - ความสามารถเรื่องการ Join หรือ Constraint ต่าง ๆ จะใช้ Native SQL ทำเป็นหลัก
เรื่อง Annotation ที่สามารถใช้ได้ ให้ดูจากเอกสารหน้านี้ https://docs.spring.io/spring-data/r2dbc/docs/1.1.4.RELEASE/reference/html/#mapping.usage.annotations
ในตัวอย่างนี้ เราจะ 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 R2dbcEntityTemplate r2dbcEntityTemplate;
@Override
public Flux<UserEntity> findAll() {
final Query query = Query.empty();
return r2dbcEntityTemplate.select(query, UserEntity.class);
}
@Override
public Mono<UserEntity> findById(final UUID id) {
final Query query = Query.query(where("id").is(id));
return r2dbcEntityTemplate.select(query, UserEntity.class)
.next()
.switchIfEmpty(Mono.error(new NotFoundException("User id \"" + id.toString() + "\"not found")));
}
@Override
public Mono<UserEntity> create(final UserEntity entity) {
entity.setId(UUID.randomUUID());
return r2dbcEntityTemplate.insert(entity);
}
...
}
หมายเหตุ
- จากตัวอย่างด้านบน จะเห็นว่าเราใช้
R2dbcEntityTemplate
Manual Query เองทั้งหมดเลย - บนหัว 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