JPA ManyToMany Join Table has all attributes as PK - java

I'm using Hibernate 3.3.1 and am following along in modelling this sample table structure, but I'm having trouble creating a join table with extra attributes.
It's the many-to-many relationship between the Order and Product table. The join table is the Order Detail table. I followed the approach mentioned here.
Now I have the entities
#Entity
#Table(name = "Orders")
public class Order {
#OneToMany(mappedBy="order")
private List<OrderDetail> orderItems;
}
and
#Entity
#Table(name="PRODUCTS")
public class Product {
#OneToMany(mappedBy="product")
private List<OrderDetail> orderItems;
}
and
#Entity
#IdClass(OrderDetail.class)
#Table(name = "ORDER_DETAIL")
public class OrderDetail implements Serializable {
#Id
#Column(name="ORDER_ID")
private Long orderId;
#Id
#Column(name="PRODUCT_ID")
private Long productId;
#Column(name = "PRICE")
private double price;
#Column(name = "LAST_UPDATED_TIME")
private Date lastUpdatedTime;
#ManyToOne
#JoinColumn(name = "ORDER_ID")
private Order order;
#ManyToOne
#JoinColumn(name = "PRODUCT_ID")
private Product product;
}
and
public class OrderDetailId implements Serializable {
private Long orderId;
private Long productId;
}
I used Apache Derby to do the test, but I'm having trouble with the generated table structure.
CREATE TABLE ORDER_DETAIL (
PRODUCT_ID BIGINT NOT NULL,
ORDER_ID BIGINT NOT NULL,
LAST_UPDATED_TIME TIMESTAMP NOT NULL,
PRICE DOUBLE NOT NULL
);
CREATE INDEX SQL120323142938020 ON ORDER_DETAIL (PRODUCT_ID ASC);
CREATE UNIQUE INDEX SQL120323142937810 ON ORDER_DETAIL (PRODUCT_ID ASC, ORDER_ID ASC, LAST_UPDATED_TIME ASC, PRICE ASC);
ALTER TABLE ORDER_DETAIL ADD CONSTRAINT SQL120323142937810 PRIMARY KEY (PRODUCT_ID, ORDER_ID, LAST_UPDATED_TIME, PRICE);
ALTER TABLE ORDER_DETAIL ADD CONSTRAINT FK4A94AA82CC6D989A FOREIGN KEY (PRODUCT_ID)
REFERENCES PRODUCTS (PROD_ID);
It seems that it has created all of my columns as the primary key. Why is this so?

You use class of your entity as an argument to IdClass. That is not correct. Class of Id should be used. Additionally separate fields for id in join entity are not needed.
Go for something like code below. I cannot guarantee that it works in such a old version of Hibernate, but works for sure in never ones. Worth of trying anyway. It would not hurt to update to at least 3.5.X version (or rather even fresher one) if you want to use JPA 2.0 features. Constructors/equals etc. are stripped away to save space.
#Entity
#Table(name = "Orders")
public class Order {
#Id Long id;
#OneToMany(mappedBy="order")
private List<OrderDetail> orderItems;
}
#Entity
#Table(name="PRODUCTS")
public class Product {
#Id Long id;
#OneToMany(mappedBy="product")
private List<OrderDetail> orderItems;
}
#Entity
#IdClass(OrderDetailId.class)
#Table(name = "ORDER_DETAIL")
public class OrderDetail implements Serializable {
#Id #ManyToOne #JoinColumn(name = "ORDER_ID")
private Order order;
#Id #ManyToOne #JoinColumn(name = "PRODUCT_ID")
private Product product;
#Column(name = "PRICE") private double price;
//Maybe you also want to use #TemporalType here
#Column(name = "LAST_UPDATED_TIME") private Date lastUpdatedTime;
}
public class OrderDetailId implements Serializable {
private Long order;
private Long product;
}
UPDATE 15/08/2017
In JPA 2.1 and above you don't need to add a class for the composite Id and you can do it like this:
#Entity
#Table(name = "ORDER_DETAIL")
public class OrderDetail implements Serializable {
#Id #ManyToOne #JoinColumn(name = "ORDER_ID")
private Order order;
#Id #ManyToOne #JoinColumn(name = "PRODUCT_ID")
private Product product;
#Column(name = "PRICE") private double price;
//Maybe you also want to use #TemporalType here
#Column(name = "LAST_UPDATED_TIME") private Date lastUpdatedTime;
}

