Implement efficient and minimalist database interaction!

  1. ORM
  2. Multi Tenant
  3. OLAP/OLTP

Configuration

Configuration path: hope.data; Configuration object: HopeDataProperties

ConfigurationNote
defaultUserIdStringDefault User ID (String)
defaultTenantStringDefault Tenant ID (String)
defaultUserIdDefault User ID (Integer)
defaultTenantIdDefault Tenant ID (Integer)

Default User ID and Tenant ID are retrieved from the context when data auditing is required. If not provided (e.g., for non-logged-in users), these default values are used.

User ID and Tenant ID must be of type: LONG | STRING | INTEGER.

Liquibase

ApiHug uses Liquibase to implement version management and migration of database schemas (for development environments; production environments should be manually maintained).

Database Initialization - Execute Liquibase Database Migrations on Startup

Configuration fully utilizes Spring Data Migration regarding the spring.liquibase section:

Configuration path: spring.liquibase; Configuration object: LiquibaseProperties.

ConfigurationNote
enabledWhether to enable Liquibase support.

There is also a no-liquibase profile setting that forces Liquibase not to start.

In production environments,recommend to switch off this flag

Table structure migration and generation configurations are maintained by the framework’s stub tasks, so development and testing environments can start without additional configuration.

JDBC

ApiHug’s entire JDBC section is built on Spring Data JDBC.

Why not use more advanced ORM frameworks like JPA or Hibernate? Due to the inherent heterogeneity of applications and databases, overly complex database interactions (OLAP) can undermine the stability and robustness of core business logic.

Therefore, we directly use JDBC; this makes complex SQL operations through JDBC cumbersome and unpleasant (we provide an alternative OLAP solution).

The core OLTP is provided by Spring Data Repository, which offers a robust and mature solution.

Spring Data only provides the core concept of Repository for data operations, completing basic CRUD operations in a simple and clean manner.

Entity

ApiHug entity objects are defined using protobuf, translated using the wire command, and finally generated as entity classes in the application module through stub.

Supports Jakarta & Spring annotations:

  1. jakarta.persistence
  2. org.springframework.data.annotation

Generates the most standard POJO objects.

Wire

Class NameIdentify
ALLSupports all protocols listed below
AUDITABLEAuditable: CreatedAt, CreatedBy, UpdatedAt, UpdatedBy
DELETABLEDeletable: Deleted, DeletedAt, DeletedBy
IDENTIFIABLEIdentifiable: Id
TENANTABLETenantable: TenantId
VERSIONABLEVersionable: Version
NONENo protocols

When defining entities, you can weave in different protocols as needed. ApiHug will automatically add extra properties and maintain these value objects.

DSL

ORM frameworks allow developers to write cleaner and more concise persistence code and domain logic.

However, building a correct and type-safe query API is a design challenge for ORM frameworks.

The widely-used Java ORM framework Hibernate (similar to the JPA standard) introduced a string-based query language called HQL (JPQL), which is very similar to SQL.

The obvious drawback of this approach is the lack of type safety and static query checking. Furthermore, in more complex scenarios (e.g., when queries need to be constructed at runtime based on certain conditions), building HQL queries often involves string concatenation, which is usually unsafe and error-prone.

Are you tired of checking SQL syntax errors in production environments? SQL’s expressive power is strong, type-safe, and has rich syntax.

ApiHug sets a DSL for SQL, allowing the Java compiler to compile SQL statements, metadata, and data types, checking the correctness of SQL statements at compile time rather than at runtime, thus improving development efficiency and reducing runtime errors.

Achieving truly Typesafe SQL!

ApiHug SQL DSL draws from JOOQ, MyBatis dynamic SQL, QueryDSL, and other solutions:

  1. proto meta-entity design
  2. annotation entity annotations
  3. Entity companion DSL classes

Combining efficiency, practicality, and safety!

  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 = ?

Equal Code implementation:

 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));

Repository

