這章節在介紹如何讓reactive Spring與資料庫連結。
若是使用關聯式資料庫,我們會引用R2DBC來做這件事,這個模組相當於Spring Data JDBC的功效:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>
大致的寫法其實就跟Spring Data一樣,只不過在不同物件的關聯上不像Spring Data JPA那麼聰明,我們會需要自訂一些做法在Service來達到物件彼此aggregate的儲存,所以在domain class的定義上,有一對多的關係時,我們得先設定成儲存該Id欄位,而不是直接儲存該class物件類別:
package demo;
import java.util.HashSet;
import java.util.Set;
import org.springframework.data.annotation.Id;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
@Data
@NoArgsConstructor
@RequiredArgsConstructor
public class Sword {
@Id
private Long id;
private @NonNull String name;
private Set<Long> ingredientIds = new HashSet<>();
public void addIngredient(Ingredient ingredient) {
ingredientIds.add(ingredient.getId());
}
}
不過這樣就得注意我們使用的資料庫有沒有支援array的欄位格式,目前已知PostgreSQL和H2是有支援的。
接下來我們來看看repository的設置:
package demo.data;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import demo.SwordOrder;
public interface OrderRepository
extends ReactiveCrudRepository<SwordOrder, Long> {
}
可以發現跟JPA的設置幾乎一樣,只差別在extends的介面變成ReactiveCrudRepository。
下面來看看這個repository會怎麼用:
@Autowired
OrderRepository orderRepo;
...
orderRepository.findAll()
.doOnNext(order -> {
System.out.println(
"Deliver to: " + order.getDeliveryName());
})
.subscribe();
剛剛也有提到由於R2DBC不像JPA那麼聰明的去把aggregate的物件彼此聯繫起來,所以會需要如下額外的code來達到目的:
@Data
public class SwordOrder {
...
@Transient
private transient List<Sword> swords = new ArrayList<>();
public void addSword(Sword sword) {
this.swords.add(sword);
if (sword.getId() != null) {
this.swordIds.add(sword.getId());
}
}
}
@Service
@RequiredArgsConstructor
public class SwordOrderAggregateService {
private final SwordRepository swordRepo;
private final OrderRepository orderRepo;
public Mono<SwordOrder> save(SwordOrder swordOrder) {
return Mono.just(swordOrder)
.flatMap(order -> {
List<Sword> swords = order.getSwords();
order.setSwords(new ArrayList<>());
return swordRepo.saveAll(swords)
.map(sword -> {
order.addSword(sword);
return order;
}).last();
})
.flatMap(orderRepo::save);
}
}
public Mono<SwordOrder> findById(Long id) {
return orderRepo
.findById(id)
.flatMap(order -> {
return swordRepo.findAllById(order.getSwordIds())
.map(taco -> {
order.addSword(taco);
return order;
}).last();
});
}