I work with an embedded H2 database in which I use the #OneToMany relationship to relate an entity instance (product) to multiple instances of the other entities (suppliers); it's useful when I have specific suppliers for a particular product.
However now, I want to associate all the suppliers with every single product; I don't want to generate in my supplier table different supplier records for each product, instead I want to have only 5 records (5 suppliers) in my supplier table which are associated to every single product, it few words I want to achieve something like "one to all", is it possible to do it using JPA annotations?
Product entity
#Entity
public class Product {
#Id
private String productCode;
#OneToMany
#JoinColumn(name = "supplier_id", referencedColumnName = "productCode")
private List<Supplier> suppliers;
}
Supplier entity
#Entity
public class Supplier {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
private String name;
}
Unidirectional #OneToMany association:
#Entity
public class Product {
#Id
// #Column(name = "id") maybe
// #GeneratedValue maybe
private String productCode;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) // according to your need
private List<Supplier> suppliers;
...
}
And,
#Entity
public class Supplier {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
...
}
#ManyToOne association:
#Entity
public class Product {
#Id
// #Column(name = "id") maybe
// #GeneratedValue maybe
private String productCode;
...
}
And,
#Entity
public class Supplier {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
#JoinColumn(name = "product_id", foreignKey = #ForeignKey(name = "PRODUCT_ID_FK"))
private Product product;
private String name;
...
}
I have the following tables ...
Object1
-------
id
...
Object2
-------
id
...
AttributeValue
--------------
id
attribute_id
object_id
value
Attribute
---------
id
name
type
... and entity classes
#Entity
#Table(name = "Attribute")
public class Attribute {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#Column(name = "type")
private String type;
}
#Entity
#Table(name = "AttributeValue")
public class AttributeValue {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "attribute_id")
private Long attributeId;
#Column(name = "object_id")
private Long objectId;
#Column(name = "value")
private String value;
}
#Entity
#Table(name = "Object1")
public class Object1 {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
// ...
// how to annotate to get all matching attribute values?
private Set<AttributeValue> values;
}
I want hibernate to fill the values instance variable with all AttributeValues that have the corresponding object_id and an attribute type of object1.
If it was only about the criterium of object_id, I would write e.g.
#JoinColumn(insertable = false, updatable = false, name = "object_id")
private Set<AttributeValue> values;
But in this case it would fill in also the values with type object2etc.
So my question is: Is this semantic expressible in Hibernate and if so, how?
EDIT: I want to highlight that the goal is to have multiple Objects (here Object1, Object2, ... ObjectN) that have no common hierarchy, but all share the feature of having attributes. The attributes for all objects will reside in one table, distinguished by some sort of discriminator (here exemplarily type).
I think the object must be:
#Entity
#Table(name = "AttributeValue")
public class AttributeValue {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "attribute_id")
private Long attributeId;
#ManyToOne
#JoinColumn(name="object_id", nullable=false)
private Object1 object1;
#Column(name = "value")
private String value;
}
#Entity
#Table(name = "Object1")
public class Object1 {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(mappedBy="object1")
private Set<AttributeValue> values;
}
Hibernate will be generate only object_id column on AttributeValue table.
Consider the following database structure
I need to implement unidirectional one to one mapping like that (structure is simplified):
#Entity
#Table(name = "entity")
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToOne
#JoinColumn(name = "customer_info", nullable = false)
private CustomerInfo customerInfo;
#OneToOne
#JoinColumn(name = "customer_credentials", nullable = false)
private CustomerCredentials customerCredentials;
// getter, setters etc
}
#Entity
#Table(name = "customer_info")
public class CustomerInfo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
// getter, setters etc
}
#Entity
#Table(name = "customer_credentials")
public class CustomerCredentials {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
// getter, setters etc
}
But somehow hibernate unable to differentiate that those joins are from different tables and throws such error:
Caused by: org.hibernate.MappingException: Repeated column in mapping for entity: com.example.Customer column: customer_id (should be mapped with insert="false" update="false")
Important notice: I do not want to use #OneToOne(mappedBy = "customer") because I need cascade save functionality
You can use #JoinTable instead of #JoinColumn to solve your problem:
#Entity #Table(name = "entity") public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToOne(cascade = CascadeType.ALL, targetEntity = CustomerInfo.class)
#JoinTable(name = "customer_info", inverseJoinColumns = {#JoinColumn(name = "customer_id", nullable = false)})
private CustomerInfo customerInfo;
#OneToOne(cascade = CascadeType.ALL, targetEntity = CustomerCredentials.class)
#JoinTable(name = "customer_credentials", inverseJoinColumns = {#JoinColumn(name = "customer_id", nullable = false)})
private CustomerCredentials customerCredentials;
// getter, setters etc }
#Entity #Table(name = "customer_info") public class CustomerInfo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
// getter, setters etc }
#Entity #Table(name = "customer_credentials") public class CustomerCredentials {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
// getter, setters etc }
You could change the cascade strategy to any strategy you need. I just left CascadeType.ALL there as an example.
There is a table ORDERS from which were created views ORDER_VIEW_A and ORDER_VIEW_B.
I have created entity classes OrderViewA and OrderViewB where in each of them is mapping on entity named 'TransactionRecord'.
It is #OneToOne relationship.
There is column ORDER_ID in TRANSACTION_RECORD table and field orderId in TransactionRecord entity.
Field orderId is same for OrderViewA.id and for OrderViewB.id, cause views are selected from the same table.
My question is, how to map in Hibernate two views in OneToOne relationship with another entity by same field.
My code looks like this and it doesn't work in any way, Hibernate always end up with:
org.hibernate.AnnotationException: Referenced property not a
(One|Many)ToOne: com.example.app.model.TransactionRecord.orderId in
mappedBy of com.example.app.model.views.OrderViewA.orderViewA
#Entity
#Immutable
#Table(name = "ORDER_VIEW_A")
public class OrderViewA {
#Id
#Column(name = "id")
private Long id;
...
#OneToOne(mappedBy = "orderId", fetch = FetchType.LAZY)
private IntegrationRecord orderARecord;
...
}
#Entity
#Immutable
#Table(name = "ORDER_VIEW_B")
public class OrderViewB {
#Id
#Column(name = "id")
private Long id;
...
#OneToOne(mappedBy = "orderId", fetch = FetchType.LAZY)
private IntegrationRecord orderBRecord;
...
}
#Entity
#Table(name = "TRANSACTION_RECORD")
public class TransactionRecord {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "order_id")
private Long orderId;
...
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "order_id")
private OrderViewA orderViewA;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "order_id")
private OrderViewB orderViewB;
...
}
I have two entities which I would like to join through multiple columns. These columns are shared by an #Embeddable object that is shared by both entities. In the example below, Foo can have only one Bar but Bar can have multiple Foos (where AnEmbeddableObject is a unique key for Bar). Here is an example:
#Entity
#Table(name = "foo")
public class Foo {
#Id
#Column(name = "id")
#GeneratedValue(generator = "seqGen")
#SequenceGenerator(name = "seqGen", sequenceName = "FOO_ID_SEQ", allocationSize = 1)
private Long id;
#Embedded
private AnEmbeddableObject anEmbeddableObject;
#ManyToOne(targetEntity = Bar.class, fetch = FetchType.LAZY)
#JoinColumns( {
#JoinColumn(name = "column_1", referencedColumnName = "column_1"),
#JoinColumn(name = "column_2", referencedColumnName = "column_2"),
#JoinColumn(name = "column_3", referencedColumnName = "column_3"),
#JoinColumn(name = "column_4", referencedColumnName = "column_4")
})
private Bar bar;
// ... rest of class
}
And the Bar class:
#Entity
#Table(name = "bar")
public class Bar {
#Id
#Column(name = "id")
#GeneratedValue(generator = "seqGen")
#SequenceGenerator(name = "seqGen", sequenceName = "BAR_ID_SEQ", allocationSize = 1)
private Long id;
#Embedded
private AnEmbeddableObject anEmbeddableObject;
// ... rest of class
}
Finally the AnEmbeddedObject class:
#Embeddable
public class AnEmbeddedObject {
#Column(name = "column_1")
private Long column1;
#Column(name = "column_2")
private Long column2;
#Column(name = "column_3")
private Long column3;
#Column(name = "column_4")
private Long column4;
// ... rest of class
}
Obviously the schema is poorly normalised, it is a restriction that AnEmbeddedObject's fields are repeated in each table.
The problem I have is that I receive this error when I try to start up Hibernate:
org.hibernate.AnnotationException: referencedColumnNames(column_1, column_2, column_3, column_4) of Foo.bar referencing Bar not mapped to a single property
I have tried marking the JoinColumns are not insertable and updatable, but with no luck. Is there a way to express this with Hibernate/JPA annotations?
This worked for me . In my case 2 tables foo and boo have to be joined based on 3 different columns.Please note in my case ,in boo the 3 common columns are not primary key
i.e., one to one mapping based on 3 different columns
#Entity
#Table(name = "foo")
public class foo implements Serializable
{
#Column(name="foocol1")
private String foocol1;
//add getter setter
#Column(name="foocol2")
private String foocol2;
//add getter setter
#Column(name="foocol3")
private String foocol3;
//add getter setter
private Boo boo;
private int id;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "brsitem_id", updatable = false)
public int getId()
{
return this.id;
}
public void setId(int id)
{
this.id = id;
}
#OneToOne
#JoinColumns(
{
#JoinColumn(updatable=false,insertable=false, name="foocol1", referencedColumnName="boocol1"),
#JoinColumn(updatable=false,insertable=false, name="foocol2", referencedColumnName="boocol2"),
#JoinColumn(updatable=false,insertable=false, name="foocol3", referencedColumnName="boocol3")
}
)
public Boo getBoo()
{
return boo;
}
public void setBoo(Boo boo)
{
this.boo = boo;
}
}
#Entity
#Table(name = "boo")
public class Boo implements Serializable
{
private int id;
#Column(name="boocol1")
private String boocol1;
//add getter setter
#Column(name="boocol2")
private String boocol2;
//add getter setter
#Column(name="boocol3")
private String boocol3;
//add getter setter
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "item_id", updatable = false)
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
}
If this doesn't work I'm out of ideas. This way you get the 4 columns in both tables (as Bar owns them and Foo uses them to reference Bar) and the generated IDs in both entities. The set of 4 columns has to be unique in Bar so the many-to-one relation doesn't become a many-to-many.
#Embeddable
public class AnEmbeddedObject
{
#Column(name = "column_1")
private Long column1;
#Column(name = "column_2")
private Long column2;
#Column(name = "column_3")
private Long column3;
#Column(name = "column_4")
private Long column4;
}
#Entity
public class Foo
{
#Id
#Column(name = "id")
#GeneratedValue(generator = "seqGen")
#SequenceGenerator(name = "seqGen", sequenceName = "FOO_ID_SEQ", allocationSize = 1)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "column_1", referencedColumnName = "column_1"),
#JoinColumn(name = "column_2", referencedColumnName = "column_2"),
#JoinColumn(name = "column_3", referencedColumnName = "column_3"),
#JoinColumn(name = "column_4", referencedColumnName = "column_4")
})
private Bar bar;
}
#Entity
#Table(uniqueConstraints = #UniqueConstraint(columnNames = {
"column_1",
"column_2",
"column_3",
"column_4"
}))
public class Bar
{
#Id
#Column(name = "id")
#GeneratedValue(generator = "seqGen")
#SequenceGenerator(name = "seqGen", sequenceName = "BAR_ID_SEQ", allocationSize = 1)
private Long id;
#Embedded
private AnEmbeddedObject anEmbeddedObject;
}
Hibernate is not going to make it easy for you to do what you are trying to do. From the Hibernate documentation:
Note that when using referencedColumnName to a non primary key column, the associated class has to be Serializable. Also note that the referencedColumnName to a non primary key column has to be mapped to a property having a single column (other cases might not work). (emphasis added)
So if you are unwilling to make AnEmbeddableObject the Identifier for Bar then Hibernate is not going to lazily, automatically retrieve Bar for you. You can, of course, still use HQL to write queries that join on AnEmbeddableObject, but you lose automatic fetching and life cycle maintenance if you insist on using a multi-column non-primary key for Bar.