Pular para o conteúdo principal

archbase-hypersistence

Módulo de integração com Hypersistence Utils que fornece suporte avançado a tipos Hibernate para persistência de dados complexos como JSON, arrays e ranges.

Visão Geral

O módulo archbase-hypersistence adiciona funcionalidades avançadas de persistência ao framework Archbase:

FuncionalidadeDescriçãoBanco de Dados
JSON TypesColunas JSON/JSONBPostgreSQL, MySQL, Oracle, H2
Array TypesArrays nativos (text[], int[], uuid[])PostgreSQL
Range TypesIntervalos (daterange, int4range, etc.)PostgreSQL
Repository OtimizadoMétodos persist(), merge(), update()Todos
N+1 DetectionDetecção de queries N+1 em testesTodos
TSID GeneratorIDs ordenados por tempoTodos

Instalação

Usando o Starter (Recomendado)

<dependency>
<groupId>br.com.archbase</groupId>
<artifactId>archbase-starter-hypersistence</artifactId>
<version>${archbase.version}</version>
</dependency>

Usando apenas o módulo core

<dependency>
<groupId>br.com.archbase</groupId>
<artifactId>archbase-hypersistence</artifactId>
<version>${archbase.version}</version>
</dependency>

Tipos JSON

O tipo JSON é a funcionalidade mais utilizada do módulo. Permite armazenar estruturas complexas em colunas JSON do banco de dados.

Map como JSON

import io.hypersistence.utils.hibernate.type.json.JsonType;
import org.hibernate.annotations.Type;

@Entity
public class ProductEntity extends TenantPersistenceEntityBase<ProductEntity, String> {

private String name;

@Type(JsonType.class)
@Column(columnDefinition = "jsonb")
private Map<String, Object> metadata;
}

Lista como JSON

@Entity
public class ArticleEntity extends PersistenceEntityBase<ArticleEntity, String> {

private String title;

@Type(JsonType.class)
@Column(columnDefinition = "jsonb")
private List<String> tags;
}

POJO customizado como JSON

// Classe POJO para armazenar como JSON
public class ProductDetails {
private String manufacturer;
private String model;
private Map<String, String> specifications;

// getters e setters
}

@Entity
public class ProductEntity extends TenantPersistenceEntityBase<ProductEntity, String> {

@Type(JsonType.class)
@Column(columnDefinition = "jsonb")
private ProductDetails details;
}

JsonNode (JSON dinâmico)

import com.fasterxml.jackson.databind.JsonNode;

@Entity
public class ConfigurationEntity extends PersistenceEntityBase<ConfigurationEntity, String> {

@Type(JsonType.class)
@Column(columnDefinition = "jsonb")
private JsonNode configuration;
}

Tipos Array (PostgreSQL)

Atenção

Tipos Array funcionam apenas com PostgreSQL. Para testes com H2, use tipos JSON.

Array de Strings

import io.hypersistence.utils.hibernate.type.array.StringArrayType;

@Entity
public class EventEntity extends PersistenceEntityBase<EventEntity, String> {

@Type(StringArrayType.class)
@Column(columnDefinition = "text[]")
private String[] participants;
}

Array de Inteiros

import io.hypersistence.utils.hibernate.type.array.IntArrayType;

@Entity
public class ScoreEntity extends PersistenceEntityBase<ScoreEntity, String> {

@Type(IntArrayType.class)
@Column(columnDefinition = "int[]")
private int[] scores;
}

Array de UUIDs

import io.hypersistence.utils.hibernate.type.array.UUIDArrayType;

@Entity
public class RelationEntity extends PersistenceEntityBase<RelationEntity, String> {

@Type(UUIDArrayType.class)
@Column(columnDefinition = "uuid[]")
private UUID[] relatedIds;
}

Lista como Array

import io.hypersistence.utils.hibernate.type.array.ListArrayType;

@Entity
public class CategoryEntity extends PersistenceEntityBase<CategoryEntity, String> {

@Type(ListArrayType.class)
@Column(columnDefinition = "text[]")
private List<String> subcategories;
}

Tipos Range (PostgreSQL)

Atenção

Tipos Range funcionam apenas com PostgreSQL.

Range de Datas

import io.hypersistence.utils.hibernate.type.range.PostgreSQLRangeType;
import io.hypersistence.utils.hibernate.type.range.Range;

@Entity
public class ReservationEntity extends PersistenceEntityBase<ReservationEntity, String> {

@Type(PostgreSQLRangeType.class)
@Column(columnDefinition = "daterange")
private Range<LocalDate> reservationPeriod;
}

// Uso
Range<LocalDate> period = Range.closed(
LocalDate.of(2024, 1, 1),
LocalDate.of(2024, 1, 31)
);

Range de Inteiros

@Entity
public class AgeGroupEntity extends PersistenceEntityBase<AgeGroupEntity, String> {

@Type(PostgreSQLRangeType.class)
@Column(columnDefinition = "int4range")
private Range<Integer> ageRange;
}

// Uso - range semi-aberto [18, 65)
Range<Integer> adults = Range.closedOpen(18, 65);

Range de BigDecimal

@Entity
public class PriceRangeEntity extends PersistenceEntityBase<PriceRangeEntity, String> {

@Type(PostgreSQLRangeType.class)
@Column(columnDefinition = "numrange")
private Range<BigDecimal> priceRange;
}

