こんな画面を作る †
- 全部で64エントリある表を10エントリずつ表示する
- 1行ごとに色を変える
- データは、一度に表示する分だけメモリに展開したい
- 数万件(数百MB)のデータを扱う、実際の業務アプリケーションで使うテクニックを練習する
- さすがに、数百MBをオンメモリには持たないでしょう*1
LazyLoadingList? †
- Wicket とは直接関係はないけれど、まずは、データベースから必要なデータのみを取ってくる機構を作る
LazyLoadingList? †
package com.snail.wicket.exam.pageable;
import java.io.Serializable;
import java.util.AbstractList;
import java.util.List;
public abstract class LazyLoadingList<E> extends AbstractList<E> implements Serializable{
private int cacheStart = -1;
private int cacheEnd = 0;
private List<E> cache = null;
@Override
public E get(int index) {
if(cache==null || index < cacheStart || cacheEnd < index ){
cache = getPage(index,getCacheSize());
cacheStart = index;
cacheEnd = index + cache.size() - 1;
System.out.println("RELOADED:"+cacheStart+"-"+cacheEnd);
}
return cache.get(index-cacheStart);
}
abstract List<E> getPage(int start,int size);
abstract int getCacheSize();
}
- Wicket の PageableListView? が参照する List
- この List は、get(int index) が呼ばれたときに、初めてデータを取ってくる
- get(int index) が呼ばれたとき、
- 要求されたデータが cache に載っていればそれを返す
- 要求されたデータが cache に載っていない場合には、getPage(int start, int size ) を呼び出して、要求されたデータとそれに続く getCacheSize?() 件のデータをキャッシュに読み込む
- getCacheSize?() は、一度に表示されるエントリ数 x n を返すようにする
- 一度に 10 エントリ表示する場合、get(0) で 10 件取得すれば、その回の DB アクセス (getPage(int start, int end))は1回のみ
- 一度に 20 件取得しておけば、ユーザがページを順に見てくれれば 2 ページ分の内容を 1 回のDBアクセスでとってこれる。しかし、ユーザが違うところを見ると、余分に取得した 10 エントリ分は無駄になる
Database †
MagicDAO †
package com.snail.wicket.exam.pageable;
import org.apache.commons.dbutils.BasicRowProcessor;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import org.apache.commons.dbutils.RowProcessor;
import org.apache.commons.dbutils.handlers.MapHandler;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
public final class MagicDAO {
private static final String CONNECTION_URL = "jdbc:derby://localhost:1527/C:\\javadb\\firstdb";
public static List<MagicVO> getPage(final int start, final int pageSize)
throws SQLException {
final int end = start + pageSize;
QueryRunner qr = new QueryRunner();
ResultSetHandler rsh = new ResultSetHandler() {
@Override
public Object handle(ResultSet rs) throws SQLException {
List<MagicVO> result = new LinkedList<MagicVO>();
RowProcessor processor = new BasicRowProcessor();
int cnt = 0;
for (; (cnt < start) && rs.next(); cnt++) {
// 読み飛ばし
}
for (; (cnt < end) && rs.next(); cnt++) {
result.add((MagicVO) processor.toBean(rs, MagicVO.class));
}
return result;
}
};
return (List<MagicVO>) qr.query(getConnection(),
"SELECT * FROM MAGIC ORDER BY ID", rsh);
}
public static int getSize() throws SQLException{
QueryRunner qr = new QueryRunner();
ResultSetHandler rsh = new MapHandler();
Map countMap = (Map)qr.query(getConnection(), "SELECT COUNT(ID) AS SIZE FROM MAGIC",rsh);
return (Integer)countMap.get("SIZE");
}
private static Connection getConnection() throws SQLException {
try {
Class.forName("org.apache.derby.jdbc.ClientDriver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return DriverManager.getConnection(CONNECTION_URL);
}
}
- LazyLoadingList?#getPage() から呼ばれる DBアクセス部品 (DAO = Data Access Object)
- DAO も 基本的に Java Java DB (Apache Derby) で作ったもの。
- getSize() ( SELECT COUNT(ID) AS SIZE FROM MAGIC ) を追加
MagicVO.java †
package com.snail.wicket.exam.pageable;
import org.apache.commons.beanutils.BeanUtils;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
public final class MagicVO implements Serializable {
public static final String EXPLANATION_COL = "effect";
public static final String ID_COL = "id";
public static final String MP_COL = "mp";
public static final String NAME_COL = "name";
private String pExplanation;
private int pId;
private String pMp;
private String pName;
public final void setExplanation(String explanation) {
pExplanation = explanation;
}
public final String getExplanation() {
return pExplanation;
}
public final void setId(int id) {
pId = id;
}
public final int getId() {
return pId;
}
public final void setMp(String mp) {
pMp = mp;
}
public final String getMp() {
return pMp;
}
public final void setName(String name) {
pName = name;
}
public final String getName() {
return pName;
}
@Override
public String toString() {
try {
return BeanUtils.describe(this).toString();
} catch (IllegalAccessException e) {
return super.toString();
} catch (InvocationTargetException e) {
return super.toString();
} catch (NoSuchMethodException e) {
return super.toString();
}
}
}
PageListPage?.html †
<?xml version="1.0" encoding="UTF-8"?>
<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:wicket="http://wicket.apache.org/">
<head>
<title>Wicket Pageable List Examination</title>
<style>
table { border: 2px solid black; border-collapse:collapse; width:300px; }
th { border:1px solid black; background-color:blue; color: white; }
td.navi { border:1px solid black; background-color:gold; color: black; }
td.odd { border:1px solid black; background-color:white }
td.even { border:1px solid black; background-color:lightcyan }
</style>
</head>
<body>
<strong>Wicket Pageable List Examination</strong>
<br/><br/>
<span wicket:id="msg"> error message will be here </span>
<table>
<tr>
<th>ID</th>
<th>NAME</th>
<th>MP</th>
</tr>
<tr wicket:id="magic">
<td wicket:id="id" class="odd">01</td>
<td wicket:id="name" class="odd">水遁の術</td>
<td wicket:id="mp" class="odd">10</td>
</tr>
<tr>
<td wicket:id="navi" colspan="3" align="center" class="navi">
Navigation will be here
</td>
</tr>
</table>
</body>
</html>
- Wicket 表を作る と基本構造は同じ
- 偶数行と奇数行のスタイルを変える
- 最下行に <td wicket:id="navi"> を設定した。ここに、表のナビゲーションが入る
package com.snail.wicket.exam.pageable;
import java.sql.SQLException;
import java.util.AbstractList;
import java.util.List;
import org.apache.wicket.AttributeModifier;
import org.apache.wicket.extensions.markup.html.repeater.data.table.NavigationToolbar;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.PageableListView;
import org.apache.wicket.markup.html.navigation.paging.PagingNavigator;
import org.apache.wicket.markup.html.panel.FeedbackPanel;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
public class PageListPage extends WebPage {
private FeedbackPanel feedback = new FeedbackPanel("msg");
public PageListPage() {
add(feedback);
final int rowPerPage = 10;
List<MagicVO> magicList = new LazyLoadingList<MagicVO>() {
@Override
public int size() {
try {
return MagicDAO.getSize();
} catch (SQLException e) {
error("DB Error:"+e.getMessage());
e.printStackTrace(System.err);
return 0;
}
}
@Override
int getCacheSize() {
return rowPerPage;
}
@Override
List<MagicVO> getPage(int start, int size) {
try {
return MagicDAO.getPage(start, size);
} catch (SQLException e) {
error("DB Error:"+e.getMessage());
e.printStackTrace(System.err);
return null;
}
}
};
PageableListView magic = new PageableListView("magic", magicList, rowPerPage) {
@Override
protected void populateItem(ListItem item) {
MagicVO vo = (MagicVO) item.getModelObject();
IModel oddEven = new Model(vo.getId() % 2 == 0 ? "even" : "odd");
item.setModel(new CompoundPropertyModel(vo));
Label idLabel = new Label(MagicVO.ID_COL);
idLabel.add(new AttributeModifier("class",oddEven));
item.add(idLabel);
Label mpLabel = new Label(MagicVO.MP_COL);
mpLabel.add(new AttributeModifier("class",oddEven));
item.add(mpLabel);
Label nameLabel = new Label(MagicVO.NAME_COL);
nameLabel.add(new AttributeModifier("class",oddEven));
item.add(nameLabel);
}
};
add(magic);
add(new PagingNavigator("navi", magic));
}
}
- Wicket 表を作る と基本構造は同じ
- 表示する List<MagicVO> には、LazyLoadingList?<MagicVO> を使い、表示されるたびにデータベースから読み込む
- PageableListView?#populateItem() では、偶数行目には even クラスを、奇数行目には odd クラスを適用するようにした。
- wicket:id="navi" に、PagingNavigator? を適用する。
Java#Wicket