JMiniORM

Micro-ORM and database utility for the Java language.

View the Project on GitHub

Build Status Coverage Status

Overview

JMiniORM is a lightweight ORM and database utility for the Java language. It has no dependencies, its footprint is small (~100 KB), it is extensible and it is very easy to learn.

Features

Configuration

At the very least, you must provide the database URL, username and password.

IDatabaseConfig config = new DatabaseConfig.Builder()
                .dataSource("<url>", "<username>", "<password>")
                .build();
IDatabase db = new Database(config);

If you do so, a HikaryCP connection pool is used.

Optional parameters :

JPA annotations

Supported annotations

This project partially supports a subset of the standard JPA 2.1 annotations, namely :

Example of an annotated class

This class is used as an example throughout the rest of this doc.

@Table(name = "users", indexes = {
        @Index(name = "login_index", columnList = "login ASC")
})
public class User {
    @Id
    @GeneratedValue
    private Integer id;
    @Column(length = 16)
    private String login;
    private String password;
    @Column(name = "registration_date")
    private LocalDate registrationDate;

    public User() {
    }

    // ...getters and setters...
}

Queries

ORM vs generic

Queries come in two flavours :

Only ORM queries are described in this doc, except for the generic select query that can be used to execute arbitrary SELECT statements and retrieve the result in a type of your choosing.

ORM queries

Insert

// Creates a User :
User user = new User(...);

// Inserts it into the users table :
db.insert(user);

// The id generated by the database is now set :
assertNotNull(user.getId());

// Mass insert (beneficial if batch execution mode is activated) :
db.insert(<collection of users>);

Delete

// By object (the id must be set) :
db.delete(user);

// By id :
db.delete(User.class, <id>);

// Mass delete (beneficial if batch execution mode is activated) :
db.delete(<collection of users>);

Update

user.setLogin(<new login>)
db.update(user);

// Mass update (beneficial if batch execution mode is activated) :
db.update(<collection of users>);

Select

// By id :
User user = db.select(User.class, <id>);

// With where, orderBy, limit, offset :
List<User> users = db.select(User.class)
        .where("login = 'test'")
        .orderBy("registration_date DESC")
        .limit(10)
        .offset(5)
    .list();

// First User of the result set :
User user = db.select(User.class).where(<where>).first();

// Single User of the result set (exception generated if 0 or more than one encountered) :
User user = db.select(User.class).where(<where>).one();

Generic queries

Select

Sometimes it is necessary to execute a select statement that spans several tables (joins) or one that doesn’t (necessarily) have a result set that maps to a class. Such queries can be executed that way :

// Result as maps :
List<Map<String,Object>> maps = db.select("select x, y, z from u, v where ...")
    .asMap()
    .list();

// Result as objects (JPA annotations are respected here too, as they are with ORM queries) :
List<SomeClass>> maps = db.select("select x, y, z from u, v where ...")
    .asObject(SomeClass.class)
    .list();

// Result as primitives :
List<Integer> db.select("select id from ...")
    .asPrimitive(Integer.class)
    .list();

// One and first are supported as well :
Long count = db.select("select count(*) from ...")
    .asPrimitive(Long.class)
    .one();

Notice that such queries don’t start by specifying a Java class, thus the select and from clause can’t be infered and must be provided explicitly.

Raw

You can execute arbitrary SQL statements like so :

db.sql("DROP TABLE test");

Transactions

The interface of transaction is the same as the one of database + commit, rollback and close. Once created, a transaction must be closed, otherwise the underlying connection won’t be returned to the pool. Closing a transaction automatically rolls back any pending operations.

Tu ensure a block of SQL statements is either executed fully or not at all, do :

try (ITransaction transaction : db.createTransaction()) {
    // ...work with the transaction...
    transaction.commit();
}

Utilities

The following utility class is provided to help manipulate result sets : RSUtils.

Example :

List<User> users = db.select(User.class).list();
Map<Integer, User> usersById = RSUtils.index(users, "id");
Map<LocalDate, List<User>> usersGroupedByRegistrationDate = RSUtils.group(users, "registrationDate");
Set<String> logins = RSUtils.distinct(users, "login");

Schema generation

To create the database table and indexes for an entity according to its JPA annotations, use :

db.createTable(User.class);

JDBC batch mode

Batch mode is disabled by default. To enable it, configure the database this way :

IDatabaseConfig config = new DatabaseConfig.Builder()
                .dataSource("<url>", "<username>", "<password>")
                .statementExecutor(new BatchStatementExecutor())
                .build();
IDatabase db = new Database(config);

Batch mode can greatly improve performance for mass insertions/deletions/udpates, especially if the database and the application are on different machines. However, when used in combination with database generated keys, it may cause problems with some drivers : when executing a batch of inserts, some drivers only return the last generated key instead of the whole lot. Try it with your driver and see if it works.

Logging

You can enable logging of SQL statements by configuring the database this way :

IDatabaseConfig config = new DatabaseConfig.Builder()
                .dataSource("<url>", "<username>", "<password>")
                .statementExecutor(new SLF4JLoggingStatementExecutor(new DefaultStatementExecutor()))
                .build();
IDatabase db = new Database(config);

Custom SQL dialect

SQL dialects are objects implementing ISQLDialect The project ships with a generic SQL dialect that tries to accomodate for most databases : GenericSQLDialect. To account for the peculiarities of your own database, subclass it and configure it that way :

IDatabaseConfig config = new DatabaseConfig.Builder()
                .dataSource("<url>", "<username>", "<password>")
                .dialect(new MyCustomSQLDialect())
                .build();
IDatabase db = new Database(config);

Automatic serialization / deserialization of properties

The project supports conversion of properties to and from text (only) columns via JPA converters.

JSON serialization / deserialization

The JsonAttributeConverter can read/write any Java object from/to a text database column. It is based on Jackson (see for example here to get started). It is invaluably useful to store complex data structures to a database without having to setup many tables and create complex mapping logic.

Suppose Users have a list of roles and a role is an instance of the Role class (which can be arbitrarily complex).

@Table(name = "users")
public class User {
    // ...other properties...
    private List<Role> roles;
}

public class Role {
    // ... properties, lists, maps, sub objects,...
}

To enable serialization of Roles to JSON, you must first define a converter class, like so :

public class RolesJsonConverter extends JsonAttributeConverter<List<Role>> {
    // Nothing to do here. The class is simply declared to capture the generic type
    // "List<Role>" in a way that is available at runtime for the JsonAttributeConverter
    // parent class.
}

Then assign the converter to the roles property :

@Table(...)
public class User {
    @Convert(converter = RolesJsonConverter.class)
    private List<Role> roles;
}