Generic Query Manager
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.
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_; }
}
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.
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
.
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.
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