Association cache strategy in orm.xml for unannotated java property

Hi.
I need second level cache for many-to-many association and in -persistence.xml- orm.xml I only found this setting for entire entity or for natural ID.

The annotation @org.hibernate.annotations.Cache can be applied to java getter metod, but I don’t use annotations (the java code below is annotated, but only to explain what I need) . The java property in question is “LVAItem_LVAType”

    <entity name="AvatarClient.LVAItem" class="bpemain.entities.avatarclient.DbLVAItem" cacheable="true">
        <table name="avatarclient$lvaitem"/>
        <caching access="READ_ONLY"/>
        <mutable>false</mutable>
        <attributes>
            <id name="id">
                <column name="id"/>
            </id>
            <basic name="obsolete">
                <column name="obsolete"/>
            </basic>
            <basic name="iden">
                <column name="iden"/>
            </basic>
            <basic name="sortKey">
                <column name="sortkey"/>
            </basic>
            <basic name="term">
                <column name="term"/>
            </basic>
            <basic name="code">
                <column name="code"/>
            </basic>
            <many-to-many name="LVAItem_LVAType">
                <join-table name="avatarclient$lvaitem_lvatype">
                    <join-column name="avatarclient$lvaitemid"/>
                    <inverse-join-column name="avatarclient$lvatypeid"/>
                </join-table>
            </many-to-many>
        </attributes>
    </entity>

_


    @Entity(name = "AvatarClient.LVAItem")
    @Table(name = "avatarclient$lvaitem")
    @Immutable
    @Cacheable
    @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
    public class DbLVAItem {
    
        public final java.util.Map<java.lang.String, java.lang.Object> members =
            new java.util.HashMap<>();
    
        @Id
        @Column(name = "id")
        public long getId() {
            return (long) this.members.get("id");
        }
    
        public void setId(final long val) {
            this.members.put("id", val);
        }
    
        @Basic
        @Column(name = "code")
        public java.lang.String getCode() {
            return (java.lang.String) this.members.get("Code");
        }
    
        public void setCode(final java.lang.String val) {
            this.members.put("Code", val);
        }
    
        @Basic
        @Column(name = "iden")
        public java.lang.Long getIden() {
            return (java.lang.Long) this.members.get("Iden");
        }
    
        public void setIden(final java.lang.Long val) {
            this.members.put("Iden", val);
        }
    
        @Basic
        @Column(name = "obsolete")
        public java.lang.Boolean getObsolete() {
            return (java.lang.Boolean) this.members.get("Obsolete");
        }
    
        public void setObsolete(final java.lang.Boolean val) {
            this.members.put("Obsolete", val);
        }
    
        @Basic
        @Column(name = "sortkey")
        public java.lang.Integer getSortKey() {
            return (java.lang.Integer) this.members.get("SortKey");
        }
    
        public void setSortKey(final java.lang.Integer val) {
            this.members.put("SortKey", val);
        }
    
        @Basic
        @Column(name = "term")
        public java.lang.String getTerm() {
            return (java.lang.String) this.members.get("Term");
        }
    
        public void setTerm(final java.lang.String val) {
            this.members.put("Term", val);
        }
    
        @ManyToMany
        @JoinTable(
                name = "avatarclient$lvaitem_lvatype",
                joinColumns = @JoinColumn(name = "avatarclient$lvaitemid"),
                inverseJoinColumns = @JoinColumn(name = "avatarclient$lvatypeid"))
        @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
        public java.util.Set<bpemain.entities.avatarclient.DbLVAType> getLVAItem_LVAType() {
            return (java.util.Set<bpemain.entities.avatarclient.DbLVAType>) this.members
                .get("AvatarClient.LVAItem_LVAType");
        }
    
        public void setLVAItem_LVAType(
                final java.util.Set<bpemain.entities.avatarclient.DbLVAType> val) {
            this.members.put("AvatarClient.LVAItem_LVAType", val);
        }
    }

Since this is a Hibernate annotation, there is obviously no way you can configure this with a JPA orm.xml. You can however use hbm.xml. If you look into the DTD for hbm.xml you will see that you can specify a <cache/> element in e.g. a <set/>.

