Micro-ORM and database utility for the Java language.
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.
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 :
This project partially supports a subset of the standard JPA 2.1 annotations, namely :
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 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.
// 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>);
// 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>);
user.setLogin(<new login>)
db.update(user);
// Mass update (beneficial if batch execution mode is activated) :
db.update(<collection of users>);
// 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();
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.
You can execute arbitrary SQL statements like so :
db.sql("DROP TABLE test");
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();
}
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");
To create the database table and indexes for an entity according to its JPA annotations, use :
db.createTable(User.class);
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.
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);
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);
The project supports conversion of properties to and from text (only) columns via JPA converters.
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;
}