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!
- You need to provide the Table Definition object that was generated by Yawn. It will be called
<your-entity-name>Table. - You can immediately provide a block in order to filter down your query. That is where you can have your
addEqand other filter methods.- You can also call
applyFilterlater if you want to pass the criteria around.
- You can also call
- The
booksparameter is an instance ofBookTable, and it is your starting point to access table columns. When you do ajoinoperation, you get other instances to access the new joined columns.- Yawn makes sure you can only access root columns of the table you are querying or tables that you have joined!
- The
.list()call is done outside the lambda. You will not have access to terminal operations inside the lambda.
The rest of the criteria can be found in TypeSafeCriteriaWithWhere such as:
addEqaddGtaddLt- 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()- column with sub-query: check the Sub-queries doc
Operations that do not require the column context are typically only available outside the lambda; such as:
offsetmaxResults
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:
applyFilterapplyProjectionminBy/maxBy
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.
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.
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(...))
}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 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:
listuniqueResultfirstminBy/maxBypaginateZeroIndexed
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")
}
}