mirror of
https://github.com/ebean-orm/ebean.git
synced 2026-10-21 04:46:57 +00:00
Docs: Improve doc guides wording
This commit is contained in:
@@ -82,6 +82,8 @@ or [github discussions](https://github.com/ebean-orm/ebean/discussions)
|
||||
Goto [https://ebean.io/docs/](https://ebean.io/docs/)
|
||||
|
||||
## Guides
|
||||
Library reference (capabilities, scope, and AI guidance): [docs/LIBRARY.md](docs/LIBRARY.md)
|
||||
|
||||
Step-by-step guides for common tasks: [docs/guides/](docs/guides/README.md)
|
||||
|
||||
Available guides:
|
||||
|
||||
@@ -20,7 +20,7 @@ existing Maven project. Complete the steps in order.
|
||||
|
||||
| Guide | Description |
|
||||
|-------|-------------|
|
||||
| [Entity Bean Creation](entity-bean-creation.md) | How to generate clean, idiomatic Ebean entity beans for AI agents; patterns and anti-patterns; minimal boilerplate without getters/setters; decision tree for code generation |
|
||||
| [Entity Bean Creation](entity-bean-creation.md) | How to generate clean, idiomatic Ebean entity beans for AI agents; patterns and anti-patterns; field visibility and accessor guidance; minimal boilerplate |
|
||||
| [Lombok with Ebean entity beans](lombok-with-ebean-entity-beans.md) | Which Lombok annotations to use and avoid on entity beans; why `@Data` is incompatible with Ebean; how to use `@Getter` + `@Setter` + `@Accessors(chain = true)` |
|
||||
|
||||
## Querying
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
This guide provides step-by-step instructions for configuring an Ebean `Database` bean
|
||||
using **Avaje Inject** (`@Factory` / `@Bean`), backed by a PostgreSQL datasource built
|
||||
with Ebean's `DataSourceBuilder`. Follow every step in order. This is Step 2 of 2.
|
||||
with Ebean's `DataSourceBuilder`. Follow every step in order. This is Step 2 of 3.
|
||||
|
||||
---
|
||||
|
||||
@@ -280,3 +280,11 @@ validation best practices, see the [ebean-datasource guides](https://github.com/
|
||||
| `Cannot connect to database` at startup | DB unreachable but `skipDataSourceCheck` is `false` | Set `.skipDataSourceCheck(true)` |
|
||||
| Ebean enhancement warnings in logs | `ebean-maven-plugin` not configured | Complete Step 1 guide |
|
||||
| `NullPointerException` reading config key | Config key not defined | Add the key to `application.yml` or environment |
|
||||
|
||||
---
|
||||
|
||||
## Next Step
|
||||
|
||||
Proceed to **Step 3: Test container setup**
|
||||
(`add-ebean-postgres-test-container.md`) to wire an injectable test `Database`
|
||||
backed by `ebean-test` containers.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
## Purpose
|
||||
|
||||
This guide provides step-by-step instructions for modifying an existing Maven `pom.xml`
|
||||
to add Ebean ORM with PostgreSQL support. Follow every step in order. This is Step 1 of 2.
|
||||
to add Ebean ORM with PostgreSQL support. Follow every step in order. This is Step 1 of 3.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
**Target Audience:** AI systems (Claude, Copilot, ChatGPT, etc.)
|
||||
**Purpose:** Learn how to generate clean, idiomatic Ebean entity beans
|
||||
**Key Insight:** Ebean requires private fields with getters/setters (or other accessors), but they don't need to follow Java bean conventions; no public fields; no equals/hashCode implementation needed
|
||||
**Key Insight:** Ebean entity fields must be non-public (no public fields). Getters/setters are optional, and if used they don't need JavaBeans naming conventions; no manual equals/hashCode implementation is needed
|
||||
**Language:** Java
|
||||
**Framework:** Ebean ORM
|
||||
|
||||
@@ -16,7 +16,7 @@ Before writing entity code, remember:
|
||||
|-------------|---------|-----------------------------------------------------------------------------------------------------------------------|
|
||||
| `@Entity` annotation | ✅ **YES** | Marks class as persistent entity |
|
||||
| `@Id` annotation | ✅ **YES** | Marks primary key field |
|
||||
| Getters/setters (or other accessors) | ✅ **YES** | Required for field access. Don't need to follow Java bean spec; can be record-style, fluent, or any accessor pattern. |
|
||||
| Getters/setters (or other accessors) | ⚠️ **OPTIONAL** | Ebean can use field access directly. Add accessors when your API/design needs them; naming can be JavaBeans, fluent, or custom. |
|
||||
| Default constructor | ❌ **NO** | Not required. Ebean can instantiate without it. |
|
||||
| equals/hashCode | ❌ **NO** | Ebean auto-enhances these at compile time. |
|
||||
| toString() | ❌ **NO** | Ebean auto-enhances this. Don't implement with getters. |
|
||||
@@ -26,8 +26,8 @@ Before writing entity code, remember:
|
||||
|
||||
**Critical:**
|
||||
- Prefer primitive `long` for `@Id` and `@Version`, NOT `Long` object.
|
||||
- Fields should be **private** or **protected** (not public). Access via getters/setters or other accessors.
|
||||
- Getters/setters do NOT need to follow Java bean conventions.
|
||||
- Fields should be non-public: **private**, **protected**, or package-private.
|
||||
- If you add accessors, they do NOT need to follow Java bean conventions.
|
||||
|
||||
---
|
||||
|
||||
@@ -112,7 +112,7 @@ public class Customer {
|
||||
- ✅ `@Entity` marks it as persistent
|
||||
- ✅ `@Id private long id` is the primary key
|
||||
- ✅ Private fields (Ebean does NOT support public fields without expert flags enabled)
|
||||
- ✅ Getters/setters don't need to follow Java bean conventions (shown above with setName but no getId setter)
|
||||
- ✅ Accessors can follow any naming convention, and can be omitted when field access is preferred
|
||||
- ✅ No default constructor needed
|
||||
- ✅ No equals/hashCode needed (Ebean enhances these)
|
||||
|
||||
@@ -640,7 +640,6 @@ public class Customer {
|
||||
|
||||
**Why:** Ebean's naming convention handles this automatically. Only use @Column when your database column doesn't match the convention.
|
||||
|
||||
---
|
||||
---
|
||||
|
||||
## What Ebean Enhancement Provides
|
||||
@@ -662,7 +661,7 @@ At compile time, Ebean enhances your entity classes:
|
||||
**Recommended for ID/Version:**
|
||||
- `long` (primitive) ✅ Use this
|
||||
- `int` (primitive) ✅ Use this
|
||||
- `uuid` (UUID) ✅ Use this
|
||||
- `UUID` ✅ Use this
|
||||
|
||||
**Not recommended:**
|
||||
- `Long` object ⚠️ Avoid (use primitive long)
|
||||
@@ -752,19 +751,19 @@ Each step adds only what's necessary. No getters/setters needed unless your desi
|
||||
### Creating and saving
|
||||
```java
|
||||
Customer customer = new Customer();
|
||||
customer.name = "Alice";
|
||||
customer.setName("Alice");
|
||||
DB.save(customer); // id auto-generated
|
||||
```
|
||||
|
||||
### Finding
|
||||
```java
|
||||
Customer found = DB.find(Customer.class, 1);
|
||||
System.out.println(found.name); // Direct field access works
|
||||
System.out.println(found.getName());
|
||||
```
|
||||
|
||||
### Updating
|
||||
```java
|
||||
found.name = "Bob";
|
||||
found.setName("Bob");
|
||||
DB.update(found); // version auto-incremented
|
||||
```
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ Problems caused:
|
||||
|
||||
`@Data` generates a `toString()` that accesses **all** fields, including
|
||||
`@OneToMany` and `@ManyToOne` associations. Accessing an unloaded lazy association
|
||||
outside of a transaction triggers a `LazyInitializationException` or fires an unexpected
|
||||
outside of a transaction triggers a `LazyInitialisationException` or fires an unexpected
|
||||
SQL query, which can:
|
||||
- Cause subtle bugs in logging statements
|
||||
- Trigger N+1 queries in test output or debug logging
|
||||
|
||||
@@ -91,7 +91,7 @@ Customer customer = new QCustomer()
|
||||
|
||||
customer.setStatus(Customer.Status.ACTIVE);
|
||||
|
||||
DB.save(customer);
|
||||
DB.update(customer);
|
||||
```
|
||||
|
||||
### When to prefer `insert()` over `save()`
|
||||
|
||||
@@ -24,17 +24,19 @@ The `TestEntityBuilder` class is provided by the `ebean-test` module.
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-test</artifactId>
|
||||
<version>16.5.0</version>
|
||||
<version>${ebean.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
**Gradle:**
|
||||
```gradle
|
||||
testImplementation 'io.ebean:ebean-test:16.5.0'
|
||||
testImplementation "io.ebean:ebean-test:${ebeanVersion}"
|
||||
```
|
||||
|
||||
Replace `16.5.0` with the version matching your Ebean installation.
|
||||
Use a version that matches your Ebean runtime (`ebean.version` /
|
||||
`ebeanVersion`), or replace with an explicit fixed version if your build does
|
||||
not centralize dependency versions.
|
||||
|
||||
### Import the Class
|
||||
|
||||
@@ -64,15 +66,14 @@ The `build()` method creates an instance with populated fields **without persist
|
||||
Product product = builder.build(Product.class);
|
||||
|
||||
// Fields are populated:
|
||||
// - id: null (not populated)
|
||||
// - id: unset (typically 0 for primitive long, null for boxed Long)
|
||||
// - name: random UUID-based string
|
||||
// - price: random BigDecimal
|
||||
// - inStock: true
|
||||
// - createdAt: current instant
|
||||
// - etc.
|
||||
|
||||
// Not persisted yet:
|
||||
assert product.getId() == null;
|
||||
// Not persisted yet (`@Id` is still unset until the entity is persisted).
|
||||
```
|
||||
|
||||
### Build and Save (Persist to Database)
|
||||
@@ -83,7 +84,7 @@ The `save()` method creates, persists, and returns an entity with the database-a
|
||||
Product product = builder.save(Product.class);
|
||||
|
||||
// Entity is now in the database:
|
||||
assert product.getId() != null;
|
||||
assert DB.find(Product.class, product.getId()) != null;
|
||||
|
||||
// Verify it was saved:
|
||||
Product found = DB.find(Product.class, product.getId());
|
||||
@@ -167,7 +168,8 @@ Order order = builder.build(Order.class);
|
||||
// Both order and customer are built:
|
||||
assert order != null;
|
||||
assert order.getCustomer() != null;
|
||||
assert order.getCustomer().getId() == null; // not persisted yet
|
||||
// Before persist, @Id values are typically unset
|
||||
// (0 for primitive IDs, null for boxed IDs).
|
||||
|
||||
// When saved, cascade handles both:
|
||||
Order saved = builder.save(Order.class);
|
||||
@@ -253,7 +255,7 @@ Subclass `RandomValueGenerator` and override individual `random*()` methods:
|
||||
|
||||
```java
|
||||
class CompanyTestDataGenerator extends RandomValueGenerator {
|
||||
|
||||
|
||||
@Override
|
||||
protected String randomString(String propName, int maxLength) {
|
||||
if (propName != null && propName.toLowerCase().contains("email")) {
|
||||
@@ -267,14 +269,14 @@ class CompanyTestDataGenerator extends RandomValueGenerator {
|
||||
}
|
||||
return super.randomString(propName, maxLength);
|
||||
}
|
||||
|
||||
|
||||
// Override other methods as needed:
|
||||
@Override
|
||||
protected Object randomEnum(Class<?> type) {
|
||||
if (type == OrderStatus.class) {
|
||||
// Bias towards common statuses for realistic test data
|
||||
return ThreadLocalRandom.current().nextDouble() < 0.8
|
||||
? OrderStatus.PENDING
|
||||
return ThreadLocalRandom.current().nextDouble() < 0.8
|
||||
? OrderStatus.PENDING
|
||||
: OrderStatus.COMPLETED;
|
||||
}
|
||||
return super.randomEnum(type);
|
||||
@@ -299,7 +301,7 @@ assert user.getEmail().endsWith("@mycompany.com");
|
||||
|
||||
```java
|
||||
public class MoneyValueGenerator extends RandomValueGenerator {
|
||||
|
||||
|
||||
@Override
|
||||
protected BigDecimal randomBigDecimal(int precision, int scale) {
|
||||
// Generate prices in a realistic range: $5.00 to $999.99
|
||||
@@ -346,7 +348,7 @@ When test requirements demand specific field values, manually override after bui
|
||||
void whenStockIsLow_thenShowWarning() {
|
||||
Product product = builder.build(Product.class);
|
||||
product.setQuantity(2); // Specific value for this test
|
||||
|
||||
|
||||
boolean shouldWarn = product.shouldShowLowStockWarning();
|
||||
assertThat(shouldWarn).isTrue();
|
||||
}
|
||||
@@ -358,23 +360,23 @@ Encapsulate common test entity setups in factory methods:
|
||||
|
||||
```java
|
||||
public class TestDataFactory {
|
||||
|
||||
private static final TestEntityBuilder builder =
|
||||
|
||||
private static final TestEntityBuilder builder =
|
||||
TestEntityBuilder.builder(DB.getDefault()).build();
|
||||
|
||||
|
||||
public static Order createPendingOrder() {
|
||||
Order order = builder.build(Order.class);
|
||||
order.setStatus(OrderStatus.PENDING);
|
||||
return order;
|
||||
}
|
||||
|
||||
|
||||
public static Order createShippedOrder() {
|
||||
Order order = builder.build(Order.class);
|
||||
order.setStatus(OrderStatus.SHIPPED);
|
||||
order.setShippedAt(Instant.now());
|
||||
return order;
|
||||
}
|
||||
|
||||
|
||||
public static Order savePendingOrder() {
|
||||
Order order = createPendingOrder();
|
||||
DB.save(order);
|
||||
@@ -400,7 +402,7 @@ void whenFetchingMultipleOrders_thenAllUnique() {
|
||||
Order order1 = builder.save(Order.class);
|
||||
Order order2 = builder.save(Order.class);
|
||||
Order order3 = builder.save(Order.class);
|
||||
|
||||
|
||||
assertThat(order1.getId()).isNotEqualTo(order2.getId());
|
||||
assertThat(order2.getId()).isNotEqualTo(order3.getId());
|
||||
assertThat(order1.getOrderNumber()).isNotEqualTo(order2.getOrderNumber());
|
||||
@@ -416,31 +418,31 @@ void whenFetchingMultipleOrders_thenAllUnique() {
|
||||
```java
|
||||
@SpringBootTest
|
||||
class OrderRepositoryTest {
|
||||
|
||||
|
||||
@Autowired
|
||||
private OrderRepository orderRepository;
|
||||
|
||||
|
||||
private TestEntityBuilder builder;
|
||||
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
builder = TestEntityBuilder.builder(DB.getDefault()).build();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void whenFindingOrdersByStatus_thenReturnsMatching() {
|
||||
// Create test data quickly
|
||||
Order pending1 = builder.build(Order.class);
|
||||
pending1.setStatus(OrderStatus.PENDING);
|
||||
|
||||
|
||||
Order pending2 = builder.build(Order.class);
|
||||
pending2.setStatus(OrderStatus.PENDING);
|
||||
|
||||
|
||||
Order shipped = builder.build(Order.class);
|
||||
shipped.setStatus(OrderStatus.SHIPPED);
|
||||
|
||||
|
||||
DB.saveAll(pending1, pending2, shipped);
|
||||
|
||||
|
||||
// Test the query
|
||||
List<Order> pending = orderRepository.findByStatus(OrderStatus.PENDING);
|
||||
assertThat(pending).hasSize(2);
|
||||
@@ -454,12 +456,13 @@ class OrderRepositoryTest {
|
||||
@Test
|
||||
void whenBuildingOrderWithCustomer_thenBothPopulated() {
|
||||
Order order = builder.build(Order.class);
|
||||
|
||||
|
||||
// Customer is recursively built because of @ManyToOne(cascade=PERSIST)
|
||||
assertThat(order.getCustomer()).isNotNull();
|
||||
assertThat(order.getCustomer().getId()).isNull(); // not persisted yet
|
||||
// Before persist, @Id values are typically unset
|
||||
// (0 for primitive IDs, null for boxed IDs).
|
||||
assertThat(order.getCustomer().getName()).isNotNull();
|
||||
|
||||
|
||||
// Saving cascades to customer:
|
||||
Order saved = builder.save(Order.class);
|
||||
assertThat(saved.getId()).isNotNull();
|
||||
@@ -486,7 +489,7 @@ void usingCustomGenerator() {
|
||||
TestEntityBuilder builder = TestEntityBuilder.builder(DB.getDefault())
|
||||
.valueGenerator(new ECommerceTestDataGenerator())
|
||||
.build();
|
||||
|
||||
|
||||
Product product = builder.build(Product.class);
|
||||
assertThat(product.getPrice())
|
||||
.isBetween(BigDecimal.TEN, BigDecimal.valueOf(500.0));
|
||||
@@ -510,19 +513,22 @@ public class Product {
|
||||
}
|
||||
```
|
||||
|
||||
### Fields are null even though I expected them to be populated
|
||||
### Fields are unset even though I expected them to be populated
|
||||
|
||||
**Cause:** `TestEntityBuilder` does **not** populate:
|
||||
- `@Id` fields (identity/primary key)
|
||||
- `@Version` fields (optimistic locking)
|
||||
- `@Id` fields (identity/primary key; left unset until persist)
|
||||
- `@Version` fields (optimistic locking; left unset until persist)
|
||||
- `@Transient` fields
|
||||
- `@OneToMany` collections
|
||||
- Non-cascade `@ManyToOne` relationships
|
||||
|
||||
**Solution:** Set these fields manually in your test if needed:
|
||||
**Solution:** Set only the fields your test scenario cares about, then persist.
|
||||
`@Id` and `@Version` are usually database-managed and should typically be left
|
||||
unset before save:
|
||||
```java
|
||||
Product product = builder.build(Product.class);
|
||||
product.setId(1L); // Set @Id manually
|
||||
product.setName("specific-name"); // test-specific override
|
||||
DB.save(product); // database assigns @Id/@Version
|
||||
```
|
||||
|
||||
### Building recursive relationships causes StackOverflowError
|
||||
@@ -543,7 +549,7 @@ person.getOrganization().setFounder(null); // Break cycle
|
||||
```java
|
||||
class DeterministicTestDataGenerator extends RandomValueGenerator {
|
||||
private int counter = 0;
|
||||
|
||||
|
||||
@Override
|
||||
protected String randomString(String propName, int maxLength) {
|
||||
return "test_" + (counter++);
|
||||
|
||||
Reference in New Issue
Block a user