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
vectortype 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 |
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: |
string |
|
The table name for storing embeddings Environment variable: |
string |
|
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: |
int |
|
Whether the vector index should be created if not already existing. Environment variable: |
boolean |
|
A fine-tuning parameter for configuring the vector index, like e.g. Environment variable: |
string |
|
The database specific type of the vector index, like e.g. Environment variable: |
string |
|
The distance function to use. Environment variable: |
|
|
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: |
boolean |
|
Type |
Default |
|
Select whether the database schema is generated or not.
<p>
Environment variable: |
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
HibernateEmbeddingStoreto ingest and retrieve embedded documents.