Ebean query futures
// find by id Order order = Ebean.find(Order.class, 12); SQL khi thực thi truy vấn: select o.id, o.order_date, o.ship_date, o.cretime, o.updtime, o.status_code, o.customer_id from or_order o where or.id = ? // these are the same Query<Order> query = ...
// find by id Order order = Ebean.find(Order.class, 12);
SQL khi thực thi truy vấn:
select o.id, o.order_date, o.ship_date, o.cretime, o.updtime, o.status_code, o.customer_id from or_order o where or.id = ?
// these are the same Query<Order> query = Ebean.createQuery(Order.class); // JPA style Query<Order> query = Ebean.find(Order.class); // fluid API style // Ví du: fluid API style with find() List<Order> list = Ebean.find(Order.class) .fetch("customer") .where().eq("status.code", "SHIPPED") .findList();
# Where clause Ta có thể chỉ định các vị từ sau mệnh đề where ``` // find all the orders shipped since a week ago List<Order> list = Ebean.find(Order.class) .where() .eq("status", Order.Status.SHIPPED) .gt("shipDate", lastWeek) .findList(); ``` Một số vị từ như : ``` eq(...) = equals ne(...) = not equals ieq(...) = case insensitve equals between(...) = between gt(...) = greater than ge(...) = greater than or equals lt(...) = less than or equals le(...) = less than or equals isNull(...) = is null isNotNull(...) = is not null like(...) = like startsWith(...) = string starts with endswith(...) = string ends with contains(...) = string conains in(...) = in a subquery, collection or array exists(...) = at least one row exists in a subquery notExists(...) = no row exists in a subquery more... ``` SQL sẽ tự động thêm phép join, nếu xử lý sau mệnh đề where cần đến một phép join hỗ trợ. Ví dụ: ``` List<Order> orders = Ebean.find(Order.class) .where().ilike("customer.name", "rob%") .findList(); ``` trong truy vấn trên, phép join tới *customer* được tự động thêm vào để hỗ trợ ``` <sql summary='[app.data.test.Order]'> select o.id, o.order_date, o.ship_date, o.cretime, o.updtime, o.status_code, o.customer_id from or_order o join or_customer c on o.customer_id = c.id where lower(c.name) like ? </sql> ```
# Query language Ebean hỗ trợ "Partial Object", giúp đơn giản hóa cú pháp join. Đặc biệt Ebean tự động xác định loại join cho ta và tự động thêm phép join khi mà xử lý ở vị từ sau mệnh đề where cần đến phép join hỗ trợ. "Partial Object" là một phần quan trọng góp phần cải thiện performance cần chú ý. Theo quan điểm thiết kế của Ebean, ta không cần một bảng thứ yếu nào mà thay vào đó là dùng "partial object". Ví dụ, đối với thông tin đặt hàng, ta có thể lấy theo các tiêu chí dữ liệu khác nhau: ``` // find all the orders fetching all the properties of order find order
// find all the orders fetching all the properties of order // ... this is the same as the first query find order (*) // find all the orders fetching the id, orderDate and shipDate // ... This is described as a "partial object query" // ... the ID property is *ALWAYS* fetched find order (orderDate, shipDate) // find all the orders (and orderDetails) // ... fetching all the properties of order // ... and all the properties of orderDetails // ... the type of fetch(Outer etc) is determined automatically find order fetch orderDetails // find all the orders (with their orderDetails) // ... fetching all the properties of order // ... and all the properties of orderDetails find order (*) fetch orderDetails (*) // find all the orders (with orderDetails and products) // ... fetching the order id, orderDate and shipDate // ... fetching all the properties for orderDetail // ... fetching the product id, sku and name find order (orderDate, shipDate) fetch orderDetails (*) fetch orderDetails.product (sku, name) String query = "find order where status.code=:status and shipDate > :shipped"; List<Order> list = Ebean.find(Order.class) .setQuery(query) .setParameter("status", Order.Status.SHIPPED) .setParameter("shipped", lastWeek) .findList();
<sql summary='[app.data.test.Order] autoFetchTuned[false]'> select o.id, o.order_date, o.ship_date, o.cretime, o.updtime, o.status_code, o.customer_id from or_order o where o.status_code = ? and o.ship_date > ? </sql>
<br> # Partial objects Có hai phương thức là "select()" và "fetch()" cho phép ta lấy các thuộc tính muốn lấy. <br> ##### Select chỉ định các thuộc tính muốn lấy trên đối tượng root
// root trong trường hợp này là customer Customer customer = Customer.find .select("name") .where().idEq(1L) .findUnique();
select t0.id c0, t0.name c1 from o_customer t0 where t0.id = ? ; --bind(1)
<br> ##### Fetch chỉ định các thuộc tính cần lấy trên bean liên kết
Order order = Order.find .select("status, orderDate, shipDate") // 3 fields from order .fetch("customer", "name") // just name field from customer .fetch("details") // all fields from details .fetch("details.product", "sku") // just sku field from product .where().idEq(1L) .findUnique();
select t0.id c0, t0.status c1, t0.order_date c2, t0.ship_date c3, t1.id c4, t1.name c5, -- customer name t2.id c6, t2.order_qty c7, t2.ship_qty c8, t2.unit_price c9, t2.version c10, t2.when_created c11, t2.when_updated c12, t2.order_id c13, t3.id c14, t3.sku c15 -- product sku from o_order t0 join o_customer t1 on t1.id = t0.customer_id left outer join o_order_detail t2 on t2.order_id = t0.id left outer join o_product t3 on t3.id = t2.product_id where t0.id = ? order by t0.id, t2.id asc; --bind(1)
<br> ##### Saving a partial object
// find customer 1 // ... just fetch the customer id, name and version property Customer customer = Ebean.find(Customer.class) .select("name") .where().idEq(1) .findUnique(); customer.setName("CoolName"); Ebean.save(customer);
<sql summary='[app.data.test.Customer]'> select c.id, c.name from or_customer c where c.id = ? </sql>
trong thông tin log, chúng ta có thể thấy thời gian update
... update or_customer set name=?, updtime=? where id=? and name=? ... Binding Update [or_customer] set[name=CoolName, updtime=2008-11-19 10:58:08.598, ] where[id=1, name=Ford, ] ... Updated [app.data.test.Customer] [1]
<br> # Query joins ##### Controlling eager loading of the object graph using query joins. Sử dụng "fetch()" để chỉ định rõ cái ta muốn lấy. Chúng ta làm điều này để giảm "lazy loading" của các bean về sau. Khi mà có nhiều hơn 1 quan hệ OneToMany, ManyToMany, ... thì Ebean sẽ tự động chuyển đổi một trong số đó sang "query joins". Và tùy vào mối quan hệ OneToMany, ManyToMany, ... mà Ebean xác định loại join. Ví dụ: "customer.contacts" và "details" là OneToMany relationships.
List<Order> orders = Order.find .select("status") .fetch("customer") .fetch("customer.contacts") // contacts is a @OneToMany .fetch("details") // details is a @OneToMany .orderBy("customer.name") .findList();
Kết quả nằm trong phép join "left outer join o_contact", và các thuộc tính liên quan đến t2.* cũng được nạp trong truy vấn luôn (vì vậy nó sẽ không lazy load sau này nữa). Các truy vấn ứng với ví dụ trên:
-- This first query includes the customer contacts -- but does not include the order details select t0.id c0, t0.status c1, t1.id c2, t1.inactive c3, t1.name c4, ... -- truncated t2.id c12, t2.first_name c13, t2.last_name c14, ... -- truncated t2.when_updated c19, t2.customer_id c20 from o_order t0 join o_customer t1 on t1.id = t0.customer_id left outer join o_contact t2 on t2.customer_id = t1.id order by t1.name;
-- This second query fetchs the order details associated -- with the orders that were returned by the first query select t0.order_id c0, t0.id c1, t0.order_qty c2, ... from o_order_detail t0 where (t0.order_id) in (?,?,?,?,?,?,?,?,?,?) order by t0.order_id, t0.id; --bind(2,1,1,1,3,3,3,4,4,4)
Một ví dụ khác, customer @OneToMany contact, và contact @OneToMany notes
List<Customer> customers = Customer.find .fetch("contacts") // contacts is a OneToMany .fetch("contacts.notes") // notes is a OneToMany .orderBy("name") .findList();
select t0.id c0, t0.inactive c1, t0.name c2, ... t0.when_updated c7, t0.billing_address_id c8, ... t1.id c10, t1.first_name c11, t1.last_name c12, ... from o_customer t0 left outer join be_contact t1 on t1.customer_id = t0.id order by t0.name, t0.id
-- fetch the contact notes for all the contacts select t0.contact_id c0, t0.id c1, t0.contact_id c2, t0.title c3, ... from contact_note t0 where (t0.contact_id) in (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
<br> ##### FETCHCONFIG Khi ta chỉ định một truy vấn với Ebean, nó có thể dẫn đến việc sinh ra nhiều hơn một câu truy vấn. Đôi khi ta muốn kiểm soát rõ rằng về điều này (eager hay lazy load, ....). Chúng ta không cần chỉ định FetchConfig nếu ta không muốn. Ebean sẽ tự động chuyển đổi một vài phép join sang query join nếu nó nhiều hơn một quan hệ OneToMany or ManyToMany, .. trong truy vấn. Nhưng cũng có trường hợp truy vấn chỉ chứa một quan hện OneToMany, ta dùng 2 truy vấn (sử dụng "query join") tốt hơn "fetch join". Trường hợp đó ứng với bản phía "One" có kích thước về số columns, và phía "Many" thì có rất nhiều bản ghi liên kết với một bản ghi phía "One". Ví dụ: Tìm kiếm Orders join details sử dụng single SQL query
// Normal fetch join results in a single SQL query List<Order> list = Ebean.find(Order.class).fetch("details").findList();
<br> Ví dụ: Using a "query join" thay vì "fetch join", we instead use 2 SQL queries
// This will use 2 SQL queries to build this object graph. List<Order> list = Ebean.find(Order.class) .fetch("details", new FetchConfig().query()) .findList();
queries: 1. find order 2. find orderDetails where order.id in (?,?...) // first 100 order id's <br> Ví dụ: Sử dụng 2 "query joins"
// This will use 3 SQL queries to build this object graph List<Order> list = Ebean.find(Order.class) .fetch("details", new FetchConfig().query()) .fetch("customer", new FetchConfig().queryFirst(5)) .findList();
queries: 1. find order 2. find orderDetails where order.id in (?,?...) // first 100 order id's 3. find customer where id in (?,?,?,?,?) // first 5 customers <br> Ví dụ: sử dụng "query joins" và partial object
// This will use 3 SQL queries to build this object graph List<Order> list = Ebean.find(Order.class) .select("status, shipDate") .fetch("details", "quantity, price", new FetchConfig().query()) .fetch("details.product", "sku, name") .fetch("customer", "name", new FetchConfig().queryFirst(5)) .fetch("customer.contacts") .fetch("customer.shippingAddress") .findList();
queries: 1. find order (status, shipDate) 2. find orderDetail (quantity, price) fetch product (sku, name) where order.id in (?,? ...) 3. find customer (name) fetch contacts (*) fetch shippingAddress (*) where id in (?,?,?,?,?) *NOTE* : khi fetch "details.product" sẽ tự động fetch tới "details". Khi fetch "customer.contacts" và "customer.shippingAddres" sẽ tự động fetch tới "customer". <br> Chúng ta cũng có thể sử dụng query() và lazy load cùng nhau trong một truy vấn join.
List<Order> list = Ebean.find(Order.class) .fetch("customer", new FetchConfig().query(10).lazy(5)) .findList();
queries: 1. find order 2. find customer where id in (?,?,?,?,?,?,?,?,?,?) // first 10 customers 3. then if lazy loading of customers is invoked, use a batch size of 5 to load the customers <br> Ví dụ sử dụng 3 SQL query để build một object:
// A more advanced example with multiple query joins List<Order> l0 = Ebean.find(Order.class) .select("status, shipDate") .fetch("details", "orderQty, unitPrice", new FetchConfig().query()) .fetch("details.product", "sku, name") .fetch("customer", "name", new FetchConfig().query(10)) .fetch("customer.contacts","firstName, lastName, mobile") .fetch("customer.shippingAddress","line1, city") .findList();
Kết quả các câu query: Query 1: main query. Chú ý rằng "customer_id" được tự động thêm vào để hỗ trợ query join.
// query 1 … the main query <sql summary='Order'> select o.id c0, o.status c1, o.ship_date c2, o.customer_id c3 from o_order o </sql>
Query 2: query join với bản customer, fetch dữ liệu 10 bản ghi đầu tiên được tham chiếu, nhưng thực tế kết quả chỉ ra chỉ lấy 2 bản ghi.
<sql mode='+query' summary='Customer, shippingAddress y:contacts' load='path:customer batch:10 actual:2'> select c.id c0, c.name c1 , cs.id c2, cs.line_1 c3, cs.city c4 , cc.id c5, cc.first_name c6, cc.last_name c7, cc.mobile c8 from o_customer c left outer join o_address cs on cs.id = c.shipping_address_id left outer join contact cc on cc.customer_id = c.id where c.id in (?,?,?,?,?,?,?,?,?,?) order by c.id </sql>
Query 3: query join với bảng details. fetch dữ liệu 100 bản ghi đầu tiên.
<sql mode='+query' summary='Order +many:details, details.product' load='path:details batch:100 actual:3'> select o.id c0 , od.id c1, od.order_qty c2, od.unit_price c3 , odp.id c4, odp.sku c5, odp.name c6 from o_order o left outer join o_order_detail od on od.order_id = o.id left outer join o_product odp on odp.id = od.product_id where o.id in (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) order by o.id </sql>
<br> ##### FetchConfig.lazy() - "Lazy joins" - Nếu phép join không định nghĩa là "fetch join" hay "query join" thì "lazy loading" sẽ mặc định fetch tất cả thuộc tính của entity. - FetchConfig.lazy() cho phép chúng ta điều khiển "lazy loading query" - như xác định batch size, các thuộc tính được chọn để lấy, cũng như đường dẫn để đưa vào lazy loading query. - Điều này tương tự như "query join" ngoại trừ việc khi tải các thuộc tính được yêu cầu nhưng chưa thực hiện tải ngay tại thời điểm chạy câu truy vấn. - Lý do chúng ta muốn điều khiển "lazy loading query"là để tối ưu hóa performance *(tránh N+1 query, định nghĩa một truy vấn chỉ bao gồm các "lazy loading queries", load các thuộc tính cần thiết và không thêm gì nữa)*
VD : // control the lazy loading of customers ... List<Order> list = Ebean.find(Order.clas) .fetch("customer", "name", new FetchConfig().lazy(5)) .fetch("customer.contacts", "contactName, phone, email") .fetch("customer.shippingAddres") .where().eq("status", Order.Status.NEW) .findList
Trong ví dụ trên *orders* đã được load. Chỉ khi ứng dụng request đến một thuộc tính của *customer* (nhưng không phải là *id* của *customer* - vì thuộc tính này mặc định luôn được load) thì sau đó "lazy loading" của customer mới được gọi. Tại thời điểm đó *name* của *customer*, cùng với *contacts* và *shippingAddress* mới được load - điều này sẽ done trong batch của 5 customers. *NOTES* : nếu thuộc tính *customer status* được chọn chứ không phải là thuộc tính *name* thì điều đó dẫn đến việc "lazy loading" cho tất cả các thuộc tính của *customer* chứ không chỉ riêng *name*
Order order = list.get(0); Customer customer = order.getCustomer(); // this invokes the lazy loading of 5 customers String name = customer.getName();
Kết quả câu truy vấn:
<sql mode='+lazy' summary='Customer, shippingAddress +many:contacts' load='path:customer batch:5 actual:2'> select c.id c0, c.name c1, cs.id c2, cs.line_1 c3, cs.line_2 c4, cs.city c5, cs.cretime c6, cs.updtime c7, cs.country_code c8, cc.id c9, cc.phone c10, cc.email c11 from o_customer c left outer join o_address cs on cs.id = c.shipping_address_id left outer join contact cc on cc.customer_id = c.id where c.id in (?,?,?,?,?) order by c.id </sql>
<br> ##### Using both Chúng ta có thể dùng cả *queryFirst()* và *lazy()* trong cùng một single query. *queryFirst()* xác định số lượng beans sẽ được load theo kiểu eager thông qua một truy vấn bổ sung. Và sau đó *lazy()* sẽ xác định batch size xảy ra sau (nếu có).
new FetchQuery.queryFirst(100).lazy(10);
<br> ##### +query and +lazy – query language syntax Để định nghĩa *query joins* và *lazy joins* trong ngôn ngữ truy vấn chúng ta có thể sử dụng "+query" và "+lazy". Tùy chọn bạn có thể chỉ rõ batch size cho cả hai.
find order join customers (+query) where status = :status
find order (status, shipDate) join customers (+lazy(10) name, status) where status = :orderStatus
<br> # Lazy loading ##### Fine grained control over lazy loading. Ví dụ khi ta sử dụng Partial Object, với thuộc tính cần lấy là *orderDate*. Nhưng khi get/set dữ liệu ta lại lấy *shipDate* - thuộc tính này không nằm trong phần dữ liệu được chọn (phần partial object) trong câu query thì nó sẽ lazy loading tất cả các thuộc tính còn lại.
// find order 12 // ... fetching the order id, orderDate and version property // .... nb: the Id and version property are always fetched Order order = Ebean.find(Order.class) .select("orderDate") .where().idEq(12) .findUnique(); // shipDate is not in the partially populated order // ... so it will lazy load all the missing properties Date shipDate = order.getShipDate(); // similarly if we where to set the shipDate // ... that would also trigger a lazy load order.setShipDate(new Date());
<br> # Named Queries
import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; ... @NamedQueries(value={ @NamedQuery( name="bugsSummary", query="find (name, email) fetch loggedBugs (title, status) where id=:id "), @NamedQuery( name="bugStatus", query="fetch loggedBugs where loggedBugs.status = :bugStatusorder by name") }) @Entity @Table(name="s_user") public class User implements Serializable { ...
Chúng ta có một *named queries*, nơi mà có thể định nghĩa một query. *NOTES* : *name* của các câu query chỉ dùng cho mỗi entity, không dùng như Global giống trong JPA. Ví dụ dưới đây chúng ta set giá trị cho parameter và thực thi nó:
User u = Ebean.createNamedQuery(User.class, "bugsSummary") .setParameter("id", 1) .findUnique();
<br> ##### Named Queries are Modifyable *Named queries* phân tích cú pháp sớm và return một query object để chúng ta có thể modify. Điều này có nghĩa rằng chúng ta có thể get một *named query* và modify chúng như add thêm các mệnh đề where, order by, ...., limit,... Dựa vào *name* của *named queries* như điểm bắt đầu:
// you can treat namedQueries as starting points... // ... in that you can modify them via code // ... prior to executing the query // you can modify a named query... Set <User> users = Ebean.createQuery(User.class, "bugStatus") .setParameter("bugStatus", "NEW") // you can add to the where clause .where().ilike("name", "rob%") // you can set/override the order by .orderBy("id desc") // you can set/override limits (max rows, first row) .setMaxRows(20) .findSet();
<br> # Large queries Kết quả truy vấn khi trả về lớn có thể mất nhiều tài nguyên bộ nhớ nếu sử dụng *findList()* để load tất cả kết quả trong bộ nhớ ra trong cùng một thời điểm. Ebean cung cấp chức năng cho việc trung chuyển các kết quả (streaming the result). Với chức năng này dữ liệu sẽ được lấy ra theo từng hàng xử lý thay vì việc load tất cả ra cùng một thời điểm.<br> **Query.findIterate()** : Thực hiện truy vấn iterating trên kết quả trả về. Khi kết quả cuối cùng được xử lý xong thì **QueryIterator.close()** sẽ được gọi để chặn rò rỉ tài nguyên.<br> **Query.findEach(QueryEachConsumer consumer)** : Thực hiện truy vấn trên từng bean tại một thời điểm. + Phương pháp này thích hợp với truy vấn lớn. Vì các bean được xử lý tại một thời điểm sau đó không cần giữ lại trong bộ nhớ (điều này khác với *findList()* và *findSet()*) + Phương pháp này tương tự như **Query.findIterate()** nhưng thay vì sử dụng một iterator nó sử dụng QueryEachConsumer, điều này thích hợp hơn với JAVA 8.
ebeanServer.find(Customer.class) .where().eq("status", Status.NEW) .order().asc("id") .findEach((Customer customer) -> { // do something with customer System.out.println("-- visit " + customer); });
<br> **Query.findEachWhile(QueryEachWhileConsumer<T> consumer)** : tương tự như **Query.findEach()** : nhưg sử dụng một giá trị return kiểu boolean để callbacks xử lý.
ebeanServer.find(Customer.class) .fetch("contacts", new FetchConfig().query(2)) .where().eq("status", Status.NEW) .order().asc("id") .setMaxRows(2000) .findEachWhile((Customer customer) -> { // do something with customer System.out.println("-- visit " + customer); // return true to continue processing or false to stop return (customer.getId() < 40); });
<br> # Paging Thay vì trả về tất cả các lết quả trả về thì Paging thông qua SQL để giới hạn kết quả (limit/offset, rownum, row_number(), ...) <br> ##### Using firstRows and maxRows or findPagedList to fetch a 'page' of results. Phù hợp với "stateless application" (không giữ PagingList qua nhiều request) Sử dụng setFirstRow() và setMaxRow() để kiểm soát số lượng bản ghi trả về. <br> ##### PagedList PagedList thông qua cơ chế query thông thường với Query.setFirstRow(int) và Query.setMaxRows(int) , nó bao gồm thêm chức năng trả về tổng số bản ghi và tổng số trang, ... Ví dụ: data trả về bao gồm tổng số trang
// We want to find the first 100 new orders // ... 0 means first page // ... page size is 100
PagedList<Order> pagedList = ebeanServer.find(Order.class) .where().eq("status", Order.Status.NEW) .order().asc("id") .findPagedList(0, 100);
// Optional: initiate the loading of the total // row count in a background thread pagedList.loadRowCount();
// fetch and return the list in the foreground thread List<Order> orders = pagedList.getList();
// get the total row count (from the future) int totalRowCount = pagedList.getTotalRowCount();
Ví dụ: data tả về không bao gồm tổng số trang
// If you are not getting the 'first page' often // you do not bother getting the total row count again // so instead just get the page list of data
// fetch and return the list in the foreground thread List<Order> orders = pagedList.getList();
<br> # Asynchronous queries Ebean xây dựng cơ chế thực thi query không đồng bộ. Những query này thực thi bởi backgroud thread và sẽ return một ***Future*** object. The "Future" objects này kế thừa *java.util.concurrent.Future*. Điều này cung cấp cơ chế hôz trợ để cancell query, kiểm tra nếu nó được cancelled hoặc done và lấy kết quả đó trong khoảng thời gian chờ (waiting) hoặc timeout. Các phương thức Query hỗ trợ bất đồng bộ (asynchronous):
public FutureList<T> findFutureList(); public FutureIds<T> findFutureIds(); public FutureRowCount<T> findFutureRowCount();
Ví dụ :
Query<Order> query = Ebean.find(Order.class); // find list using a background thread FutureList<Order> futureList = query.findFutureList(); // do something else ... if (!futureList.isDone()){ // you can cancel the query. If supported by the JDBC // driver and database this will actually cancel the // sql query execution on the database futureList.cancel(true); } // wait for the query to finish ... no timeout List<Order> list = futureList.get(); // wait for the query to finish ... with a 30sec timeout List<Order> list2 = futureList.get(30, TimeUnit.SECONDS);
<br> # RawSql ##### Using RawSql to fully control the SQL used to load an object graph. Ta có thể chỉ định rõ ràng SQL được sử dụng và ánh xạ tới đối tượng nào. Ví dụ như sử dụng các hàm *sum(), count(), max(), ... etc..* điều này rất hữu ích với chức năng như Reporting. Có 2 cách sử dụng, một là SQL thô, hai là cơ chế mapping qua file ebean-orm.xml và reference chúng thông qua "named query". Chúng ta có thể sử dụng RawSQL với các bean hỗ trợ (các bean này chứa một phần thuộc tính của các beans - bean mà gắn với model). Và các bean hỗ trợ này được gọi theo kiểu lazy load. Ví dụ :
// 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 o_order o" + " join o_customer c on c.id = o.kcustomer_id " + " join o_order_detail d on d.order_id = o.id " + " group by order_id, o.status "; 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(); Query<OrderAggregate> query = Ebean.find(OrderAggregate.class); query.setRawSql(rawSql) // add expressions to the WHERE and HAVING clauses .where().gt("order.id", 0) .having().gt("totalAmount", 20); List<OrderAggregate> list = query.findList();
Ví dụ sau đây sử dụng FetConfig để fetch dữ liệu. Sau khi SQL query được thực thi Ebean ử dụng "query joins" để fetch một số thuộc tính của "order" và "customer".
// You can also use FetchConfig to get Ebean to // 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 o_order_detail d" + " group by order_id "; RawSql rawSql = RawSqlBuilder .parse(sql) .columnMapping("order_id", "order.id") .create(); Query<OrderAggregate> query = Ebean.find(OrderAggregate.class); query.setRawSql(rawSql) // get ebean to fetch parts of the order and customer // after the raw SQL query is executed .fetch("order", "status,orderDate",new FetchConfig().query()) .fetch("order.customer", "name") .where().gt("order.id", 0) .having().gt("totalAmount", 20) .order().desc("totalAmount") .setMaxRows(10);
Class OrderAggregate trong ví dụ trên như sau:
package com.avaje.tests.model.basic; import javax.persistence.Entity; import javax.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; } }
<br> ##### tableAliasMapping() tableAliasMapping() sẽ tự động map columns từ query results tới associated object dựa trên path của nó. Nó sử dingj qurrey alias. Cùng xem ví dụ sau để hiểu hơn vấn đền này:
String rs = "select o.id, o.status, c.id, c.name, "+ " d.id, d.order_qty, p.id, p.name " + "from o_order o join o_customer c on c.id = o.kcustomer_id " + "join o_order_detail d on d.order_id = o.id " + "join o_product p on p.id = d.product_id " + "where o.id <= :maxOrderId and p.id = :productId "+ "order by o.id, d.id asc"; RawSql rawSql = RawSqlBuilder.parse(rs) .tableAliasMapping("c", "customer") .tableAliasMapping("d", "details") .tableAliasMapping("p", "details.product") .create(); List<Order> ordersFromRaw = Ebean.find(Order.class) .setRawSql(rawSql) .setParameter("maxOrderId", 2) .setParameter("productId", 1) .findList();
Cách phía trên hay hơn cách làm chi tiết dưới đây:
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();
# SqlQuery ##### Performing sql queries returning relational SqlRow's rather than beans / object graphs. SqlQuery là nơi chúng ta chỉ rõ cái chúng ta muốn SELECT và return cái gì (list, sets, or maps of SqlRow objects)
String sql = "select b.id, b.title, b.type_code, b.updtime" +" ,p.name as product_name " +"from b_bug b join b_product p on p.id = b.product_id " +"where b.id = :id";
SqlRow bug = Ebean.createSqlQuery(sql) .setParameter("id", 1) .findUnique(); String prodName = bug.getString("product_name"); String title = bug.getString("title");
*NOTES: * chúng ta có thể sử dụng "Named" queries và đạt các câu sql trong file orm.xml hơn là đặt trong code. <br> # L2 Cache ##### How to use the L2 cache with queries. ...(under construction)... <br> # Autofetch ##### Let Ebean automatically tune you queries using Autofetch. Ebean có thể tự động điều chỉnh câu query sử dụng một tính năng "AutoFetch" . Điều này tốt hơn khi thực thi truy vấn và giảm thiểu lazy loading. AutoFetch tự dộng định nghĩa câu query - thiết lập điều khiển mệnh đề select() và fetch() để fetch tất cả dữ liệu cần thiết. Điều này làm giảm số lượng các lazy loading và chỉ load các dữ liệu thật sự cần. AutoFetch cũng có thê sử dụng với một truy vấn, cái mà ta đã chỉ rõ đường dẫn lấy (fetch() path). AutoFetch có thể thêm "fetch() path" và điều chỉnh để lấy dữ liệu theo đường dẫn vừa thiết lập. Ví dụ : đoạn code fetch một vài orders .. và xử lý chúng
// fetch new orders List<Order> orders = Ebean.find(Order.class) .where().eq("status", Order.Status.NEW) .findList(); // just read the orderDate for (Order order : orders) { Date orderDate = order.getOrderDate(); }
Ban đầu Ebean không có thông tin (profiling) vì vậy lần đầu chạy câu query thì chưa phải là tối ưu
<sql summary='[app.data.test.Order]'> select o.id, o.order_date, o.ship_date, o.cretime, o.updtime, o.status_code, o.customer_id from or_order o where o.status_code = ? </sql>
Tuy nhiên, sau lần chạy đầu tiên đó, Ebean đã thu thập một số thông tin . Cụ thể nó biết rằng đối với truy vấn này (dựa trên lời gọi được lưu lại trong stack) chương trình chỉ đọc "orderDate". Vì vậy lần chạy thứ 2 sẽ như sau:
<sql summary='[app.data.test.Order] autoFetchTuned[true]'> select o.id, o.order_date, o.updtime from or_order o where o.status_code = ? </sql>
Chúng ta thấy rằng có một ghi chú *autoFetchTuned[true]* in ra trên output. Điều này chỉ ra rằng query đã được điều chỉnh qua AuttoFetch. Trường hợp này chỉ ra rằng chỉ có thuộc tính "orderDate, "id", và "version column" được fetch. Ví dụ: Bây giờ thay đổi chương trình. Chúng ta sử dụng thuộc tính khác và chúng ta cũng lấy ra được thuộc tính "name" của customer.
List<Order> orders = Ebean.find(Order.class) .where().eq("status", Order.Status.NEW) .findList(); for (Order order : orders) { Date orderDate = order.getOrderDate(); // also get the ship date Date shipDate = order.getShipDate(); // ... and get the customer name Customer customer = order.getCustomer(); String customerName = customer.getName(); }
Ebean sẽ chạy tối ưu hóa cho chương trình trong một vài lazy b loading xảy ra. Phần còn lại của "order" được lazy load khi "shipDate" được đọc... và sau đó "customer" được lazy load khi "name" được đọc.
<sql summary='[app.data.test.Order] autoFetchTuned[true]'> select o.id, o.order_date, o.updtime from or_order o where o.status_code = ? </sql>
-- LAZY LOADING: ... khi lấy shipDate
<sql summary='[app.data.test.Order]'> select o.id, o.order_date, o.ship_date, o.cretime, o.updtime, o.status_code, o.customer_id from or_order o where o.id = ? </sql>
-- LAZY LOADING: ... khi lấy customers name
<sql summary='[app.data.test.Customer]'> select c.id, c.name, c.cretime, c.updtime, c.billing_address_id, c.status_code, c.shipping_address_id from or_customer c where c.id = ? </sql>
Các truy vấn trên chưa phải là tốt. Tuy nhiên Ebean đã lưa lại thông tin (profiling infomation) , vì vậy khi chúng ta chạy lại chương trình m một lần nữa ...
<sql summary='[app.data.test.Order, customer] autoFetchTuned[true]'> select o.id, o.order_date, o.updtime, o.ship_date , c.id, c.name, c.updtime from or_order o join or_customer c on o.customer_id = c.id where o.status_code = ? <sql>
... bây giờ Ebean đã thêm "shipDate" và join với "customer" để lấy customer name. Vì vậy nếu chúng ta bật (turn on) "autoFetch" thì Ebean có thể tối ưu hóa query của chúng ta. Giúp chúng ta làm ít công việc hơn vì không phải xác định join và những lợi ích về hiệu suất của "partial object" mang lại. AutoFetch có thể tiếp tục update profiling infomation của nó khi ứng dụng có sự thay đổi, các truy vấn sẽ được tự động được điều chỉnh cho phù hợp. ##### Explicit control on queries Trên query chúng ta có thể chỉ định rõ răng việc có sử dụng autofetch hay không
// explicitly turn on Autofetch for this query query.setAutofetch(true);
##### Implicit control from configuration Một số thuộc tính điều khiển việc autofetch hoạt động như thế nào trong file ebean.properties
# enable autofetch ebean.autofetch.querytuning=true # enable collection of profiling information ebean.autofetch.profiling=true # implicit autofetch mode # default_off, default_on, default_on_if_empty ebean.autofetch.implicitmode=default_on # minimum amount of profiling to collect before # autofetch will start tuning the query ebean.autofetch.profiling.min=1 # profile every query up to base ebean.autofetch.profiling.base=10 # after base collect profiling on 5% of queries ebean.autofetch.profiling.rate=0.05
|property | type/values | description | | -------- | -------- | -------- | | Text | Text | Text | | ebean.autofetch.querytuning | boolean | If true enables Autofetch to tune queries | | ebean.autofetch.profiling | boolean | If true enables profiling information to be collected | | ebean.autofetch.implicitmode | default_off, default_on, default_on_if_empty | default_on_if_empty means autofetch will only tune the query if neither select() nor fetch() has been explicitly set on the query. | | ebean.autofetch.profiling.min | integer | The minimum amount of profiled queries to be collected before the automatic query tuning will start to occur | | ebean.autofetch.profiling.base | integer | Will profile every query up to this number and after than will profile based on the profiling.rate (5% of queries etc) | <br> # Other Bits ##### Other features for controlling queries. forUpdate filterMany bufferFetchSize timeout distinct Fetching the 'row count'. ...(under construction)... Reference: http://ebean-orm.github.io/docs/query/features