The code below seems to generate tables as desired, I have tested it on MySQL (just the table creation, not CRUD):
#Entity
#Table(name = "orders")
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "orderDetailId.order")
private List<OrderDetail> orderItems;
//get set …..
}
#Entity
#Table(name="products")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "orderDetailId.product")
private List<OrderDetail> orderItems;
//get set ……
}
#Entity
#Table(name = "order_detail")
public class OrderDetail {
#Id
private OrderDetailId orderDetailId;
private double price;
#Temporal(TemporalType.TIMESTAMP)
private Date lastUpdatedTime;
//get set ….
}
#Embeddable
public class OrderDetailId implements Serializable{
private Order order;
private Product product;
#ManyToOne(fetch=FetchType.LAZY)
#Access(AccessType.PROPERTY)
public Order getOrder() {
return order;
}
public void setOrder(Order order) {
this.order = order;
}
#ManyToOne(fetch=FetchType.LAZY)
#Access(AccessType.PROPERTY)
public Product getProduct() {
return product;
}
public void setProduct(Product product) {
this.product = product;
}
//hash code equals override
}
Hibernate DEBUG details as below
DEBUG: org.hibernate.tool.hbm2ddl.SchemaUpdate - create table order_detail (lastUpdatedTime datetime, price double precision not null, product_id bigint, order_id bigint, primary key (order_id, product_id)) ENGINE=InnoDB
DEBUG: org.hibernate.tool.hbm2ddl.SchemaUpdate - create table orders (id bigint not null auto_increment, primary key (id)) ENGINE=InnoDB
DEBUG: org.hibernate.tool.hbm2ddl.SchemaUpdate - create table products (id bigint not null auto_increment, primary key (id)) ENGINE=InnoDB
DEBUG: org.hibernate.tool.hbm2ddl.SchemaUpdate - alter table order_detail add index FK23AE5A622128CF91 (order_id), add constraint FK23AE5A622128CF91 foreign key (order_id) references orders (id)
DEBUG: org.hibernate.tool.hbm2ddl.SchemaUpdate - alter table order_detail add index FK23AE5A62EB201631 (product_id), add constraint FK23AE5A62EB201631 foreign key (product_id) references products (id)

Related

How to join column with multiple primary keys or specific primary key in jpa

#Entity
#Table(name = "A")
public class A {
#Id
#Column(name = "id")
private Long id;
#Id
#Column(name = "name")
private String name;
#JoinColumn(name = "test_id")
private List<Test> testId;
}
#Entity
#Table(name = "Test")
public class Test {
#Id
#Column(name = "test_id")
private Long testId;
}
Error Result is
" JPA trouble with OneToOne relationship: A Foreign key refering has the wrong number of column. should be 2 "
How to specific primary key for join Test table ?
Table A : column id
map with
Table B : column test_id
Since your table A has a composite key, you should separate the columns out into another key class and then join on the individual part of the key of the table.
For instance, create AKey
#Embeddable
public class AKey {
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
//getters and setters
}
Then replace the ids in class A
#Entity
#Table(name = "A")
public class A {
#EmbeddedId
private AKey key;
#JoinColumn(name = "test_id")
private List<Test> testId;
}
Then you can do a join on Test.testId = A.key.id

How to create composite key objects with existing object using hibernate

