HighLite
An SQLite ORM for Android with automatic database migrations built with annotation processing
Install / Use
/learn @jeppeman/HighLiteREADME
HighLite
HighLite is an SQLite library for Android that makes use of annotation processing to generate boilerplate for your SQLite operations.
<b>Key features:</b>
- Automated table creation and table upgrades (column additions / changes / deletions are automatic) with opt-out possibilities for those who do not want it.
- Query builder that eliminates the need to have to deal with the null-argument passing to the standard Android SQLite API.
- Easy to use API with simple but flexible operations for get, save and delete.
- Reactive! Each operation can be Rx-ified for those who use RxJava.
- Supports inheritance of database models
- Support for foreign keys and relationships in database models.
- Support for the rest of the column constraints available for SQLite, i.e. UNIQUE, NOT NULL and AUTOINCREMENT
- Support for multiple databases
<b>Other positives:</b>
- Fast! No reflection resolving at runtime, all operations are carried out through compile time generated code
- Errors in user setup are caught and reported at compile time
- Lint warnings for errors that can't be caught at compile time
- Comprehensive test coverage
- Type safe operations
- No need to subclass SQLiteOpenHelper; all necessary interactions with it are done under the hood.
Getting started
dependencies {
compile 'com.jeppeman:highlite:1.1.3'
annotationProcessor 'com.jeppeman:highlite-compiler:1.1.3'
}
Kotlin users will have to replace annotationProcessor with kapt.
Basic setup
Annotate a class with @SQLiteDatabaseDescriptor as follows:
@SQLiteDatabaseDescriptor(
dbName = "companyDatabase",
dbVersion = 1 // Increment this to trigger an upgrade
)
public class CompanyDatabase {
// Optional: define a method like this if you want to manually handle onOpen.
// Note: PRAGMA foreign_keys = ON is set automatically if any foreign
// keys are found for any table in the database.
@OnOpen
public static void onOpen(SQLiteDatabase db) {
...
}
// Optional: define a method like this if you want to manually handle onCreate;
// i.e. if you opt out from automatic table creation on some table.
@OnCreate
public static void onCreate(SQLiteDatabase db) {
...
}
// Optional: define a method like this if you want to manually handle onUpgrade;
// i.e. if you opt out from automatic upgrades on some table
@OnUpgrade
public static void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
...
}
}
Then define a class for a table that links to the database class
@SQLiteTable(
database = CompanyDatabase.class,
tableName = "companies", // If left empty, the name of the table defaults to the class name snake cased
autoCreate = true, // defaults to true, set to false if you do not want the table to be created automatically
autoAddColumns = true, // defaults to true, set to false if you do not want new columns to be added automatically on upgrades
autoDeleteColumns = false // defaults to false, set to true if you want deleted fields to be removed from the database automatically on upgrades
)
public class Company {
@SQLiteColumn(primaryKey = @PrimaryKey(autoIncrement = true))
long id; // fields annotated with @SQLiteColumn need to be at least package local
@SQLiteColumn("companyName") // Column name becomes companyName here
String name;
@SQLiteColumn
Date created; // Dates are stored as INTEGER's with the amount of seconds since UNIX epoch
@SQLiteColumn
List<String> employees; // Fields with types that cannot be matched against an SQLite data type will be serialized and stored as BLOB's
}
That's it, you're now ready to start doing some actual database operations.
<b>Note to Kotlin users</b>
For now, Kotlin properties with primitive types have to be annotated with @JvmField, and non-primitive have to be marked with lateinit or be annotated with @JvmField. Here follows an example:
@SQLiteTable(database = CompanyDatabase::class)
class Company {
@JvmField
@SQLiteColumn(primaryKey = PrimaryKey(autoIncrement = true))
var id : Int = 0 // Primitive type, annotate with @JvmField
@SQLiteColumn("companyName")
lateinit var name : String // Non-primitive, mark with lateinit
}
The reason for this is that a Kotlin property by default is compiled to a private Java field with a getter
and a setter method. With @JvmField and lateinit the compiled java class has its corresponding field exposed publicly
Operations
Insert an object
SQLiteOperator<Company> operator = SQLiteOperator.from(getContext(), Company.class);
final Company companyObject = new Company();
companyObject.name = "My awesome company";
companyObject.created = new Date();
companyObject.employees = Arrays.asList("John", "Bob");
// Blocking
operator.save(companyObject).executeBlocking(); // the save method inserts if the object's id is not present in the table, otherwise updates
// Non-blocking
operator.save(companyObject)
.asCompletable() // or .asFlowable(), .asObservable(), .asSingle() or .asMaybe();
Fetch by id and update
// If you pass an argument to getSingle it will be matched against the table's primary key field,
// in this case `id` = 1
final Company fetchedObject = operator.getSingle(1).executeBlocking();
fetchedObject.name = "My not so awesome company";
operator.save(fetchedObject).executeBlocking();
Fetch by query
final List<Company> list = operator
.getList()
.withQuery(
SQLiteQuery
.builder()
.where("`id` = ? AND `companyName` = ?", 1, "My not so awesome company")
.build()
).executeBlocking();
Fetch by raw query and delete
final List<Company> list = operator
.getList()
.withRawQuery("SELECT * FROM companies where `id` = ?", 1)
.executeBlocking();
operator.delete(list).executeBlocking();
Delete by query
operator
.delete()
.withQuery(
SQLiteQuery
.builder()
.where("`id` = ?", 1)
.build()
).executeBlocking();
Save by query
operator
.save()
.withQuery(
SQLiteQuery
.builder()
.set("companyName", "Changed name")
.set("created", new Date())
.where("`id` = ?", 1)
.build()
).executeBlocking();
Foreign keys and relationships
HighLite supports foreign keys and relationships, here's an example of how you can use them:
@SQLiteTable(
database = CompanyDatabase.class,
tableName = "companies"
)
public class Company {
@SQLiteColumn(primaryKey = @PrimaryKey(autoIncrement = true))
long id;
@SQLiteColumn("companyName")
String name;
@SQLiteRelationship(backReference = "company") // backReference needs to be the name of the foreign key field of the class it is referring to
List<Employee> employeeList; // When a company is fetched from the database, its related employees gets fetched as well
}
@SQLiteTable(
database = CompanyDatabase.class,
tableName = "employees"
)
public class Employee {
@SQLiteColumn(primaryKey = @PrimaryKey(autoIncrement = true))
long id; // fields annotated with @SQLiteColumn need to be package local
@SQLiteColumn("employeeName")
String name;
@SQLiteColumn
float salary;
@SQLiteColumn(foreignKey = @ForeignKey(
fieldReference = "id", // Note: this is the name of the field of the class you are referring to, not the database column name; the field has to be unique
cascadeOnDelete = true, // defaults to false
cascadeOnUpdate = true // defaults to false
))
Company company; // When an employee is fetched, this field is automatically instantiated as its corresponding Company
}
Let's create a company with a couple of employees:
SQLiteOperator<Company> companyOperator = SQLiteOperator.from(getContext(), Company.class);
Company company = new Company();
company.name = "My awesome company";
companyOperator.save(company).executeBlocking();
SQLiteOperator<Employee> employeeOperator = SQLiteOperator.from(getContext(), Employee.class);
Employee john = new Employee(),
bob = new Employee();
john.name = "John";
john.salary = 1000f;
john.company = company;
bob.name = "Bob";
bob.salary = 10000f;
bob.company = company;
employeeOperator.save(john, bob).executeBlocking();
Now if we fetch the company from the database the employees will follow:
Company companyFromDatabase = companyOperator
.getSingle()
.withRawQuery("SELECT * FROM companies WHERE `name` = ?", "My awesome company")
.executeBlocking();
Log.d("employees", companyFromDatabase.employeeList /* <- this is now [john, bob]*/)
Inheritance
HighLite supports inheritance of classes annotated with SQLiteTable, consider the following:
@SQLiteTable(
database = CompanyDatabase.class
)
public class Developer extends Employee {
@SQLiteColumn
String type;
}
Here the class Developer extends Employee, which is already annotated with SQLiteTable, the
create statement that is generated from this setup looks like this:
CREATE TABLE IF NOT EXISTS developer (
`type` TEXT,
`employees_ptr_id` INTEGER PRIMARY KEY NOT NULL,
FOREIGN KEY(`employees_ptr_id`) REFERENCES employees(`id`) ON DELETE CASCADE ON UPDATE CASCADE
);
So we have a one-to-one relationship between Developer and Employee, therefore the primary key
for Developer is automatically created as a pointer to the primary key of Employee.
Let's have a look
Related Skills
feishu-drive
339.5k|
things-mac
339.5kManage Things 3 via the `things` CLI on macOS (add/update projects+todos via URL scheme; read/search/list from the local Things database)
clawhub
339.5kUse the ClawHub CLI to search, install, update, and publish agent skills from clawhub.com
yu-ai-agent
2.0k编程导航 2025 年 AI 开发实战新项目,基于 Spring Boot 3 + Java 21 + Spring AI 构建 AI 恋爱大师应用和 ReAct 模式自主规划智能体YuManus,覆盖 AI 大模型接入、Spring AI 核心特性、Prompt 工程和优化、RAG 检索增强、向量数据库、Tool Calling 工具调用、MCP 模型上下文协议、AI Agent 开发(Manas Java 实现)、Cursor AI 工具等核心知识。用一套教程将程序员必知必会的 AI 技术一网打尽,帮你成为 AI 时代企业的香饽饽,给你的简历和求职大幅增加竞争力。
