Just Another Object-Relational Mapping
JAORM is a lightweight modular compile-time based Java ORM.
JAORM use Java Annotation Processor JSR 269 for Entity Mapping Generation instead of Runtime Reflection API-based mappers which have high performance cost.
JAORM is divided in modules that are used from main module using Java SPI
Modules
Core Module
<dependency>
<groupId>io.github.ulisse1996</groupId>
<artifactId>jaorm</artifactId>
<version>${jaorm.version}</version>
</dependency>
DSL Module
<dependency>
<groupId>io.github.ulisse1996</groupId>
<artifactId>jaorm-dsl</artifactId>
<version>${jaorm.version}</version>
</dependency>
Cache Module
<dependency>
<groupId>io.github.ulisse1996</groupId>
<artifactId>jaorm-cache</artifactId>
<version>${jaorm.version}</version>
</dependency>
Transaction Module
<dependency>
<groupId>io.github.ulisse1996</groupId>
<artifactId>jaorm-transaction</artifactId>
<version>${jaorm.version}</version>
</dependency>
Lombok Support Module
<dependency>
<groupId>io.github.ulisse1996</groupId>
<artifactId>jaorm-lombok</artifactId>
<version>${jaorm.version}</version>
</dependency>
Sql Vendor Specific (DSL Plugin)
Oracle
<dependency>
<groupId>io.github.ulisse1996</groupId>
<artifactId>jaorm-sql-specific-oracle</artifactId>
<version>${jaorm.version}</version>
</dependency>
SQL Server
<dependency>
<groupId>io.github.ulisse1996</groupId>
<artifactId>jaorm-sql-specific-oracle</artifactId>
<version>${jaorm.version}</version>
</dependency>
PostgreSQL
<dependency>
<groupId>io.github.ulisse1996</groupId>
<artifactId>jaorm-sql-specific-oracle</artifactId>
<version>${jaorm.version}</version>
</dependency>
1 Core
Core Module contains Entity Mapper and Query annotated services.
User must define and register a custom implementation of DatasourceProvider that return an instance of javax.sql.Datasource for execute SQL Statements.
import io.github.ulisse1996.jaorm.entity.sql.DataSourceProvider;
import javax.enterprise.inject.spi.CDI;
import javax.enterprise.util.AnnotationLiteral;
import javax.sql.DataSource;
public class DataSourceProviderImpl extends DataSourceProvider {
@Override
public DataSource getDataSource() {
return CDI.current().select(DataSource.class)
.get();
}
}
1.1 Entity Mapper
An Entity is a POJO representing data that can be persisted to the database
Each Entity could contain:
-
1..1 @Table annotation that represent database table name
-
0..1 @Cacheable annotation that define an Entity as cacheable
-
1..N @Column annotated fields that represent column in database table
-
1..N @Id annotated fields that represent a primary/unique key in database table
-
0..N @TableGenerated annotated fields that represent an autogenerated primary/unique key with a backed table
-
0..N @CustomGenerated annotated fields that represent an autogenerated primary/unique key with custom logic
-
0..N @Converter annotated fields that represent a logical conversione between a sql type and a custom/not supported type (like enum o user custom defined type)
-
0..N @Relationship annotated fields that represent a logical relationship between two database tables
-
0..N @Cascade annotated fields that are affected by operation done on parent Entity
import io.github.ulisse1996.jaorm.annotation.*;
import io.github.ulisse1996.jaorm.entity.converter.BooleanIntConverter;
import java.util.List;
@Table(name = "TABLE_NAME")
@Cacheable
public class Entity {
@Id(autoGenerated = true)
@Column(name = "COLUMN_1")
private String column1;
@Column(name = "COLUMN_2")
private int column2;
@Column(name = "READ_CONFIRM")
@Converter(BooleanIntConverter.class)
private boolean aBoolean;
@Relationship(columns =
@Relationship.RelationshipColumn(sourceColumn = "COLUMN_1", targetColumn = "COLUMN_REF_1")
)
@Cascade(CascadeType.ALL)
private List<RefEntity> refEntities;
// Getter and Setter omitted for brevity
}
For each @Column annotated fields , user must also define a getter and a setter for that field |
A standard Constructor with no args must also be provided |
Entities also supports Inheritance for @Columns , @Relationship or Local/Global Events
@Data
public abstract class AbstractEntity implements PrePersist<RuntimeException> {
@Column(name = "CREATE_DATE")
protected Date createDate;
@Column(name = "CREATE_USER")
protected String creationUser;
@Override
public void prePersist() {
this.createDate = new Date();
this.creationUser = "1";
}
}
@EqualsAndHashCode(callSuper = true)
@Table(name = "COMPOUND_ENTITY")
@Data
public class CompoundEntity extends AbstractEntity {
@Id
@Column(name = "COMPOUND_ID")
private int compoundId;
}
1.1.1 Id (Primary/Unique Key)
In contrast to JPA or others ORM , Jaorm does not use compound id class
User can declare one or more @Id annotation in a single entity that are used to define how Entity must be selected, updated and deleted from database
Jaorm use each @Id for create specifics where conditions
An @Id annotation can also be marked as auto-generated.
Jaorm retrieve and set auto-generated keys during persist events |
1.1.1.1 Auto Generated Id/Column
User can also mark @Id or @Column for custom auto-generation using @TableGenerated or @CustomGenerated
1.1.1.2 TableGenerated
TableGenerated will use a specific table for select keyColumn that match matchKey. If a custom converter is provided, Jaorm will convert matchKey to the selected Type.
@Table(name = "ENTITY")
public class Entity {
@Id(autoGenerated = true)
@Column(name = "MY_ID")
@TableGenerated(tableName = "TABLE", keyColumn = "MY_KEY", valueColumn = "NUMBER",
matchKey = "MY_MATCH", matchConverter = ParameterConverter.NONE)
private int myId;
@Column(name = "MY_COL")
@CustomGenerated(CustomGenerator.class)
private String col;
// Getter And Setter Omitted for brevity
}
Jaorm will lock and update atomically valueColumn during auto-generation process. User must provide a custom implementation for lock select-update using jaorm specific sql syntax (see Vendor Specific) |
1.1.1.3 Custom Generated
CustomGenerated will generate a custom value using a custom implementation of CustomGenerator provided by the user.
Provided value must be compatible with field type. |
1.1.1.4 Default Initializers
Jaorm will generate default values for annotated field with DefaultNumeric, DefaultString and DefaultTemporal during persist event
@Table(name = "ENTITY_WITH_DEFAULTS")
public class EntityWithDefaults {
@Id
@Column(name = "E_ID")
private BigInteger id;
@Column(name = "E_STR")
@DefaultString("STRING")
private String str;
@Column(name = "E_NUM")
@DefaultNumeric(0.390)
private BigDecimal dec;
@Column(name = "E_DATE")
@DefaultTemporal
private Date date;
@Column(name = "E_DATE_FORMAT")
@DefaultTemporal(format = "dd-MM-yyyy'T'HH:mm:ss", value = "20-10-2022T00:00:00")
private Date dateWithFormat;
public Date getDateWithFormat() {
return dateWithFormat;
}
public void setDateWithFormat(Date dateWithFormat) {
this.dateWithFormat = dateWithFormat;
}
public BigInteger getId() {
return id;
}
public void setId(BigInteger id) {
this.id = id;
}
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
public BigDecimal getDec() {
return dec;
}
public void setDec(BigDecimal dec) {
this.dec = dec;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
}
1.1.2 Column
User can declare one or more @Column annotated fields that are used by Jaorm for retrieve all selected columns from a table.
Each field that is not annotated with @Column are treated as ignored property by Jaorm |
import io.github.ulisse1996.jaorm.annotation.*;
import io.github.ulisse1996.jaorm.entity.converter.BooleanIntConverter;
import java.util.List;
@Table(name = "TABLE_NAME")
@Cacheable
public class Entity {
@Id(autoGenerated = true)
private String column1;
@Column(name = "COLUMN_2")
private int column2;
@Column(name = "READ_CONFIRM")
private boolean aBoolean;
// Getter and Setter omitted for brevity
}
1.1.3 Converter
User can define a custom Converter between a custom defined type (or a simple different type from the one defined on table column) and table column referenced by @Column annotation using an implementation of ValueConverter<T,R> where T is the table column type and R is the custom defined type
Jaorm contains two custom implementation for Boolean type :
-
BooleanStringConverter, that convert a matching "Y" string to true , otherwise to false
-
BooleanIntConverter, that convert 1 to true, otherwise to false
Each of them contains standard implementation for ValueConverter with a public static instance (aka Singleton) named INSTANCE.
public class BooleanIntConverter implements ValueConverter<Integer, Boolean> {
public static final BooleanIntConverter INSTANCE = new BooleanIntConverter();
private BooleanIntConverter() {}
@Override
public Boolean fromSql(Integer val) {
return val != null && val == 1;
}
@Override
public Integer toSql(Boolean val) {
return val != null && val ? 1 : 0;
}
}
User can follow standard implementation (defining a public INSTANCE field) or defining a public constructor |
1.1.4 Relationships
User can define one of One To One, One To Many, Many-To-Many relationships between two entities using @Relationship annotation on a field.
Each annotated fields are treated as a linked entity that will be retrieved using a lazy strategy calling getter method on parent Entity
import io.github.ulisse1996.jaorm.annotation.*;
import io.github.ulisse1996.jaorm.entity.Result;
import io.github.ulisse1996.jaorm.entity.ParameterConverter;
import io.github.ulisse1996.jaorm.entity.converter.BooleanIntConverter;
import java.util.List;
@Table(name = "TABLE_NAME")
@Cacheable
public class Entity {
@Id(autoGenerated = true)
@Column(name = "COLUMN_1")
private String column1;
@Column(name = "COLUMN_2")
private int column2;
@Column(name = "READ_CONFIRM")
@Converter(BooleanIntConverter.class)
private boolean aBoolean;
@Relationship(columns =
@Relationship.RelationshipColumn(sourceColumn = "COLUMN_1", targetColumn = "COLUMN_REF_1")
)
@Cascade(CascadeType.ALL)
private List<RefEntity> refEntities;
@Relationship(columns =
@Relationship.RelationshipColumn(defaultValue="1",
converter=ParameterConverter.BIG_DECIMAL, targetColumn = "COLUMN_REF_1")
)
private Result<OptEntity> refEntities;
@Relationship(columns =
@Relationship.RelationshipColumn(defaultValue="aString", targetColumn = "COLUMN_REF_1")
)
private AnotherEntity refEntities;
// Getter and Setter omitted for brevity
}
Each @Relationship must contain 1..N @RelationshipColumn
Each @RelationshipColumn could contain :
-
a default value , for relationship with a constant value
-
a converter from ones defined in ParameterConverter that convert default value in a different sql type
-
a source column , that match a table column defined in current entity
-
a target column , that match a table column defined in relationship Entity
The following table define how user must use @RelationshipColumn
Default Value | Converter | Source Column | Target Column | |
---|---|---|---|---|
Default Value |
Optional |
Not Required |
Required |
|
Converter |
Required |
Not Required |
Required |
|
Source Column |
Not Required |
Not Required |
Required |
Supported type for @Relationship annotated fields are :
-
io.github.ulisse1996.jaorm.entity.Result<T> , for an optional One to One relationship that match Optional<T> specifications
-
java.util.List<T> , for One To Many or Many-To-Many relationship
-
a User defined Entity, for One to One relationship
If Jaorm could not find a linked Entity and the linked Entity is not defined as a List or a Result , Jaorm will throw a Runtime SQL Exception |
1.1.5 Entity Events
User can observe Local and/or Global Entity Events during Persist, Update or Remove events performed by Jaorm
1.1.5.1 Local Entity Events
User can observe Local Entity events implementing one or more of the followings interface on Entity class:
-
PrePersist<X>
-
PostPersist<X>
-
PreUpdate<X>
-
PostUpdate<X>
-
PreRemove<X>
-
PreDelete<X>
@Table(name = "USER")
public class User implements PostPersist<IllegalArgumentException>, PreUpdate<IllegalArgumentException> {
@Column(name = "USER_ID")
@Id(autoGenerated = true)
private BigDecimal userId;
@Column(name = "USERNAME")
private String username;
@Column(name = "EMAIL")
private String email;
@Column(name = "NAME")
private String name;
@Column(name = "LAST_NAME")
private String lastName;
Each interface use a custom exception parameter for user checked exception.
If an Exception is thrown, Jaorm will throw a custom EventException
1.1.5.2 Global Entity Events
User can handle Global Events (Persist, Delete or Update) registering a custom GlobalEventListener
Jaorm will handle only Entity annotated with GlobalListener
import io.github.ulisse1996.jaorm.annotation.Column;
import io.github.ulisse1996.jaorm.annotation.GlobalListener;
import io.github.ulisse1996.jaorm.annotation.Id;
import io.github.ulisse1996.jaorm.annotation.Table;
@GlobalListener
@Table(name = "CITY")
public class GlobalEntityCity {
@Id
@Column(name = "CITY_ID")
private int cityId;
@Column(name = "CITY_NAME")
private String name;
public int getCityId() {
return cityId;
}
public void setCityId(int cityId) {
this.cityId = cityId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
1.1.6 Cascade
Jaorm also supports Cascade operation with @Cascade annotation on relationship fields
@Table(name = "USER")
public class User {
@Id
@Column(name = "COL1")
private String id;
@Column(name = "COL2")
private String col2;
@Cascade(CascadeType.ALL)
@Relationship(columns = @Relationship.RelationshipColumn(defaultValue = "DEPARTMENT_ID",
targetColumn = "DEPARTMENT_ID"))
private Department department;
// getter and setters
}
Supported operations :
-
ALL , on Insert , Update or Delete , linked entities are affected by the operation on the entity
-
PERSIST , on Insert , linked entities are affected by the insert on the entity
-
REMOVE , on Delete , linked entities are affected by the remove on the entity
-
UPDATE , on Update , linked entities are affected by the update on the entity
If nested links are created , only annotated fields with @Cascade and with matched CascadeType are affected by the operation |
1.1.7 Equality And Distinct
In Jaorm , equality between two Entity can be tricky.
Jaorm use Delegation pattern for Entity Mapping and Lazy fetching of linked entities , so a fetched Entity retrieved with Query, Dao or DSL is an instance of selected Entity but doesn’t have the same class at runtime
public class Example {
@Table(name = "USER")
public static class User {
@Id
@Column(name = "COL1")
private String id;
@Column(name = "COL2")
private String col2;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(id, id) && Objects.equals(col2, user.col2);
}
@Override
public int hashCode() {
return Objects.hash(id, col2);
}
// getter and setters
}
public void should_return_true_for_same_entity() {
UserDAO dao = QueriesService.getInstance().getQuery(UserDAO.class);
User user = new User();
user.setId("id");
user.setName("name");
User found = dao.read(user);
// Return true, delegate instance underline check equality with two User instance
boolean foundEquals = found.equals(user);
// Return false , because found.getClass() != User.class
boolean userEquals = user.equals(found);
}
}
For Equals check between two Entities , EntityComparator must be used if equals implementation use getClass() checks
public class Test {
public static void main(String[] args) {
User user1 = new User();
User user2 = new User();
EntityComprator.getInstance(User.class)
.equals(user1, user2);
EntityComprator.getInstance(User.class)
.equals(Collections.singletonList(user1), Collections.singletonList(user2));
}
}
For support with distinct() in Java 8 Stream API , user can also use EntityComparator.distinct(Function<? super T,?> function)
public class Test {
void method() {
List<User> users = QueriesService.getQuery(UserDao.class)
.readAll();
List<User> usersDistinctId =
users.stream()
.filter(EntityComparator.distinct(User::getUserId))
.collect(Collectors.toList);
}
}
1.2 Projections
Jaorm also supports Projections, a subset of an Entity that only store data without supporting persist/update/delete events.
User can use a Projection for store a set of selected columns from a custom query
For defining a new Projection, just annotate a POJO with Projection and Column/Converter for each field that JAORM needs to read/convert.
@Projection
public class MyProjection {
@Column(name = "ID_COL")
private BigDecimal id;
@Column(name = "SUB_NAME")
private String subName;
@Column(name = "VALID")
@Converter(BooleanIntConverter.class)
private boolean valid;
private Date other;
public boolean isValid() {
return valid;
}
public void setValid(boolean valid) {
this.valid = valid;
}
public Date getOther() {
return other;
}
public void setOther(Date other) {
this.other = other;
}
public BigDecimal getId() {
return id;
}
public void setId(BigDecimal id) {
this.id = id;
}
public String getSubName() {
return subName;
}
public void setSubName(String subName) {
this.subName = subName;
}
}
1.3 Query Processor
For each interface that contains a method with @Query annotation or is annotated with @Dao annotation, an implementation is generated.
Implemented Method execute sql value in the annotation and return an object for a non-void method. If returned Object is an Entity , Core module create the mapped entity else the first column is returned.
Custom runtime mapper are also supported with the use of TableRow return type. |
Query supports different arguments matchers likes :
-
Standard Wildcards
-
A named parameter (es :name)
-
Ordered Wildcards (es: ?1,?2)
-
At Names (es: @name, @name2)
-
No Args (@Query.noArgs must be set to true for this special case)
Query uses parameter name or annotated parameter with Param for retrieve the current value during compile-time
public interface UserDao extends BaseDao<User> {
@Query(sql = "SELECT * FROM USER WHERE USER_ID = ? AND USERNAME = ?")
User getUser(String userId, String username);
@Query(sql = "SELECT * FROM USER WHERE USER_ID = ? AND USERNAME = ?")
Optional<User> getUserOpt(String userId, String username);
@Query(sql = "SELECT * FROM USER WHERE USER_ID = ? AND USERNAME = ?")
List<User> getAllUsers(String userId, String username);
@Query(sql = "SELECT * FROM USER WHERE USER_ID = :userId
AND USERNAME = :username AND NAME = :username")
User getUserNamed(String userId, String username);
@Query(sql = "SELECT * FROM USER WHERE USER_ID = :userId
AND USERNAME = :username AND NAME = :username")
User getUserNamed2(String userId, @Param(name = "USERNAME") String name);
@Query(sql = "SELECT * FROM USER WHERE USER_ID = ?1 AND USERNAME = ?2 AND NAME = ?1")
User getUserOrdered(String userId, String username);
@Query(sql = "SELECT * FROM USER WHERE USER_ID = @userId
AND USERNAME = @username AND NAME = @username")
User getUserAtNamed(String userId, String username);
@Query(sql = "SELECT * FROM USER WHERE USER_ID = @userId
AND USERNAME = @username AND NAME = @username")
TableRow getUserTableRow(String userId, String username);
@Query(sql = "SELECT * FROM USER WHERE USER_ID = @userId
AND USERNAME = @username AND NAME = @username")
Optional<TableRow> getUserTableRowOpt(String userId, String username);
@Query(sql = "SELECT * FROM USER WHERE USER_ID = @userId
AND USERNAME = @username AND NAME = @username")
Stream<TableRow> getUserTableRowStream(String userId, String username);
@Query(sql = "UPDATE USER SET USER_ID = :userId where USERNAME = :username")
void updateUser(String userId, String username);
@Query(sql = "DELETE FROM USER", noArgs = true)
void deleteAll();
}
Annotations on an interface with @Query methods or @Dao annotation are also propagated in the implementation
@Dao
@RequestScoped
public interface MyDao extends BaseDao<User> {}
@RequestScoped
public class MyDaoImpl implements MyDao {} // @RequestScoped is propagated
User can also use custom types that can be converted to sql types if the converter is already used on an Entity |
1.2.1 Supported Return Type
Custom generated Queries supports return types like :
-
Void, for Update/Delete SQL
-
Optional<T>, for an optional entity/projection
-
Optional<TableRow>, for an optional mappable row
-
List<T>, for a collection of entities/projections
-
Stream<T>, for a stream of entities/projections
-
Stream<TableRow>, for a stream of mappable rows
-
TableRow, for a mappable row
-
T, for an entity or a projection or a supported accessible type
1.2.2 Fast Table Read
User can also use generated Tables entry point for type-safe read of an Entity.
Tables use a custom DSL for retrieve each Entity annotated with Table
public class Test {
public static void main(String... args) {
User user = Tables.USER_TABLE.userId(10).read();
}
}
1.2.3 SQL in File
User can also use stored SQL located in resources.
Jaorm will read and replace SQL location with full text during compile phase before validation. |
@Dao
public interface CustomUserDao {
@Query(sql = "/mySql.sql")
Optional<User> getUserOpt(int userId);
}
1.4 Logging
A default NoOp Logger is created for each service that use JaormLogger.
User can redirect and use logged information implementing and registering a custom JaormLoggerHandler
1.5 Pagination
User can retrieve paginated results using default method page in BaseDao<T>.
Page is a 0-index based page that use a lady-fetching strategy for retrieve data using a custom set of sorting orders.
public class Test {
public static final main(String... args) {
UserDAO userDAO = QueriesService.getInstance()
.getQuery(UserDAO.class);
Page<User> userPage = userDAO.page(0, 10, Collections.emptyList());
}
}
User can retrieve an optional previous or next page using getNext() and getPrevious() or simply check for hasNext() and hasPrevious()
Jaorm provide a default Page implementation for BaseDao<T> and for DSL module == 2 DSL |
DSL Module is an abstraction over simple SQL Queries using a DSL.
It can be combined with Query annotated implementation or used in a stand-alone class.
2.1 Query Builder
QueryBuilder is the entry-point for create custom query and can be used for create complete queries with where, join, order and limit/offset clause
QueryBuilder.select(MyEntity.class)
.join(MyEntityJoin.class, "A")
.on(COL_4).eq(COL_2)
.on(COL_3).eq(COL_1).orOn(COL_4).ne(COL_2)
.where(COL_1).eq(1)
.andWhere(COL_3, "A").ne(2)
.read();
User can define a custom QueryConfig or use standard implementation with select(Class<T>) method
QueryBuilder will produce :
-
A java.util.Optional<T>, for an optional result, using readOpt method
-
A java.util.List<T>, for a list of result, using readAll method
-
An Entity instance , for a single result, using read method
Input Class in select methods must be a valid Entity or Jaorm will throw a Runtime Exception |
2.1.1 Entity Columns
Processor check if DSL is present in the classpath during processing annotation phase and create custom <EntityName>Columns class that contains constant defined columns used for type-safety building for where, join and order by clause
public final class UserColumns {
public static final SqlColumn<User, Integer> USER_ID = SqlColumn.instance(User.class, "USER_ID", Integer.class);
public static final SqlColumn<User, Integer> DEPARTMENT_ID = SqlColumn.instance(User.class, "DEPARTMENT_ID", Integer.class);
public static final SqlColumn<User, String> USER_NAME = SqlColumn.instance(User.class, "USER_NAME", String.class);
}
2.1.2 Query Config
User can configure how DSL should manage different options like:
-
Case Sensitive Like
-
Where Clause Checks
2.1.2.1 Case Insensitive Like
If DSL is configured with case-insensitive configuration
QueryConfig.builder().caseInsensitive().build()
will transform like conditions from
SELECT MY_TABLE.COL1, MY_TABLE.COL2 FROM MY_TABLE WHERE (MY_TABLE.COL2 LIKE CONCAT('%',?,'%'))
to
SELECT MY_TABLE.COL1, MY_TABLE.COL2 FROM MY_TABLE WHERE (UPPER(MY_TABLE.COL2) LIKE CONCAT('%',UPPER(?),'%'))
2.1.2.2 Where Clause Checks
Default DSL WhereChecker for Where Clause will check required not null arguments in where conditions
public class DefaultWhereChecker implements WhereChecker {
@Override
public boolean isValidWhere(SqlColumn<?, ?> column, Operation operation, Object value) {
Objects.requireNonNull(value, "Value can't be null !");
return true;
}
}
User can define a custom WhereChecker for check if a Where Clause must be used in the final SQL statement like
public class DefaultChecks {
private static final QueryConfig DEFAULT_CONFIG = QueryConfig.builder()
.caseInsensitive()
.withWhereChecker((sqlColumn, operation, o) -> {
if (o instanceof String) {
return ObjectUtils.isNotEmpty((String) o);
} else {
return Objects.nonNull(o);
}
}).build();
}
With previous defined configuration and with current Query Builder
public class TestDSL {
public static void main(String[] args) {
QueryBuilder.select(MyEntity.class, DEFAULT_CONFIG)
.where(COL_1).eq(null)
.where(COL_2).eq("Hello")
.read();
}
}
QueryBuilder will produce SQL
SELECT MY_TABLE.COL1, MY_TABLE.COL2 FROM MY_TABLE WHERE (MY_TABLE.COL2 = ?)
2.1.3 Where Clause
User can filter selected entity with a subset of where clauses using generated Columns classes for type-safety and different sql operations
public class TestDSL {
public static void main(String[] args) {
QueryBuilder.select(MyEntity.class)
.where(MyEntityColumn.COL_2).eq("Hello")
.read();
}
}
2.1.3.1 El Expression
Compatibile el expression operations available for create where clause
Method | SQL Result |
---|---|
eq(value) |
WHERE (MY_TABLE.COL_2 = ?) |
ne(value) |
WHERE (MY_TABLE.COL_2 <> ?) |
lt(value) |
WHERE (MY_TABLE.COL_2 < ?) |
gt(value) |
WHERE (MY_TABLE.COL_2 > ?) |
le(value) |
WHERE (MY_TABLE.COL_2 ⇐ ?) |
ge(value) |
WHERE (MY_TABLE.COL_2 >= ?) |
2.1.3.2 Standard Expression
Compatibile standard expression operations available for create where clause
Method | SQL Result |
---|---|
equalsTo(value) |
WHERE (MY_TABLE.COL_2 = ?) |
notEqualsTo(value) |
WHERE (MY_TABLE.COL_2 <> ?) |
lessThan(value) |
WHERE (MY_TABLE.COL_2 < ?) |
greaterThan(value) |
WHERE (MY_TABLE.COL_2 > ?) |
lessOrEqualsTo(value) |
WHERE (MY_TABLE.COL_2 ⇐ ?) |
greaterOrEqualsTo(value) |
WHERE (MY_TABLE.COL_2 >= ?) |
2.1.3.3 Complex Operations
Where clauses could also be used with complex operations like
Method | SQL |
---|---|
in(Iterable) |
WHERE (MY_TABLE.COL_2 IN (?,?)) |
in(Sub query) |
WHERE (MY_TABLE.COL_2 IN (SELECT OTHER_TABLE.COL1 FROM OTHER_TABLE)) |
notIn(Iterable) |
WHERE (MY_TABLE.COL_2 NOT IN (?,?)) |
notIn(Sub query) |
WHERE (MY_TABLE.COL_2 IN (SELECT OTHER_TABLE.COL1 FROM OTHER_TABLE)) |
isNull() |
WHERE (MY_TABLE.COL_2 IS NULL) |
isNotNull() |
WHERE (MY_TABLE.COL_2 IS NOT NULL) |
like(LikeType) |
WHERE (MY_TABLE.COL_2 LIKE ?) |
notLike(LikeType) |
WHERE (MY_TABLE.COL_2 NOT LIKE ?) |
DSL will automatically generate 1..n wildcards for each element in Iterable |
2.1.3.4 Alias Support
User can also use alias for select where clause column
public class TestDSL {
public static void main(String[] args) {
QueryBuilder.select(MyEntity.class)
.where(MyEntityColumn.COL_2, "B").eq("Hello")
.read();
}
}
2.1.3.5 Complex Clause Generation
With custom method and/or , User can also generate complex where clause like
public class TestDSL {
public static void main(String[] args) {
QueryBuilder.select(MyEntity.class)
.where(COL_1).eq(1).and(COL_2).ne("3")
.orWhere(COL_1).ne(1).and(COL_2).ne("3")
.read();
}
}
that will generate
SELECT MY_TABLE.COL1, MY_TABLE.COL2 FROM MY_TABLE WHERE (MY_TABLE.COL1 = ? AND MY_TABLE.COL2 <> ?) OR (MY_TABLE.COL1 <> ? AND MY_TABLE.COL2 <> ?)
2.1.4 Join Clause
User can filter selected entity with a subset of join clauses using generated Columns classes for type-safety and different sql operations
public class TestDSL {
public static void main(String[] args) {
QueryBuilder.select(MyEntity.class)
.join(MyEntityJoin.class, "A").on(COL_3).eq(COL_1)
.where(COL_1).eq(null)
.andWhere(COL_2).eq("Hello")
.read();
}
}
2.1.4.1 El Expression
Compatibile el expression operations available for create join clause
Method | SQL Result |
---|---|
eq(Column) |
JOIN MY_TABLE ON (MY_TABLE.COL_2 = OTHER_TABLE.COL_1) |
ne(Column) |
JOIN MY_TABLE ON (MY_TABLE.COL_2 <> OTHER_TABLE.COL_1) |
lt(Column) |
JOIN MY_TABLE ON (MY_TABLE.COL_2 < OTHER_TABLE.COL_1) |
gt(Column) |
JOIN MY_TABLE ON (MY_TABLE.COL_2 > OTHER_TABLE.COL_1) |
le(Column) |
JOIN MY_TABLE ON (MY_TABLE.COL_2 ⇐ OTHER_TABLE.COL_1) |
ge(Column) |
JOIN MY_TABLE ON (MY_TABLE.COL_2 >= OTHER_TABLE.COL_1) |
2.1.4.2 Standard Expression
Compatibile standard expression operations available for create join clause
Method | SQL Result |
---|---|
equalsTo(Column) |
JOIN MY_TABLE ON (MY_TABLE.COL_2 = OTHER_TABLE.COL_1) |
notEqualsTo(Column) |
JOIN MY_TABLE ON (MY_TABLE.COL_2 <> OTHER_TABLE.COL_1) |
lessThan(Column) |
JOIN MY_TABLE ON (MY_TABLE.COL_2 < OTHER_TABLE.COL_1) |
greaterThan(Column) |
JOIN MY_TABLE ON (MY_TABLE.COL_2 > OTHER_TABLE.COL_1) |
lessOrEqualsTo(Column) |
JOIN MY_TABLE ON (MY_TABLE.COL_2 ⇐ OTHER_TABLE.COL_1) |
greaterOrEqualsTo(Column) |
JOIN MY_TABLE ON (MY_TABLE.COL_2 >= OTHER_TABLE.COL_1) |
2.1.4.3 Complex Operations
Join clauses could also be used with complex operations like
Method | SQL |
---|---|
like(LikeType, Column) |
JOIN MY_TABLE ON (MY_TABLE.COL_2 LIKE OTHER_TABLE.COL_1) |
notLike(LikeType, Column) |
JOIN MY_TABLE ON (MY_TABLE.COL_2 NOT LIKE OTHER_TABLE.COL_1) |
2.1.4.4 Alias Support
User can also use alias for multiple joins between different tables
public class TestDSL {
public static void main(String[] args) {
QueryBuilder.select(MyEntity.class)
.join(MyEntityJoin.class, "A").on(COL_3).eq(COL_1)
.join(MyOtherJoin.class, "B").on(COL_5).eq(COL_3, "A")
.where(COL_1).eq(null)
.andWhere(COL_2).eq("Hello")
.read();
}
}
2.1.4.5 Complex Clause Generation
With custom method on/orOn , User can also generate complex join clause like
public class TestDSL {
public static void main(String[] args) {
QueryBuilder.select(MyEntity.class)
.join(MyEntityJoin.class, "A")
.on(COL_4).eq(COL_2)
.on(COL_3).eq(COL_1).orOn(COL_4).ne(COL_2)
.where(COL_1).eq(1)
.andWhere(COL_3, "A").ne(2)
.read()
}
}
that will generate
SELECT MY_TABLE.COL1, MY_TABLE.COL2 FROM MY_TABLE JOIN MY_TABLE_JOIN AS A ON (A.COL4 = MY_TABLE.COL2) AND (A.COL3 = MY_TABLE.COL1) OR (A.COL4 <> MY_TABLE.COL2) WHERE (MY_TABLE.COL1 = ?) AND (A.COL3 <> ?)
2.1.5 Order Clause
User can also order selected Entity with order clause
Order clause like where and join also supports alias
public class TestDSL {
public static void main(String[] args) {
QueryBuilder.select(MyEntity.class)
.orderBy(OrderType.DESC, COL_1)
.read();
QueryBuilder.select(MyEntity.class)
.orderBy(OrderType.ASC, COL_1, "A")
.read();
}
}
2.1.6 Limit/Offset Clause
User can limit and skip selected rows with Offset or Limit clause
Jaorm will only accept value that are greater than 0 for offset and limit
public class TestDSL {
public static void main(String[] args) {
QueryBuilder.select(MyEntity.class)
.limit(10)
.read();
QueryBuilder.select(MyEntity.class)
.offset(10)
.read();
QueryBuilder.select(MyEntity.class)
.offset(10)
.limit(10)
.read();
}
}
2.1.6.1 Vendor Specific Clause
Jaorm will generate custom Offset and Limit clause with vendor-specific modules in classpath during runtime.
User must provide only one sql-specific vendor implementation for entire project
3 Vendor Specific
VendorSpecific use customized modules for modify unsupported SQL Syntax in DSL Module like
-
Like Support
-
Limit Offset Support
-
Read Lock For Update
4 Cache
Cache module implements a key based cache for @Cacheable annotated entities.
Each time a request for a select query is done with @Query implementation, an entity is stored with selected keys. In the followings requests , cached entities are returned if previous request was successful.
@Cacheable
@Table(name = "TABLE_NAME")
public class Entity {
@Id
@Column(name = "COLUMN_1")
private String column1;
@Column(name = "COLUMN_2")
private int column2;
}
Default cache implementation is Caffeine but user can override it implementing custom JaormCache and JaormAllCache using an AbstractCacheConfiguration.
If custom implementation is used , user must create an SPI file for <b>CacheService</b> and cache module must be omitted from dependencies. |
Default cache implementation require a startup configuration for each entity that are @Cacheable annotated.
public class Startup {
public void doStartup() {
CacheService cacheService = CacheService.getInstance();
MyCacheConfiguration configuration = MyCacheConfiguration.INSTANCE;
// or use default StandardConfiguration in cache module
cacheService.setConfiguration(User.class, configuration);
}
}
5 Transaction
Jaorm natively supports @org.springframework.transaction.annotation.Transactional and @javax.transaction.Transactional for JTA or Spring Managed Beans.
For SE applications, Transactional module can also be used.
Transactional.exec(Callable<V> callable, Class<X> exceptionClass) create a new Transaction and execute Callable<V> as first argument.
If an exception , matched with the second argument is thrown , the exception is propagated, current transaction execute a rollback and close current transaction
If an exception that doesn’t match with second argument , is thrown , an UnexpectedException is thrown , current transaction execute a rollback and close current transaction
If Callable<V> is correctly executed , exec return the returned value if any is provided, current transaction execute a commit and close current transaction
Transaction is propagated on the current Thread and on each child Thread
public class TransactionalTest {
public static void main(String[] args) {
Transactional.exec(() -> {
UserDAO dao = QueriesService.getInstance().getQuery(UserDAO.class);
User user = new User();
user.setId("id");
user.setName("name");
dao.insert(user);
// After this , commit is done on current transaction
}, Exception.class);
}
}
6 Lombok
Jaorm offers supports for Lombok annotations such @Getter , @Setter or @Data
Core Module only use custom generated getter and setter from Lombok if Jaorm Lombok is provided as dependency
@Data
@Table(name = "LOMB")
public class Lombok {
@Id
@Column(name = "LOMBOK_ID")
private int lombokId;
@Column(name = "LOMBOK_NAME")
private String name;
}