简体   繁体   中英

Hibernate custom foreign key

I need to use a database where 2 tables of some custom foreign key. It will reference one field under certain condition and another field otherwise.

How do I annotate this using Hibernate ?

Here are the tables.

---------------------------                      ---------------------------
|         customer        |                      |         address         |
---------------------------                      ---------------------------
| - id                    |                      | - address_code          |
| address_code            |                      | - customer_id           |
| primary_language_code   |                      | - language_code         |
| secondary_language_code |                      | ...                     |
| ...                     |                      |                         |
---------------------------                      ---------------------------

Fields beginning with - are (part of) primary key. Foreign key is as follow :

customer join address on 
    (customer.id = address.customer_id
    and customer.address_code = address.address_code
    and (case when customer.primary_language_code <= 2 
              then customer.primary_language_code 
              else customer.secondary_language_code
         end) = address.COD_LAN)

Here are the models :

@Getter              \
@Setter              |   //lombok
@NoArgsConstructor   /
@Entity
@Table(name = "customer")
public class Customer{

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

    @Column(name="primary_language_code")
    private int primaryLanguageCode;

    @Column(name="secondary_language_code")
    private int secondaryLanguageCode;

    ?
    private Address address;
}

@Getter              \
@Setter              |   //lombok
@NoArgsConstructor   /
@Entity
@Table(name = "address")
public class Address {

    @EmbeddedId
    private AddressId id;
}

@Getter              \
@Setter              |   //lombok
@NoArgsConstructor   /
@Embeddable
public class AddressId implements Serializable, Comparable<AddressId> {

    @Column(name = "customer_id")
    private int customerId;

    @NonNull
    @Column(name = "address_code")
    private String addressCode;

    ?
    private int languageCode;
}

Edit :

  • I tried using a calculated field and map from this field. Problem : how can I map this calculated field ?
  • I tried using @Where clause but model field name is concatenated to sql query field customer0_.address_primary_language_code :
@Where(clause = "(customer.primary_language_code <= 2 then customer.primary_language_code else customer.secondary_language_code end) = address.COD_LAN)")
  • I tried several other things without much success either.

So, I found some creepy hack using sql injection that solves my problem but can be prone to bugs in some conditions.

If join was only on primary_language , code would be like this :

@OneToOne
@JoinColumns({@JoinColumn(name = "id", referencedColumnName = "customer_id"),
              @JoinColumn(name = "primary_language_code", referencedColumnName = "language_code"),
              @JoinColumn(name = "address_code", referencedColumnName = "address_code")})
private Address address;
@Column(name = "language_code", nullable = false)
private int languageCode;

Hibernate will generate a query like this :

select customer0_.id as ...,
       customer0_.primary_language_code as ...,
       customer0_.secondary_language_code as ...,
       customer0_.address_code as ...

The idea is to change the name value to inject sql code :

@OneToOne
@JoinColumns({@JoinColumn(name = "id", referencedColumnName = "customer_id"),
              @JoinColumn(name = "id, case when customer.primary_language_code <= 2 " +
                                          "then customer.primary_language_code " +
                                          "else customer.secondary_language_code end",
                          referencedColumnName = "language_code"),
              @JoinColumn(name = "address_code", referencedColumnName = "address_code")})
private Address address;

Generated sql query will look like this :

select customer0_.id as ...,
       customer0_.primary_language_code as ...,
       customer0_.secondary_language_code as ...,
       customer0_.id,                                    <--- sql injection here
       case
           when customer.primary_language_code <= 2 then customer.primary_language_code 
           else customer.secondary_language_code 
       end as ...,
       customer0_.address_code as ...

This will work unless you have primary_language or secondary_language in another table you need to join on the customer table.

I'd be glad to find a better solution to this question as I don't really dare to use it in production.

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