11

We'd like to create unidirectional @OneToOne mapping with foreign key that is not in the master table, but in the slave. By providing following java code, Hibernate tries to find column product_ID in the table product but not in productimage. Is it possible to make it working with the only modifications to annotations?

All unnecessary fields and columns have been stripped from the example.

JPA entities:

@Entity
public class Product {

    @Id
    @Column(name = "ID", unique = true)
    private String id;

    // this doesn't work
    @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name = "product_ID", nullable = false)
    private ProductImage productImage;

    // this works, but we want one-to-one
    // @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
    // @JoinColumn(name = "product_ID", nullable = false)
    // private List<ProductImage> productImages;

    // getters/setters omitted
}

@Entity
public class ProductImage {

    @Id
    @Column(name = "ID", unique = true)
    private String id;

    @Column
    @Lob
    private Blob data;

    // getters/setters omitted
}

Database tables:

CREATE TABLE `product` (
  `ID` varchar(255) NOT NULL,
  PRIMARY KEY (`ID`)
)

CREATE TABLE `productimage` (
  `ID` varchar(255) NOT NULL,
  `data` longblob NOT NULL,
  `product_ID` varchar(255) NOT NULL,
  PRIMARY KEY (`ID`),
  KEY `FK_123` (`product_ID`),
  CONSTRAINT `FK_123` FOREIGN KEY (`product_ID`) REFERENCES `product` (`ID`)
)
Martin Ždila
  • 2,998
  • 3
  • 31
  • 35
  • Read jpa documentation for @JoinColumn. For OneToOne you should use referencedColumnName in your use case. From performance point of view, you should consider placing reference to product table – Sami Korhonen Jun 15 '15 at 18:05
  • After a set of modification to annotations you end up with invalid schema definition. – Roman C Jun 15 '15 at 19:34
  • Sometimes you need to question why a database schema is the way it is. Why does `productimage` reference `product`, instead of `product` referencing `productimage`? If you really want the schema this way, why not define the association in `ProductImage` back to `Product` and then use a `mappedBy` association in `Product`? – DuncanKinnear Jun 15 '15 at 21:13
  • 1
    @DuncanKinnear, The schema is prepared for a possible future case where product will be able to have multiple images. Then we will only change the annotation to `@OneToMany` and use `List` instead of the single `Product` reference. We don't want to reference `Product` from `ProductImage` unles really necessary. – Martin Ždila Jun 16 '15 at 06:03
  • @SamiKorhonen, I've tried `@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(referencedColumnName = "product_ID", nullable = false) private ProductImage productImage;` but then I got the exception `org.hibernate.MappingException: Unable to find column with logical name: product_ID in org.hibernate.mapping.Table(ProductImage) and its related supertables and secondary tables`. – Martin Ždila Jun 16 '15 at 07:22
  • @Martin Can you explain why you don't want to reference `Product` from `ProductImage`? That is the most logical way to do this, especially if you might change this to be a `@OneToMany` later on. That way the association in `Product` can use `mappedBy` for either a `@OneToOne` or a `@OneToMany`. If you are saying that a `ProductImage` _belongs_ to a `Product`, then it is natural to have a reference from `ProductImage` to the `Product` that _owns_ it. – DuncanKinnear Jun 16 '15 at 21:09
  • @DuncanKinnear, there is no place in our code where we would directly benefit from referencing `Product` from `ProductImage`, it would be just to satisfy JPA. The same will be true once we'll have `List` of `ProductImage`s in `Product`. Also `@OneToMany` can already be done unidirectionally, in my example just by uncommenting the commented block. We are using this approach in other places of our code already. For example see http://stackoverflow.com/a/2096971/289827. – Martin Ždila Jun 17 '15 at 07:25
  • @Martin Yes, but your argument could be just as easily applied to a foreign key in the database. If you don't use it, why have it? Because it benefits the _structure_ of your application. In this case, it is explicitly there to make your JPA structure a lot easier. Don't fight it just because _you_ think it is bad. – DuncanKinnear Jun 17 '15 at 20:44

3 Answers3

2

JPA entities, more on @PrimaryKeyJoinColumn:

@Entity
public class Product {
    @Id
    private String id;

    @PrimaryKeyJoinColumn
    @OneToOne(orhanRemoval = true)
    private ProductImage image;
}

@Entity
public class ProductImage {
    @Id
    private String productId; // this will be foreign key as well as primary

   //other properties goes here
}

Database tables:

CREATE TABLE product (
    id VARCHAR PRIMARY KEY
);

CREATE TABLE product_image (
    product_id VARCHAR PRIMARY KEY REFERENCES product(id)
    -- other columns goes here
);
Ostap
  • 69
  • 6
  • This has nothing to do with the original question. Also, this answer assumes that the `Product` and `ProductImage` share the same primary key - without explaining it - which is not what the OP wants. – csisy Feb 07 '23 at 11:12
1

@OneToOne is a little misleading. It is most likely different from what you want. Classic database "one to one" relationship is modeled via @ManyToOne JPA annotation in most cases. Try it. Never had a problem with uni-directional relationships via @ManyToOne and @JoinColumn. I'd also explicitly specify productId field in the second class.

Alex Rogachevsky
  • 514
  • 1
  • 3
  • 14
  • Thanks. If I have specified `product` reference in `ProductImage` then it would work, but the relation would be bidirectional. Also, maybe you meant `@OneToMany` in `Product` class. With `@ManyToOne` I wasn't successful. – Martin Ždila Jun 16 '15 at 07:31
  • Yes. Looks like it is a good approach. But we have to have a unique constraint on the FK column (if we use it in place of a primary key joining). – v.ladynev May 21 '21 at 10:10
1

Whether you map it as @OneToOne or @OneToMany is really neither here nor there: clients can only use what you expose so Map it as @OneToMany and hide the implementation.

@Entity
public class Product {

    @Id
    @Column(name = "ID", unique = true)
    private String id;

    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name = "product_ID", nullable = false)
    private List<ProductImage> productImages;

    //no getters or setters for productImages;

    public ProductImage getProductImage(){
         return productImages.size() > 0 ? productImages.get(0) : null;
    }
}

I also do not see any particular issue with making the relationship bidirectional either as 1-2-1 or 1-2-M. Again, don't expose the reference from ProductImage > Product if you don't want it publicly available.

Alan Hay
  • 22,665
  • 4
  • 56
  • 110
  • "map it as @OneToMany and hide the implementation" - that's my current workaround :-). With `@OneToOne @JoinColumn(name = "product_ID", insertable = false, updatable = false) private ProductImage productImage;` I still get the error `org.hibernate.HibernateException: Missing column: product_ID in acme.product`. – Martin Ždila Jun 16 '15 at 09:08
  • 1
    I do not see any practical difference between 1-2-1 or 1-2-m in terms of db access so why not just go with that then or make it a bidirectional 1-2-1. As I say, no client code needs be aware of how this implemented. – Alan Hay Jun 16 '15 at 09:11