One of the reasons I migrated from hbm.xml was that they didn’t support many-to-many. I’m trying to follow your advice, but it seems I’ll have to fill all mandatory association properties otherwise there’s am NPE:

    public static void addGeneratedMappings(
            final MetadataSources sources,
            final JdbcEntities jdbcEntities,
            final String genentitiesPackage) throws Exception {
        final JaxbEntityMappings ormRoot = generateEntityMappings(jdbcEntities, genentitiesPackage);
        final Origin origin = new Origin(SourceType.OTHER, "mendix-metadata");
        final Binding<JaxbEntityMappings> binding = new Binding<>(ormRoot, origin);
        sources.addXmlBinding(binding);

        // new code
        final JaxbHbmHibernateMapping hbmBindings = new JaxbHbmHibernateMapping();
        final List<JaxbHbmRootEntityType> hbmClasses = hbmBindings.getClazz();
        final JaxbHbmRootEntityType hbmEntity = new JaxbHbmRootEntityType();
        hbmEntity.setName(DbLVAItem.class.getName());
        hbmEntity.setEntityName(DbLVAItem.entityName);
        final JaxbHbmSimpleIdType hbmId = new JaxbHbmSimpleIdType();
        hbmId.setName("id");
        hbmEntity.setId(hbmId);
        final List<Serializable> hbmAttrs = hbmEntity.getAttributes();
        final JaxbHbmSetType hbmAssoc = new JaxbHbmSetType();
        hbmAssoc.setName("LVAItem_LVAType");
        final JaxbHbmCacheType hbmCache = new JaxbHbmCacheType();
        hbmCache.setUsage(org.hibernate.cache.spi.access.AccessType.READ_ONLY);
        hbmAssoc.setCache(hbmCache);
        hbmAttrs.add(hbmAssoc);
        hbmClasses.add(hbmEntity);
        sources
            .addXmlBinding(
                new Binding<>(hbmBindings, new Origin(SourceType.OTHER, "mendix-metadata-hbm")));
    }

_

Exception in thread "main" java.lang.NullPointerException
	at org.hibernate.boot.model.source.internal.hbm.PluralAttributeKeySourceImpl.<init>(PluralAttributeKeySourceImpl.java:40)

Can I instead call JavaReflectionManager.setMetadataProvider() with something that will create the missing annotation proxies?

One of the reasons I migrated from hbm.xml was that they didn’t support many-to-many.

I don’t know where you got this wrong information from. hbm.xml supports mapping many-to-many associations just fine.

Just search for “hibernate hbm.xml many-to-many” on Google and you will find plenty of examples on how to map this. If you don’t want to use hbm.xml then use annotations.

orm.xml 3.1.0 does not have @org.hibernate.annotations.Cache counterpart inside <attributes/>, therefore JPAXMLOverriddenAnnotationReader can’t create the annotation proxies needed to enable Hibernate Collection Cache. We create a wrapper for the annotation reader that creates the missing annotation proxies. This uses Hibernate 6.1.7 internal API. The alternative would be generating .hbm.xml JaxbHbmHibernateMapping object and calling addXmlBinding() just like we do for JaxbEntityMappings, because .hbm.xml supporst collection cahe, but there are reasons not to do that: an instance of JaxbHbmRootEntityType containing only the cache-related elements fails to be parsed: a fully populated converted copy of JaxbEntity is needed, but it’s too much work to go back from orm.xml to .hbm.xml. .hbm.xml is deprecated and its syntax is cryptic and they promise to extend orm.xml in Hibernate 7.

            final MetadataBuilderImplementor internal =
                (MetadataBuilderImplementor) metadataBuilder;
            final BootstrapContext bootstrapContext = internal.getBootstrapContext();
            final ReflectionManager reflectionManager = bootstrapContext.getReflectionManager();
            final MetadataProviderInjector internal2 = (MetadataProviderInjector) reflectionManager;
            final CollectionCacheSupplementingMetadataProvider myMetadataProvider =
                new CollectionCacheSupplementingMetadataProvider(
                    bootstrapContext,
                    collectionCacheGetters);
            internal2.setMetadataProvider(myMetadataProvider);

Well, I can’t help you with that if you don’t want to use hbm.xml or annotations. Eventually Hibernate ORM will provide extensions for orm.xml to model this, but it’s not there yet. Note that Hibernate ORM 6.1 is also unsupported. You should upgrade to 6.4.