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)
Related
I try to create a ManyToMany relation with one extra column (OrderItem.quantity). So I use following entities:
Orders table:
#Entity(name = "EshopOrder")
#Table(name="eshop_order")
public class EshopOrder implements Serializable{
#Id
#Column(name="id_order")
private int idOrder;
#Column(name="date_created")
private Date dateCreated;
#Column(name="total")
private float total;
#ManyToOne
#JoinColumn(name="id_customer")
private Customer customer;
#OneToMany(mappedBy = "product", cascade = CascadeType.ALL, orphanRemoval = true)
private List<OrderItem> items = new ArrayList<OrderItem>();
public EshopOrder() {
}
... getters, etc.
}
"Join table":
#Entity(name = "OrderItem")
#Table(name="order_item")
public class OrderItem {
#EmbeddedId
private OrderItemId id;
#Column(name="quantity")
private Integer quantity;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("idOrder")
private EshopOrder order;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("idProduct")
private Product product;
public OrderItem() {
}
public OrderItem(EshopOrder order, Product product, Integer quantity) {
this.order = order;
this.product = product;
this.quantity = quantity;
this.id = new OrderItemId(order.getIdOrder(), product.getIdProduct());
}
... getters, etc.
}
Product(s) which are order items:
#Entity(name="product")
#Table(name="product")
#NaturalIdCache
#Cache(
usage = CacheConcurrencyStrategy.READ_WRITE
)
public class Product implements Serializable{
#Id
#Column(name="id_product")
private int idProduct;
#Column(name="id_category")
private int idCategory;
#OneToOne
#JoinColumn(name="id_category", insertable=false, updatable =false)
private Category category;
#Column(name="name")
private String name;
#Column(name="description")
private String description;
#Column(name="availability")
private int availability;
#OneToMany(
mappedBy = "order",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<OrderItem> orders = new ArrayList<OrderItem>();
#Column(name="price")
private float price;
public Product() {
}
public Product(int idProduct, int idCategory, String name, String description, int availability, float price) {
this.idProduct = idProduct;
this.idCategory = idCategory;
this.name = name;
this.description = description;
this.availability = availability;
this.price = price;
}
...getters, etc
}
And finally, in order to model the quantity column I use this extra class:
#Embeddable
public class OrderItemId implements Serializable{
#Column(name = "id_order")
private int idOrder;
#Column(name = "id_product")
private int idProduct;
public OrderItemId() {
}
public OrderItemId(int idOrder, int idProduct) {
this.idOrder = idOrder;
this.idProduct = idProduct;
}
... getters, etc.
}
SQL scripts to create database:
CREATE TABLE `product`(
`id_product` int (11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`description` varchar(255) DEFAULT NULL,
`availability` int(11) NOT NULL,
`id_category` int(11) NOT NULL,
`price` float(10,2) NOT NULL)
CREATE TABLE `eshop_order`(
`id_order` int (11) NOT NULL auto_increment,
`date_created` date NOT NULL,
`id_customer` int(11) NOT NULL,
`total` float(10,2) NOT NULL)
CREATE TABLE `order_item`(
`id_order` int (11) NOT NULL,
`id_product` int(11) NOT NULL,
`quantity` int(11) DEFAULT NULL)
These are just snippets in order to show the DB background (column names).
When I try to view items of a order it keeps giving me this exception:
java.sql.SQLSyntaxErrorException: Unknown column 'items0_.order_id_order' in 'field list'. This error looks kinda understandable, there really is no column named order_id_order. Which leads me to idea like it forgot to add a dot operator (items0_.order.id_order) which would make totally sense and should work. Do you know what happened? Can you see what is wrong with it?
The thing is during development proces I completely followed this tutorial. But somehow it doesn't work. I even tried to use various naming strategies, but it didn't have any successful result.
I'd like to create a composite primary key with hibernate. Usually I'd go for #IdClass.
But this time I want to use a foreign key also inside the composite primary key.
Question: is that possible at all?
Example:
#Entity
class Person {
long id;
}
class CarPK implements Serializable {
private int code;
private String name;
public CarPK(int code, String name) {
this.code = code;
this.name = name;
}
}
#Entity
#IdClass(CarPK.class)
class Car {
#Id
private int code;
#Id
private String name;
//can I also mark "person.id" with #Id?
#ManyToOne
#JoinColumn(name = "fk_person_id", foreignKey = #ForeignKey(name = "fk_person"))
private Person person; //assume car is shared
}
The person reference will show in database as fk_person_id. Is it possible to also add this column to the primary key of the car table?
So I'd be getting similar to: CONSTRAINT car_pkey PRIMARY KEY (code, name, fk_person_id)?
Update:
#ManyToOne
#JoinColumn(name = "id")
private Person person;
Results in: Property of #IdClass not found in entity path.to$Car: id
Yes, you can add the #Id to the join column, but you must use the key type in your IdClass. I'm doing exactly the same thing in my current project.
#Entity
#IdClass(MyIdClass.class)
public class MyObject {
#Id
private String key;
#Column
#Lob
private String value;
#ManyToOne(cascade = CascadeType.PERSIST)
#Id
#JoinColumn(name = "id")
private MyOtherObject otherObject;
...
and
public class MyIdClass implements Serializable {
private long otherObject;
private String key;
...
MyOtherObject.id is a long in this scenario.
This is my sql table structure,
create table TBL_DEPARTMENT_ONE(
ID integer primary key generated always as identity (start with 50, increment by 1),
name varchar(100)
)
create table TBL_EMPLOYEE_THREE(
ID integer primary key generated always as identity (start with 100, increment by 1),
name varchar(100),
dept_ID integer references TBL_DEPARTMENT_ONE
)
Here we i'v done a structure of one to many relation between Employee and Department where many employees can belong to one Department,
Now, here is the JPA mapping code as follows,
For Employee,
#Entity
#Table(name="TBL_EMPLOYEE_THREE")
public class EmployeeEntityThree implements Serializable{
public EmployeeEntityThree(){}
public EmployeeEntityThree(String name,String mobileNo,DepartmentEntityOne dept){
this.empName = name;
this.department = dept;
this.mobileNo = mobileNo;
}
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="ID")
private Integer employeeId;
#Column(name="MOBILE_NO")
private String mobileNo;
#ManyToOne(cascade={CascadeType.PERSIST,
CascadeType.MERGE},
fetch= FetchType.LAZY,targetEntity=DepartmentEntityOne.class)
#JoinColumn(name="DEPT_ID")
private DepartmentEntityOne department;
.....
...
}
the code below is of Department Entity,
#Entity
#Table(name="TBL_DEPARTMENT_ONE")
public class DepartmentEntityOne implements Serializable{
public DepartmentEntityOne(){ }
public DepartmentEntityOne(String name){
this.deptName = name;
}
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="ID")
private Integer deptId;
#Column(name="NAME")
private String deptName;
#OneToMany(cascade= { CascadeType.MERGE,
CascadeType.PERSIST},
fetch= FetchType.LAZY,mappedBy="department")
#MapKeyColumn(name="xxxxx")
private Map<String,EmployeeEntityThree> employees;
...
..
}
This is the code in my main method for testing,
DepartmentEntityOne deptOne = new DepartmentEntityOne("Mechanical Engineering");
Map<String,EmployeeEntityThree> empMap = new HashMap<String,EmployeeEntityThree>();
EmployeeEntityThree[] array = new EmployeeEntityThree[]{
new EmployeeEntityThree("Amar","9000000001",deptOne),
new EmployeeEntityThree("Akbar","9000000002",deptOne),
new EmployeeEntityThree("Anthony","9000000003",deptOne)
};
empMap.put(array[0].getMobileNo(),array[0]);
empMap.put(array[1].getMobileNo(),array[1]);
empMap.put(array[2].getMobileNo(),array[2]);
deptOne.setEmployees(empMap);
em = emf.createEntityManager();
em.persist(deptOne);
The code works fine with all the inserts done successfully
Now my Question is for the Entity Department
where is have used an #MapKeyColumn(name="xxxx"), where "xxxx" is some garbage value,
Here what should be the name = ?
because prior to this it was name = "mobileNo" which is the property in Employee entity.
This worked too.
So what shoud be the actualy vaue for #MapKetColumn(name= ?)
You should refer to the name column of the Employee table.
#MapKeyColumn(name="NAME")
private Map<String,EmployeeEntityThree> employees;
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)
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.