Skip to content

Latest commit

 

History

History
181 lines (133 loc) · 5.28 KB

File metadata and controls

181 lines (133 loc) · 5.28 KB

Basic Queries

Basic queries can be performed using a very familiar API to Hibernate 5's Criteria API: addEq, addGt, etc. are all available inside the Yawn-scoped lambda.

To create a query with Yawn, you will need to construct an instance of Yawn with a query factory.

You will also need to annotate the entity you are querying with the @YawnEntity annotation. That will cause KSP to compile it into an additional object called <your-entity-name>Table.

Then, you are able to use the query method to create a new query:

val results = yawn.query(BookTable) { books ->
    addEq(books.name, "The Hobbit")
}.list()

Let’s break it down!

  1. You need to provide the Table Definition object that was generated by Yawn. It will be called <your-entity-name>Table.
  2. You can immediately provide a block in order to filter down your query. That is where you can have your addEq and other filter methods.
    1. You can also call applyFilter later if you want to pass the criteria around.
  3. The books parameter is an instance of BookTable, and it is your starting point to access table columns. When you do a join operation, you get other instances to access the new joined columns.
    1. Yawn makes sure you can only access root columns of the table you are querying or tables that you have joined!
  4. The .list() call is done outside the lambda. You will not have access to terminal operations inside the lambda.

Other Available Criteria

The rest of the criteria can be found in TypeSafeCriteriaWithWhere such as:

  • addEq
  • addGt
  • addLt
  • etc…

Note that these methods have a few overloads available, depending on what you want to compare:

  • column with a value
val results = yawn.query(BookTable) { books ->
    addEq(books.name, "The Hobbit")
}.list()
  • column with other column:
val results = yawn.query(BookTable) { books ->
    val authors = join(books.author)
    val bestWorks = join(authors.bestWork)
    addGt(books.rating, bestWorks.rating)
}.list()

Non-Column-Based Operations

Operations that do not require the column context are typically only available outside the lambda; such as:

  • offset
  • maxResults

For example:

val results = yawn.query(BookTable) { books ->
    addEq( ... ) // <- requires the column definition context
}
    .maxResults(10)
    .list()

Some operations that do require the column context are also available outside the lambda context (by providing their own lambda), such as:

  • applyFilter
  • applyProjection
  • minBy / maxBy

Lock Modes

Yawn supports pessimistic locking through the setLockMode (or the forUpdate and forShare helpers). These are useful when you need to prevent concurrent modifications to rows you're reading.

forUpdate (PESSIMISTIC_WRITE)

Use forUpdate() when you intend to update the selected rows. This adds FOR UPDATE to the SQL query, acquiring an exclusive lock:

val results = yawn.query(BookTable) { books ->
    addEq(books.name, "The Hobbit")
}
    .forUpdate()
    .list()

This is commonly used in event consumers and batch update operations where you need to prevent concurrent modifications.

forShare (PESSIMISTIC_READ)

Use forShare() for "find or create" patterns where you need to prevent concurrent creates but allow concurrent reads. This adds FOR SHARE to the SQL query:

val existing = yawn.query(BookTable) { books ->
    addEq(books.token, bookToken)
}
    .forShare()
    .uniqueResult()

if (existing == null) {
    // Safe to create - other transactions will wait
    session.save(DbBook(...))
}

Explicit Lock Mode

You can also use setLockMode directly with any YawnLockMode value:

val results = yawn.query(BookTable) { books ->
    addEq(books.name, "The Hobbit")
}
    .setLockMode(YawnLockMode.PESSIMISTIC_WRITE)
    .list()

Available lock modes:

  • YawnLockMode.NONE - No lock (default behavior)
  • YawnLockMode.PESSIMISTIC_READ - Shared lock (FOR SHARE)
  • YawnLockMode.PESSIMISTIC_WRITE - Exclusive lock (FOR UPDATE)

Terminal Operations

Terminal operations such as uniqueResult or list can only be called outside the lambda, and they do not return the builder as they terminate the query. These operations are defined in BaseTypeSafeCriteriaBuilder , such as:

  • list
  • uniqueResult
  • first
  • minBy / maxBy
  • paginateZeroIndexed

Pass/Modify Queries Around

If you want to “pass” a query object around, you can just call applyFilter multiple times.

For example:

fun mainQuery(): List<DbBook> {
    val baseCriteria = yawn.query(BookTable)
    applyFilters(baseCriteria)
    return baseCriteria.list()
}

private fun applyFilters(
    criteria: TypeSafeCriteriaBuilder<Book, BookTable>,
) {
    criteria.applyAuthorFilters()
    criteria.applyPublisherFilters()
}

private fun TypeSafeCriteriaBuilder<Book, BookTable>.applyAuthorFilters() { 
    applyFilter { books ->
        val authors = join(books.author)
        addEq(authors.name, "J.K. Rowling")
    }
}

private fun TypeSafeCriteriaBuilder<Book, BookTable>.applyPublisherFilters() {
    applyFilter { books ->
        val publishers = join(books.publisher)
        addEq(publishers.name, "HarperCollins")
    }
}