简体   繁体   中英

Hibernate/JPA unidirectional OneToMany with join condition on constant value in source table

I want to use Hibernate annotations to represent a unidirectional one-to-many relationship using a join. I want an added condition on the join so it only happens when a column in the source table (the "one") is equal to a constant value. For example.

SELECT *
FROM buildings b
LEFT JOIN building_floors bf on bf.building_id = b.id AND b.type = 'OFFICE'

I want to represent the b.type = 'OFFICE' part of that query.

My question is quite similar to this one, except I have a condition on the source table. JPA/Hibernate Join On Constant Value

The Java entities look like this:

@Entity
@Table(name = "buildings")
public class Building {

    @Id
    @Column(name = "id")
    private int id;

    @Column(name = "type")
    private String type;

    @OneToMany(mappedBy = "buildingId",
            fetch = FetchType.EAGER,
            cascade = {CascadeType.ALL},
            orphanRemoval = true)
    @Fetch(FetchMode.JOIN)
    // buildings.type = 'OFFICE'   ????
    private Set<BuildingFloors> buildingFloors;

    // getters/setters
}

@Entity
@Table(name = "building_floors")
public class BuildingFloor {

    @Id
    @Column(name = "building_id")
    private int buildingId;

    @Id
    @Column(name = "floor_id")
    private int floorId;

    @Column(name = "description")
    private String description;

    // getters/setters
}

I've tried a few things where I have that placeholder comment:

@Where annotation

This doesn't work since that applies to the target entity.

@JoinColumns annotation

@JoinColumns({
        @JoinColumn(name = "building_id", referencedColumnName = "id"),
        @JoinColumn(name = "'OFFICE'", referencedColumnName = "type")
})

This doesn't work because I get the following error (simplified for clarity): Syntax error in SQL statement "SELECT * FROM buildings b JOIN building_floors bf on bf.building_id = b.id AND bf.'OFFICE' = b.type"

A different @JoinColumns annotation

@JoinColumns({
        @JoinColumn(name = "building_id", referencedColumnName = "id"),
        @JoinColumn(name = "buildings.type", referencedColumnName = "'OFFICE'")
})

This doesn't work because when using a unidirectional OneToMany relationship, the referencedColumnName is from the source table. So I get the error: org.hibernate.MappingException: Unable to find column with logical name: 'OFFICE' in buildings

Thanks in advance!

Why not use inheritance ? (I use it with JPA, I never use hibernate directly)

@Entity
@Inheritance
@Table(name = "buildings")
@DiscriminatorColumn(name="type")
public class Building {

    @Id
    @Column(name = "id")
    private int id;

    @Column(name = "type")
    private String type;
}

And :

@Entity
@DiscriminatorValue("OFFICE")
public class Office extends Building {
    @OneToMany(mappedBy = "buildingId",
        fetch = FetchType.EAGER,
        cascade = {CascadeType.ALL},
        orphanRemoval = true)
    private Set<BuildingFloors> buildingFloors;
}

Create database View with the following select:

SELECT bf.* FROM building_floors bf JOIN buildings b on bf.building_id = b.id AND b.type = 'OFFICE'

Map it to a class OfficeBuildingFloors as an ordinary entity and then use @OneToMany for it in Building class.

Of course, you won't be able to modify such collection and to avoid any exception you can use @Immutable on OfficeBuildingFloors .

In my opinion you should create a specific query to achieve your goals, rather than put specific annotations with constant parameter. I'm not see you mention another frameworks besides Hibernate so I would give some example with Hibernate. In your Building class your unidirectional mappings look like this:

@OneToMany(fetch = FetchType.Lazy, cascade = {CascadeType.ALL}, orphanRemoval = true)
@JoinTable(name = "building_floors", joinColumns = @JoinColumn(name = "id"), inverseJoinColumns = @JoinColumn(name = "building_id")
private Set<BuildingFloor> buildingFloors;

Then you can fetch your data using TypedQuery like this.

TypedQuery<Customer> query = getEntityManager().createNamedQuery("select b from building b inner join fetch b.buildingFloors where b.type = 'OFFICE'", Building.class);
List<Building> result = query.getResultList();

My solutions is not Hibernate specific, actually you could perform this with simple JPA. Hope this can help you to achieve your goals.

As you want filter source table you could use @Loader annotation

@Entity
@Table(name = "buildings")
@Loader(namedQuery = "building")
@NamedNativeQuery(name="building", 
    query="SELECT * FROM buildings b"
        + " LEFT JOIN building_floors bf on bf.building_id = b.id"
        + " WHERE b.type = 'OFFICE' AND b.id = ?",
    resultClass = Building.class)
class Building

Approach with view in DB would be better and more clearly, if it could be used inside DB also. Otherwise rename Building to something which explicitly represent filtering.

Another approaches to mention: @Filter, @FilterDef.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM