> For the complete documentation index, see [llms.txt](https://bxorm.ortusbooks.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://bxorm.ortusbooks.com/getting-started/performance.md).

# Performance

bx-orm is designed to be as fast as possible, but there are some factors that can affect performance. Let's look at a few key areas where you can optimize performance in your application.

## ORM Startup

You want the short answer?

* Commit your XML mapping files to disk and set `this.ormSettings.generateMappings = false` to eliminate the XML mapping generation time on startup.
* Fine-tune `this.ormSettings.entityPaths` to minimize the number of candidate files that need to be parsed for entity discovery.

See also [Startup Metrics Logs](/getting-started/configuration/logging.md#startup-metrics) for consolidated examples of the debug log output for ORM startup performance metrics.

### Entity Discovery

The initial step in ORM startup is to discover ORM entities and parse the entity metadata into internal structures. The time will be proportional to the number of entities found and the number of candidate files that need to be parsed to find those entities. By default, bx-orm will scan all CFC files in the specified entity paths, which can lead to a large number of candidate files and increased startup time.

This can be minimized by fine-tuning `this.ormSettings.entityPaths` [in your orm configuration](https://github.com/ortus-docs/bxorm/tree/master/intro/configuration/README.md).

Enable debug logging to see the time taken for entity discovery and metadata parsing in the logs:

```bash
ORM startup metric - entity file discovery: 42ms (187 candidate files found)
ORM startup metric - entity metadata parsing: 310ms (23 persistent entities found, sequential mode)
```

### XML Mapping Generation

Once persistent entities are located and metadata is collected, bx-orm generates XML mapping files for each entity. This process can be time-consuming, especially if you have a large number of entities.

This startup penalty can be mostly eliminated by committing your hbm.xml mappings to disk and setting `this.ormSettings.generateMappings = false`.

Note that for any ORM entity changes, you will need to regenerate the XML mapping files by setting `this.ormSettings.generateMappings = true` and running `ormReload()` or restarting your application. This will regenerate the XML mappings to pick up any changes.

You can also pre-generate `.hbm.xml` files from the command line, without booting a full application, using the [GenerateMappings CLI tool](/reference/cli-tools.md).

Enable debug mode to see the time taken for XML mapping generation in the logs:

```bash
ORM startup metric - XML mapping generation: 88ms (23 mapping files written)
```

### SessionFactory Build

The bulk of this time is spent in hibernate startup itself. You can minimize this by investigating and applying standard Hibernate performance optimizations.

Enable debug mode to see the time taken for SessionFactory building in the logs:

```bash
ORM startup metric - Hibernate SessionFactory build time [myDatasource]: 1243ms
```

## Runtime Performance

Most ORM performance problems happen after startup. Measure the SQL generated by Hibernate and the time spent in the database before changing fetch strategies or caching. A faster application usually comes from loading fewer rows, issuing fewer queries, and keeping transactions short.

### Use Lazy Loading Deliberately

Keep large to-many relationships lazy so loading a parent entity does not also load its entire object graph. This is usually the right default for `one-to-many` and `many-to-many` relationships. See [Modeling Relationships](/modeling/relationships.md#lazy-loading) for the available `lazy` values.

Lazy loading is not a request to load data for free. Accessing a lazy relationship triggers another SQL query while the entity is attached to an ORM session. Access lazy properties inside the transaction or session boundary, and avoid returning entities to code that will access unloaded relationships after the session has closed.

Do not make every relationship eager to avoid lazy-loading errors. Eager to-many relationships can load a large amount of data and can multiply rows when several collections are joined. Choose the fetch plan for each use case instead.

### Prevent N+1 Queries

An N+1 query occurs when one query loads a list of parent entities and then one additional query runs for each parent's relationship. It is easy to create accidentally:

```js
var users = entityLoad( "User", {}, "name asc", { maxResults: 50 } );

for ( var user in users ) {
	// Accessing posts here can issue one query per user.
	println( user.getPosts().size() );
}
```

When a screen or operation needs a relationship for every result, fetch it intentionally with an HQL join:

```js
var users = ORMExecuteQuery(
	hql    : "SELECT DISTINCT u FROM User u LEFT JOIN FETCH u.posts WHERE u.active = true",
	params : {},
	options: { maxResults: 50 }
);
```

Use fetch joins for the specific use case that needs the related data. Do not fetch-join multiple to-many collections in one query, and be cautious with pagination over a collection fetch join because the database paginates joined rows rather than distinct parent entities. For larger result sets, prefer batch fetching or separate queries.

### Batch Fetching

Batch fetching lets Hibernate initialize several lazy associations with fewer `SELECT` statements. Configure a moderate default batch size through `hibernateProperties`, then tune it using query logs and database metrics:

```js
this.ormSettings.hibernateProperties = {
	"hibernate.default_batch_fetch_size": 25
};
```

Batch fetching is useful when a request loads many entities and then accesses the same lazy relationship on several of them. It does not replace a good fetch plan: a fetch join is often better when the relationship is always required, while batching is useful when access is conditional.

### Batch Writes

For large insert or update jobs, use one transaction and enable JDBC batching. Hibernate can group compatible statements into fewer database round trips:

```js
this.ormSettings.hibernateProperties = {
	"hibernate.jdbc.batch_size": 25,
	"hibernate.order_inserts": true,
	"hibernate.order_updates": true
};
```

Use a batch size appropriate for the database and driver, and commit in bounded chunks for very large jobs. Keep an eye on memory because the session retains managed entities until they are flushed and cleared. Bulk HQL `UPDATE` and `DELETE` statements can be more efficient than loading and changing thousands of entities, but they bypass entity lifecycle events and require careful cache handling.

### Select Only What You Need

For reports, exports, and list screens, avoid loading a complete entity graph when a few columns are sufficient. Use an HQL projection or a purpose-built query, apply `maxResults` and `offset` for pagination, and return read-only results when the data will not be changed:

```js
var rows = ORMExecuteQuery(
	hql    : "SELECT u.id, u.name, u.email FROM User u WHERE u.active = true ORDER BY u.name",
	params : {},
	options: {
		maxResults: 100,
		offset    : 0,
		readOnly  : true
	}
);
```

For deep pagination, large offsets can become expensive because the database still has to find and discard preceding rows. Prefer keyset pagination, such as `WHERE u.id > :lastId ORDER BY u.id`, when the workflow supports it.

### Keep Transactions and Sessions Focused

Open the transaction as late as practical, perform the required work, and commit promptly. Do not hold a transaction open while rendering a response, calling a remote service, or waiting for user input. Long sessions also increase dirty-checking work and memory usage.

For read-heavy code, use `readOnly: true` on queries where supported so Hibernate does not need to track changes for those results. Do not use read-only mode for entities that the operation intends to modify.

### Index the Queries You Actually Run

Hibernate cannot compensate for missing database indexes. Add indexes for columns used frequently in `WHERE`, `JOIN`, and `ORDER BY` clauses, especially foreign keys used by relationships. Validate index choices with the database's execution plan; an index that helps one query can hurt write performance or be ignored when selectivity is poor.

### Inspect Before Optimizing

Enable SQL logging temporarily and inspect:

* the number of SQL statements for one request;
* repeated queries that differ only by an identifier, which often indicates N+1 loading;
* large result sets caused by eager relationships or fetch joins;
* slow statements in the database execution plan;
* flushes or unexpected updates before a query.

See [Querying](/usage/querying.md), [Session Management](/usage/session-management.md), and [Caching](/usage/caching.md) for bx-orm-specific behavior. For the underlying fetch and batching rules, see Hibernate's [Fetching](https://docs.hibernate.org/orm/5.6/userguide/html_single/Hibernate_User_Guide.html#fetching), [Batching](https://docs.hibernate.org/orm/5.6/userguide/html_single/Hibernate_User_Guide.html#batch), and [Querying](https://docs.hibernate.org/orm/5.6/userguide/html_single/Hibernate_User_Guide.html#hql) chapters.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://bxorm.ortusbooks.com/getting-started/performance.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