Repository Otimizado

O módulo fornece métodos de repositório mais eficientes que o save() padrão.

Interface do Repository

public interface ProductRepository
extends ArchbaseJpaRepository<ProductEntity, String, Long>,
ArchbaseHypersistenceRepository<ProductEntity, String> {
}

Usando persist() vs save()

@Service
public class ProductService {

private final ProductRepository repository;

/**
* persist() é mais eficiente que save() para entidades NOVAS.
* Não faz SELECT antes do INSERT.
*/
public ProductEntity createProduct(ProductEntity product) {
return repository.persist(product);
}

/**
* Para múltiplas entidades, use persistAll().
*/
public List<ProductEntity> createProducts(List<ProductEntity> products) {
return repository.persistAll(products);
}

/**
* update() para entidades existentes.
*/
public ProductEntity updateProduct(ProductEntity product) {
return repository.update(product);
}
}

Comparação de Performance

MétodoOperaçãoSELECTsINSERTs/UPDATEs
save() (novo)INSERT11
persist()INSERT01
save() (existente)UPDATE11
update()UPDATE01

Detecção de N+1 Queries

Use SQLStatementCountAssertions em testes para detectar problemas de N+1.

Exemplo de Teste

import br.com.archbase.hypersistence.util.SQLStatementCountAssertions;

@SpringBootTest
class OrderRepositoryTest {

@Autowired
private OrderRepository orderRepository;

@Test
void shouldNotHaveNPlusOneQueries() {
// Reset dos contadores
SQLStatementCountAssertions.reset();

// Executa operação que pode ter N+1
List<Order> orders = orderRepository.findAllWithItems();

// Verifica que apenas 1 SELECT foi executado
SQLStatementCountAssertions.assertSelectCount(1);
}

@Test
void shouldExecuteExactlyOneInsert() {
SQLStatementCountAssertions.reset();

Order order = new Order();
orderRepository.persist(order);
orderRepository.flush();

SQLStatementCountAssertions.assertInsertCount(1);
SQLStatementCountAssertions.assertNoSelect();
}
}

TSID Generator

TSID (Time-Sorted Unique Identifier) é uma alternativa ao UUID que é ordenável por tempo.

Uso

import io.hypersistence.utils.hibernate.id.Tsid;

@Entity
public class OrderEntity {

@Id
@Tsid
private Long id;

// ou como String
@Id
@Tsid
private String id;
}

Vantagens do TSID sobre UUID

CaracterísticaTSIDUUID
Tamanho8 bytes16 bytes
String13 caracteres36 caracteres
Ordenável por tempoSimNão
Performance em índicesMelhorPior

Tipos PostgreSQL Específicos

HStore (Map key-value)

import io.hypersistence.utils.hibernate.type.basic.PostgreSQLHStoreType;

@Entity
public class MetadataEntity extends PersistenceEntityBase<MetadataEntity, String> {

@Type(PostgreSQLHStoreType.class)
@Column(columnDefinition = "hstore")
private Map<String, String> attributes;
}

Inet (Endereços IP)

import io.hypersistence.utils.hibernate.type.basic.PostgreSQLInetType;
import io.hypersistence.utils.hibernate.type.basic.Inet;

@Entity
public class AccessLogEntity extends PersistenceEntityBase<AccessLogEntity, String> {

@Type(PostgreSQLInetType.class)
@Column(columnDefinition = "inet")
private Inet clientIp;
}

Configuração

application.yml

archbase:
hypersistence:
enabled: true
json:
enabled: true
postgresql:
array-types-enabled: true
range-types-enabled: true
hstore-enabled: false
inet-enabled: false
repository:
enhanced-methods-enabled: false

Desabilitar o módulo

archbase:
hypersistence:
enabled: false

Compatibilidade de Banco de Dados

FuncionalidadePostgreSQLMySQLOracleH2
JSON Typesjsonb/jsonjsonJSON/VARCHARjson
Array TypesSimNãoNãoNão
Range TypesSimNãoNãoNão
HStoreSimNãoNãoNão
InetSimNãoNãoNão
Repository MethodsSimSimSimSim
TSIDSimSimSimSim

Migração de Converters Existentes

O módulo coexiste com os converters existentes do Archbase:

Converter ArchbaseHypersistence TypeRecomendação
MonetaryAmountAttributeConverterMonetaryAmountTypeManter Archbase (1 coluna)
YearMonthConverterYearMonthTypeManter Archbase
N/AJsonTypeUsar Hypersistence
N/AStringArrayTypeUsar Hypersistence

Troubleshooting

Causa: Tipo não reconhecido pelo Hibernate.

Solução: Verifique se a anotação @Type está correta e a dependência está presente.

Erro: "Array types not supported"

Causa: Tentando usar tipos array em banco que não é PostgreSQL.

Solução: Use tipos JSON em vez de array para H2/MySQL.

Erro em testes com H2

Causa: H2 não suporta alguns tipos PostgreSQL específicos.

Solução:

  1. Use tipos JSON (funcionam com H2)
  2. Ou use Testcontainers com PostgreSQL para testes de integração
@Testcontainers
class PostgreSQLIntegrationTest {

@Container
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:15");

// seus testes aqui
}

Próximos Passos