The core interface in the Spring Data abstraction is the Repository. It takes the domain class to manage as well as the identifier type of the domain class as type arguments. This interface acts primarily as a marker interface to capture the types to work with and to help you to discover interfaces that extend this one.

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 combines SimpleJdbcRepository + DSL

  1. HopeJdbcRepository extends springframework.SimpleJdbcRepository (CrudRepository|PagingAndSortingRepository|QueryByExampleExecutor)
  2. Entity DSL definitions are mixed in through Stub

A simple Student entity Repository definition:

public interface StudentEntityRepository 
    extends HopeJdbc<StudentEntity>, 
            SampleJdbcSupport, 
            StudentEntityDSL, 
            ListCrudRepository<StudentEntity, Long> {
}

OLAP

In existing business systems at the database interaction layer, besides the inherent heterogeneous nature of the database world and the application world, another complexity arises from the indistinction between OLTP and OLAP.

As mentioned above, ApiHug uses the most basic JDBC for OLTP, and DSL solves type safety. So how does it address OLAP?

Inspired by CQRS (Command Query Responsibility Segregation) from DDD (Domain-Driven Design), ApiHug presents:

CQRS is an architectural pattern that separates command (modifying data) and query (reading data) operations.

Thus, OLTP and OLAP should naturally be separated, even if they are based on the same data source! Even if some logic needs to be re-implemented, ApiHug recommends the following practices:

  1. Cross-table statistics and queries
  2. Complex statistics (beyond basic single-table statistics, such as sum/avg, etc.)

The DSL example code mentioned above is a great illustration.

What about the underlying technology? It is based on mybatis-dynamic-sql and the Spring extension NamedParameterJdbcTemplateExtensions.

The template classes are all generated uniformly by the stub command, allowing the business layer to simply wire in the logic. Isn’t that convenient?

Converter

Involves serialization and deserialization of non-primitive data types, such as list objects and Enum type values.

Built-in:

Class NameRemarks
BigDecimalListReaderConverterBigDecimal List deserialization
BigDecimalListWriterConverterBigDecimal List serialization
BooleanListWriterConverterBoolean List serialization
BooleanListReaderConverterBoolean List deserialization
ByteListReaderConverterByte List serialization
ByteListWriterConverterByte List deserialization
DateListReaderConverterLocalDate List serialization
DateListWriterConverterLocalDate List deserialization
DateTimeListReaderConverterLocalDateTime List serialization
DateTimeListWriterConverterLocalDateTime List deserialization
DoubleListReaderConverterDouble List serialization
DoubleListWriterConverterDouble List deserialization
FloatListReaderConverterFloat List serialization
FloatListWriterConverterFloat List deserialization
IntegerListReaderConverterInteger List serialization
IntegerListWriterConverterInteger List deserialization
LongListReaderConverterLong List serialization
LongListWriterConverterLong List deserialization
ShortListReaderConverterShort List serialization
ShortListWriterConverterShort List deserialization
StringListReaderConverterString List serialization
StringListWriterConverterString List deserialization
TimeListWriterConverterLocalTime List serialization
TimeListReaderConverterLocalTime List deserialization

In addition to the built-in converters, you can also extend them. By defining Enum value types through proto, the framework has already pre-generated the relevant converters and automatically registered them in the context.

For instance, if there is an AccountStatusEnum enum type, ApiHug will automatically generate and register the following converters:

Class NameRemarks
AccountStatusEnumReaderTitleConverterAccountStatusEnum deserialization by title
AccountStatusEnumWriterTitleConverterAccountStatusEnum serialization by title
AccountStatusEnumListWriterConverterAccountStatusEnum List deserialization by title
AccountStatusEnumListReaderConverterAccountStatusEnum List serialization by title

Pageable

You can implement pagination queries using 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));
}

Multi Data Source

To be done 🏗️

Refer

  1. jOOQ generates Java code from your database and lets you build type safe SQL queries through its fluent API.
  2. mybatis-dynamic-sql About SQL DSL (Domain Specific Language) for Kotlin and Java. Supports rendering for MyBatis or Spring JDBC Templates
  3. querydsl Unified Queries for Java.Querydsl is compact, safe and easy to learn.
  4. Intro to Querydsl
  5. Spring Data Using jOOQ
  6. JPQL
  7. HQL