AnyDao is a Kotlin JPA (Java Persistence Api) wrapper library which makes your queries short, clean and easy to read.
- DSL that is syntactic sugar for JPA criteria builder
- Shorter and cleaner queries (see examples below)
- Use of kotlin properties as type-sefe static Metamodel replacement
- Supports JPA critera update and criteria delete (compatible with JPA 2.1)
- One-line pagination with
forEach
,forEachFlat
support - Type-safe multiselects (Pair, Triple, Quadruple)
- Using DSL directly with spring data repositories is available with any-dao-spring
AnyDao query (kotlin):
val results = anyDao.all(UserODB::class) {
it[UserODB::email] like "email1"
}
Same query, built with jpa criteria builder (plain java):
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<UserODB> criteriaQuery = cb.createQuery(UserODB.class);
Root<UserODB> root = criteriaQuery.from(UserODB.class);
criteriaQuery.select(root);
criteriaQuery.where(cb.like(root.get("email"),"email1"));
TypedQuery<UserODB> query = em.createQuery(criteriaQuery);
List<UserODB> result = query.getResultList();
More complicated example - AnyDao query (kotlin):
val res = anyDao.all(UserODB::class) {
it[UserODB::task] isIn subqueryFrom(TaskODB::class) {
it[TaskODB::name] like "task1"
or {
it[TaskODB::name] like "task2"
it[TaskODB::createDateTime] lessThan LocalDateTime.now().minusDays(1)
}
}
it[UserODB::userRole] notEq UserRole.ADMIN
}
Same query, built with jpa criteria builder (plain java):
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<UserODB> criteriaQuery = cb.createQuery(UserODB.class);
Root<UserODB> root = criteriaQuery.from(UserODB.class);
criteriaQuery.select(root);
Subquery<TaskODB> subquery = criteriaQuery.subquery(TaskODB.class);
Root<TaskODB> taskRoot = subquery.from(TaskODB.class);
subquery.select(taskRoot);
subquery.where(
cb.or(
cb.like(taskRoot.get("name"), "task1"),
cb.and(
cb.like(taskRoot.get("name"), "task2"),
cb.lessThan(taskRoot.get("createDateTime"), LocalDateTime.now().minusDays(1))
)
)
);
criteriaQuery.where(
root.get("task").in(subquery),
cb.not(cb.equal(root.get("userRole"), UserRole.ADMIN))
);
TypedQuery<UserODB> query = em.createQuery(criteriaQuery);
List<UserODB> result = query.getResultList();
Add to your project maven/gradle dependency:
<dependency>
<groupId>com.kameocode</groupId>
<artifactId>any-dao</artifactId>
<version>1.0.2</version>
</dependency>
compile 'com.kameocode:any-dao:1.0.2'
Create instance of AnyDao:
import com.kameocode.anydao.AnyDao
...
val em: EntityManager = ...
val anyDao = AnyDao(em)
Use like this:
val results: List<UserODB> = anyDao.all(UserODB::class) { it[UserODB::id] lessThan 10L }
Example application with SpringBoot can be found at any-dao-example.
There is number of utility methods, most with parameters clz: KClass<E>, query: KRoot<E>.(KRoot<E>) -> (KSelect<RESULT>)
:
anyDao.all
anyDao.one
anyDao.first
anyDao.count
anyDao.exists
anyDao.allMutable
anyDao.update
anyDao.remove
anyDao.page
anyDao.pages
Construct paths using indexed access operator or get methods:
{ it[UserODB::address, AddressODB::city] like "Cracow" }
{ it.get(UserODB::address).get(AddressODB::city) like "Cracow" }
If fields are private (or entities were written in plain java),
use getter references instead of property references:
(UserODB::getEmail
instead of UserODB::email
)
You can also assign paths and expressions to variables and reference them later:
val listOfUsers = anyDao.all(UserODB::class) {
val taskName = it[UserODB::task, TaskODB::name]
val a = it[UserODB::counter].prod(1)
val b = it[UserODB::counter].abs()
val c = taskName.length()
a eq 1
b greatherThan 0
c greatherThan 0
}
Return other type than in query
Use select
as last clause in order to auto detect return type.
Below query returns list of Strings:
val listOfStrings = anyDao.all(UserODB::class) {
select(it[UserODB::task, TaskODB::name])
}
Use logical expressions (or/and/not predicates)
val listOfUsers = anyDao.all(UserODB::class) {
or {
it[UserODB::getAddress, AddressODB::city] like "Cracow"
it[UserODB::getAddress, AddressODB::city] like "Warsaw"
}
}
or shorter:
val listOfUsers = anyDao.all(UserODB::class) {
it[UserODB::getAddress, AddressODB::city] or {
it like "Cracow"
it like "Warsaw"
}
}
By default, predicates are AND-ed. All predicates applied inside or
are OR-ed.
For the above example, code will be translated into sql query [...] or (city like 'Cracow' and city like 'Warsaw')
.
You can also use or
without clause, like this:
val listOfUsers = anyDao.all(UserODB::class) {
it[UserODB::getAddress, AddressODB::city] like "Cracow"
or
it[UserODB::getAddress, AddressODB::city] like "Warsaw"
}
Expressions or, and, not can be nested into each other.
Use logical expressions (not predicate)
Add not
clause
val listOfUsers = anyDao.all(UserODB::class) {
not { it[UserODB::email] like "task1" }
}
For negating equality, you can use directly:
val listOfUsers = anyDao.all(UserODB::class) {
it[UserODB::email] notEq "task1"
}
Use arithmetic/string expressions
These expressions should be accessible directly on expression/path elements:
val listOfUsers = anyDao.all(UserODB::class) {
it[UserODB::counter].prod(it[UserODB::severity]) eq 1
it[UserODB::counter].abs() greatherThan 2
it[UserODB::email].length() greatherThan 0
}
Use joins
val listOfUsers = anyDao.all(UserODB::class) {
it.join(UserODB::task) {
it[TaskODB::name] like "task1"
}
}
val listOfUsers = anyDao.all(UserODB::class) {
it.joinList(UserODB::allTasks) {
it[TaskODB::name] like "task1"
}
val listOfUsers = anyDao.all(UserODB::class) {
it.joinMap(UserODB::userRoles) {
it.key() like "GUEST"
it.value() like "true"
}
}
Use subquery
val listOfUsers = anyDao.all(UserODB::class) {
it[UserODB::task] isIn subqueryFrom(TaskODB::class) {
it[TaskODB::name] like "task1"
}
}
Use group by/having
val listOfPopularTaskNames = anyDao.all(UserODB::class) {
it.groupBy(it[UserODB::task, TaskODB::name])
it.having {
it[UserODB::task, TaskODB::name].count() greaterThan 100L
}
select(it[UserODB::task, TaskODB::name])
}
Perform criteria update query
val updatedCount = anyDao.update(UserODB::class) {
it[UserODB::email] = "email0"
it[UserODB::email] like "email2"
}
Perform criteria delete query
val removedCount = anyDao.delete(UserODB::class) {
it[UserODB::email] like "email1"
}
Apply JPA predicate
val listOfUsers = anyDao.all(UserODB::class) {
val root = it.getJpaExpression();
it predicate { cb -> cb.equal(root.get<String>("email"), "email1") }
}
Combine query from external predicates
// define predicates as function literals with receiver of proper type:
fun KRoot<UserODB>.hasHighCounter(): KSelect<UserODB> {
return this[UserODB::counter] ge 15
}
private fun KRoot<UserODB>.hasEmail1InName() =
this[UserODB::email] like "%email1%"
// then use them wherever makes sense:
val res = anyDao.all(UserODB::class) {
hasHighCounter()
or
hasEmail1InName()
}
Paging
val pagedListOfUsers = anyDao.pages(UserODB::class, KPage(100)) {
it[UserODB::email] like "email1"
}
pagedListOfUsers.forEach { list->
list.forEach { println(it) }
}
pagedListOfUsers.forEachFlat {
println(it)
}
Typesafe multiselects
val listOfPairs: List<Pair<TaskODB, String>> = anyDao.all(UserODB::class) {
select(it[UserODB::task], it[UserODB::email], it[UserODB::id])
}
val listOfTriples: List<Triple<TaskODB, String, Long>> = anyDao.all(UserODB::class) {
select(it[UserODB::task], it[UserODB::email], it[UserODB::id])
}
val listOfQuadruples: List<Quadruple<TaskODB, String, Long, Long>> = anyDao.all(UserODB::class) {
select(it[UserODB::task], it[UserODB::email], it[UserODB::id], it[UserODB::id])
}
Return custom DTO
class UserTaskDTO(val concatenated: String, val userId: Long, val taskName: String)
val listOfDTOs: List<UserTaskDTO> = anyDao.all(UserODB::class) {
val taskName = it[UserODB::task, TaskODB::name]
select(UserTaskDTO::class,
it[UserODB::email].concat("_").concat(taskName),
it[UserODB::id],
taskName)
}
For more examples, see: tests
Changelog
v 1.0.1
- AnyDao is now AnyDao
- added support for pure java classes for anyDao.exist and anyDao.count
v 1.0.2
- Support for nullable select
- Page is now KPage