1

I want to apply a filter/search to a Spring Data repository utilizing a Specification. When building the predicates, in a case of a subselect on a many to many result set, the wrong entity's Id is selected. I use a generated JPA Metamodel and the Id is held in a superclass of both joined entities.

Here's the Specification:

    public class KontaktSpecification implements Specification<Kontakt> {

    private final KontaktSearchObject kontaktSearchObject;

    public KontaktSpecification(KontaktSearchObject kontaktSearchObject) {
        this.kontaktSearchObject = kontaktSearchObject;
    }

    @Override
    public Predicate toPredicate(Root<Kontakt> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
        List<Predicate> predicates = new ArrayList<Predicate>();            

        if (kontaktSearchObject.getGruppeId() != null) {
            Subquery<Integer> sq = query.subquery(Integer.class);       
            ListJoin<Kontakt, Gruppe> join = sq.from(Kontakt.class).join(Kontakt_.gruppen);
            sq.select(join.get(Kontakt_.id));

            Predicate paramPredicate = cb.equal(join.get(Gruppe_.id), kontaktSearchObject.getGruppeId());

            sq.where(paramPredicate);

            predicates.add(cb.in(root.get(Kontakt_.id)).value(sq));
        }

        if(kontaktSearchObject.getArchiv() != null) {
            predicates.add(cb.equal(root.get(Kontakt_.archiv), kontaktSearchObject.getArchiv().toString()));
        }   

        return cb.and(predicates.toArray(new Predicate[]{}));
    }

}

Excerpt of the Kontakt entity:

    @Entity
    @Table(name = "kontakt", uniqueConstraints = @UniqueConstraint(columnNames = "emailAdresse"))
    public class Kontakt extends AbstractModifiableEntity {
    .
    .
    .
        @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
        @JoinTable(name = "kontakt_gruppe", uniqueConstraints = @UniqueConstraint(columnNames = {"gruppeID", "kontaktID" }), joinColumns = { @JoinColumn(name = "kontaktID", nullable = false, updatable = false) }, inverseJoinColumns = { @JoinColumn(name = "gruppeID", nullable = false, updatable = false) })
        @OrderBy("name")
        @BatchSize(size = 30)   
        public synchronized List<Gruppe> getGruppen() {
            return this.gruppen;
        }
    }

Excerpt of the Gruppe entity:

    @Entity
    @Table(name = "gruppe")
    public class Gruppe extends AbstractEntity {
    .
    .
    .
        @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
        @JoinTable(name = "kontakt_gruppe", uniqueConstraints = @UniqueConstraint(columnNames = { "gruppeID",
        "kontaktID" }) , joinColumns = {
                @JoinColumn(name = "gruppeID", nullable = false, updatable = false) }, inverseJoinColumns = {
                        @JoinColumn(name = "kontaktID", nullable = false, updatable = false) })
        @BatchSize(size = 40)
        public Set<Kontakt> getKontakts() {
            return this.kontakts;
        }
    }

AbstractEntity (AbstractModifiableEntity extends AbstractEntity and is also annotated with @MappedSuperclass):

@MappedSuperclass
public abstract class AbstractEntity implements Serializable {
    private static final long serialVersionUID = 1L;

    protected Integer id;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", unique = true, nullable = false)
    public Integer getId() {
        return this.id;
    }

    public void setId(Integer id) {
        this.id = id;
    }
}

Metamodel:

Kontakt_:

@Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
@StaticMetamodel(Kontakt.class)
public abstract class Kontakt_ extends de.chamaeleon.jnewsletter.model.entities.AbstractModifiableEntity_ {
    public static volatile SingularAttribute<Kontakt, String> strasse;
    public static volatile SingularAttribute<Kontakt, String> vorname;
    public static volatile SingularAttribute<Kontakt, Anrede> anrede;
    public static volatile SingularAttribute<Kontakt, Boolean> bestaetigt;
    public static volatile SetAttribute<Kontakt, AttributKontakt> attributKontakts;
    public static volatile SingularAttribute<Kontakt, String> fremdschluessel;
    public static volatile SingularAttribute<Kontakt, String> emailAdresseInternal;
    public static volatile ListAttribute<Kontakt, KontaktEreignis> kontaktEreignisse;
    public static volatile SetAttribute<Kontakt, MailVersand> mailVersands;
    public static volatile SingularAttribute<Kontakt, String> adminLogin;
    public static volatile SingularAttribute<Kontakt, String> ort;
    public static volatile SingularAttribute<Kontakt, String> institution;
    public static volatile SingularAttribute<Kontakt, String> password;
    public static volatile ListAttribute<Kontakt, MailEreignis> versandEreignisse;
    public static volatile SingularAttribute<Kontakt, Boolean> archiv;
    public static volatile SingularAttribute<Kontakt, String> nachname;
    public static volatile SingularAttribute<Kontakt, String> land;
    public static volatile SingularAttribute<Kontakt, MailFormat> mailFormat;
    public static volatile ListAttribute<Kontakt, Gruppe> gruppen;
    public static volatile SingularAttribute<Kontakt, String> einwilligungstext;
    public static volatile ListAttribute<Kontakt, NewsletterKategorie> kategorien;
    public static volatile SingularAttribute<Kontakt, String> plz;

}

Gruppe_:

@Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
@StaticMetamodel(Gruppe.class)
public abstract class Gruppe_ extends de.chamaeleon.jnewsletter.model.entities.AbstractEntity_ {

    public static volatile SetAttribute<Gruppe, Kontakt> kontakts;
    public static volatile SetAttribute<Gruppe, TempAuthentifizierungGruppe> tempAuthentifizierungGruppes;
    public static volatile SingularAttribute<Gruppe, String> name;
    public static volatile SingularAttribute<Gruppe, Boolean> archiv;
    public static volatile SingularAttribute<Gruppe, Boolean> published;
    public static volatile SingularAttribute<Gruppe, String> fremdschluessel;
    public static volatile SetAttribute<Gruppe, MailVersand> mailVersands;

}

AbstractEntity_:

@Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
@StaticMetamodel(AbstractEntity.class)
public abstract class AbstractEntity_ {

    public static volatile SingularAttribute<AbstractEntity, Integer> id;

}

The expected (or wanted) query is (select kontakt1_.id):

select count(kontakt0_.id) as col_0_0_
from kontakt kontakt0_
where kontakt0_.id in (
    select kontakt1_.id 
    from kontakt kontakt1_ 
        inner join kontakt_gruppe gruppen2_ on kontakt1_.id=gruppen2_.kontaktID 
        inner join gruppe gruppe3_ on gruppen2_.gruppeID=gruppe3_.id 
    where gruppe3_.id=431
)

but the actual query is (select gruppe3_.id):

select count(kontakt0_.id) as col_0_0_
from kontakt kontakt0_
where kontakt0_.id in (
    select gruppe3_.id
    from kontakt kontakt1_ 
        inner join kontakt_gruppe gruppen2_ on kontakt1_.id=gruppen2_.kontaktID 
        inner join gruppe gruppe3_ on gruppen2_.gruppeID=gruppe3_.id 
    where gruppe3_.id=431
)

Why is that? Does it have something to do with the Id being in a shared superclass, also in the static Metamodel?

Sgoda
  • 11
  • 3

0 Answers0