Я нашел свой ответ - можно настроить Hibernate + ehcache для выполнения того, что я описал выше.
Объявляя моего Ребенка как тип значения, а не как тип сущности (я полагаю, что это термины, используемые сообществом Hibernate), я могу по существу трактовать своего Ребенка как компонент Родителя, а не как отдельную сущность. Вот пример моего пересмотренного отображения:
<class name="Parent" table="Parent">
<cache usage="read-only"/>
<id name="primary_key"/>
<property name="natural_key"/>
<set name="children" lazy="false" fetch="join" table="Child">
<cache usage="read-only"/>
<key-column name="parent_id"/>
<composite-element class="Child">
<property name="property1" column="PROP1" type="string">
<property name="property2" column="PROP2" type="string">
</composite-element>
</set>
</class>
Поведение моих дочерних объектов в этой конфигурации немного отличается от того, что было раньше - сейчас не определен отдельный первичный ключ для дочернего объекта, нет общих ссылок и пустых полей / столбцов. См. Документы Hibernate для более подробной информации об этом.
Мой Родитель и Ребенок доступны только для чтения, и я действительно хочу получить доступ к экземплярам Child только через Parent - я не использую Child независимо от Parent, поэтому обработка типа значения подходит для моего случая использования .
Большой победой для меня стало то, как коллекция кэшируется в моей новой конфигурации. Кэш коллекции теперь кэширует мою коллекцию в целом, используя ключ parent_id. Некоторые, но не все, из моей коллекции больше не могут находиться в кеше. Коллекция кэшируется и высвобождается в целом. Что еще более важно, если Hibernate отправляется на поиски моей коллекции в кэше 2-го уровня и получает ошибку, она выбирает всю коллекцию из БД одним запросом выбора.
Вот моя конфигурация ehcache:
<ehcache>
<cache name="query.Parent"
maxElementsInMemory="10"
eternal="false"
overflowToDisk="false"
timeToIdleSeconds="0"
timeToLiveSeconds="43200"
</cache>
<cache name="Parent"
maxElementsInMemory="10"
eternal="false"
overflowToDisk="false"
timeToIdleSeconds="0"
timeToLiveSeconds="43200"
</cache>
<cache name="Parent.children"
maxElementsInMemory="10"
eternal="false"
overflowToDisk="false"
timeToIdleSeconds="0"
timeToLiveSeconds="43200"
</cache>
<ehcache>
Надеюсь, этот пример поможет кому-то еще.