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.
Related
I have a situation with repeatable class fields which I want to mark as #embeddable, however the question is - does JPA allow re-utilizing a class multiple times as embeddable in other different classes?
E.g. my embeddable class looks as follows:
#Embeddable
#Data
public class Audit{
private String name;
private Audit auditor;
private LocalDateTime creationDate;
}
Is it possible to embed the Audit into multiple different classes as for ex.:
#Entity
#Table(name = "BANK")
public class Bank{
#Id
private Long id;
#Column(name = "BANK_NAME")
private String bankName;
#Embedded
private Audit audit;
}
AND
#Entity
#Table(name = "CORPORATION")
public class Corporation{
#Id
private Long id;
#Column(name = "CORPORATION_NAME")
private String corporationName;
#Embedded
private Audit audit;
}
Historically Hibernate called these components. JPA calls them embeddables. Either way, the concept is the same: a composition of values.
Most often, embeddable types are used to group multiple basic type mappings and reuse them across several entities.
Java Code Example:
#Data
#Entity(name = "Book")
public class Book {
#Id
#GeneratedValue
private Long id;
private String title;
private String author;
private Publisher publisher;
}
#Data
#Embeddable
public static class Publisher {
#Column(name = "publisher_name")
private String name;
#Column(name = "publisher_country")
private String country;
}
And this is SQL to show how your table should look like:
create table Book (
id bigint not null,
author varchar(255),
publisher_country varchar(255),
publisher_name varchar(255),
title varchar(255),
primary key (id)
)
More details can be found in the documentation :)
I'm trying to retrieve a list of entities from a table with two primary keys which are ids to a foreign key each.
MySQL tables:
Paintings table:
id - int, PK, Auto increment
name - varchar(45)
Pictures table:
id - int, PK, Auto increment
name - varchar(45)
location - varchar(45)
painting_to_picture_link table:
painting_id - int, FK to id in painting
picture_id - int, FK to id in painting
I've set primary key (painting_id, picture_id)
and set them to their foreign keys also as written above.
In Java:
Painting.java
#Entity
#Table(name = "paintings")
public class Painting {
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Column(name = "name")
private String name;
...
}
Picture.java
#Entity
#Table(name = "pictures")
public class Pictures {
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Column(name = "name")
private String name;
#Column(name = "location")
private String location;
...
}
PaintingPictureLink.class
public class PaintingPictureLink implements Serializable {
#<SOME ANNOTATION HERE>
private Painting painting;
#<SOME ANNOTATION HERE>
private Picture picture;
...
}
I've seen many examples, but didn't work for me.
I've tried putting #Id annotations, #EmbeddedId, etc... non worked.
The errors I get are that table isn't mapped, could not determine type for the models, missing #Id annotation... :|
Would appreciate help with querying this table and getting a list of PaintingPictureLink.
Some of the examples I've followed:
https://access.redhat.com/documentation/en-US/JBoss_Enterprise_Web_Server/1.0/html/Hibernate_Annotations_Reference_Guide/ch03s02s06.html
Using an Entity (and their Primary Key) as another Entity's Id
Thanks,
Guy
Derp
Found the solution.
I created a primary key class:
#Embeddable
public class PicturePaintingPK implements Serializable {
#ManyToOne
private Painting painting;
#ManyToOne
private Picture picture;
public PicturePaintingPK() {}
// getters and setters //
}
In the PicturePaintingLink class:
#Entity
#Table(name = "painting_to_picture_link")
public class PaintingPictureLink implements Serializable {
#Id
private PicturePaintingPK primaryKey = new PicturePaintingPK()
...
//constructor//
...
public TTPK getPrimaryKey() {
return primaryKey;
}
public void setPrimaryKey(TTPK primaryKey) {
this.primaryKey = primaryKey;
}
// ... all the other getters and setters needed .... //
}
The source for this solution was from:
Example from Hibernate forum
Guy
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)
Can someone please explain to me #MapsId in hibernate? I'm having a hard time understanding it.
It would be great if one could explain it with an example and in what kind of use cases is it most applicable?
Here is a nice explanation from Object DB.
Designates a ManyToOne or OneToOne relationship attribute that provides the mapping for an EmbeddedId primary key, an attribute within an EmbeddedId primary key, or a simple primary key of the parent entity. The value element specifies the attribute within a composite key to which the relationship attribute corresponds. If the entity's primary key is of the same Java type as the primary key of the entity referenced by the relationship, the value attribute is not specified.
// parent entity has simple primary key
#Entity
public class Employee {
#Id long empId;
String name;
...
}
// dependent entity uses EmbeddedId for composite key
#Embeddable
public class DependentId {
String name;
long empid; // corresponds to primary key type of Employee
}
#Entity
public class Dependent {
#EmbeddedId DependentId id;
...
#MapsId("empid") // maps the empid attribute of embedded id
#ManyToOne Employee emp;
}
Read the API Docs here.
I found this note also useful: #MapsId in hibernate annotation maps a column with another table's column.
It can be used also to share the same primary key between 2 tables.
Example:
#Entity
#Table(name = "TRANSACTION_CANCEL")
public class CancelledTransaction {
#Id
private Long id; // the value in this pk will be the same as the
// transaction line from transaction table to which
// this cancelled transaction is related
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ID_TRANSACTION", nullable = false)
#MapsId
private Transaction transaction;
....
}
#Entity
#Table(name = "TRANSACTION")
#SequenceGenerator(name = "SQ_TRAN_ID", sequenceName = "SQ_TRAN_ID")
public class Transaction {
#Id
#GeneratedValue(generator = "SQ_TRAN_ID", strategy = GenerationType.SEQUENCE)
#Column(name = "ID_TRANSACTION", nullable = false)
private Long id;
...
}
IMHO, the best way to think about #MapsId is when you need to map a composite key in a n:m entity.
For instance, a customer can have one or more consultant and a consultant can have one or more customer:
And your entites would be something like this (pseudo Java code):
#Entity
public class Customer {
#Id
private Integer id;
private String name;
}
#Entity
public class Consultant {
#Id
private Integer id;
private String name;
#OneToMany
private List<CustomerByConsultant> customerByConsultants = new ArrayList<>();
public void add(CustomerByConsultant cbc) {
cbc.setConsultant(this);
this.customerByConsultant.add(cbc);
}
}
#Embeddable
public class CustomerByConsultantPk implements Serializable {
private Integer customerId;
private Integer consultantId;
}
#Entity
public class CustomerByConsultant{
#EmbeddedId
private CustomerByConsultantPk id = new CustomerByConsultantPk();
#MapsId("customerId")
#JoinColumn(insertable = false, updatable = false)
private Customer customer;
#MapsId("consultantId")
#JoinColumn(insertable = false, updatable = false)
private Consultant consultant;
}
Mapping this way, JPA automagically inserts Customer and Consultant ids in the EmbeddableId whenever you save a consultant. So you don't need to manually create the CustomerByConsultantPk.
As he explained Vladimir in his tutorial, The best way to map a #OneToOne relationship is to use #MapsId. This way, you don’t even need a bidirectional association since you can always fetch the Child entity by using the Parent entity identifier.
MapsId lets you use the same primary key between two different entities/tables. Note: when you use MapsId, the CASCADE.ALL flag becomes useless, and you will need to make sure that your entities are saved manually.
I’m working with an existing schema that I’d rather not change. The schema has a one-to-one relationship between tables Person and VitalStats, where Person has a primary key and VitalStats uses the same field as both its primary key and its foreign key to Person, meaning its value is the value of the corresponding PK of Person.
These records are created by external processes, and my JPA code never needs to update VitalStats.
For my object model I’d like my Person class to contain a VitalStats member, BUT:
When I try
#Entity
public class Person{
private long id;
#Id
public long getId(){ return id; }
private VitalStats vs;
#OneToOne(mappedBy = “person”)
public VitalStats getVs() { return vs; }
}
#Entity
public class VitalStats{
private Person person;
#OneToOne
public Person getPerson() { return person; }
}
I have the problem that VitalStats lacks an #Id, which doesn’t work for an #Entity.\
If I try
#Id #OneToOne
public Person getPerson() { return person; }
that solves the #Id problem but requires that Person be Serializable. We’ll get back to that.
I could make VitalStats #Embeddable and connect it to Person via an #ElementCollection, but then it would have to be accessed as a collection, even though I know that there’s only one element. Doable, but both a little bit annoying and a little bit confusing.
So what’s preventing me from just saying that Person implements Serializable? Nothing, really, except that I like everything in my code to be there for a reason, and I can’t see any logic to this, which makes my code less readable.
In the meantime I just replaced the Person field in VitalStats with a long personId and made that VitalStats’s #Id, so now the #OneToOne works.
All of these solutions to what seems (to me) like a straightforward issue are a bit clunky, so I’m wondering whether I’m missing anything, or whether someone can at least explain to me why Person has to be Serializable.
TIA
To map one-to-one association using shared primary keys use #PrimaryKeyJoinColumn and #MapsId annotation.
Relevant sections of the Hibernate Reference Documentation:
PrimaryKeyJoinColumn
The PrimaryKeyJoinColumn annotation does say that the primary key of
the entity is used as the foreign key value to the associated entity.
MapsId
The MapsId annotation ask Hibernate to copy the identifier from
another associated entity. In the Hibernate jargon, it is known as a
foreign generator but the JPA mapping reads better and is encouraged
Person.java
#Entity
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "person_id")
private Long id;
#OneToOne(cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private VitalStats vitalStats;
}
VitalStats.java
#Entity
public class VitalStats
{
#Id #Column(name="vitalstats_id") Long id;
#MapsId
#OneToOne(mappedBy = "vitalStats")
#JoinColumn(name = "vitalstats_id") //same name as id #Column
private Person person;
private String stats;
}
Person Database Table
CREATE TABLE person (
person_id bigint(20) NOT NULL auto_increment,
name varchar(255) default NULL,
PRIMARY KEY (`person_id`)
)
VitalStats Database Table
CREATE TABLE vitalstats
(
vitalstats_id bigint(20) NOT NULL,
stats varchar(255) default NULL,
PRIMARY KEY (`vitalstats_id`)
)
In my case this made the trick:
Parent class:
public class User implements Serializable {
private static final long serialVersionUID = 1L;
/** auto generated id (primary key) */
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(unique = true, nullable = false)
private Long id;
/** user settings */
#OneToOne(cascade = CascadeType.ALL, mappedBy = "user")
private Setting setting;
}
Child class:
public class Setting implements Serializable {
private static final long serialVersionUID = 1L;
/** setting id = user id */
#Id
#Column(unique = true, nullable = false)
private Long id;
/** user with this associated settings */
#MapsId
#OneToOne
#JoinColumn(name = "id")
private User user;
}