findNative preferred over RawSql

Note that findNative is preferred over RawSql. findNative uses automatic mapping of columns to properties and is nicer and simpler to use.

We really only need to use RawSql when we hit one of the limitations of findNative.

RawSql - unparsed

With unparsed RawSql we explicitly specify the SQL to use and explicitly map columns to properties. The SQL can contain positioned parameters with ? or named parameters like :foo.

The RawSql instance is immutable and safe for multi-threaded use and can be declared as a static final.

With unparsed RawSql we can NOT specify additional expressions to the WHERE and HAVING clauses as well as set the ORDER BY and LIMIT OFFSET clauses. We can only do that with parsed RawSql.

Example - using positioned parameters
static final RawSql rawSql =
  RawSqlBuilder
    .unparsed("select r.id, r.name from o_customer r where r.id >= ? and r.name like ?")
    .columnMapping("r.id", "id")
    .columnMapping("r.name", "name")
    .create();

...

List<Customer> list = DB.find(Customer.class)
  .setRawSql(rawSql)
  .setParameter(42)
  .setParameter("R%")
  .findList();
Example - using named parameters
static final RawSql rawSql =
  RawSqlBuilder
    .unparsed("select r.id, r.name from o_customer r where r.id >= :a and r.name like :b")
    .columnMapping("r.id", "id")
    .columnMapping("r.name", "name")
    .create();

...

List<Customer> list = DB.find(Customer.class)
  .setRawSql(rawSql)
  .setParameter("a", 42)
  .setParameter("b", "R%")
  .findList();

RawSql - parsed

With parsed RawSql we can dynamically add expressions to the WHERE and HAVING clauses as well as set the ORDER BY and LIMIT OFFSET clauses.

The RawSql instance is immutable and safe for multi-threaded use and can be declared as a static final.

// Use raw SQL with an aggregate function

String sql
= " select order_id, o.status, c.id, c.name, sum(d.order_qty*d.unit_price) as totalAmount"
+ " from orders o"
+ " join customer c on c.id = o.customer_id "
+ " join order_detail d on d.order_id = o.id "
+ " group by order_id, o.status ";

static final RawSql rawSql = RawSqlBuilder
  // let ebean parse the SQL so that it can add
  // expressions to the WHERE and HAVING clauses
  .parse(sql)
    // map resultSet columns to bean properties
    .columnMapping("order_id", "order.id")
    .columnMapping("o.status", "order.status")
    .columnMapping("c.id", "order.customer.id")
    .columnMapping("c.name", "order.customer.name")
    .create();

...

List<OrderAggregate> list = DB.find(OrderAggregate.class);
  query.setRawSql(rawSql)
  // add expressions to the WHERE and HAVING clauses
  .where().gt("order.id", 42)
  .having().gt("totalAmount", 20)
  .findList();

fetch

When using RawSql we can use fetchQuery to fetch other parts of the object graph. That is, we are using RawSql for the "root" or "origin" query and can use fetchQuery to fetch other related parts of the graph.

// fetch additional parts of the object graph
// after the Raw SQL query is executed.

String sql
= " select order_id, sum(d.order_qty*d.unit_price) as totalAmount "
+ " from order_detail d"
+ " group by order_id ";

static final RawSql rawSql = RawSqlBuilder
  .parse(sql)
  .columnMapping("order_id", "order.id")
  .create();

...

Query<OrderAggregate> query = DB.find(OrderAggregate.class);
  query.setRawSql(rawSql)
    // get ebean to fetch parts of the order and customer
    // after the raw SQL query is executed
    .fetchQuery("order", "status,orderDate",new FetchConfig().query())
    .fetchQuery("order.customer", "name")
    .where().gt("order.id", 0)
    .having().gt("totalAmount", 20)
    .order().desc("totalAmount")
    .setMaxRows(10);

This is the OrderAggregate bean used in the examples above.

package com.avaje.tests.model.basic;

import jakarata.persistence.Entity;
import jakarata.persistence.OneToOne;

import com.avaje.ebean.annotation.Sql;

/**
* An example of an Aggregate object.
*
* Note the @Sql indicates to Ebean that this bean is not based on a table but
* instead uses RawSql.
*
*/
@Entity
@Sql
public class OrderAggregate {

  @OneToOne
  Order order;

  Double totalAmount;

  Double totalItems;

  public String toString() {
    return order.getId() + " totalAmount:" + totalAmount + " totalItems:" + totalItems;
  }

  public Order getOrder() {
    return order;
  }

  public void setOrder(Order order) {
    this.order = order;
  }

  public Double getTotalAmount() {
    return totalAmount;
  }

  public void setTotalAmount(Double totalAmount) {
    this.totalAmount = totalAmount;
  }

  public Double getTotalItems() {
    return totalItems;
  }

  public void setTotalItems(Double totalItems) {
    this.totalItems = totalItems;
  }
}

tableAliasMapping()

tableAliasMapping() will automatically map columns from the query results to the associated object based on its path. It does this using the query alias. This is a convenience method so that you don't have to map each column individually.

So a test case looks like:

static final String rs =
  "select o.id, o.status, c.id, c.name, "+
  "       d.id, d.order_qty, p.id, p.name " +
  "from orders o join customer c on c.id = o.customer_id " +
  "join order_detail d on d.order_id = o.id " +
  "join product p on p.id = d.product_id " +
  "where o.id <= :maxOrderId and p.id = :productId "+
  "order by o.id, d.id asc";

static final RawSql rawSql = RawSqlBuilder.parse(rs)
    .tableAliasMapping("c", "customer")
    .tableAliasMapping("d", "details")
    .tableAliasMapping("p", "details.product")
    .create();

...

List<Order> ordersFromRaw = DB.find(Order.class)
    .setRawSql(rawSql)
    .setParameter("maxOrderId", 2)
    .setParameter("productId", 1)
    .findList();

Rather than using columnMapping like:

static final RawSql rawSql = RawSqlBuilder.parse(rs)
    .columnMapping("t0.id", "id")
    .columnMapping("t0.status", "status")
    .columnMapping("t1.id", "customer.id")
    .columnMapping("t1.name", "customer.name")
    .columnMapping("t2.id", "details.id")
    .columnMapping("t2.order_qty", "details.orderQty")
    .columnMapping("t3.id", "details.product.id")
    .columnMapping("t3.name", "details.product.name")
    .create();

Named queries

We can programmatically use raw SQL like the following examples or put the Raw SQL and column mappings into ebean-orm.xml file and reference them as 'named queries' - see database.createNamedQuery().