This project is a sample implementation of an OR-Mapper from scratch. It is a Code First approach with Annotations, which means it will use Java Source Code to create a meta modell in the background.
First an Entity has to be defined with all the wanted features. In this case we have a User with a 1:n relationship to courses.
@Table("user")
@Getter
@Setter
public class User {
@Id
private Long id;
private String name;
private int age;
@OneToMany(foreignKeyName = "user_id")
private List<Course> courses;
}After the Entities are defined in Code using annotations, the DslContext can be used to access the database.
class Sample {
public static void main(String[] args) {
// Database Access API
DslContext dsl = new DslContext(getDatabaseConfig());
// select Entity by ID
User userEntity = dsl.findById(User.class, 1L).orElseThrow();
// make change & save changes
userEntity.setName("different");
Long id = dsl.save(userEntity);
// delete entity
dsl.delete(userEntity);
// Custom Where Query
List<User> users = dsl.findBy(User.class, Query.where()
.like("name", "%ifferent")
.and()
.greaterThan("age", 20));
}
}- Code First Approach
- Relationship support for
1:n&m:n - Custom
WhereQueries (NOT,OR,AND,=,LIKE,IN,>,>=,<,<=)- Fluent API with Context => e.g.
ANDonly possible after a Operator was used
- Fluent API with Context => e.g.
- Save Entities (decides automatically if
INSERTorUPDATEis required)- Includes tracking changes on relationships on both sides (
1:norn:1) - Not implemented for
n:m
- Includes tracking changes on relationships on both sides (
- Caching after every
SELECT,UPDATE,INSERT- Includes tracking which columns really changed in order to only changed columns
- Lazy Loading implemented with Proxies
- No wrapper around objects required
- Used on default for best performance
- Can be disabled
- Database Support
- Currently only PostgreSQL
A Java Class represents a SQL Table. These Annotations are required:
- The Class must be marked with
@Table("tableName"). - To assign an ID Field use
@Id. Any primitive datatype can be used but I recommendLong. - Every class variable will be used as a column. If you wish to exclude a variable use
@Ignore.
Optional Annotations:
-
@Column(name = "columnName")- On default, the variable name is used as the column name. Use this annotation to overwrite this behaviour.
-
Relations:
@ManyToOne/@OneToMany@ManyToOneis used on table were the foreign key is located!- The Type must be
TwereTis the table of the foreign key
- The Type must be
@OneToManycan be used on the other table to create an automatic join- The Type must be
List<T>wereTis the joined table type
- The Type must be
- Default behaviour is lazy but can be overwritten with
@ManyToOne(lazy = false)/@OneToMany(lazy = false)
@Table("user")
@Getter
@Setter
public class User {
@Id
private Long id;
@Column(name = "lastname")
private String name;
private int age;
@Ignore
private String unrelatedToTable;
@OneToMany(foreignKeyName = "user_id")
private List<Course> courses;
@ManyToOne(foreignKeyName = "home_id", lazy = false)
private Home home;
}First the database connection string, username & password must be configured via ConnectionConfig:
ConnectionConfig connectionConfig = ConnectionConfig.builder()
.connectionString("jdbc:postgresql://localhost:5432/sample")
.username("sample")
.password("password")
.build();
The Database API DslContext can be instantiated with connectionConfig e.g. new DslContext(connectionConfig);
The API exposes following methods:
| Method Name | Signature | Return Type | Remark |
|---|---|---|---|
findById |
Class<T> type, Object id |
Optional<T> |
Optional is empty if no row was returned |
findBy |
Class<T> type, Query query |
List<T> |
--- |
findFirstBy |
Class<T> type, Query query |
Optional<T> |
Optional is empty if no row was returned |
findAll |
Class<T> type |
List<T> |
--- |
save |
T entity |
Object |
returns the ID |
delete |
T entity |
boolean |
returns true if the delete was successful |
deleteAll |
List<T> entities |
boolean |
returns true if all deletes were successful |
Supports NOT,OR,AND,=,LIKE,IN,>,>=,<,<= as Java Fluent API. However, currently no support for grouping or brackets.
class Examples {
public static void main(String[] args) {
// Example 1
Query.where()
.like("name", "%ifferent")
.and()
.greaterThan("age", 20);
// Example 2
Query.where()
.in("name", "Stefan", "Max", "Maxine")
.or()
.not().equals("name", "test");
}
}An example application is located in the demo-orm folder.
This feature is enabled per default and cannot be disabled. There is no way to interact with it at the moment.
Requirements:
- Docker + Docker Compose
- Java JDK 17
- Gradle (wrapper already included)
Setup DB
Run docker-compose.yml in docker folder. It will create a Postgres 13 Container and will also automatically mount and run the school_init.sql schema.
Run Demo App:
Run ./gradlew publishToMavenLocal && ./gradlew demo-orm:shadowJar && java -jar ./demo-orm/build/libs/demo-orm-1.0-SNAPSHOT-all.jar
in the root directory using Bash