Lets say I have the following database schema
CREATE TABLE employee(
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
hrid VARCHAR (50)
);
CREATE TABLE territory(
id BIGINT PRIMARY KEY,
name varchar (50)
);
CREATE TABLE transcode(
id BIGINT AUTO_INCREMENT PRIMARY KEY,
code VARCHAR (10) NOT NULL
);
create table employee_territory_function(
employee_id BIGINT NOT NULL,
territory_id BIGINT NOT NULL,
transcode_id BIGINT NOT NULL,
PRIMARY KEY (employee_id,territory_id),
CONSTRAINT employeeref FOREIGN KEY (employee_id) REFERENCES employee (id),
CONSTRAINT territoryref FOREIGN KEY (territory_id) REFERENCES territory (id) ,
CONSTRAINT transcoderef FOREIGN KEY (transcode_id) REFERENCES transcode (id)
);
Now I have the following JPA mapped entities
Employee entity
#Entity
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
private String hrid;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "employee", cascade = CascadeType.ALL)
private Set<EmployeeTerritoryFunction> employeeTerritoryFunctionList = new HashSet<>();
//getters and setters
}
Territory entity:
#Entity
public class Territory implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
// getters and setters for all field
}
Transcode Entity:
#Entity
public class Territory implements Serializable {
#Id
private long id;
private String name;
//getters and setters
}
EmployeeTerritoryFunction entity (composite key table)
#Entity
#IdClass(value = EmployeeTerritoryFunctionPK.class)
public class EmployeeTerritoryFunction implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#ManyToOne
private Employee employee;
#Id
#ManyToOne
private Territory territory;
#ManyToOne
#JoinColumn(name = "transcode_id", referencedColumnName = "id")
private Transcode transcode;
//getters and setters
}
EmployeeTerritoryFunction pk
public class EmployeeTerritoryFunctionPK implements Serializable {
private static final long serialVersionUID = 1L;
private Long employee;
private Long territory;
//getters and setters, no args constructor, equals and hashcode
}
Below sample insertion
Employee employee = this.employeeRepository.findByHrid("111");
if (employee == null) {
employee = new Employee();
employee.setName("Marie");
employee.setHrid("333");
}
Territory territory = new Territory();
territory.setId(2L);
territory.setName("T2");
Territory territory2 = new Territory();
territory2.setId(3L);
territory2.setName("T3");
Transcode transcode = this.transcodeRepository.findByCode("ASC");
Transcode transcode2 = this.transcodeRepository.findByCode("CC");
EmployeeTerritoryFunction employeeTerritoryFunction1 = new EmployeeTerritoryFunction();
employeeTerritoryFunction1.setTranscode(transcode);
employeeTerritoryFunction1.setTerritory(territory);
employeeTerritoryFunction1.setEmployee(employee);
employee.getEmployeeTerritoryFunctionList().add(employeeTerritoryFunction1);
EmployeeTerritoryFunction employeeTerritoryFunction2 = new EmployeeTerritoryFunction();
employeeTerritoryFunction2.setTranscode(transcode2);
employeeTerritoryFunction2.setTerritory(territory2);
employeeTerritoryFunction2.setEmployee(employee);
employee.getEmployeeTerritoryFunctionList().add(employeeTerritoryFunction2);
employeeRepository.save(employee);
when I run above code with only new objects, I have no issue because hibernate automatically insert the employee, the territory and the list of employee_territory_function but when I first delete all existing territory, employee_territory_function and try to insert using an existing employee, hibernate is not able auto insert or update employee, auto insert in territory, employee_territory_function.
Below the error
Caused by: org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.example.demo.Employee.employeeTerritoryFunctionList, could not initialize proxy - no Session
When I replace oneToMany fetch type to EAGER, I got below error
Caused by: javax.persistence.EntityNotFoundException: Unable to find com.example.demo.Territory with id 3
It seams that hibernate try to query Territory table but I do not when him to do that because I remove all data on Territory and EmployeeTerritoryFunction table and only employee existing data is not removed.
How to fixe please ?
Fields in both classes EmployeeTerritoryFunction and EmployeeTerritoryFunctionPK should be named exactly same and have same types which you don't have. Try like this:
#Entity
#IdClass(EmployeeTerritoryFunctionPK.class)
public class EmployeeTerritoryFunction implements Serializable {
#Id
#ManyToOne
private Employee employee;
#Id
#ManyToOne
private Territory territory;
}
public class EmployeeTerritoryFunctionPK implements Serializable {
private Employee employee;
private Territory territory;
public int hashCode() { //TODO }
public boolean equals(Object obj) { //TODO }
}

manyToOne and oneToMany in hibernate&spring

I have 2 tables in database side(oracle)
create table GROUPS
(
ID NUMBER not null,
GROUP_NAME VARCHAR2(30)
)alter table GROUPS
add constraint ID primary key (ID)
and
create table ITEM_GROUP
(
ITEM_ID VARCHAR2(30) not null,
GROUP_ID NUMBER not null
)
alter table ITEM_GROUP
add constraint ITEM_GROUPD_ID primary key (ITEM_ID, GROUP_ID)
alter table ITEM_GROUP
add constraint ITEM_GROUP_FK01 foreign key (GROUP_ID)
references GROUPS (ID);
Than I have mapping classes in Java side. I want to make thing, when I am selecting group to take all his items too, and I want to save item with hibernate it is all .
#Entity
#Table(name = "GROUPS")
public class Group {
#Id
#Column(name = "ID", nullable = false)
#javax.persistence.SequenceGenerator(name = "groupIdGenerator", sequenceName = "GROUP_SEQ", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "groupIdGenerator")
private int id;
#Column(name = "GROUP_NAME")
private String groupName;
#JsonManagedReference
#OneToMany(fetch = FetchType.EAGER, mappedBy="group",cascade = CascadeType.ALL)
private List<GroupItems> groupItems = new ArrayList<>();
// setters and getters
}
#SuppressWarnings("serial")
#Embeddable
public class GroupItemPK implements Serializable {
#Column(name = "ITEM_ID")
private String merchantId;
#Column(name = "GROUP_ID")
private int id;
// getters , setters , constructors , equals hashcode methods
}
#Entity
#Table(name = "ITEM_GROUP")
public class GroupITEM {
#EmbeddedId
private GroupITEMtPK id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ID")
#JsonBackReference
private Group group;
}
I am interested in did i make any mistakes in build relationship ? If I did what is my mistakes , because I can not do my select and save queries without exceptions.
I am trying to do in my Code
List<Group> list = sessionFactory.getCurrentSession().createQuery("from Group a").list();
and here is my Exception
org.hibernate.engine.jdbc.spi.SqlExceptionHelper could not extract ResultSet [n/a]
java.sql.SQLSyntaxErrorException: ORA-00904: "GROUPITE0_"."ID": invalid identifier

How to implement a complex many to many relationship in JPA?

Here the db schema
CREATE TABLE Products
(
id INT NOT NULL AUTO_INCREMENT,
category_id INT NOT NULL,
description VARCHAR(100),
price DECIMAL(10, 2) NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (category_id) REFERENCES Categories(id)
) ENGINE = INNODB;
CREATE TABLE Orders
(
id INT NOT NULL AUTO_INCREMENT,
customer_id INT NOT NULL,
status VARCHAR(20) NOT NULL,
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
FOREIGN KEY (customer_id) REFERENCES Customers(id)
) ENGINE = INNODB;
CREATE TABLE OrderDetails
(
product_id INT NOT NULL,
order_id INT NOT NULL,
quantity INT NOT NULL,
subtotal DECIMAL(10, 2) NOT NULL,
PRIMARY KEY (product_id, order_id),
FOREIGN KEY (product_id) REFERENCES Products(id),
FOREIGN KEY (order_id) REFERENCES Orders(id)
) ENGINE = INNODB;
The models
#Embeddable
public class OrderDetailPK
{
private Product product;
private Order order;
public OrderDetailPK() {}
public OrderDetailPK(Product product, Order order)
{
this.product = product;
this.order = order;
}
}
public class OrderDetail {
#EmbeddedId
private OrderDetailPK id;
#ManyToOne(cascade=CascadeType.ALL)
#JoinColumn(name="product_id", insertable=false, updatable=false)
private Product product;
#ManyToOne(cascade=CascadeType.ALL)
#JoinColumn(name="order_id", insertable=false, updatable=false)
private Order order;
private int quantity;
private double subtotal;
public OrderDetail() {}
public OrderDetail(OrderDetailPK id, int quantity, double subtotal)
{
this.product = id.getProduct();
this.order = id.getOrder();
this.quantity = quantity;
this.subtotal = subtotal;
}
// getters, setters
}
public class Product {
#Id
private int id;
private String description;
private double price;
#ManyToOne
#JoinColumn(name="category_id")
private Category category;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "Products")
private List<OrderDetail> orderDetail;
}
public class Order {
#Id
private int id;
#ManyToOne
#JoinColumn(name="customer_id")
private Customer customer;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "Orders")
private List<OrderDetail> orderDetail;
}
And for some reasons I keep getting the error
Concrete type "class models.OrderDetail" with application identity does not declare any primary key fields.
Could anyone point me out where the problem is ? Thanks
When i did this before (as detailed in this question and answer), i made the fields in the embeddable ID primitives (corresponding to the ID fields of the entities referred to), and then used #MapsId in the entity. I believe this is the simplest (and dare i say correct) of meeting all the requirements: that the fields in the entity are relationships, that the fields in the ID class are primitive, that every column is mapped exactly once (the #MapsId fields not really being mappings, but sort of aliases).
Applying that to your case, the ID class looks like:
#Embeddable
public class OrderDetailPK {
private final int productId;
private final int orderId;
public OrderDetailPK(int productId, int orderId) {
this.productId = productId;
this.orderId = orderId;
}
}
And the entity class looks like:
public class OrderDetail {
#EmbeddedId
private OrderDetailPK id;
#ManyToOne(cascade = CascadeType.ALL)
#MapsId("productId")
private Product product;
#ManyToOne(cascade = CascadeType.ALL)
#MapsId("orderId")
private Order order;
private int quantity;
private double subtotal;
public OrderDetail(Product product, Order order, int quantity, double subtotal) {
this.id = new OrderDetailPK(product.getId(), order.getId());
this.product = product;
this.order = order;
this.quantity = quantity;
this.subtotal = subtotal;
}
protected OrderDetail() {}
}
First of all OrderDetailPK has to implement Serializable.
For second please specify which ID's you are going to use, because you has specified columns product_id and order_id as insertable=false, updatable=false (read-only).
So you need to try something like the following:
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name = "product_id",column = #Column(name = "product_id")),
#AttributeOverride(name = "listingId",column= #Column(name = "order_id"))
})
private OrderDetailPK id;
More information you may find here:
http://docs.oracle.com/javaee/6/api/javax/persistence/EmbeddedId.html
http://docs.oracle.com/javaee/6/api/javax/persistence/AttributeOverride.html
From the EmbeddedId javadoc:
Relationship mappings defined within an embedded id class are not supported.
So you cannot do it this way. I don't think JPA 1 specifies a standard way to implement this (in JPA 2 there is #MapsId but I never tried), but this is what I usually do and most implementations (I think at least Hibernate, EclipseLink and OpenJPA) support it:
Declare your primary key class using primitive types:
#Embeddable
public class OrderDetailPK implements Serializable
{
private int product;
private int order;
public OrderDetailPK() {}
...
}
Annotate your entity with #IdClass and declare the fields using the same name but the desired types:
#Entity
#IdClass(OrderDetailPK.class)
public class OrderDetail {
#Id
#ManyToOne(cascade=CascadeType.ALL)
#JoinColumn(name="product_id", insertable=false, updatable=false)
private Product product;
#Id
#ManyToOne(cascade=CascadeType.ALL)
#JoinColumn(name="order_id", insertable=false, updatable=false)
private Order order;
...
}
(I have always kept the #Id on the fields in the entity but I didn't recheck if they are mandatory)

JPA many to many with extra column

I have a following problem that I need to solve.
The core issues is that I want to add additional column into JoinTable for ManyToMany relation in JPA. In my case I have following entities.
The Topic is a simple entity which has many RemoteDocument's (one RemoteDocument may be refered by many Topic's, hence it should be ManyToMany relation). Also RemoteDocument entity is read only because it may be read only from Oracle Materialized View moreover any altering of this Materialized View is forbidden. So I want to store order of RemoteDocuments related to some Topic. In fact I can do something like that with additional entity:
#Entity
public class Topic {
#Id
private Long id;
#Basic
private String name;
#OneToMany
private Set<TopicToRemoteDocument> association;
}
#Entity
public class RemoteDocument {
#Id
private Long id;
#Basic
private String description;
}
#Entity
public class TopicToRemoteDocument {
#OneToOne
private Topic topic;
#OneToOne
private RemoteDocument remoteDocument;
#Basic
private Integer order;
}
In this case additional entity TopicToRemoteDocument helps me to replace ManyToMany association with OneToMany and add extra field order.
But I want to have ManyToMany relation but with configured additional column in join table
Use list instead of set, together with the #OrderColumn annotation and JPA will automatically take care of the order:
#MappedSuperclass
public class BaseEntity{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
public Long getId(){
return id;
}
public void setId(final Long id){
this.id = id;
}
}
#Entity
public class Topic extends BaseEntity{
#ManyToMany(mappedBy = "topics")
#OrderColumn
private List<Document> documents = new ArrayList<Document>();
public List<Document> getDocuments(){
return documents;
}
public void setDocuments(final List<Document> documents){
this.documents = documents;
}
}
#Entity
public class Document extends BaseEntity{
#ManyToMany
#OrderColumn
private List<Topic> topics = new ArrayList<Topic>();
public List<Topic> getTopics(){
return topics;
}
public void setTopics(final List<Topic> topics){
this.topics = topics;
}
}
Generated DDL (using hibernate and HSQL):
create table Document (
id bigint generated by default as identity (start with 1),
primary key (id)
);
create table Document_Topic (
documents_id bigint not null,
topics_id bigint not null,
topics_ORDER integer not null,
documents_ORDER integer not null,
primary key (documents_id, topics_ORDER)
);
create table Topic (
id bigint generated by default as identity (start with 1),
primary key (id)
);
alter table Document_Topic
add constraint FK343B5D0B481100B2
foreign key (documents_id)
references Document;
alter table Document_Topic
add constraint FK343B5D0B558627D0
foreign key (topics_id)
references Topic;
I would try to avoid using a List unless you allow duplicates.
There is a #OrderColumn annotation that automatically does this. Have you tried it?
#Entity
public class Topic {
#Id
private Long id;
#Basic
private String name;
#OneToMany
#OrderColumn
private Set<TopicToRemoteDocument> association;
}
One technique that is useful when creating the many-to-many mapping class entity is to attribute the id's in the class along with #ManyToOne designation which makes this class act as the composite key class:
#Entity
#Table(name = "market_vendor")
public class MarketVendor implements Serializable
{
#Id
#ManyToOne
#JoinColumn(name = "market_id")
private Market market;
#Id
#ManyToOne
#JoinColumn(name = "vendor_id")
private Vendor vendor;
#Basic
#Column(name="active")
private boolean active;
public MarketVendor(Market market, Vendor vendor, boolean active)
{
this.market = market;
this.vendor = vendor;
this.active = active;
}
}
This allows you to have the composite primary key defined within the same class without having to have a separate primary key class. You also need to make the class serializable.

Categories