Hibernate ORM Embedding Store

The Quarkus LangChain4j Hibernate ORM extension provides an Embedding Store implementation that can be used with a custom entity to make use of databases supporting vector search for Retrieval-Augmented Generation (RAG). This extension enables you to persist and query embedding vectors for document retrieval and is based on the LangChain4j Hibernate ORM integration.

Contrary to other embedding store extensions, this extension have various advantages thanks to its usage of Hibernate ORM:

  • Support multiple databases through a unified configuration and user API

  • Customization of metadata through Hibernate ORM entities

  • Advanced queries based on HQL and JPA Criteria

Prerequisites

To use Hibernate ORM as an Embedding store:

  • A supported database with vector search capability is required.

  • A Quarkus datasource must be configured.

  • The embedding vector dimension must match the dimension of vectors produced by your embedding model.

The currently supported databases are:

  • DB2

  • MariaDB

  • MySQL Heatwave

  • PostgreSQL with pgvector

  • Oracle

  • SQL Server

Dependency

To enable the LangChain4j Hibernate ORM extension in your Quarkus project, add the following Maven dependency:

<dependency>
  <groupId>io.quarkiverse.langchain4j</groupId>
  <artifactId>quarkus-langchain4j</artifactId>
  <version>1.12.0.CR2</version>
</dependency>

