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; + } + } + +}