Using Schema Types To Create SQL Queries in Java
In this article, discover more about how schema types can simplify and streamline JDBC query construction in Java with examples.
Join the DZone community and get the full member experience.
Join For FreeKilo (formerly known as HTTP-RPC) is an open-source framework for creating and consuming RESTful and REST-like web services in Java. It is extremely lightweight and requires only a Java runtime environment and a servlet container. The entire framework is less than 150KB in size, making it an ideal choice for applications where a minimal footprint is desired.
This article begins with an introduction to two fundamental Kilo classes, QueryBuilder
and ResultSetAdapter
. It then discusses Kilo’s support for “schema types,” which allow SQL queries to be expressed via Java language constructs rather than plain text. In other words, they provide a “domain-specific language,” or DSL, for queries.
Getting Kilo
Kilo is distributed via Maven Central:
- org.httprpc:kilo-client – includes support for consuming web services, interacting with relational databases, and working with common file formats (Java 17 or later required)
- org.httprpc:kilo-server – depends on client; includes support for creating web services (Jakarta Servlet specification 5.0 or later required)
QueryBuilder and ResultSetAdapter
Kilo’s QueryBuilder
class provides support for programmatically constructing and executing SQL queries. For example, given the following table from the MySQL sample database:
create table pet (
name varchar(20),
owner varchar(20),
species varchar(20),
sex char(1),
birth date,
death date
);
This code could be used to create a query that returns all rows associated with a particular owner:
var queryBuilder = new QueryBuilder();
queryBuilder.append("select * from pet where owner = :owner");
The colon character identifies “owner” as a parameter or variable. Parameter values, or arguments, can be passed to QueryBuilder
’s executeQuery()
method as shown below:
try (var statement = queryBuilder.prepare(getConnection());
var results = new ResultSetAdapter(queryBuilder.executeQuery(statement, mapOf(
entry("owner", owner)
)))) {
...
}
Kilo’s ResultSetAdapter
class provides access to the contents of a JDBC result set via the Iterable
interface. Individual rows are represented by Map
instances produced by the adapter’s iterator. The results could be serialized to JSON or CSV, or used as the data dictionary for a template document. Alternatively, they could be mapped to a list of Pet
instances and returned to the caller:
public interface Pet {
String getName();
String getOwner();
String getSpecies();
String getSex();
Date getBirth();
Date getDeath();
}
return results.stream().map(result -> BeanAdapter.coerce(result, Pet.class)).toList();
Schema Types
QueryBuilder
also supports a more structured approach to query construction using “schema types.” For example, the preceding query could be written as follows using a schema type:
public interface Pet {
@Table("pet")
enum Schema implements SchemaElement {
@Column("name")
NAME,
@Column("owner")
OWNER,
@Column("species")
SPECIES,
@Column("sex")
SEX,
@Column("birth")
BIRTH,
@Column("death")
DEATH
}
...
}
var queryBuilder = QueryBuilder.selectAll()
.from(Pet.Schema.class)
.where(OWNER.eq("owner"));
Schema types are enums that implement the SchemaElement
interface. They are associated with table and column names using the Table
and Column
annotations, respectively. They are often preferable to string literals as they promote code reuse and are more resilient to refactoring. They can also help improve readability, particularly when working with complex queries. In the example above, the OWNER
constant has been statically imported to reduce verbosity.
Insert, update, and delete operations are also supported. For example, given the following type (representing an item in a hypothetical product catalog):
@Description("Represents an item in the catalog.")
public interface Item {
@Table("item")
enum Schema implements SchemaElement {
@Column("id")
ID,
@Column("description")
DESCRIPTION,
@Column("price")
PRICE
}
@Key("id")
@Description("The item's ID.")
Integer getID();
void setID(Integer id);
@Description("The item's description.")
@Required
String getDescription();
void setDescription(String description);
@Description("The item's price.")
@Required
Double getPrice();
void setPrice(Double price);
}
This service method could be used to add a row to the item
table:
@RequestMethod("POST")
@ResourcePath("items")
@Description("Adds an item to the catalog.")
@Creates
public Item addItem(
@Description("The item to add.") Item item
) throws SQLException {
var queryBuilder = QueryBuilder.insertInto(Item.Schema.class, DESCRIPTION, PRICE)
.values("description", "price");
try (var statement = queryBuilder.prepare(getConnection())) {
queryBuilder.executeUpdate(statement, new BeanAdapter(item));
}
return getItem(BeanAdapter.coerce(queryBuilder.getGeneratedKeys().get(0), Integer.class));
}
Similarly, the following two methods could be used to update or delete an existing item:
@RequestMethod("PUT")
@ResourcePath("items/?")
@Description("Updates an item.")
public void updateItem(
@Description("The item ID.") Integer itemID,
@Description("The updated item.") Item item
) throws SQLException {
item.setID(itemID);
var queryBuilder = QueryBuilder.update(Item.Schema.class, DESCRIPTION, PRICE)
.set("description", "price")
.where(ID.eq("id"));
try (var statement = queryBuilder.prepare(getConnection())) {
queryBuilder.executeUpdate(statement, new BeanAdapter(item));
}
}
@RequestMethod("DELETE")
@ResourcePath("items/?")
@Description("Deletes an item.")
public void deleteItem(
@Description("The item ID.") Integer itemID
) throws SQLException {
var queryBuilder = QueryBuilder.deleteFrom(Item.Schema.class).where(ID.eq("id"));
try (var statement = queryBuilder.prepare(getConnection())) {
queryBuilder.executeUpdate(statement, mapOf(
entry("id", itemID)
));
}
}
See the project README for more information.
Published at DZone with permission of Greg Brown, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments