@Transient override for subclass

I know there are no plans to be able to remove an attribute mapped by a superclass but … ^^

I am working on a big and old project (efluid - created year 2000) and it is complex to modify the modeling of main objects which does not respect this constraint

A solution that I implemented to overcome this is to put @Transient on the attributes of the parent class (which in my case is abstract …), to use on the daughter class @Access ( AccessType.PROPERTY) and then redefine the getters of the fields I want to map.

@MappedSuperclass
public class EfluidBusinessObject {
  @Id
  String id;

  @Transient
  private Timestamp dateCreation;

  public Timestamp getDateCreation() {
    return dateCreation;
  }
...
}

@Entity
@Access(PROPERTY)
public class Contrat extends EfluidBusinessObject {

  @Override
  public Timestamp getDateCreation() {
    return super.getDateCreation();
  }
...
}

What do you think ?

Also this constraint is very restrictive or even blocking :slight_smile:
Is it technical or political not to want @Transient an attribute mapped by the parent class?

Your solution looks fine to me. What’s your problem with that? There is no way fields can be removed from super classes, that’s just impossible design-wise.

My solution is operational, it was just to find out if there was another way to do it or if it was not right to do it like this.
But a priori it is good, so I leave it like that and it is that I would propose if it is not possible to lower the attributes in the child classes.

Thanks to you

I have another solution that I wanted to share with you to get your opinion.
I am exploiting @Parent to use the same attributes and @Embedded declaring the mapped attributes of the parent class.

The only surprising thing is that I have to put the setter in nullSafe because in the @Parent valuation mechanism seems late during the first call of the setter. The second call the parent is well valued.

Your opinion ? :slight_smile:

public class HermesRisque extends HermesBusinessObject {

  @Embedded
  private DatesActeursTechniquesEtatObjetEmbarque attributsTechniques;
@Embeddable
@Access(AccessType.PROPERTY)
public class DatesActeursTechniquesEtatObjetEmbarque implements Serializable {

  private HermesBusinessObject proprietaire;

  @Parent
  public HermesBusinessObject getProprietaire() {
    return proprietaire;
  }

  public void setProprietaire(HermesBusinessObject proprietaire) {
    this.proprietaire = proprietaire;
  }

  public Timestamp getDateCreation() {
    return proprietaire.getDateCreation();
  }

  public void setDateCreation(Timestamp dateCreation) {
    if (proprietaire != null) {
      proprietaire.setDateCreation(dateCreation);
    }
  }
}

I don’t know if there are guarantees about what is initialized in which order, but to be safe, I would suggest you put the dateCreation value into a field and set that on the parent as soon as the parent is set. Apart from that, it looks ok as long as this does what you are looking for.

I am afraid of overwriting bad values in the parent object … If I have any problems I would leave with intermediate variables as you suggest.

Thank you for confirmation ! :slight_smile:

I probably found a simpler and less invasive solution.

The idea is to annotate the child classes that do not map the parent attributes and to exclude the latter via an integrator.

The example below does not show the final solution but just the extraction of the property that we want to exclude.

public class TransientIntegrator implements Integrator {

  @Override
  public void integrate(Metadata metadata, SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
    PersistentClass entityBinding = metadata.getEntityBinding("com.hermes.arc.commun.businessobject.TraceLivraison");
    Iterator i = entityBinding.getUnjoinedPropertyIterator();
    Property prop;
    while (i.hasNext()) {
      prop = (Property) i.next();
      if (prop.getName().equals("acteurCreation")) {
        i.remove();
      }
    }
    System.out.println("");
  }

  @Override
  public void disintegrate(SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
    System.out.println("");
  }
}
1 Like

Here is the final solution :
I had to remove and the property but also the column from the table definition

package com.efluid.hibernate.integrator;

import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.*;

/**
 * Permet d'indiquer les attributs persistés par une classe parent que l'on souhaite rendre Transient.
 */
@Target({ TYPE })
@Retention(RUNTIME)
public @interface TransientOverride {

  GroupeAttributs[] groupesAttributs() default {};
  String[] attributs() default {};
}
package com.efluid.hibernate.integrator;

import static com.hermes.arc.commun.util.HermesBusinessObjectChamps.*;

/**
 * Permet d'indiquer les groupes d'attributs.
 */
public enum GroupeAttributs {
  ACTEURS_TECHNIQUES(CHAMP_DATE_CREATION, CHAMP_ACTEUR_CREATION, CHAMP_ACTEUR_MODIFICATION, CHAMP_DATE_MODIFICATION, CHAMP_DATE_SUPPRESSION, CHAMP_ACTEUR_SUPPRESSION),
  SUPPRESSION_LOGIQUE(CHAMP_ETATOBJET),
  CONFIDENTIALITE(CHAMP_CODEGRD, CHAMP_CODEFOURNISSEUR),
  AGENCE(CHAMP_AGENCE);

  private String[] attributs;

  GroupeAttributs(String... attributs) {
    this.attributs = attributs;
  }

  public String[] getAttributs() {
    return attributs;
  }
}
package com.efluid.hibernate.integrator;

import static java.util.Arrays.stream;
import static java.util.stream.Collectors.toList;

import java.util.List;
import java.util.*;
import java.util.stream.Stream;

import org.hibernate.boot.Metadata;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.integrator.spi.Integrator;
import org.hibernate.mapping.*;
import org.hibernate.service.spi.SessionFactoryServiceRegistry;
import org.slf4j.*;

/**
 * Permet de rendre transient (non persisté) certains attributs des classes parents que l'on ne souhaite pas mapper.
 */
public class TransientIntegrator implements Integrator {

  private static final Logger LOG = LoggerFactory.getLogger(TransientIntegrator.class);

  @Override
  public void integrate(Metadata metadata, SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
    metadata.getEntityBindings().stream().forEach(this::executer);
  }

  private void executer(PersistentClass persistentClass) {
    TransientOverride transientOverride = (TransientOverride) persistentClass.getMappedClass().getAnnotation(TransientOverride.class);
    if (transientOverride != null) {
      supprimerLeMappingDesAttributsDeclaresTransient(persistentClass, transientOverride);
    }
  }

  private void supprimerLeMappingDesAttributsDeclaresTransient(final PersistentClass persistentClass, final TransientOverride transientOverride) {
    List<String> attributsTransient = getAttributsNonMappes(transientOverride);
    Iterator proprietes = persistentClass.getUnjoinedPropertyIterator();
    while (proprietes.hasNext()) {
      Property property = (Property) proprietes.next();
      if (attributsTransient.contains(property.getName())) {
        LOG.debug("Attribut {} de la classe {} indiqué non persisté", property.getName(), persistentClass.getClassName());
        proprietes.remove();
        supprimerColonnesAssociées(property);
      }
    }
  }

  private void supprimerColonnesAssociées(Property property) {
    property.getValue().getColumnIterator().forEachRemaining(colonne -> supprimerColonneDeLaTable(property, colonne));
  }

  private void supprimerColonneDeLaTable(final Property property, final Selectable colonne) {
    Iterator<Column> iteratorColonnes = property.getValue().getTable().getColumnIterator();
    while (iteratorColonnes.hasNext()) {
      Column column = iteratorColonnes.next();
      if (colonne.equals(column)) {
        LOG.debug("Colonne {} de la table {} supprimée pour la configuration de la propriété {}", column.getName(), column.getValue().getTable().getName(), property.getName());
        iteratorColonnes.remove();
        break;
      }
    }
  }

  private List<String> getAttributsNonMappes(final TransientOverride transientOverride) {
    List<String> attributsTransient = stream(transientOverride.groupesAttributs()).map(GroupeAttributs::getAttributs).map(Arrays::stream).flatMap(Stream::distinct).collect(toList());
    attributsTransient.addAll(stream(transientOverride.attributs()).toList());
    return attributsTransient;
  }

  @Override
  public void disintegrate(SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
    LOG.debug("Aucune action");
  }
}

And in “resources\META-INF\services\org.hibernate.integrator.spi.Integrator”

com.efluid.hibernate.integrator.TransientIntegrator

I have a concern because in the metadata the instantiation of the model of a MappedSuperClass is done at the first entity found and references the latter for its properties.

And the problem that appears is if the first class iterates is a class annotated with @AttributesTransient which has the effect of removing the properties defined on the current entity, then the properties of the MappedSuperClass will point to nowhere.

I am trying to work a solution in the integration to retrieve a MappedSuperClass which is based on an entity which does not have an @AttributesTransient annotation

It’s not possible to make an attribute from a super class transient in a subclass. I would suggest you rethink your modelling. Use interfaces for polymorphism and accept that you will have to declare fields and getters/setters in multiple implementations.

Well I persevered and found this solution.
For it to work, the 1st child class must not be annotated by @AttributesTransient, so I created a dummy class inheriting from the superclass in question.

@Entity
public class HibernateHermesBusinessObjectFactice extends HermesBusinessObject {}

And I sorted it 1st during Scan and then read metadata during session factory creation

1. Sorting after scanning :

<property name="hibernate.archive.scanner" value="com.efluid.hibernate.core.EfluidScanner"/>

public class EfluidScanner extends AbstractScannerImpl {

  public EfluidScanner() {
    this(EfluidArchiveDescriptorFactory.INSTANCE);
  }

  public EfluidScanner(ArchiveDescriptorFactory value) {
    super(value);
  }

  @Override
  public ScanResult scan(ScanEnvironment environment, ScanOptions options, ScanParameters parameters) {
    ScanResult scan = super.scan(environment, options, parameters);
    return new EfluidScanResult(scan);
  }
}

/**
 * Permet de placer en 1er une classe héritant de HermesBusiness mais sans avoir de {@link TransientOverride}.
 *
 * @See {@link TransientIntegrator}
 **/
public class EfluidScanResult implements ScanResult {

  private ScanResult scanResult;

  public EfluidScanResult(ScanResult scanResult) {
    this.scanResult = scanResult;
  }

  @Override
  public Set<PackageDescriptor> getLocatedPackages() {
    return scanResult.getLocatedPackages();
  }

  @Override
  public Set<ClassDescriptor> getLocatedClasses() {
    ClassDescriptor classDescriptorTypeCritere = scanResult.getLocatedClasses().stream().filter(o -> o.getName().equals(HibernateHermesBusinessObjectFactice.class.getName())).findFirst().get();
    List<ClassDescriptor> classDescriptors = scanResult.getLocatedClasses().stream().collect(Collectors.toList());
    classDescriptors.remove(classDescriptorTypeCritere);

    LinkedHashSet<ClassDescriptor> locatedClasses = new LinkedHashSet();
    locatedClasses.add(classDescriptorTypeCritere);
    locatedClasses.addAll(classDescriptors);
    return locatedClasses;
  }

  @Override
  public Set<MappingFileDescriptor> getLocatedMappingFiles() {
    return scanResult.getLocatedMappingFiles();
  }
}

2. Sorting when building the session factory :

File: src/main/resources/META-INF/services/org.hibernate.service.spi.ServiceContributor
Content: com.efluid.hibernate.core.EfluidServiceContributor

public class EfluidServiceContributor implements ServiceContributor {

  @Override
  public void contribute(StandardServiceRegistryBuilder serviceRegistryBuilder) {
    serviceRegistryBuilder.addService(SessionFactoryBuilderService.class, EfluidSessionFactoryBuilderService.INSTANCE);
  }
}

public class EfluidSessionFactoryBuilderService implements SessionFactoryBuilderService {

  protected static final EfluidSessionFactoryBuilderService INSTANCE = new EfluidSessionFactoryBuilderService();

  @Override
  public SessionFactoryBuilderImplementor createSessionFactoryBuilder(MetadataImpl metadata, BootstrapContext bootstrapContext) {
    return new SessionFactoryBuilderImpl(new EfluidDelegatingMetadata(metadata), bootstrapContext);
  }
}

public class EfluidDelegatingMetadata extends AbstractDelegatingMetadata {

  private final List<PersistentClass> entityBindingsTriees;

  public EfluidDelegatingMetadata(MetadataImplementor delegate) {
    super(delegate);
    entityBindingsTriees = super.getEntityBindings().stream().collect(toList());
    triEntityBindings();
  }

  @Override
  public Collection<PersistentClass> getEntityBindings() {
    return entityBindingsTriees;
  }

  private void triEntityBindings() {
    PersistentClass typeCriterePersistentClass = entityBindingsTriees.stream().filter(o -> o.getClassName().equals(HibernateHermesBusinessObjectFactice.class.getName())).findFirst().get();
    entityBindingsTriees.remove(typeCriterePersistentClass);
    entityBindingsTriees.add(0, typeCriterePersistentClass);
  }
}