Preface
目前国内使用较多的持久层(关系型)有,mybatis,data-jpa/hibernate,data-jdbc/jdbc,r2dbc,
基于mybatis的有广泛的用户群,以前面试我说用hibernate和data-jdbc还被几家公司嘲笑了,
个人推荐:r2dbc > data-jdbc > jpa > mybatis,但reactive全套对成员平均水平要求较高,
很多思想一到两年内,大众很难达到一个相对成熟的水平,基于这方面不是很推荐,但必须是趋势;
第二个推荐的是data-jdbc,这个优势是多方面的,比如缓存,关联,领域驱动等,
目前spring data是把新增和修改合并在一起,统一使用save方法,这需要额外处理,
至于jpa是标准,但想要运用得很灵活还是比较难,hibernate封装得比较复杂,
mybatis既然有这么大的用户群体,就不是好不好的问题了,
只是说有些方面已经超出了面向对象编程,如果只是简单的crud,其实选啥都没多少关系,
相反spring-data抽象得比较好,尤其是对entity class,会有审计等等
<dependency>
<groupId>dev.miku</groupId>
<artifactId>r2dbc-mysql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
Persistence:Generic
public abstract class AbstractEntity implements Serializable,Persistable<String> {
private static final long serialVersionUID = -2396899308150035721L;
@Id
protected String id;
protected String status = Const.STATUS_ENABLE;
@CreatedBy
protected String creator;
@CreatedDate
protected LocalDateTime createTime = LocalDateTime.now();
@LastModifiedBy
protected String editor;
@LastModifiedDate
protected LocalDateTime editTime = LocalDateTime.now();
//不推荐使用此属性
@Transient
protected Boolean newFlag;
public final String getId() {
return id;
}
public final AbstractEntity setId(String id) {
this.id = id;
return this;
}
public final String getStatus() {
return status;
}
public final AbstractEntity setStatus(String status) {
this.status = status;
return this;
}
public final String getCreator() {
return creator;
}
public final AbstractEntity setCreator(String creator) {
this.creator = creator;
return this;
}
public final LocalDateTime getCreateTime() {
return createTime;
}
public final AbstractEntity setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
return this;
}
public final String getEditor() {
return editor;
}
public final AbstractEntity setEditor(String editor) {
this.editor = editor;
return this;
}
public final LocalDateTime getEditTime() {
return editTime;
}
public final AbstractEntity setEditTime(LocalDateTime editTime) {
this.editTime = editTime;
return this;
}
public final Boolean getNewFlag() {
return newFlag;
}
public final AbstractEntity setNewFlag(Boolean newFlag) {
this.newFlag = newFlag;
return this;
}
@Override
public boolean isNew() {
return Objects.isNull(id) || this.newFlag;
}
}
Persistence:Entity
public class Product extends AbstractEntity {
private static final long serialVersionUID = -3999986688148474587L;
private String productTypeId;
private String productName;
public Product() {}
public Product(String id,String productTypeId,String productName) {
this.id = id;
this.productTypeId = productTypeId;
this.productName = productName;
}
public final String getProductTypeId() {
return productTypeId;
}
public final void setProductTypeId(String productTypeId) {
this.productTypeId = productTypeId;
}
public final String getProductName() {
return productName;
}
public final void setProductName(String productName) {
this.productName = productName;
}
@Override
public int hashCode() {
return Objects.hash(productName,productTypeId,
id,status,creator,createTime,editor,editTime);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Product other = (Product) obj;
return Objects.equals(id, other.id)
&& Objects.equals(productName, other.productName)
&& Objects.equals(productTypeId, other.productTypeId)
&& Objects.equals(status, other.status)
&& Objects.equals(creator, other.creator)
&& Objects.equals(createTime, other.createTime)
&& Objects.equals(editor, other.editor)
&& Objects.equals(editTime, other.editTime);
}
}
Repository
public interface ProductRepository extends ReactiveCrudRepository<Product, String>{
@Query("select * from product where product_type_id = :productTypeId")
Flux<Product> getByProductTypeId(String productTypeId);
}
Service Api and RI
public interface ProductService {
Mono<Product> save(Product product);
Flux<Product> saveAll(Iterable<Product> productIterable);
Mono<Product> deleteById(String productId);
Mono<Product> getById(String productId);
Flux<Product> getAll();
Flux<Product> getByProductTypeId(String productTypeId);
}
@Service
public class ProductServiceImpl implements ProductService{
private ProductRepository productRepository;
@Autowired
public ProductServiceImpl(ProductRepository productRepository) {
this.productRepository = productRepository;
}
@Override
public Mono<Product> save(Product product) {
return productRepository.save(product);
}
@Override
public Flux<Product> saveAll(Iterable<Product> productIterable) {
return productRepository.saveAll(productIterable);
}
@Override
public Mono<Product> deleteById(String productId) {
return productRepository.findById(productId)
.flatMap(product ->
productRepository.deleteById(product.getId())
.thenReturn(product)
);
}
@Override
public Mono<Product> getById(String productId) {
return productRepository.findById(productId);
}
@Override
public Flux<Product> getAll() {
return productRepository.findAll();
}
@Override
public Flux<Product> getByProductTypeId(String productTypeId) {
return productRepository.getByProductTypeId(productTypeId);
}
}
Yaml Configuration
server:
port: 8080
servlet:
context-path: /r2dbc
spring:
r2dbc:
url: r2dbc:mysql://localhost:3306
name: test
username: root
password: our cipher code
properties:
locale: zh_US
useServerPrepareStatement: true
logging:
level:
org.springframework.data.r2dbc: debug
@DataR2dbcTest
@Import(ProductServiceImpl.class)
public class ProductServiceTest {
private static final Logger logger = LoggerFactory
.getLogger(MethodHandles.lookup().lookupClass());
private ProductService productService;
@Autowired
public ProductServiceTest(ProductService productService) {
this.productService = productService;
}
@Test
public void testSave() {
Product product = createProduct();
product.setNewFlag(Boolean.TRUE);
Mono<Product> mp = productService.save(product);
StepVerifier.create(mp)
.expectNextMatches(
p -> StringUtils.hasText(p.getProductName()))
.verifyComplete();
logger.info("save test!");
}
@Test
public void testUpdate() {
Product product = createProduct();
product.setNewFlag(Boolean.TRUE);
Mono<Product> mp = productService.save(product);
Product savedProduct = mp.block(Duration.ofSeconds(3));
String productName = "update product name";
savedProduct.setCreator("1");
savedProduct.setEditor("1");
savedProduct.setProductName(productName);
product.setNewFlag(Boolean.FALSE);
Mono<Product> ump = productService.save(savedProduct);
StepVerifier.create(ump)
.expectNextMatches(
p -> p.getProductName().equals(productName))
.verifyComplete();
logger.info("update test!");
}
@Test
public void testDelete() {
Product product = createProduct();
product.setNewFlag(Boolean.TRUE);
Mono<Product> mp = productService.save(product)
.flatMap(p -> productService.deleteById(product.getId()));
StepVerifier.create(mp)
.expectNextMatches (
p -> p.getProductName().equals("elf"))
.verifyComplete();
logger.info("delete test!");
}
@Test
public void testRetrieve() {
Product product = createProduct();
product.setNewFlag(Boolean.TRUE);
Mono<Product> mp = productService.save(product)
.flatMap(p -> productService.getById(product.getId()));
StepVerifier.create(mp)
.expectNextMatches (
p -> p.getProductName().equals("elf"))
.verifyComplete();
logger.info("retrieve test!");
}
private Product createProduct() {
String uuid = UUID.randomUUID().toString();
return new Product(uuid.substring(0,20), "1","elf");
}
}
Tbd...