I want to join an order table to with a different item table (book or food) based on the item_type value. If item_type is 0, item_id should be from the book table. If item_type is 1, item_id should be from the food table.
Below are the sample tables. I hope they can help you understand my question.
create table order{
id int(11) unsigned NOT NULL AUTO_INCREMENT,
item_type int,
item_id int
}
create table book{
id int(11) unsigned NOT NULL AUTO_INCREMENT,
desc varchar(100)
}
create table food{
id int(11) unsigned NOT NULL AUTO_INCREMENT,
field1 varchar(100)
}
I have tried using the #wherejointable annotaion.
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "item_id",insert="false" update="false")
#WhereJoinTable(clause = "item_type=0")
public Book getBook() {
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "item_id",insert="false" update="false")
#WhereJoinTable(clause = "item_type=1")
public Food getFood() {
}
However, I get the following error:
Repeated column in mapping for entity: column: item_id (should be mapped with insert="false" update="false")
Is this possible to achieve in hibernate?
You need to use the #Any annotation for non-inheritable joins:
#Any(metaColumn = #Column(name = "ITEM_TYPE"))
#AnyMetaDef(idType = "int", metaType = "int",
metaValues = {
#MetaValue(targetEntity = Book.class, value = "0"),
#MetaValue(targetEntity = Food.class, value = "1")
})
#JoinColumn(name="ITEM_ID")
private Object item;
So item can be loaded as a Book or as a Food.
I have another solution to solve this problem , ie to use Inheritance for the Order table. In your question you have to put two types of orders and Object oriented approach those types are BookOrder and FoodOrder and they share the same table by using the Single Table Inheritance Strategy.
Book class.
#Entity
public class Book {
#Id
#GeneratedValue
private int id;
private String description;
}
Food Class
#Entity
public class Food {
#Id
#GeneratedValue
private int id;
private String field1;
}
Let us create an abstract order class
#MappedSuperclass
public abstract class Order {
#Id
#GeneratedValue
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
Now we create two subclasses to this Order table and we use single table inheritance, so we need to provide the discrimination column (this is the column using which the hibernate maps to the object) and we define that to "item_type" column.
BookOrder
#Entity
#Table(name = "orders")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "item_type", discriminatorType = DiscriminatorType.STRING)
#DiscriminatorValue(value = "0")
public class BookOrder extends Order {
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "item_id")
private Book book;
}
Now we have the FoodOrder table which again extends the Order table
#Entity
#Table(name = "orders")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "item_type", discriminatorType = DiscriminatorType.STRING)
#DiscriminatorValue("1")
#Data
public class FoodOrder extends Order {
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "item_id")
private Food food;
}
The above mappings creates the tables exactly you wanted.
Related
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
I have a hierarchy of classes like next:
#Entity
#Inheritance(strategy=InheritanceType.JOINED)
#Table(name="Person")
public class Person implements Serializable{
#Id
#Column(name = "PersonID", unique = true, nullable = false)
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
}
#Entity
#Table(name="Student")
#PrimaryKeyJoinColumn(name="PersonID")
public class Student extends Person{
}
#Entity
#Table(name="Bachelor")
#PrimaryKeyJoinColumn(name="PersonID")
public class Bachelor extends Student{
#OneToMany(mappedBy = "bachelor", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<BachelorExam> exams;
}
#Entity
#Table(name="Exam")
public class Exam implements Serializable {
#Id
#Column(name = "ExamID", unique = true, nullable = false)
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
}
#Entity
#Table(name="BachelorExam")
public class BachelorExam implements Serializable {
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "PersonID_FK", referencedColumnName = "PersonID")
private Bachelor bachelor;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "ExamID_FK", referencedColumnName = "ExamID")
private Exam exam;
}
I want to get users (regular student or bachelor) from appropriate table by ID using generic method like next:
<T extends Person> T getStudentById(Long studentId);
This method can be something like
public <T extends Person> T getUserById(Long personId) {
List<Class<?>> studentTypes = new LinkedList<>();
studentTypes.add(Student.class);
studentTypes.add(Bachelor.class);
for (Class aClass : studenTypes) {
List<T> results = getDatabaseProvider().getDataFromDatabase(String.format("select u %s as u " +
"where u.userId = '%d'", aClass.getName(), userId));
return results.get(0);
}
}
The problem is that when I save a bachelor object in database, hibernate also saves bachelor's id to 'Student' table so when I get data from database going through whole list of inherited classes, query returns record from table Bachelor and also record from table Student, because both contain required student ID.
I've tried to use InheritanceType Table_Per_class but in this case hibernate doesn't create foreign key for bachelor in table BachelorExam.
How can I receive only records from table Bachelor by id?
Thanks!
I have the following scheme: TableA1 and TableA2 exist in the database and each is represented by an entity bean. Since they are related, I created an abstract class (TableA, it's an entity but does not exist in the database) where both entities inherit from this class. In addition, TableA has a one-to-one relationship with TableB.
My objective is to query TableB and from there get information of TableA1 or TableA2 depending on the type.
TableA1 and TableA2 each has an id (each table generates automatically a sequential number, so you may have repetition).
In TableB I have two columns that combined represent the foreign key: type and id. Type = 1 means that id is in TableA1. Similarly with TableA2.
My problem is that I don't know how to define these two columns as an external foreign key.
This is what I've got:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
#DiscriminatorColumn(name="type")
public abstract class TableA {
#Id
#Column(name = "type")
protected int type;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
protected int id;
#Column(name = "name")
private String name;
// Getters and setters
}
#Entity
#DiscriminatorValue("1")
#Table (name="tableA1")
public class TableA1 extends TableA {
#Column(name="col1")
private String col1;
// Getters and setters
}
#Entity
#DiscriminatorValue("2")
#Table (name="tableA2")
public class TableA2 extends TableA {
#Column(name="col2")
private String col2;
// Getters and setters
}
#Entity
#Table (name="tableB")
public class TableB {
#Id
#Column(name="someId")
private Integer someId;
#Column(name="type")
private int type;
#Column(name="id")
private Integer id;
#OneToOne(optional = false, cascade = CascadeType.ALL)
#JoinColumns({
#JoinColumn(name = "type"),
#JoinColumn(name = "id" )
})
private TableA tableA;
// Getters and setters
}
Update
Am I looking for the impossible? This is what I found:
Polymorphic relations to non-leaf classes in a table-per-class hierarchy have many limitations. When the concrete subclass is not known, the related object could be in any of the subclass tables, making joins through the relation impossible. This ambiguity also affects identity lookups and queries; these operations require multiple SQL SELECTs (one for each possible subclass), or a complex UNION.
Update 2
TableA1, TableA2 and TableB already exist in the database and have the following structure:
CREATE TABLE TableA1 (
surrogate_key int AUTO_INCREMENT,
some_char char(30),
PRIMARY KEY (surrogate_key)
);
CREATE TABLE TableA2 (
surrogate_key int AUTO_INCREMENT,
some_int int,
PRIMARY KEY (surrogate_key)
);
CREATE TABLE TableB (
surrogate_key int AUTO_INCREMENT,
type int, // if type=1, sk represents the surrogate_key of tableA1
// if type=2, sk represents the surrogate_key of tableA2
sk int,
description varchar(200),
PRIMARY KEY (surrogate_key)
);
Update answer:
Updated to match database
You can use getDiscriminatorValue() to access the DiscriminatorValue.
Define mappings like this:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
#DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.INTEGER)
public abstract class TableA implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
#Column(name = "surrogate_key")
protected int id;
#Id
#Column(name = "type")
protected int type;
// Constructors & getters/setters
#Transient
public String getDiscriminatorValue() {
DiscriminatorValue val = this.getClass().getAnnotation(DiscriminatorValue.class);
return val == null ? null : val.value();
}
}
#Entity
#DiscriminatorValue("1")
public class TableA1 extends TableA {
#Column(name = "some_char", length = 1)
private char someChar;
// Constructors & getters/setters & toString/equals
}
#Entity
#DiscriminatorValue("2")
public class TableA2 extends TableA {
#Column(name = "some_int")
private int someInt;
// Constructors & getters/setters & toString/equals
}
#Entity
public class TableB implements Serializable {
#Id
#GeneratedValue
#Column(name = "surrogate_key")
private int id;
#OneToOne
#Cascade(value = CascadeType.SAVE_UPDATE)
#JoinColumns({#JoinColumn(name = "sk", referencedColumnName = "surrogate_key"),
#JoinColumn(name = "type", referencedColumnName = "type")})
private TableA tableA;
#Column(name = "description", length = 200)
private String description;
// Constructors & getters/setters & toString/equals
}
and query like this:
newSession.createQuery("from TableB tb where tb.tableA.type=:type order by tb.id asc").setParameter("type", 1));
I have three table:
CREATE TABLE catalog (
id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
type_id INT,
genre_id INT,
product_name VARCHAR(100),
FOREIGN KEY ( genre_id ) REFERENCES genres ( genre_id ),
FOREIGN KEY ( type_id ) REFERENCES types ( type_id )
);
CREATE TABLE genres (
genre_id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
genre_name VARCHAR(50)
);
CREATE TABLE types (
type_id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
type_name VARCHAR(50)
);
Also I have Java classes
#Entity
#Table(name = "catalog", catalog = "media_store_db")
public class Catalog implements Serializable {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "product_name", length = 100)
private String productName;
#ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "genre_id", referencedColumnName = "genre_id")
private Genre genre;
#ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "type_id", referencedColumnName = "type_id")
private Type type;
#Entity
#Table(name = "genres", catalog = "media_store_db")
public class Genre implements Serializable {
#Id
#Column(name = "genre_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "genre_name")
private String name;
#Entity
#Table(name = "types", catalog = "media_store_db")
public class Type implements Serializable {
#Id
#Column(name = "type_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "type_name")
private String name;
Is it possible to save (using save() method of Hibernate Session) Catalog object like this
Catalog catalog = new Catalog();
catalog.setProductName("Product");
catalog.setGenre(new Genre());
catalog.setType(new Type());
save(catalog);
without writing SQL? And what I need to do with Genre and Type? Should I set id of both instances?
UPD:
This code works just fine
Catalog catalog = new Catalog();
catalog.setProductName("12 Years a Slave");
catalog.setGenre(genreRepository.get(Long.valueOf(1)));
catalog.setType(typeRepository.get(Long.valueOf(1)));
Session session = cfg.getSession();
Transaction tx = session.beginTransaction();
session.save(catalog);
tx.commit();
session.close();
Sure, you can persist the generated Object in the database using persist(Object obj).
Well, you should test the function in a JUnit Test. In the business code it should do your DAO.
No, all the Ids are generated, you don't need to set the id. It is managed by Hibernate.
For your example the UnitTest should look like:
public class DataGenerationTest {
private EntityManager em;
#Before
public void init(){
EntityManagerFactory emf = Persistence.createEntityManagerFactory("test");
em = emf.createEntityManager();
}
#Test
public void shouldAddSomeCatalogs(){
em.getTransaction().begin();
Catalog catalog = new Catalog();
catalog.setProductName("Proguct");
catalog.setGenre(new Genre());
catalog.setType(new Type());
em.persist(catalog);
em.getTransaction().commit();
em.close();
}
}
(Sure you have to rename the PersistenceUnit test from the EntityManagerFactory. It should match your named PersistenceUnit in the persistence.xml)
Other interesting lecture:
Hiberante Session Doc
Small example (GitHub)
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)