Many to many to many: how to?

I’m looking for solid guidance and example for putting together a many-to-many-to-many entity relationship. As a trivial example, I’m thinking of classes: Who, What, and Where, each with an int id field. I imagine the nexus of the three would come together in a single class, WhoWhatWhere, with a composite key of three int fields; I expect the class itself could be used as an @IdClass. When I tried this out, it seemed to create the table structure I expected: three int columns each being both a PK and FK. However, when I tried to save an instance (by persisting one of each and then adding them to the composite class), I got an exception about casting an int to the encapsulated class. Am I taking the wrong approach? Ultimately, I’d like to be able to manipulate the What & Where as they relate to the Who. What’s the right way to create and annotate these classes?

I would recommend you use @Embeddable instead for the id as I have shown here: How to join more tables to the same id (FK)? - #2 by beikov

Ok, so there must be something more to your Table4 class. Perhaps a constructor that takes the nested classes and populates its EmbeddedId class? Because I can’t just add tables 1-3 and then persist it as I get a null id exception.

Please share your model and the code you use to persist

Here’s my full set of code; the method at the bottom is my test that I’m using to try it out. It looks like it’s working - my next challenge is selecting a Who, with an eager fetch so that I can serialize it into json. When I try, though, I’m getting an infinite recursion through the composite table.

@Entity
@Data
@Table(name = "_Who")
public class Who {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(mappedBy = "who")
    Set<WhoWhatWhere> storage;
}

@Entity
@Data
@Table(name = "_What")
public class What {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String thing;
}

@Entity
@Data
@Table(name = "_Where")
public class Where {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String place;
}

@Data
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Entity
@NoArgsConstructor
@Table(name = "_WhoWhatWhere")
public class WhoWhatWhere {
    public WhoWhatWhere(Who who, What what, Where where) {
        this.who = who;
        this.what = what;
        this.where = where;
        this.setId(new WhoWhatWhereId(who.getId(), what.getId(), where.getId()));
    }

    @EmbeddedId
    WhoWhatWhereId id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "who_id", insertable = false, updatable = false)
    private Who who;

    @ManyToOne
    @JoinColumn(name = "what_id", insertable = false, updatable = false)
    private What what;

    @ManyToOne
    @JoinColumn(name = "where_id", insertable = false, updatable = false)
    private Where where;
}

@Embeddable
@NoArgsConstructor
public class WhoWhatWhereId implements Serializable {
    public WhoWhatWhereId(Long whoId, Long whatId, Long whereId) {
        this.whoId = whoId;
        this.whatId = whatId;
        this.whereId = whereId;
    }

    @Column(name = "who_id")
    Long whoId;
    @Column(name = "what_id")
    Long whatId;
    @Column(name = "where_id")
    Long whereId;

}

@Repository
public interface WhoRepository extends PagingAndSortingRepository<Who, Long> {
    Iterable<Who> findWhoByName (String name);
}
@Repository
public interface WhatRepository extends PagingAndSortingRepository<What, Long> {
}
@Repository
public interface WhereRepository extends PagingAndSortingRepository<Where, Long> {
}
@Repository
public interface WhoWhatWhereRepository extends PagingAndSortingRepository<WhoWhatWhere, WhoWhatWhereId> {
}

@Test
public void tryMe() {
	Who who =
//                new Who();
//        who.setName("Carl");
//        whoRepository.save(who);
			whoRepository.findById(1L).get();
	What what =
			new What();
	what.setThing("picture");
	whatRepository.save(what);
//                whatRepository.findById(2L).get();
	Where where =
			new Where();
	where.setPlace("wall");
	whereRepository.save(where);
//                whereRepository.findById(1L).get();
	WhoWhatWhere whoWhatWhere = new WhoWhatWhere(who, what, where);
	whoWhatWhereRepository.save(whoWhatWhere);
	LOGGER.debug("finished");

	Iterable<Who> examples = whoRepository.findWhoByName("Carl");
	LOGGER.debug("found some:");
}

Discovered that, perhaps, the recursion was due to the hashCode() method that was injected. I fixed this by using @EqualsAndHashCode(onlyExplicitlyIncluded = true)

Now my problem is that when I select a Who, it’s only filling the Set<WhoWhatWhere> storage; with one record (the 1,1,1 record), despite making it eager.

Please create a new question stating your current model and problem. It’s hard to follow when you post code and then change something which you only describe in text.