Max,
This is, unfortunately, the response I expected <sigh>. I can place enough detail in the defect to allow someone else to (probably) reproduce this problem, but it can take quite a bit of time to abstract the problem into simple test cases. I will place that detail here and leave the decision up to you as to whether a defect should be created.
Thanx,
Jim
Problem:
Memory leak with native SQL queries
Hibernate Versions:
3.1.1 and up (no leak in 3.1)
Detail:
When using native sql queries, a JProbe memory comparrison shows many org.hibernate.hql.ast.tree.IdentNode objects and their associated objects consuming memory. Running the same queries in version 3.1 of Hibernate does not show the same memory consumption problem. Also, when the same query is translated to hql, the memory consumption problem goes away (this is how I have worked around the problem).
The query listed below that I
know causes the leak is very complex. I suspect that a more simple native sql query would also cause the same problem, but I have not tested this.
Query that causes the leak:
Code:
SELECT ud.BASE_DATA_ID id
FROM PORTAL_PAGE ud
JOIN BASE_DATA bd ON bd.BASE_DATA_ID = ud.BASE_DATA_ID
inner join NLS_DATA name on ud.BASE_DATA_ID = name.NAME_BASE_DATA_ID
inner join NLS_DATA description on ud.BASE_DATA_ID = description.DESCRIPTION_BASE_DATA_ID
WHERE ( ( (UPPER(name.STR_VALUE_TXT) LIKE UPPER(:searchString))
AND ( (UPPER(name.LOCALE_TXT) = UPPER(:variant))
OR ((UPPER(name.LOCALE_TXT) = UPPER(:country))
AND (ud.BASE_DATA_ID NOT IN (select ud2.BASE_DATA_ID
FROM USER_DATA ud2
inner join NLS_DATA name2 on ud2.BASE_DATA_ID = name2.NAME_BASE_DATA_ID
WHERE (ud.BASE_DATA_ID = ud2.BASE_DATA_ID)
AND (UPPER(name2.LOCALE_TXT) = UPPER(:variant)))))
OR ((UPPER(name.LOCALE_TXT) = UPPER(:language))
AND (ud.BASE_DATA_ID NOT IN (select ud2.BASE_DATA_ID
FROM USER_DATA ud2
inner join NLS_DATA name2 on ud2.BASE_DATA_ID = name2.NAME_BASE_DATA_ID
WHERE (ud.BASE_DATA_ID = ud2.BASE_DATA_ID)
AND ( (UPPER(name2.LOCALE_TXT) = UPPER(:variant)) OR (UPPER(name2.LOCALE_TXT) = UPPER(:country)) ) )))
OR ((name.NAME_POS_NO = 0)
AND (ud.BASE_DATA_ID NOT IN (select ud2.BASE_DATA_ID
FROM USER_DATA ud2
inner join NLS_DATA name2 on ud2.BASE_DATA_ID = name2.NAME_BASE_DATA_ID
WHERE (ud.BASE_DATA_ID = ud2.BASE_DATA_ID)
AND ( (UPPER(name2.LOCALE_TXT) = UPPER(:variant)) OR (UPPER(name2.LOCALE_TXT) = UPPER(:country)) OR (UPPER(name2.LOCALE_TXT) = UPPER(:language)) ) )))) )
OR ( (UPPER(description.STR_VALUE_TXT) LIKE UPPER(:searchString))
AND ( (UPPER(description.LOCALE_TXT) = UPPER(:variant))
OR ((UPPER(description.LOCALE_TXT) = UPPER(:country))
AND (ud.BASE_DATA_ID NOT IN (select ud2.BASE_DATA_ID
FROM USER_DATA ud2
inner join NLS_DATA description2 on ud2.BASE_DATA_ID = description2.DESCRIPTION_BASE_DATA_ID
WHERE (ud.BASE_DATA_ID = ud2.BASE_DATA_ID)
AND (UPPER(description2.LOCALE_TXT) = UPPER(:variant)))))
OR ((UPPER(description.LOCALE_TXT) = UPPER(:language))
AND (ud.BASE_DATA_ID NOT IN (select ud2.BASE_DATA_ID
FROM USER_DATA ud2
inner join NLS_DATA description2 on ud2.BASE_DATA_ID = description2.DESCRIPTION_BASE_DATA_ID
WHERE (ud.BASE_DATA_ID = ud2.BASE_DATA_ID)
AND ( (UPPER(description2.LOCALE_TXT) = UPPER(:variant)) OR (UPPER(description2.LOCALE_TXT) = UPPER(:country)) ) )))
OR ((description.DESCRIPTION_POS_NO = 0)
AND (ud.BASE_DATA_ID NOT IN (select ud2.BASE_DATA_ID
FROM USER_DATA ud2
inner join NLS_DATA description2 on ud2.BASE_DATA_ID = description2.DESCRIPTION_BASE_DATA_ID
WHERE (ud.BASE_DATA_ID = ud2.BASE_DATA_ID)
AND ( (UPPER(description2.LOCALE_TXT) = UPPER(:variant)) OR (UPPER(description2.LOCALE_TXT) = UPPER(:country)) OR (UPPER(description2.LOCALE_TXT) = UPPER(:language)) ) )))) ) )
AND ( (bd.OMR_LOCATION_TXT in ("group1")) )
Relevant Table Structure:Code:
create table BASE_DATA (BASE_DATA_ID bigint not null, OBJECT_VERSION_NO bigint not null, CREATED_ON_DTTM timestamp not null, MODIFIED_ON_DTTM timestamp not null, CREATED_BY_NM varchar(255) not null, MODIFIED_BY_NM varchar(255) not null, OMR_LOCATION_TXT varchar(1024) not null, primary key (BASE_DATA_ID));
create table NLS_DATA (NLS_DATA_ID bigint not null, OBJECT_VERSION_NO bigint not null, LOCALE_TXT varchar(8), STR_VALUE_TXT varchar(8000), KEYWORD_ID bigint, KEYWORD_POS_NO integer, PORTLET_NAME_ID bigint, PORTLET_NAME_POS_NO integer, PORTLET_DESCRIPTION_ID bigint, PORTLET_DESCRIPTION_POS_NO integer, NAME_BASE_DATA_ID bigint, NAME_POS_NO integer, DESCRIPTION_BASE_DATA_ID bigint, DESCRIPTION_POS_NO integer, NOTES_BASE_DATA_ID bigint, NOTES_POS_NO integer, primary key (NLS_DATA_ID));
create table PORTAL_PAGE (BASE_DATA_ID bigint not null, PAGE_RANK_NO integer not null, LAYOUT_TYPE_CD integer not null, SHARE_TYPE_CD integer not null, MIME_TYPE_TXT varchar(50), TEMPLATE_BASE_DATA_ID bigint, LAYOUT_BASE_DATA_ID bigint, primary key (BASE_DATA_ID));
create table USER_DATA (BASE_DATA_ID bigint not null, primary key (BASE_DATA_ID));
Relavent Mapping Files:BaseData.hbm.xmlCode:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Copyright (c) 2006 by SAS Institute Inc., Cary, NC 27513 USA. All Rights Reserved. -->
<hibernate-mapping package="com.sas.portal.data.model.hibernate">
<class name="BaseData" table="BASE_DATA">
<meta attribute="class-description">
The BaseData class provides some common data for objects.
</meta>
<meta attribute="class-code">
<![CDATA[
private static final long serialVersionUID = -5605415560820797542L;
public static final String READ_SECURITY_FILTER = "readSecurityFilter";
public static final String PARAM_SESSION_ID = "sessionId";
public String toString() {
String className = getClass().getName();
className = className.substring(className.lastIndexOf('.')+1);
return className + ":" + getId();
}
]]>
</meta>
<cache usage="read-write" />
<!-- ID -->
<id name="id" column="BASE_DATA_ID" type="long">
<meta attribute="scope-set">public</meta>
<generator class="native"/>
</id>
<version column="OBJECT_VERSION_NO" name="version" type="long">
<meta attribute="scope-set">public</meta>
</version>
<!-- Attributes -->
<property name="createdOn" column="CREATED_ON_DTTM" type="timestamp" not-null="true">
<meta attribute="field-description">The timestamp that the data was created on.</meta>
</property>
<property name="modifiedOn" column="MODIFIED_ON_DTTM" type="timestamp" not-null="true">
<meta attribute="field-description">The timestamp that the data was last modified on.</meta>
</property>
<property name="createdBy" column="CREATED_BY_NM" type="string" not-null="true">
<meta attribute="field-description">The userid of the creating user</meta>
</property>
<property name="modifiedBy" column="MODIFIED_BY_NM" type="string" not-null="true">
<meta attribute="field-description">The userid of the last modifying user</meta>
</property>
<property name="location" column="OMR_LOCATION_TXT" type="string" length="1024" not-null="true">
<meta attribute="field-description">
The location that this data is conceptually stored in. The value of this string is the SBIP URL of
the Portal Data OMR object.
</meta>
</property>
<!--
<filter name="readSecurityFilter"
condition="(select perm from BASE_DATA_PERMISSIONS(:sessionId, BASE_DATA_ID, 1)) = 1" />
-->
</class>
<filter-def name="readSecurityFilter">
<filter-param name="sessionId" type="string" />
</filter-def>
</hibernate-mapping>
NlsData.hbm.xmlCode:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Copyright (c) 2006 by SAS Institute Inc., Cary, NC 27513 USA. All Rights Reserved. -->
<hibernate-mapping package="com.sas.portal.data.model.hibernate">
<class name="NlsData" table="NLS_DATA">
<meta attribute="class-description">
The NlsData class provides internationalization of all user entered
strings in the Portal..
</meta>
<meta attribute="class-code">
<![CDATA[
private static final long serialVersionUID = -9108781422229084969L;
/* These constants need to be kept in sync with the lengths if the xml below */
public static final int LOCALE_MAX_LENGTH = 8;
public static final int VALUE_MAX_LENGTH = 8000;
public String toString() {
String className = getClass().getName();
className = className.substring(className.lastIndexOf('.')+1);
return className + ":" + getId() + ":" + getLocale() + ":" + getValue();
}
]]>
</meta>
<cache usage="read-write" />
<!-- ID -->
<id name="id" column="NLS_DATA_ID" type="long">
<meta attribute="scope-set">public</meta>
<generator class="native"/>
</id>
<version column="OBJECT_VERSION_NO" name="version" type="long">
<meta attribute="scope-set">public</meta>
</version>
<!-- Attributes -->
<property name="locale" column="LOCALE_TXT" type="string" length="8">
<meta attribute="field-description">
The locale of this internationalized string.
</meta>
</property>
<property name="value" column="STR_VALUE_TXT" type="string" length="8000">
<meta attribute="field-description">
The internationalized string.
</meta>
</property>
</class>
</hibernate-mapping>
PortalPage.hbm.xmlCode:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Copyright (c) 2006 by SAS Institute Inc., Cary, NC 27513 USA. All Rights Reserved. -->
<hibernate-mapping package="com.sas.portal.data.model.hibernate">
<joined-subclass extends="UserData" name="PortalPage" table="PORTAL_PAGE">
<meta attribute="class-description">
The PortalPage class represents a page in the Portal.
</meta>
<meta attribute="extra-import">java.util.ArrayList</meta>
<meta attribute="class-code">
<![CDATA[
private static final long serialVersionUID = -4158481172144981203L;
public static final int SHARE_TYPE_AVAILABLE = 0;
public static final int SHARE_TYPE_DEFAULT = 1;
public static final int SHARE_TYPE_STICKY = 2;
public static final int LAYOUT_TYPE_COLUMN = 0;
public static final int LAYOUT_TYPE_GRID = 1;
public static final int LAYOUT_TYPE_CUSTOM = 2;
public static final int LAYOUT_TYPE_MAXIMIZED = 3;
public static final int DEFAULT_PAGE_RANK = 100;
public static final String DEFAULT_SHARE_TAG = "Default";
public static final String STICKY_SHARE_TAG = "Sticky";
public static final String AVAILABLE_SHARE_TAG = "Available";
/* These constants need to be kept in sync with the lengths if the xml below */
public static final int MIME_TYPE_MAX_LENGTH = 50;
public void addPortletList (PortletList portletList) {
if (portletList != null) {
List portletLists = getPortletLists();
if (portletLists == null) {
portletLists = new ArrayList();
}
portletLists.add(portletList);
setPortletLists(portletLists);
portletList.setPage(this);
}
}
public String getShareTypeString() {
String retval = "";
switch (getShareType()) {
case SHARE_TYPE_DEFAULT:
retval = DEFAULT_SHARE_TAG;
break;
case SHARE_TYPE_STICKY:
retval = STICKY_SHARE_TAG;
break;
case SHARE_TYPE_AVAILABLE:
default:
retval = AVAILABLE_SHARE_TAG;
break;
}
return retval;
}
public void setShareTypeString(String shareTypeString) {
if (shareTypeString.equals(DEFAULT_SHARE_TAG)) {
setShareType(SHARE_TYPE_DEFAULT);
} else if (shareTypeString.equals(STICKY_SHARE_TAG)) {
setShareType(SHARE_TYPE_STICKY);
} else {
setShareType(SHARE_TYPE_AVAILABLE);
}
}
]]>
</meta>
<!-- Key -->
<key column="BASE_DATA_ID" />
<!-- Attributes -->
<property name="pageRank" column="PAGE_RANK_NO" type="int" not-null="true">
<meta attribute="field-description">
Page Rank, if the user has not chosen a specific order in which to show the page
</meta>
</property>
<property name="layoutType" column="LAYOUT_TYPE_CD" type="int" not-null="true">
<meta attribute="field-description">
LayoutType for this page. The value should be one of the constants defined in this class.
</meta>
</property>
<property name="shareType" column="SHARE_TYPE_CD" type="int" not-null="true">
<meta attribute="field-description">
Share type for this page. The value should be one of the constants defined in this class.
</meta>
</property>
<property name="mimeType" column="MIME_TYPE_TXT" type="string" length="50">
<meta attribute="field-description">
HTTP mime-type for this page
</meta>
</property>
<!-- Associations -->
<list name="portletLists" lazy="true" fetch="select" cascade="all-delete-orphan">
<meta attribute="field-description">The ordered list of portlet lists defined for this page.</meta>
<key column="PAGE_BASE_DATA_ID"/>
<index column="POSITION_IN_PORTLET_LIST_NO" />
<one-to-many class="com.sas.portal.data.model.hibernate.PortletList" />
</list>
<!-- <many-to-one name="templatePage" column="TEMPLATE_BASE_DATA_ID" class="com.sas.portal.data.model.hibernate.TemplatePortalPage" fetch="select" not-null="false" /> -->
<!-- <many-to-one name="layoutTemplate" column="LAYOUT_BASE_DATA_ID" class="com.sas.portal.data.model.hibernate.LayoutTemplate" fetch="select" not-null="false" /> -->
<property name="templatePageId" column="TEMPLATE_BASE_DATA_ID" type="long">
<meta attribute="field-description">
The id of the template page for this page. By specifying an id instead of
a many-to-one for the object, we can facilitate a weak link between the 2
objects. We can then manipulate the referenced object (delete) without
having to moddify this object.
</meta>
</property>
<property name="layoutTemplateId" column="LAYOUT_BASE_DATA_ID" type="long">
<meta attribute="field-description">
The id of the layout template for this page. By specifying an id instead of
a many-to-one for the object, we can facilitate a weak link between the 2
objects. We can then manipulate the referenced object (delete) without
having to moddify this object.
</meta>
</property>
<list name="columnWidths" cascade="all-delete-orphan" lazy="true" fetch="select" table="PAGE_COLUMN_WIDTHS">
<meta attribute="field-description">
Provides an ordered list of column widths. The number of columns will be determined
by the length of this list. This field is not used for custom layout.
</meta>
<cache usage="read-write" />
<key column="PAGE_BASE_DATA_ID"/>
<index column="POSITION_IN_COLUMN_LIST_NO" />
<element column="COLUMN_WIDTH_NO" type="integer" />
</list>
</joined-subclass>
</hibernate-mapping>
UserData.hbm.xmlCode:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Copyright (c) 2006 by SAS Institute Inc., Cary, NC 27513 USA. All Rights Reserved. -->
<hibernate-mapping package="com.sas.portal.data.model.hibernate">
<joined-subclass extends="BaseData" name="UserData" table="USER_DATA">
<meta attribute="class-description">
The UserData class provides some base data and associations for objects.
UserData is used for objects typically created and/or manipulated by the user.
</meta>
<meta attribute="extra-import">java.util.Locale</meta>
<meta attribute="extra-import">java.util.Set</meta>
<meta attribute="extra-import">java.util.Iterator</meta>
<meta attribute="class-code">
<![CDATA[
private static final long serialVersionUID = 8292663888938139975L;
public String toString() {
return super.toString() + ":" + getNlsName(Locale.getDefault());
}
public void putNlsName(Locale locale, String name) {
putNlsName(locale.toString(), name);
}
public void putNlsName(String locale, String name) {
// Get the list of nls names
List nlsNames = getNlsName();
if (nlsNames == null) {
nlsNames = new java.util.ArrayList();
}
// Add the string
putNlsString(nlsNames, locale, name);
// Set the list back again
setNlsName(nlsNames);
}
public void removeNlsName(Locale locale) {
List names = getNlsName();
if (names != null) {
removeLocaleEntry(names, locale.toString());
setNlsName(names);
}
}
public String getNlsName(Locale locale) {
// Get the string from the map based on the locale
List nlsNames = getNlsName();
return findNlsValue(nlsNames, locale);
}
public void putNlsDescription(Locale locale, String description) {
putNlsDescription(locale.toString(), description);
}
public void putNlsDescription(String locale, String description) {
// Get the map for the nls descriptions
List nlsDescriptions = getNlsDescription();
if (nlsDescriptions == null) {
nlsDescriptions = new java.util.ArrayList();
}
// Add the string
putNlsString(nlsDescriptions, locale, description);
// Set the list back again
setNlsDescription(nlsDescriptions);
}
public void removeNlsDescription(Locale locale) {
List nlsDescriptions = getNlsDescription();
if(nlsDescriptions != null) {
removeLocaleEntry(nlsDescriptions, locale.toString());
setNlsDescription(nlsDescriptions);
}
}
public String getNlsDescription(Locale locale) {
// Get the string from the map based on the locale
List nlsDescriptions = getNlsDescription();
return findNlsValue(nlsDescriptions, locale);
}
public void addKeyword(Keyword keyword) {
if (keyword != null) {
Set keywords = getKeywords();
if (keywords == null) {
keywords = new java.util.HashSet();
}
keywords.add(keyword);
setKeywords(keywords);
keyword.setUserData(this);
}
}
public void removeKeyword(Keyword keyword) {
if(keyword != null) {
if(getKeywords() != null) {
if(getKeywords().remove(keyword)) {
keyword.setUserData(null);
}
}
}
}
public static String findNlsValue(List values, Locale locale) {
if(values == null || locale == null || values.size() == 0)
return null;
String retval = null;
String searchKey = null;
boolean useVariant = locale.getVariant() != null;
boolean useCountry = locale.getCountry() != null;
// First try locale passed in
if(useVariant) {
searchKey = locale.getLanguage() + "_" + locale.getCountry() + "_" +
locale.getVariant();
retval = getNlsString(values, searchKey);
if(retval != null) {
return retval;
}
}
if(useCountry) {
searchKey = locale.getLanguage() + "_" + locale.getCountry();
retval = getNlsString(values, searchKey);
if(retval != null) {
return retval;
}
}
searchKey = locale.getLanguage();
retval = getNlsString(values, searchKey);
// last ditch effort - just return the first one found
if(retval == null) {
NlsData data = (NlsData) values.get(0);
retval = data.getValue();
}
return retval;
}
public static NlsData getLocaleEntry(List values, String locale) {
return getLocaleEntry(values, locale, false);
}
public static void removeLocaleEntry(List values, String locale) {
getLocaleEntry(values, locale, true);
}
public static String getNlsString(List values, String locale) {
String retval = null;
NlsData data = getLocaleEntry(values, locale);
if (data != null) {
retval = data.getValue();
}
return retval;
}
public static void putNlsString(List values, String locale, String value) {
NlsData currentData = getLocaleEntry(values, locale);
if (currentData != null) {
currentData.setValue(value);
} else {
values.add(new NlsData(locale, value));
}
}
private static NlsData getLocaleEntry(List values, String locale, boolean removeIfFound) {
NlsData retval = null;
// Find an entry that matches the given locale
for (Iterator it = values.iterator(); it.hasNext(); ) {
NlsData data = (NlsData) it.next();
if (data.getLocale().equals(locale)) {
retval = data;
if (removeIfFound) {
it.remove();
}
break;
}
}
return retval;
}
]]>
</meta>
<!-- Key -->
<key column="BASE_DATA_ID" />
<!-- Attributes -->
<!-- How do we guarntee that a name is always provided? -->
<list name="nlsName" cascade="all-delete-orphan" lazy="true" fetch="select" table="USER_DATA_NAMES">
<meta attribute="field-description">
The list of internationalized names.
</meta>
<meta attribute="scope-set">public</meta>
<cache usage="read-write" />
<key column="NAME_BASE_DATA_ID"/>
<index column="NAME_POS_NO" />
<one-to-many class="com.sas.portal.data.model.hibernate.NlsData" />
</list>
<list name="nlsDescription" cascade="all-delete-orphan" lazy="true" fetch="select" table="USER_DATA_DESCRIPTIONS">
<meta attribute="field-description">
The list of internationalized descriptions.
</meta>
<meta attribute="scope-set">public</meta>
<cache usage="read-write" />
<key column="DESCRIPTION_BASE_DATA_ID"/>
<index column="DESCRIPTION_POS_NO" />
<one-to-many class="com.sas.portal.data.model.hibernate.NlsData" />
</list>
<!-- Associations -->
<set name="keywords" cascade="all-delete-orphan" lazy="true" >
<meta attribute="field-description">
A list of user defined keywords that can be used for searching.
</meta>
<cache usage="read-write" />
<key column="USER_DATA_ID"/>
<one-to-many class="com.sas.portal.data.model.hibernate.Keyword" />
</set>
</joined-subclass>
</hibernate-mapping>