Thanks for the feedback. I paste the source code of my custom component here. (being used as a tab)
LazyBeanItem or LazyBeanContainer is no different than BeanItem or BeanContainer except they can take argument of exclusion list on bean properties. They are not the cause of this problem, because I’ve tried stock BeanItem or BeanContainer with same result.
The business logic is simple. There is a table which will be loaded with search result by pressing search button. Each time the items in the container will be removed and refilled with newer result. I’ve noticed that if I do many search during the same session, the memory consumption (MethodProperty) will not increase. With different session, the memory will start to build up and never released even the sessions were released!
package com.kymco.resume.composite;
import java.util.List;
import java.util.Set;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
import org.hibernate.sql.JoinFragment;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.util.StringUtils;
import org.vaadin.dialogs.ConfirmDialog;
import com.kymco.model.resume.Application;
import com.kymco.model.resume.Resume;
import com.kymco.resume.common.AppCountColumnGenerator;
import com.kymco.resume.common.GenderColumnGenerator;
import com.kymco.resume.common.ProcessColumnGenerator;
import com.kymco.resume.common.UserCodeContainerFactory;
import com.kymco.resume.dao.ResumeDAO;
import com.kymco.resume.window.ProcessChangeWindow;
import com.kymco.resume.window.ResumeEditWindow;
import com.kymco.vaadin.data.util.LazyBeanContainer;
import com.kymco.vaadin.data.util.LazyBeanItem;
import com.kymco.vaadin.table.DateColumnGenerator;
import com.vaadin.annotations.AutoGenerated;
import com.vaadin.data.Property;
import com.vaadin.data.Property.ValueChangeEvent;
import com.vaadin.ui.AbstractSelect;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.ComboBox;
import com.vaadin.ui.CustomComponent;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.PopupDateField;
import com.vaadin.ui.Table;
import com.vaadin.ui.TextField;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Window;
import com.vaadin.ui.AbstractSelect.Filtering;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.Window.Notification;
@Configurable
public class MemberComp extends CustomComponent
{
@AutoGenerated
private VerticalLayout mainLayout;
@AutoGenerated
private Table table;
@AutoGenerated
private HorizontalLayout hl2;
@AutoGenerated
private Button removeBtn;
@AutoGenerated
private Button resumeBtn;
@AutoGenerated
private HorizontalLayout hl1;
@AutoGenerated
private Button searchBtn;
@AutoGenerated
private TextField tf1;
@AutoGenerated
private ComboBox combo2;
@AutoGenerated
private PopupDateField toDate;
@AutoGenerated
private PopupDateField fromDate;
@AutoGenerated
private TextField tf2;
@AutoGenerated
private ComboBox combo1;
private static int MAX_COUNT=2000;
@Resource private ResumeDAO dao;
private LazyBeanContainer<String,Resume> ctr;
@Resource private UserCodeContainerFactory ucFactory;
private static final long serialVersionUID = 178821687368574870L;
/**
* The constructor should first build the main layout, set the
* composition root and then do any custom initialization.
*
* The constructor will not be automatically regenerated by the
* visual editor.
*/
public MemberComp() {
buildMainLayout();
setCompositionRoot(mainLayout);
// TODO add user code here
}
public DetachedCriteria buildCriteria()
{
DetachedCriteria dc = DetachedCriteria.forClass(Resume.class,"r")
.createCriteria("r.applications", "app", JoinFragment.LEFT_OUTER_JOIN)
.createCriteria("app.job", "j", JoinFragment.LEFT_OUTER_JOIN)
.createCriteria("app.process", "p", JoinFragment.LEFT_OUTER_JOIN);
if(combo1.getValue()!=null)
dc.add(Restrictions.eq("j.jobUuid", combo1.getValue().toString()));
if(StringUtils.hasLength(tf2.getValue().toString()))
dc.add(Restrictions.like("j.jobTitle", tf2.getValue().toString(), MatchMode.ANYWHERE));
if(fromDate.getValue()!=null)
dc.add(Restrictions.ge("app.applyDate", fromDate.getValue()));
if(toDate.getValue()!=null)
dc.add(Restrictions.le("app.applyDate", toDate.getValue()));
if(combo2.getValue()!=null)
dc.add(Restrictions.eq("p.processUuid", combo2.getValue().toString()));
if(StringUtils.hasLength(tf1.getValue().toString()))
dc.add(Restrictions.like("r.name", tf1.getValue().toString(), MatchMode.ANYWHERE));
dc.addOrder(Order.desc("app.applyDate"));
dc.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
return dc;
}
@PostConstruct
public void init()
{
//initialize search ui
combo1.setContainerDataSource(dao.getJobCtr(40));
combo1.setItemCaptionPropertyId("jobTitle");
combo1.setItemCaptionMode(AbstractSelect.ITEM_CAPTION_MODE_PROPERTY);
combo1.setFilteringMode(Filtering.FILTERINGMODE_CONTAINS);
combo2.setContainerDataSource(dao.getProcessCtr(20));
combo2.setItemCaptionPropertyId("processName");
combo2.setItemCaptionMode(AbstractSelect.ITEM_CAPTION_MODE_PROPERTY);
combo2.setFilteringMode(Filtering.FILTERINGMODE_CONTAINS);
DetachedCriteria dc = buildCriteria();
List<Resume> result = dao.getRsumeByDC(dc, MAX_COUNT);
ctr = new LazyBeanContainer<String,Resume>(Resume.class,null,null);
ctr.setBeanIdProperty("resumeUuid");
// ctr.addAll(result);
table.setSelectable(true);
table.setMultiSelect(false);
table.setImmediate(true);
table.setColumnHeader("name", "應徵人員");
table.setColumnWidth("name", 50);
table.setColumnHeader("gender", "性別");
table.setColumnWidth("gender", 25);
table.setColumnHeader("nid", "身分證號");
table.setColumnWidth("nid", 80);
table.setColumnHeader("birthday", "生日");
table.setColumnWidth("birthday", 70);
table.setColumnHeader("createDate", "建立日期");
table.setColumnWidth("createDate", 70);
table.setColumnHeader("applications", "應徵次數");
table.setColumnWidth("applications", 60);
table.setColumnHeader("isVerify", "是否認證");
table.setColumnWidth("isVerify", 50);
table.addGeneratedColumn("createDate", new DateColumnGenerator("yyyy/MM/dd"));
table.addGeneratedColumn("birthday", new DateColumnGenerator("yyyy/MM/dd"));
table.addGeneratedColumn("gender", new GenderColumnGenerator());
table.addGeneratedColumn("applications", new AppCountColumnGenerator());
table.setPageLength(30);
table.setContainerDataSource(ctr);
table.setVisibleColumns(new String[]{"name","applications","gender","nid","birthday","createDate","isVerify"});
table.setRowHeaderMode(Table.ROW_HEADER_MODE_INDEX);
table.setColumnExpandRatio("isVerify", 1.0f);
table.addListener(new Property.ValueChangeListener()
{
private static final long serialVersionUID = 4523110264052715214L;
public void valueChange(ValueChangeEvent event)
{
String selected = (String) table.getValue();
if(selected==null)
{
resumeBtn.setEnabled(false);
removeBtn.setEnabled(false);
}
else
{
resumeBtn.setEnabled(true);
removeBtn.setEnabled(true);
}
}
});
searchBtn.addListener(new ClickListener()
{
private static final long serialVersionUID = 5384262902530395296L;
@Override
public void buttonClick(ClickEvent event)
{
DetachedCriteria dc = buildCriteria();
List<Resume> result = dao.getResumeByDC(dc, MAX_COUNT);
ctr.removeAllItems();
ctr.addAll(result);
}
});
resumeBtn.setEnabled(false);
resumeBtn.addListener(new ClickListener()
{
private static final long serialVersionUID = 7436262902530396271L;
@SuppressWarnings("unchecked")
@Override
public void buttonClick(ClickEvent event)
{
Window main = event.getButton().getApplication().getMainWindow();
LazyBeanItem<Resume> i = (LazyBeanItem<Resume>) table.getItem(table.getValue());
ResumeEditWindow rewin = new ResumeEditWindow(i.getBean());
rewin.setModal(true);
main.addWindow(rewin);
}
});
removeBtn.setEnabled(false);
removeBtn.addListener(new ClickListener()
{
private static final long serialVersionUID = -5639801170771445147L;
@Override
public void buttonClick(ClickEvent event)
{
final Window main = event.getButton().getApplication().getMainWindow();
ConfirmDialog.show(main, "注意", "確定要刪除本應徵人員與所有紀錄?", "確定", "取消",
new ConfirmDialog.Listener() {
@SuppressWarnings("unchecked")
public void onClose(ConfirmDialog dialog) {
if (dialog.isConfirmed())
{
try
{
LazyBeanItem<Resume> i = (LazyBeanItem<Resume>) table.getItem(table.getValue());
dao.removeResumeAssociates(i.getBean());
table.removeItem(table.getValue());
main.showNotification("刪除應徵人員成功!", Notification.TYPE_HUMANIZED_MESSAGE);
}
catch(Exception e)
{
e.printStackTrace();
main.showNotification("刪除失敗!請洽系統管理員", Notification.TYPE_ERROR_MESSAGE);
}
}
}
});
}
});
}
@AutoGenerated
private VerticalLayout buildMainLayout() {
// common part: create layout
mainLayout = new VerticalLayout();
mainLayout.setImmediate(false);
mainLayout.setWidth("100%");
mainLayout.setHeight("100%");
mainLayout.setMargin(false);
// top-level component properties
setWidth("100.0%");
setHeight("100.0%");
// hl1
hl1 = buildHl1();
mainLayout.addComponent(hl1);
// hl2
hl2 = buildHl2();
mainLayout.addComponent(hl2);
// table
table = new Table();
table.setImmediate(false);
table.setWidth("100.0%");
table.setHeight("100.0%");
mainLayout.addComponent(table);
mainLayout.setExpandRatio(table, 1.0f);
return mainLayout;
}
@AutoGenerated
private HorizontalLayout buildHl1() {
// common part: create layout
hl1 = new HorizontalLayout();
hl1.setImmediate(false);
hl1.setWidth("-1px");
hl1.setHeight("-1px");
hl1.setMargin(false);
hl1.setSpacing(true);
// combo1
combo1 = new ComboBox();
combo1.setCaption("近期職缺選擇");
combo1.setImmediate(false);
combo1.setWidth("250px");
combo1.setHeight("-1px");
hl1.addComponent(combo1);
// tf2
tf2 = new TextField();
tf2.setCaption("職缺名稱");
tf2.setImmediate(false);
tf2.setWidth("100px");
tf2.setHeight("-1px");
hl1.addComponent(tf2);
// fromDate
fromDate = new PopupDateField();
fromDate.setCaption("送出日期(起)");
fromDate.setImmediate(false);
fromDate.setWidth("-1px");
fromDate.setHeight("-1px");
fromDate.setInvalidAllowed(false);
fromDate.setResolution(4);
hl1.addComponent(fromDate);
// toDate
toDate = new PopupDateField();
toDate.setCaption("送出日期(迄)");
toDate.setImmediate(false);
toDate.setWidth("-1px");
toDate.setHeight("-1px");
toDate.setInvalidAllowed(false);
toDate.setResolution(4);
hl1.addComponent(toDate);
// combo2
combo2 = new ComboBox();
combo2.setCaption("處理狀態");
combo2.setImmediate(false);
combo2.setWidth("100px");
combo2.setHeight("-1px");
hl1.addComponent(combo2);
// tf1
tf1 = new TextField();
tf1.setCaption("姓名");
tf1.setImmediate(false);
tf1.setWidth("80px");
tf1.setHeight("-1px");
hl1.addComponent(tf1);
// searchBtn
searchBtn = new Button();
searchBtn.setCaption("搜尋");
searchBtn.setImmediate(true);
searchBtn.setWidth("-1px");
searchBtn.setHeight("-1px");
hl1.addComponent(searchBtn);
hl1.setComponentAlignment(searchBtn, new Alignment(9));
return hl1;
}
@AutoGenerated
private HorizontalLayout buildHl2() {
// common part: create layout
hl2 = new HorizontalLayout();
hl2.setImmediate(false);
hl2.setWidth("-1px");
hl2.setHeight("-1px");
hl2.setMargin(false);
// resumeBtn
resumeBtn = new Button();
resumeBtn.setCaption("履歷內容");
resumeBtn.setImmediate(true);
resumeBtn.setWidth("-1px");
resumeBtn.setHeight("-1px");
hl2.addComponent(resumeBtn);
// removeBtn
removeBtn = new Button();
removeBtn.setCaption("刪除應徵紀錄");
removeBtn.setImmediate(true);
removeBtn.setWidth("-1px");
removeBtn.setHeight("-1px");
hl2.addComponent(removeBtn);
return hl2;
}
}