From 5095ae5cfb67bcfb1d20cd7a170eb22c79da9757 Mon Sep 17 00:00:00 2001
From: blinkfox <1181062873@qq.com>
Date: Mon, 28 Mar 2022 12:17:08 +0800
Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=20Active=20Record=20?=
=?UTF-8?q?=E7=9B=B8=E5=85=B3=E7=9A=84=20FenixModel=20=E7=9A=84=E4=BB=A3?=
=?UTF-8?q?=E7=A0=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
blinkfox-checks.xml | 4 +-
checkstyle-suppressions.xml | 11 --
.../com/blinkfox/fenix/ar/FenixModel.java | 102 ++++++++++++++++--
.../fenix/jpa/FenixSimpleJpaRepository.java | 4 +-
.../blinkfox/fenix/entity/ar/ArEntity.java | 68 ++++++++++++
.../repository/ar/ArEntityRepository.java | 16 +++
.../repository/ar/ArEntityRepositoryTest.java | 66 ++++++++++++
.../repository/ar/BaseRepositoryTest.java | 44 ++++++++
8 files changed, 289 insertions(+), 26 deletions(-)
delete mode 100644 checkstyle-suppressions.xml
create mode 100644 src/test/java/com/blinkfox/fenix/entity/ar/ArEntity.java
create mode 100644 src/test/java/com/blinkfox/fenix/repository/ar/ArEntityRepository.java
create mode 100644 src/test/java/com/blinkfox/fenix/repository/ar/ArEntityRepositoryTest.java
create mode 100644 src/test/java/com/blinkfox/fenix/repository/ar/BaseRepositoryTest.java
diff --git a/blinkfox-checks.xml b/blinkfox-checks.xml
index eda380e8..cb197f95 100644
--- a/blinkfox-checks.xml
+++ b/blinkfox-checks.xml
@@ -249,7 +249,7 @@
-
+
@@ -271,7 +271,7 @@
-
+
diff --git a/checkstyle-suppressions.xml b/checkstyle-suppressions.xml
deleted file mode 100644
index e6ad5c31..00000000
--- a/checkstyle-suppressions.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/src/main/java/com/blinkfox/fenix/ar/FenixModel.java b/src/main/java/com/blinkfox/fenix/ar/FenixModel.java
index 428b89c3..c02f3dbc 100644
--- a/src/main/java/com/blinkfox/fenix/ar/FenixModel.java
+++ b/src/main/java/com/blinkfox/fenix/ar/FenixModel.java
@@ -7,11 +7,15 @@
import lombok.Setter;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
+import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.CrudRepository;
import org.springframework.transaction.annotation.Transactional;
/**
- * Fenix 提供的 ActiveRecord 模式的基础 Model 类. TODO 待进一步开发...
+ * Fenix 提供的 ActiveRecord 模式的基础 Model 类.
+ *
+ *
本类仅提供操作单个实体对象的若干快捷方法,如果你想进行批量操作或者查询,可以通过本类中的 {@link #getRepository()}、
+ * {@link #getJpaRepository()} 或者 {@link #getFenixJpaRepository()} 等方法来调用或实现.
*
* @param 实体类的的泛型参数
* @param 主键 ID
@@ -32,11 +36,14 @@ public abstract class FenixModel {
private CrudRepository repository;
/**
- * 获取实体类的 ID 值.
+ * 获取本实体对象的 ID 值.
+ *
+ * 通常建议将实体类的 ID 字段取名为 {@code id},这样就能自动重写该字段的值,
+ * 否则当你继承本抽象类时,就需要你自己手动实现此方法.
*
- * @return ID
+ * @return ID 值
*/
- abstract ID getEntityId();
+ public abstract ID getId();
/**
* 获取本实体类所对应的 Repository 对象在 Spring 中 Bean 的名称.
@@ -61,7 +68,7 @@ private String getRepositoryClassName() {
}
/**
- * 获取本实体类所对应的 Repository 对象
+ * 获取本实体类所对应的 Repository 对象.
*
* @return 基本的 CrudRepository 对象
*/
@@ -104,6 +111,23 @@ private void initCheckAndSetRepository() {
}
}
+ /**
+ * 获取本实体类所对应的 {@link JpaRepository} 的对象实例.
+ *
+ * 注意:要能真正获取到 {@link JpaRepository} 的对象实例,你需要确保你实体类所对应的 Repository 接口
+ * 是继承了 {@link JpaRepository} 接口的,否则会抛出 {@link FenixException} 异常.
+ *
+ * @return 本实体类的 JpaRepository 对象实例
+ */
+ public JpaRepository getJpaRepository() {
+ CrudRepository crudRepository = this.getRepository();
+ if (crudRepository instanceof JpaRepository) {
+ return (JpaRepository) crudRepository;
+ }
+ throw new FenixException("【Fenix 异常】未获取到本实体类所对应的 JpaRepository 的对象实例,请确保你实体类所对应的 "
+ + "Repository 接口已经继承了 JpaRepository 接口!");
+ }
+
/**
* 获取本实体类所对应的 {@link FenixJpaRepository} 的对象实例.
*
@@ -118,7 +142,7 @@ public FenixJpaRepository getFenixJpaRepository() {
return (FenixJpaRepository) crudRepository;
}
throw new FenixException("【Fenix 异常】未获取到本实体类所对应的 FenixJpaRepository 的对象实例,请确保你实体类所对应的 "
- + "Repository 接口是继承了 FenixJpaRepository 接口的!");
+ + "Repository 接口已经继承了 FenixJpaRepository 接口!");
}
/**
@@ -133,20 +157,68 @@ public T save() {
}
/**
- * 保存本实体对象.
+ * 立即刷新(提交)数据到数据库中.
+ */
+ @Transactional
+ public void flush() {
+ this.getJpaRepository().flush();
+ }
+
+ /**
+ * 保存并刷新(提交)本实体对象的数据到数据库中.
*
* @return 保存后的对象
*/
@Transactional
- public Iterable saveAll(Iterable entities) {
- return this.getRepository().saveAll(entities);
+ @SuppressWarnings("unchecked")
+ public S saveAndFlush() {
+ return this.getJpaRepository().saveAndFlush((S) this);
}
/**
- * 根据本实体对象的 ID 查找数据库中的完整的实体对象记录信息.
+ * 新增或更新本实体对象中所有非 null 属性的字段值.
+ *
+ * 注意:该方法保存每条数据时会先查询判断本对象在数据库中是否存在,再根据是否存在的结果再进行插入或者更新.
+ *
+ *
+ * - 如果实体对象的主键 ID 为空,说明是新增的情况,就插入一条新的数据;
+ * - 如果实体对象的主键 ID 不为空,会先判断是否存在该 ID 的数据,如果不存在也会新增插入一条数据;
+ * 否则说明是更新的情况,会仅更新实体类属性中不为 null 值的属性字段到数据库中;
+ *
+ *
+ * @return 原实体类,注意:如果是更新的情况,返回的值不一定有数据库中之前的值
+ */
+ @Transactional
+ @SuppressWarnings("unchecked")
+ public S saveOrUpdateByNotNullProperties() {
+ return this.getFenixJpaRepository().saveOrUpdateByNotNullProperties((S) this);
+ }
+
+ /**
+ * 根据本实体对象的 ID 查找数据库中的完整的实体对象记录信息,本实体对象信息会以 Optional 来包裹返回.
+ *
+ * @return 本实体对象的 Optional 对象
*/
public Optional findById() {
- return this.getRepository().findById(this.getEntityId());
+ return this.getRepository().findById(this.getId());
+ }
+
+ /**
+ * 根据本实体对象的 ID 查找数据库中的完整的实体对象记录信息.
+ *
+ * @return 本实体对象信息
+ */
+ public T getById() {
+ return this.getJpaRepository().getById(this.getId());
+ }
+
+ /**
+ * 根据本实体对象的 ID 查找数据库中是否存在该对象.
+ *
+ * @return 布尔值
+ */
+ public boolean existsById() {
+ return this.getRepository().existsById(this.getId());
}
/**
@@ -158,4 +230,12 @@ public void delete() {
this.getRepository().delete((T) this);
}
+ /**
+ * 根据本实体对象的 ID 值查找并删除此对象.
+ */
+ @Transactional
+ public void deleteById() {
+ this.getRepository().deleteById(this.getId());
+ }
+
}
diff --git a/src/main/java/com/blinkfox/fenix/jpa/FenixSimpleJpaRepository.java b/src/main/java/com/blinkfox/fenix/jpa/FenixSimpleJpaRepository.java
index c1cccac2..4e8cd6a6 100644
--- a/src/main/java/com/blinkfox/fenix/jpa/FenixSimpleJpaRepository.java
+++ b/src/main/java/com/blinkfox/fenix/jpa/FenixSimpleJpaRepository.java
@@ -146,6 +146,7 @@ public void updateBatch(Iterable entities, int batchSize) {
* @param 泛型实体类
* @return 原实体类,注意:如果是更新的情况,返回的值不一定有数据库中之前的值
*/
+ @SuppressWarnings("unchecked")
@Transactional(rollbackFor = RuntimeException.class)
@Override
public S saveOrUpdateByNotNullProperties(S entity) {
@@ -284,8 +285,7 @@ private String[] getNullProperties(Object entity) {
List nullProperties = new ArrayList<>();
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
String propertyName = propertyDescriptor.getName();
- Object propertyValue = beanWrapper.getPropertyValue(propertyName);
- if (propertyValue == null) {
+ if (beanWrapper.getPropertyValue(propertyName) == null) {
nullProperties.add(propertyName);
}
}
diff --git a/src/test/java/com/blinkfox/fenix/entity/ar/ArEntity.java b/src/test/java/com/blinkfox/fenix/entity/ar/ArEntity.java
new file mode 100644
index 00000000..7098d37d
--- /dev/null
+++ b/src/test/java/com/blinkfox/fenix/entity/ar/ArEntity.java
@@ -0,0 +1,68 @@
+package com.blinkfox.fenix.entity.ar;
+
+import com.blinkfox.fenix.ar.FenixModel;
+import java.time.LocalDateTime;
+import java.util.Date;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+import org.hibernate.annotations.GenericGenerator;
+
+/**
+ * 用来测试 Active Record 的实体类.
+ *
+ * @author blinkfox on 2022-03-28.
+ * @since 2.7.0
+ */
+@Getter
+@Setter
+@Accessors(chain = true)
+@Entity
+@Table(name = "t_ar_table")
+public class ArEntity extends FenixModel {
+
+ /**
+ * ID.
+ */
+ @Id
+ @Column(name = "c_id")
+ @GeneratedValue(generator = "nanoId")
+ @GenericGenerator(name = "nanoId", strategy = "com.blinkfox.fenix.id.NanoIdGenerator")
+ private String id;
+
+ /**
+ * 标题.
+ */
+ @Column(name = "c_title")
+ private String title;
+
+ /**
+ * 描述.
+ */
+ @Column(name = "c_desc")
+ private String desc;
+
+ /**
+ * 年龄.
+ */
+ @Column(name = "n_age")
+ private Integer age;
+
+ /**
+ * 创建时间.
+ */
+ @Column(name = "dt_create_time")
+ private Date createTime;
+
+ /**
+ * 最后更新时间.
+ */
+ @Column(name = "dt_update_time")
+ private LocalDateTime lastUpdateTime;
+
+}
diff --git a/src/test/java/com/blinkfox/fenix/repository/ar/ArEntityRepository.java b/src/test/java/com/blinkfox/fenix/repository/ar/ArEntityRepository.java
new file mode 100644
index 00000000..7bdbb5c4
--- /dev/null
+++ b/src/test/java/com/blinkfox/fenix/repository/ar/ArEntityRepository.java
@@ -0,0 +1,16 @@
+package com.blinkfox.fenix.repository.ar;
+
+import com.blinkfox.fenix.entity.ar.ArEntity;
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.stereotype.Repository;
+
+/**
+ * ArEntity 所对应的 Repository.
+ *
+ * @author blinkfox on 2022-03-28.
+ * @since 1.0.0
+ */
+@Repository
+public interface ArEntityRepository extends CrudRepository {
+
+}
diff --git a/src/test/java/com/blinkfox/fenix/repository/ar/ArEntityRepositoryTest.java b/src/test/java/com/blinkfox/fenix/repository/ar/ArEntityRepositoryTest.java
new file mode 100644
index 00000000..452e5b39
--- /dev/null
+++ b/src/test/java/com/blinkfox/fenix/repository/ar/ArEntityRepositoryTest.java
@@ -0,0 +1,66 @@
+package com.blinkfox.fenix.repository.ar;
+
+import com.blinkfox.fenix.FenixTestApplication;
+import com.blinkfox.fenix.entity.ar.ArEntity;
+import java.time.LocalDateTime;
+import java.util.Date;
+import java.util.Optional;
+import javax.annotation.PostConstruct;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * ArEntityRepository 的单元测试类.
+ *
+ * @author blinkfox on 2022-03-28.
+ * @since v2.7.0
+ */
+@Slf4j
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(classes = FenixTestApplication.class)
+public class ArEntityRepositoryTest extends BaseRepositoryTest {
+
+ public static final String TITLE = "这是测试标题";
+
+ /**
+ * 初始化.
+ */
+ @PostConstruct
+ public void init() {
+ super.initLoad();
+ }
+
+ @Test
+ public void saveEntity() {
+ ArEntity arEntity = new ArEntity()
+ .setTitle(TITLE)
+ .setDesc("这是测试的描述信息")
+ .setAge(20)
+ .setCreateTime(new Date())
+ .setLastUpdateTime(LocalDateTime.now())
+ .save();
+
+ // 查询保存的结果.
+ Optional arOption = arEntity.findById();
+ Assert.assertTrue(arOption.isPresent());
+
+ // 断言查询结果是否正确.
+ ArEntity newArEntity = arOption.get();
+ Assert.assertNotNull(newArEntity.getId());
+ Assert.assertEquals(newArEntity.getTitle(), TITLE);
+ Assert.assertNotNull(newArEntity.getDesc());
+ Assert.assertNotNull(newArEntity.getAge());
+ Assert.assertNotNull(newArEntity.getCreateTime());
+ Assert.assertNotNull(newArEntity.getLastUpdateTime());
+
+ // 再次查询断言.
+ Optional arOption2 = newArEntity.getRepository().findById(newArEntity.getId());
+ Assert.assertTrue(arOption2.isPresent());
+ Assert.assertEquals(arOption2.get().getTitle(), newArEntity.getTitle());
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/blinkfox/fenix/repository/ar/BaseRepositoryTest.java b/src/test/java/com/blinkfox/fenix/repository/ar/BaseRepositoryTest.java
new file mode 100644
index 00000000..cca8a8b6
--- /dev/null
+++ b/src/test/java/com/blinkfox/fenix/repository/ar/BaseRepositoryTest.java
@@ -0,0 +1,44 @@
+package com.blinkfox.fenix.repository.ar;
+
+import com.blinkfox.fenix.ar.FenixModel;
+import com.blinkfox.fenix.config.FenixConfigManager;
+import com.blinkfox.fenix.exception.FenixException;
+import javax.annotation.Resource;
+import lombok.Setter;
+import org.springframework.context.ApplicationContext;
+
+/**
+ * 一个基础的公用的 Repository 的测试类.
+ *
+ * @author blinkfox on 2022-03-28.
+ * @since 2.7.0
+ */
+public class BaseRepositoryTest {
+
+ /**
+ * 是否加载过的标识.
+ */
+ @Setter
+ private static Boolean isLoad = false;
+
+ @Resource
+ private ApplicationContext applicationContext;
+
+ /**
+ * 初始化加载 Fenix 的配置.
+ */
+ protected void initLoad() {
+ if (!isLoad) {
+ // 初始化加载 Fenix 配置.
+ FenixConfigManager.getInstance().initLoad();
+
+ // 设置应用上下文.
+ if (applicationContext == null) {
+ throw new FenixException("未成功注入 Spring 应用上下文 applicationContext 的对象实例.");
+ }
+ FenixModel.setApplicationContext(applicationContext);
+ isLoad = true;
+ }
+ }
+
+}