I have two classes Foo and Bar. The tables in the database look like this:
|Foo|
|id : INT (PK) | bar_id : INT (PK, FK) |
|Bar|
|id : INT (PK) |
Normally I would map it like this:
#Entity
public class Bar
{
#Id
#Column(name = "id")
private int id;
#OneToMany
private Set<Foo> foo;
}
#Entity
public class Foo
{
#EmbeddedId
private FooPK key;
#MapsId("barId")
#ManyToOne
#JoinColumn(name = "bar_id", referencedColumnName = "id")
private Bar bar;
}
#Embeddable
public class FooPK
{
#Column(name = "id")
private int id;
#Column(name = "bar_id")
private int barId;
}
However the id's in FooPK are loosely mapped and need to be connected manually. I would prefer a solution that maps using Objects in stead of loose ids.
I tried the following but (of course) it didn't work, but I think it gives an idea of what I would like to achieve:
#Entity
public class Bar
{
#Id
#Column(name = "id")
private int id;
#OneToMany
private Set<Foo> foo;
}
#Entity
public class Foo
{
#EmbeddedId
private FooPK key;
#MapsId("barId")
#ManyToOne
#JoinColumn(name = "bar_id", referencedColumnName = "id")
#Access(AccessType.FIELD)
private Bar getBar()
{
return key.getBar();
}
}
#Embeddable
public class FooPK
{
#Column(name = "id")
private int id;
#Transient
private Bar bar;
//....
#Column(name = "bar_id")
#Access(AccessType.PROPERTY)
private int getBarId
{
return bar.getId();
}
}
Another problem with the latter solution is that the getBarId() method in FooPK needs to have a setBarId(Int) method. Setting the Object using the ID can be done by accessing the data access layer, however this (in my opinion) violates the separation of business/domain/data layers.
So what to do? Go with the first solution and keep the ids in sync manually or is there another (best) practice?
Referring to the JPA 2: composite primary key classes discussion, make the following changes to the Foo and FooPK classes:
#Entity
public class Foo {
#EmbeddedId
private FooPK key;
#MapsId("barId") //references EmbeddedId's property
#JoinColumn(name = "bar_id", referencedColumnName = "id")
#ManyToOne
private Bar bar;
}
#Embeddable
public class FooPK {
#Column(name = "id")
private int id;
#Column(name = "bar_id")
private int barId;
}
I suggest first making it work with FIELD access and then apply PROPERTY access.
So what to do? Go with the first solution and keep the ids in sync
manually or is there another (best) practice?
Save yourself from pain - generate ids automatically.
Related
I have a Product :
#Data
#Entity
#Table(name = "products", schema = "laboratory", catalog = "laboratory")
#JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" })
public class Product {
#Id
#GeneratedValue
private int id;
#ManyToOne(fetch = FetchType.LAZY, cascade= CascadeType.ALL)
#JoinColumn(name = "project_id")
#Transient
private Project project; // this one is for read only
#Column(name="project_id") // this will save the id in db for the project
private int projectId;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="id")
private Inspection inspection;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="id")
private Information information;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="id")
private Departure departure;
private String un;
private String tc;
}
There is 3 class that this product needs in order to be a Product : Information, Inpection, Departure
All 3 of these classes are similar.
I want to link them by the Product.id witch is a #GeneratedValue AI in sql.
Here is one of the 3 class :
Information
#Data
#Entity
#Table(name = "products_informations", schema = "laboratory", catalog = "laboratory")
#JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" })
public class Information {
#Id
private int productId;
private String description;
private String model;
private int year;
private String serialNumber;
private int odometre;
private int noCrochet;
private int nbKeys;
private String localisation;
private String cemeteryPosition;
#JsonFormat(pattern = "yyyy-MM-dd")
private Date receptionDate;
}
I want, WHEN I save() the product, that the private String productId in this class to automatically take the Id from the Product class without having to do it manually in my controller.
You have the mappings backwards in your model.
By using
public class Product {
#Id
#GeneratedValue
private int id;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="id")
private Information information;
You've told JPA to use the PRODUCT.ID primary key as a foreign key to the Information table; foreign keys are controlled by the relationship, so it means your ID value is pulled from the information.productId value. Opposite of what you are asking for and it means you have 4 mappings trying to set the PRODUCT.ID column value (set them different and see for yourself).
Try this instead:
public class Product {
#Id
#GeneratedValue
private int id;
#OneToOne(mappedby="product", cascade = CascadeType.ALL)
private Information information;
..
}
public class Information {
#Id
private int productId;
#MapsId
private Product product;
..
}
With this you will need to set the Information.product reference, but JPA will use that to set your productId value, using the one you set within the product.id property. You just need to set this relationship when you add an Information instance to a product. Do the same for the other relationships
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 am trying to define JPA layer over a db I cannot redesign.
That layer contains a OneToMany relationship where the Many part has a foreign key consisting of the same 2 fields that the one part has as primary key.
#Embeddable
public class FooKey implements Serializable {
#Column(name="foo_id")
private String id;
private String secondaryId;
}
public class Foo {
#EmbeddedId
private FooKey id;
(...)
}
public class Bar {
#Id
private Long id;
(...)
//#Embedded FooKey fooKey;
#ManyToOne
private Foo foo;
}
How can I solve this? I am getting the error #Column(s) not allowed on a #ManyToOne property
I managed to do it by mean of:
#ManyToOne
#JoinColumns({
#JoinColumn (name = "foo_id", referencedColumnName = "foo_id"),
#JoinColumn (name = "secondary_id", referencedColumnName = "secondary_id")
})
private Foo foo;
Although if anyone has a more elegant solution, I will accept it
I have a class:
#Entity
public class myClass {
#Id
#Column(name="id")
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
...
private String orderIndex;
}
I would like to put orderIndex in a different table, since only few entities will have this property. I would like the tables to looks like this:
table myClass:
id | name | ...
table myClass_orderIndex:
myClass_id | orderIndex
I could not find the annotations to do this in hibernate
Perhaps a one-to-one relation will fit your requirements. The one-to-one is guaranteed by a #JoinColumn(unique=true)
#Entity
#Table(name="myClass_orderIndex")
public class OtherClass {
private String orderIndex;
#ManyToOne
#JoinColumn(unique=true)
private MyClass myClass;
...
}
#Entity
public class MyClass {
#Id
#Column(name="id")
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
#OneToMany(mappedBy="myClass", cascade = javax.persistence.CascadeType.ALL, orphanRemoval = true)
#Cascade(CascadeType.ALL)
private List<OtherClass> list;
#Transient
public String getOrderIndex() {
if (list != null && list.size() == 1) return list.get(0).getOrderIndex();
return null;
}
}
This will create two tables as per your requirement.
For example, I've got a table A with structure:
int id | int ref_id | varchar name
0 - hello
1 0 world
And entity class:
#Entity
#Table(name = "mypack.A")
public class A
{
#Id
#Column(name = "ID")
private int id;
#Column(name = "REF_ID", nullable=true)
private int ref_id;
#Column(name = "NAME")
private String name;
// getters and setters
}
Row with id 1 refers to row with id 0. How can I do this kind of relation using Hibernate? I have an idea to create A class object inside that. Is it ok?
You can use instead of the property ref_id of type int use a reference to another A object.
#Entity
#Table(name = "mypack.A")
public class A implements Serializable {
#Id
#GeneratedValue
#Column(name = "ID")
private Long id;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "REF_ID")
private A refA;
#Column(name = "NAME")
private String name;
// getters and setters
}