Install

To use the latest snapshot jpersis-{version} add the following dependency to your project:

<dependency>
   <artifactId>jpersis</artifactId>
   <groupId>com.github.myrealitycoding</groupId>
   <version>{version}</version>
</dependency>

Getting Started

Table of contents

I. Why JPersis?
II. Models
III. Mappers
IV. Example
V. Drivers
VI. Annotations
VII. Naming
VIII. Modes
IX. References

I. Why JPersis?

When using an object oriented language like Java it is time consuming to map data manually from a database to models and vise versa. You have to remember data types, validate input and output and you have to change your code for each use case. JPersis is a light-weighted library which does not require much configuration. You define a datastore, bind a model to it and use so called mappers to interact with the datastore.

II. Models

A model is a simple Java class with attributes. There are some restrictions to fullfill the role:

Imagine we have a model, called User, it could look something like this:

public class User {

    @PrimaryKey 
    private int id;

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

You have created a valid JPersis model, because all requirements are fullfilled.

III. Mappers

Now we need to do something with our model. We want to store, read and manipulate data. Each model needs a mapper which is responsible. To implement a mapper you have the following restrictions:

A sample mapper for our previously created model could look something like this:

@Mapper("my.package.to.the.model.User")
public interface UserMapper {

   @Insert
   boolean insert(User user);

   @Delete
   boolean delete(User user);

   @Count
   int count();

   @Select(condition = "userName = $1")
   User findByUserName(String userName);
}

That's it! You have created your first mapper.

IV. Example

Now we want some action. We want to use a SQLite datasource. We create a driver first (provided by JPersis) and create an JPersis object:

Driver driver = new SQLiteDriver("database.sql");
JPersis jpersis = new JPersis(driver);

Now our application is ready to use. Let's create a user first:

User user = new User();
user.setName("Max");

Next we need our mapper. JPersis will provide a mapper for you:

UserMapper mapper = jpersis.map(UserMapper.class);

Let's insert our user and check if it worked:

mapper.insert(user); // returns true, seems to be okay!
mapper.count(); // returns 1, there is a user in the database
mapper.findByName("Max"); // Hey Max, how are you doing?

Nothing more to do. JPersis created a user table internally and inserted the user into it. If we want to avoid custom mappers, we can fall back to default mapping:

DefaultMapper<User> mapper = jpersis.mapDefault(User.class);
Collection<User> allUsers = mapper.find();

// We do not want to query always the datastore.
// Instead we are using caching
// Equivalent to:
// CachedDefaultMapper<User> cachedMapper = jpersis.mapDefaultCached(User.class);
CachedDefaultMapper<User> cachedMapper = jpersis.cached(mapper);

V. Drivers

To interact with the database, a so called Driver is required. A driver maps a general functionality to a technology based functionality (like a general insert command to an MongoDB insert). Furthermore a driver is used by annotation methods.

By default, the following drivers will be supported:

VI. Annotations

The annotations provided by Jpersis are predefined but it is possible to register custom annotations to Jpersis. Given the following annotation:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ReturnOne { }

Annotating a method with this annotation should always return for example the value 1. To do so we need to define a so called MapperMethod:

public class ReturnOneMethod extends AbstractMapperMethod<ReturnOne> {

  public ReturnOneMethod(ReturnOne one) {
    super(one);
  }

  @Override
  public void on(Class<?> model, Object[] params, Query query) {
    // we build our query internally to count one element
    // note that this would return 0 if no data is available
    query.limit(1).count();
  }

  @Override
  protected Class<?>[] supportedReturnTypes(Class<?> model) {
    return new Class<?>[] { Integer.class, int.class };
  }
}

Finally we need to register our mapper method:

jpersis.register(ReturnOne.class, ReturnOneMethod.class);

We are now able to annotate mappers with the @ReturnOne annotation:

public interface MyMapper {
    /* ... */
    @ReturnOne
    int returnOneOrZero();
    /* ... */
}

VII. Naming

It is also possible to customize conversion between Java and database names. To do so, you have to define a Naming. By default, the CamelCaseNaming is used:

myField -> my_field
my_field -> myField
MyClass -> my_collection
my_collection -> MyCollection

To do so, implement your own converter:

public class CustomNaming implements Naming {

   @Override
   public String collectionToJava(String name) {
      return name;
   }

   @Override
   public String javaToCollection(String name) {
      return name;
   }

   @Override
   public String fieldToJava(String name) {
      return name;
   }

   @Override
   public String javaToField(String name) {
      return name;
   }
}

Afterwards you can set your converter:

jpersis.setNaming(new CustomNaming());

Modes

JPersis currently supports two modes:

The mode itself can be changed in the driver:

Driver driver = retrieveDriver();
driver.setMode(DriverMode.CUSTOM);

References

As of Version 1.1.0 JPersis does support references:

public class CarPark {

   @PrimaryKey
   private String id;

   @Reference("de.test.Car")
   private Collection<Car> cars;
}

This will give you all cars which are referenced to this object. For more information refer to the actual WIP ticket.