Even better, if you use the Quarkus platform BOM (default for projects generated), add the Quarkus Langchain4J BOM and all dependency versions will align:

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>${quarkus.platform.group-id}</groupId>
                <artifactId>${quarkus.platform.artifact-id}</artifactId>
                <version>${quarkus.platform.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>${quarkus.platform.group-id}</groupId>
                <artifactId>quarkus-langchain4j-bom</artifactId> (1)
                <version>${quarkus.platform.version}</version> (2)
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
      <dependency>
        <groupId>io.quarkiverse.langchain4j</groupId>
        <artifactId>quarkus-langchain4j</artifactId>
        (3)
      </dependency>
    </dependencies>
1 In your dependencyManagement section, add the quarkus-langchain4j-bom
2 Inherit the version from your platform version
3 Voilà, no need for version alignment anymore

This extension requires a configured Hibernate ORM persistence unit. For configuration details, refer to the Hibernate ORM Guide.

Please beware that if you’re using PostgreSQL, you have to make sure that the pgvector extension is available. This can be done through Quarkus DevServices by configuring quarkus.datasource.devservices.image-name=pgvector/pgvector:pg17.

How It Works

The extension maps each ingested document to an entity. Each entity contains:

  • The original text content

  • Optional metadata

  • The vector embedding (stored as a vector type column)

During retrieval, a similarity search (e.g., cosine distance) is performed using a SELECT query with ORDER BY embedding +<⇒+ :query_vector FETCH FIRST N ROWS ONLY.

Comparison with quarkus-langchain4j-pgvector extension

The Quarkus LangChain4j Hibernate ORM extension provides similar features to the Quarkus LangChain4j pgvector extension and can also be used with pgvector on PostgreSQL.

The major difference is that the Quarkus LangChain4j Hibernate ORM extension is implemented through Hibernate ORM APIs, which abstract away the data store details. This extension can thus also be used with other supported data stores.

This also means the same entities defined for use in Quarkus LangChain4j Hibernate ORM can be used in Quarkus Hibernate ORM, unlocking additional APIs and addressing additional use cases:

  • Managed entities with Session

  • Quarkus Panache

  • Jakarta Data repositories

  • …​

Entity definition

To use a Hibernate ORM entity as source for the EmbeddingStore API, the entity class needs at least one persistent attribute, representing the embedding vector, that is annotated with @EmbeddingVector and a Map attribute annotated with @UnmappedMetadata. The embedding vector size must be specified via the @Array(length = …​) annotation on the @EmbeddingVector annotated attribute.

The embedding vector size value depends on the embedding model in use. For example:

  • AllMiniLmL6V2QuantizedEmbeddingModel → 384

  • OpenAI text-embedding-ada-002 → 1536

If the embedding dimension is missing or mismatched, ingestion and retrieval will fail or produce inaccurate results.

If you switch to a different embedding model, ensure the dimension value is updated accordingly.

You may specify an @EmbeddedText attribute if a single entity attribute is the source for the embedding vector, but it is optional. Note that you require a @EmbeddedText attribute if you want to use metadata via the TextSegment API that is exposed in the search result EmbeddingMatch. During ingestion, TextSegment metadata is automatically mapped to entity attributes.

To map metadata to entity attributes/columns rather than JSON payload content, you can annotate entity attributes with @MetadataAttribute. During ingestion, TextSegment#metadata attributes are mapped into dedicated entity attributes if possible, while other metadata is stored in the JSON entity attribute, annotated with @UnmappedMetadata.

@Entity
public class MyEmbeddingEntity {
    @Id
    UUID id;
    @EmbeddingVector
    @Array(length = 384)                // The dimension of the embedding vector based on the embedding model
    float[] embedding;
    @EmbeddedText
    String text;
    @UnmappedMetadata
    Map<String, Object> metadata;       // Can be either a Map<String, Object> or a String

    @MetadataAttribute
    String mimeType;                    // Explicitly mapped. Synchronizes TextSegment#metadata with this attribute
    @MetadataAttribute
    String fileName;                    // Explicitly mapped. Synchronizes TextSegment#metadata with this attribute
}
In development and test mode, the pgvector extension is automatically enabled (controlled via quarkus.langchain4j.hibernate-orm.setup-vector-config), but keep in mind to enable the vector extension in your PostgreSQL database schema via create extension if not exists vector; with a schema management tool like Liquibase or Flyway.

Embedding Index

When defining a custom entity, you are in charge of the database schema, so the extension does not attempt to create any database objects.

In production, you will most likely need an index to achieve acceptable performance for the vector search though. It’s best to use a schema management tool (like Liquibase or Flyway) to create your indexes. Take a look into the LangChain4j Hibernate ORM integration documentation for details about creating vector search indexes, as well as the documentation of your database vendor to get more information about vector search indexes.

Usage Example

Once the extension is installed and configured, you can ingest documents into your database using the following code:

package io.quarkiverse.langchain4j.samples;

import static dev.langchain4j.data.document.splitter.DocumentSplitters.recursive;

import java.util.List;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

import dev.langchain4j.data.document.Document;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;
import dev.langchain4j.store.embedding.hibernate.HibernateEmbeddingStore;

@ApplicationScoped
public class IngestorExampleWithHibernate {

    /**
     * The embedding store (the database).
     * The bean is provided by the quarkus-langchain4j-hibernate extension.
     */
    @Inject
    HibernateEmbeddingStore<MyEmbeddingEntity> store;

    /**
     * The embedding model (how is computed the vector of a document).
     * The bean is provided by the LLM (like openai) extension.
     */
    @Inject
    EmbeddingModel embeddingModel;

    public void ingest(List<Document> documents) {
        EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
                .embeddingStore(store)
                .embeddingModel(embeddingModel)
                .documentSplitter(recursive(500, 0))
                .build();
        // Warning - this can take a long time...
        ingestor.ingest(documents);
    }
}

This example shows how to embed and persist documents using the Hibernate ORM store, enabling efficient similarity search during RAG queries. TextSegment#metadata is automatically mapped to explicit @MetadataAttribute and @UnmappedMetadata attributes on the entity instances.

Alternatively, you can also persist your entities through Hibernate ORM APIs directly, like:

package io.quarkiverse.langchain4j.samples;

import java.util.ArrayList;
import java.util.List;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;

import org.hibernate.Session;

import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;

@ApplicationScoped
public class PersistExampleWithHibernate {

    /**
     * The Hibernate Session (the database).
     * The bean is provided by the quarkus-hibernate extension.
     */
    @Inject
    Session session;

    /**
     * The embedding model (how is computed the vector of a document).
     * The bean is provided by the LLM (like openai) extension.
     */
    @Inject
    EmbeddingModel embeddingModel;

    @Transactional
    public void ingest(List<MyEmbeddingEntity> entities) {
        List<TextSegment> textSegments = new ArrayList<>(entities.size());
        for (MyEmbeddingEntity entity : entities) {
            textSegments.add(TextSegment.from(entity.text));
        }
        List<Embedding> embeddings = embeddingModel.embedAll(textSegments).content();
        for (int i = 0; i < entities.size(); i++) {
            MyEmbeddingEntity entity = entities.get(i);
            entity.embedding = embeddings.get(i).vector();
            session.persist(entity);
        }
    }
}

Query Example

Assuming that data was ingested, one of the various search methods on the HibernateEmbeddingStore can be used to search for matching entities.

package io.quarkiverse.langchain4j.samples;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.store.embedding.EmbeddingMatch;
import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
import dev.langchain4j.store.embedding.EmbeddingSearchResult;
import dev.langchain4j.store.embedding.hibernate.HibernateEmbeddingStore;

@ApplicationScoped
public class QueryExampleWithHibernate {

    /**
     * The embedding store (the database).
     * The bean is provided by the quarkus-langchain4j-hibernate extension.
     */
    @Inject
    HibernateEmbeddingStore<MyEmbeddingEntity> store;

    /**
     * The embedding model (how is computed the vector of a document).
     * The bean is provided by the LLM (like openai) extension.
     */
    @Inject
    EmbeddingModel embeddingModel;

    public void ingest() {
        // User's question
        String question = "What is the refund policy?";

        // Generate embedding for the question
        Embedding questionEmbedding = embeddingModel.embed(question).content();

        // Search for the most similar text segments (top 3 results)
        EmbeddingSearchResult<TextSegment> searchResult = store.search(
                EmbeddingSearchRequest.builder()
                        .queryEmbedding(questionEmbedding)
                        .maxResults(3) // Retrieve top 3 most similar chunks
                        .build());

        for (EmbeddingMatch<TextSegment> match : searchResult.matches()) {
            System.out.println("Matching id: " + match.embeddingId());
        }
    }
}

For further examples, take a look into the LangChain4j Hibernate ORM documentation examples.

Configuration

Customize the behavior of the extension using one of the following configuration quarkus.langchain4j.hibernate-orm options:

Configuration property fixed at build time - All other configuration properties are overridable at runtime

Configuration property

Type

Default

The name of the configured datasource to use for this store. If not set, the default datasource from the Agroal extension will be used.

Environment variable: QUARKUS_LANGCHAIN4J_GENERIC_DATASOURCE

string

The table name for storing embeddings

Environment variable: QUARKUS_LANGCHAIN4J_GENERIC_TABLE

string

embeddings

The dimension of the embedding vectors. This has to be the same as the dimension of vectors produced by the embedding model that you use. For example, AllMiniLmL6V2QuantizedEmbeddingModel produces vectors of dimension 384. OpenAI’s text-embedding-ada-002 produces vectors of dimension 1536.

Environment variable: QUARKUS_LANGCHAIN4J_GENERIC_DIMENSION

int

Whether the vector index should be created if not already existing.

Environment variable: QUARKUS_LANGCHAIN4J_GENERIC_CREATE_INDEX

boolean

false

A fine-tuning parameter for configuring the vector index, like e.g. lists=1 for the pgvector IVFFlat index. Consult the database vendor documentation for details about the possible index options.

Environment variable: QUARKUS_LANGCHAIN4J_GENERIC_INDEX_OPTIONS

string

The database specific type of the vector index, like e.g. hnsw or ivfflat on pgvector. Consult the database vendor documentation for details about the possible vector index types.

Environment variable: QUARKUS_LANGCHAIN4J_GENERIC_INDEX_TYPE

string

The distance function to use.

Environment variable: QUARKUS_LANGCHAIN4J_GENERIC_DISTANCE_FUNCTION

cosine, euclidean, euclidean-squared, manhattan, inner-product, negative-inner-product, hamming, jaccard

cosine

Whether the vector configuration of the database should be setup e.g. the PG extension should be created on Start. By Default, if it’s dev or test environment the value is overridden to true

Environment variable: QUARKUS_LANGCHAIN4J_HIBERNATE_ORM_SETUP_VECTOR_CONFIG

boolean

false

Schema management configuration

Type

Default

Select whether the database schema is generated or not. <p> drop-and-create is awesome in development mode. <p> This defaults to 'none'. <p> However if Dev Services is in use and no other extensions that manage the schema are present the value will be automatically overridden to 'drop-and-create'. <p> Accepted values: none, create, drop-and-create, drop, update, validate.

Environment variable: QUARKUS_LANGCHAIN4J_GENERIC_SCHEMA_MANAGEMENT_STRATEGY

tooltip:none[No schema action.], tooltip:create[Create the schema.], tooltip:drop-and-create[Drop and then recreate the schema.], tooltip:drop[Drop the schema.], tooltip:update[Update (alter) the database schema.], tooltip:validate[Validate the database schema.]

tooltip:none[No schema action.]

Summary

To use Hibernate ORM as an embedding store with Quarkus LangChain4j:

  • Ensure your database is configured correctly for vector search.

  • Add the extension dependency.

  • Configure a datasource and set the correct embedding dimension.

  • Use HibernateEmbeddingStore to ingest and retrieve embedded documents.