Framework
ApiHug SDK spring data extension
实现 高效极简数据库交互!
配置路径:hope.data
; 配置对象: HopeDataProperties
配置 | 备注 |
---|---|
defaultUserIdString | 默认 用户 ID(String) |
defaultTenantString | 默认 租户 ID(String) |
defaultUserId | 默认 用户 ID(Integer) |
defaultTenantId | 默认 租户 ID(Integer) |
默认用户ID, 租户ID 在涉及到,数据落地 audit 需要时从上下文获取,如未提供(如非登录用户)使用此默认值。
用户ID, 租户ID,值类型必须是:LONG
|STRING
|INTEGER
之一。
ApiHug
使用 Liquibase
实现数据库Schema 的版本管理和迁移(开发环境,线上环境暂且手动维护不建议自动化);
Database Initialization-Execute Liquibase Database Migrations on Startup
配置完全使用 Spring Data Migration 关于 spring.liquibase
部分:
配置路径 spring.liquibase
; 配置对象 LiquibaseProperties
。
配置 | 备注 |
---|---|
enabled | Whether to enable Liquibase support. |
同时有 no-liquibase
强制不启动 Liquibase 的profile设置;
生产环境,建议关闭自动迁移
表结构迁移和生成配置,均由框架 stub 任务维护, 所以开发测试环境,免配置开箱即用。
ApiHug JDBC 基于 Spring Data JDBC。
为什么没有使用更高级的 ORM 框架如JPA, Hibernate?
应用程序和数据库天然的异构性,使得过于复杂的数据库交互(如OLAP)会削弱核心业务逻辑的稳定性和健壮性。
所以直接使用 JDBC; 使得如果通过JDBC进行复杂的SQL 操作变得非常别扭和难受(我们提供另外一套OLAP 方案,下文)。
核心的 OLTP 基于 Spring Data JDBC; 她已经是一个非常健壮、成熟的框架。
Spring Data 对于数据操作其实只提供了 Repository 核心概念,完成基本的 CRUD 操作,简洁又直观。
ApiHug 实体对象都是通过 protobuf
定义,通过 wire
命令转译,最后通过 stub
在应用模块生成实体类。
支持 jakarta & spring data annotation 标注;
jakarta.persistence
org.springframework.data.annotation
生成标准的 POJO 对象;
类名 | Identify |
---|---|
ALL | 支持下面所有协议 |
AUDITABLE | Auditable: CreatedAt,CreatedBy,UpdatedAt,UpdatedBy |
DELETABLE | Deletable: Deleted,DeletedAt,DeletedBy |
IDENTIFIABLE | Identifiable: Id |
TENANTABLE | Tenantable:TenantId |
VERSIONABLE | Versionable:Version |
NONE | 没有协议 |
定义实体时候可以根据需要,织入不同的协议,ApiHug 将自动为你添加相关属性,且自动维护这些属性值。
ORM框架使得开发者能够编写更干净、简洁的持久化代码和领域逻辑。
然而,对于ORM框架来说,构建正确且类型安全的查询API是一个设计上的挑战。
在广泛使用的Java ORM框架Hibernate(类JPA标准),提出了一个基于字符串的查询语言HQL(JPQL),与SQL非常相似。
这种方法的明显缺点是缺乏类型安全性和静态查询检查。此外,在更复杂的情况下(例如,当查询需要根据某些条件在运行时构建时),构建HQL查询通常涉及到字符串的拼接,这通常是不安全的,并且容易出错。
你是否厌倦了在线上环境检测SQL语法错误? SQL语言本身的表达能力强大,类型安全,并且具有丰富的语法。
而 ApiHug 为 SQL设计DSL, 可使用Java编译器来编译SQL语句、元数据和数据类型, 在编译时检查SQL语句的正确性,而不是在运行时,从而提高开发效率和减少运行时错误。
做到真正意义上的 Typesafe SQL!
ApiHug SQL DSL 借鉴了 JOOQ, Mybatis dynamic SQL, QueryDsl 等解决方案:
proto
元语实体设计annotation
实体标注兼具效率、实用和安全!
SELECT
SYSTEM_PLATFORM_ROLE_AUTHORITY.AUTHORITIES
FROM
SYSTEM_PLATFORM_ROLE_AUTHORITY
JOIN
SYSTEM_PLATFORM_ROLE ON SYSTEM_PLATFORM_ROLE_AUTHORITY.ROLE_ID = SYSTEM_PLATFORM_ROLE.ID
JOIN
SYSTEM_PLATFORM_ACCOUNT_ROLE_MAPPER ON SYSTEM_PLATFORM_ROLE.ID = SYSTEM_PLATFORM_ACCOUNT_ROLE_MAPPER.ROLE_ID
WHERE
SYSTEM_PLATFORM_ACCOUNT_ROLE_MAPPER.ACCOUNT_ID = ?
等同表达式:
final Buildable<SelectModel> selectStatement =
select(SYSTEM_PLATFORM_ROLE_AUTHORITY.Authorities)
.from(SYSTEM_PLATFORM_ROLE_AUTHORITY)
.join(
SYSTEM_PLATFORM_ROLE,
on(SYSTEM_PLATFORM_ROLE_AUTHORITY.RoleId, equalTo(SYSTEM_PLATFORM_ROLE.Id)))
.join(
SYSTEM_PLATFORM_ACCOUNT_ROLE_MAPPER,
on(SYSTEM_PLATFORM_ROLE.Id, equalTo(SYSTEM_PLATFORM_ACCOUNT_ROLE_MAPPER.RoleId)))
// Base on specific account
.where(SYSTEM_PLATFORM_ACCOUNT_ROLE_MAPPER.AccountId, isEqualTo(accountId));
Spring Data 抽象中的核心接口是 Repository。她负责领域实体管理,以及实体的唯一标识ID,以及通用的CRUD系列操作。
Spring Data considers domain types to be entities, more specifically aggregates. So you will see the term “entity” used throughout the documentation that can be interchanged with the term “domain type” or “aggregate”. As you might have noticed in the introduction it already hinted towards domain-driven concepts. We consider domain objects in the sense of DDD. Domain objects have identifiers (otherwise these would be identity-less value objects), and we somehow need to refer to identifiers when working with certain patterns to access data. Referring to identifiers will become more meaningful as we talk about repositories and query methods.
ApiHug 混合了 SimpleJdbcRepository
+ DSL
HopeJdbcRepository
继承自 springframework.SimpleJdbcRepository
(CrudRepository
|PagingAndSortingRepository
|QueryByExampleExecutor
)Stub
混入 Entity DSL 定义一个简单的 Student
实体 Repository
定义:
public interface StudentEntityRepository
extends HopeJdbc<StudentEntity>,
SampleJdbcSupport,
StudentEntityDSL,
ListCrudRepository<StudentEntity, Long> {
}
现有业务系统在数据库交互层, 除上文提到数据库和应用程序世界天然的异构性,另外一个增加复杂度就是 OLTP/OLAP 不分。
ApiHug 采用最原始的JDBC来做 OLTP 和 DSL来解决类型安全,基本搞定OLTP, 那么OLAP 如何解决呢?
从 DDD(领域驱动设计)的 CQRS(命令查询责任分离), ApiHug 得到了灵感:
CQRS 是一种架构模式,将系统的命令(修改数据)和查询(读取数据)操作分开处理。
所以 OLTP/OLAP 天然就应该分开的,哪怕基于同一个数据源!哪怕需要部分逻辑冗余, ApiHug 推荐最佳实践:
上文的 DSL举例代码就是个很好的例子。
技术的底座呢?基于 mybatis-dynamic-sql,spring扩展 NamedParameterJdbcTemplateExtensions。
模板类生成均由 stub
命令统一生成,业务层只需 wire
进来实现逻辑就可以,是不是很便捷呢?
涉及非 primitive
数据类型的序列和反序列化,比如列表对象, Enum 类型值。
内置:
类名 | 备注 |
---|---|
BigDecimalListReaderConverter | BigDecimal List 反序列化 |
BigDecimalListWriterConverter | BigDecimal List 序列化 |
BooleanListWriterConverter | Boolean List 序列化 |
BooleanListReaderConverter | Boolean List 反序列化 |
ByteListReaderConverter | Byte List 序列化 |
ByteListWriterConverter | Byte List 反序列化 |
DateListReaderConverter | LocalDate List 序列化 |
DateListWriterConverter | LocalDate List 反序列化 |
DateTimeListReaderConverter | LocalDateTime List 序列化 |
DateTimeListWriterConverter | LocalDateTime List 反序列化 |
DoubleListReaderConverter | Double List 序列化 |
DoubleListWriterConverter | Double List 反序列化 |
FloatListReaderConverter | Float List 序列化 |
FloatListWriterConverter | Float List 反序列化 |
IntegerListReaderConverter | Integer List 序列化 |
IntegerListWriterConverter | Integer List 反序列化 |
LongListReaderConverter | Long List 序列化 |
LongListWriterConverter | Long List 反序列化 |
ShortListReaderConverter | Short List 序列化 |
ShortListWriterConverter | Short List 反序列化 |
StringListReaderConverter | String List 序列化 |
StringListWriterConverter | String List 反序列化 |
TimeListWriterConverter | LocalTime List 序列化 |
TimeListReaderConverter | LocalTime List 反序列化 |
除预置converter外, 也可以自扩展, 通过 proto
定义的 Enum 值类型,框架已经帮你预生成了相关converter 自动注册到上下文了;
如有一个 AccountStatusEnum
枚举类型,ApiHug 将自动生成注册如下几个 converter:
类名 | 备注 |
---|---|
AccountStatusEnumReaderTitleConverter | AccountStatusEnum 按照标题 反序列化 |
AccountStatusEnumWriterTitleConverter | AccountStatusEnum 按照标题 反序列化 |
AccountStatusEnumListWriterConverter | AccountStatusEnum List 按照标题 反序列化 |
AccountStatusEnumListReaderConverter | AccountStatusEnum List 按照标题 反序列化 |
通过 Criteria + Pageable 实现分页查询:
@Derived
@Query
default Page<Account> queryPlatformAccount(QueryPlatformAccountRequest request, PageRequest pageParameter) {
Criteria criteria = EasyCriteria
.in(Domain.Type,Constant.AccountTypes.PLATFORM_ACCOUNT_TYPE_NAME)
.and(EasyCriteria.like(Domain.Name, request.getName()))
.and(EasyCriteria.eq(Domain.Email, request.getEmail()));
return findAll(criteria, page(pageParameter));
}
To be done 🏗️