Skip to content

Generic Query Manager

Geert Bevin edited this page Oct 22, 2023 · 8 revisions

In Java, your domain models are often expressed as beans that encapsulate state in a standard and generic manner. RIFE2 can leverage this beans structure and generate many of your SQL queries automatically through the GenericQueryManager. The only requirement is that your bean has an id property, which will be used as the primary key of your table and for any other database operations that require the identification of a single object instance.

Create a bean for your model

For instance, let's change the query builder example by introducing a Person bean:

public class Person {
    private Integer id_;
    private String name_;

    public void    setId(Integer id)    { id_ = id; }
    public Integer getId()              { return id_; }
    public void    setName(String name) { name_ = name; }
    public String  getName()            { return name_; }
}

Replace all queries with simple method calls

Instead of writing the SQL queries yourself with the query builder, you can now use a GenericQueryManager for that class and directly perform the database operations with it.

The whole upper section of the previous example with all the queries, can be replaced with:

    // ...
    Datasource datasource = new Datasource(
        "org.h2.Driver", "jdbc:h2:./embedded_dbs/h2/hello", "sa", "", 5);
    GenericQueryManager<Person> manager =
        GenericQueryManagerFactory.instance(datasource, Person.class);
    // ...

The install element now simply becomes:

    manager.install();

Similarly for the remove element:

    manager.remove();

To store the bean from the addition form, we can now write:

    var person = c.parametersBean(Person.class);
    manager.save(person);

Finally, listing all the names can now be done like this:

    manager.restore(person -> c.print(person.getName() + "<br>"));

TIP : You can find this generic query manager example in the RIFE2 GitHub repository.

Customize queries

When you restore the person entries like above, you'll notice that they're sorted in the order they've been added to the database, which is the natural sort order. You'll probably want this to be the alphabetical order of the names instead. Instead of writing queries from scratch like in the previous example, you can get the queries that the GenericQueryManager uses as a base and customize them for your needs.

For instance:

    manager.restore(manager.getRestoreQuery().orderBy("name"),
        person -> c.print(person.getName() + "<br>"));

You can find the complete example in the RIFE2 GitHub repository.

In the next section you'll learn how to add constraints to your JavaBean to customize and enhance the behavior of the GenericQueryManager.

Many-to-many and many-to-one

RIFE2 also supports many-to-many and many-to-one relationships in the GenericQueryManager, all that is required is for you to set up the necessary constraints with manyToMany(), manyToManyAssociation(), manyToOne(), and manyToOneAssociation(). There are useful options to consider for these constraints, so make sure look at the available methods in ConstrainedProperty.

When installing the GenericQueryManager database structure, the necessary join tables will be created for many-to-many to work properly. Make sure to create the structure for each bean class that is involved in the database relationship, and to do so in the right order.

Imagine we have two bean classes: Friend and Email where each friend can have multiple emails.

public class Email extends MetaData {
    private Integer id_;
    private String email_;
    private Friend friend_;

    public void activateMetaData() {
        addConstraint(new ConstrainedProperty("id")
            .identifier(true));
        addConstraint(new ConstrainedProperty("email")
            .notNull(true)
            .maxLength(50)
            .email(true));
        addConstraint(new ConstrainedProperty("friend")
            .manyToOne());
    }
    // setters and getters
}
public class Friend extends MetaData {
    private Integer id_;
    private String name_;
    private List<Email> emails_;

    public void activateMetaData() {
        addConstraint(new ConstrainedBean()
            .defaultOrder("name"));

        addConstraint(new ConstrainedProperty("id")
            .identifier(true));
        addConstraint(new ConstrainedProperty("name")
            .notNull(true)
            .maxLength(20));
        addConstraint(new ConstrainedProperty("emails")
            .manyToOneAssociation());
    }
    // setters and getters
}

These can now be installed like this:

var friends = GenericQueryManagerFactory.instance(datasource, Friend.class);
var emails = GenericQueryManagerFactory.instance(datasource, Email.class);
friends.install();
emails.install();

NOTE: Friends need to be installed before emails, since the many-to-one relationship will create a foreign key from the email table to the friend.

The rest of the GenericQueryManager will understand when to use the friend property of an Email instance, and when to use the emails property of a Friend instance.

You can simply continue using statements like:

friends.save(friend);

friends.restore(friend -> {
    // do something with the instance
});

TIP : You can find a complete example of how to use database relationships with the GenericQueryManager in the RIFE2 GitHub repository.

Lazy loading

RIFE2 support lazy loading of many-to-many and many-to-one relationship collections, this is achieved through bytecode manipulation and requires the RIFE2 agent to be activated. Please follow the instructions in the continuations section for more information.


Next learn more about Constraints