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));
Related
#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
(Aware that this is not the first time question being asked, but I tried all of the suggested solutions and it did no good.)
So, I have a Parent entity and a Child entity with a OneToOne mapping - the issue is that the join isn't (and shouldn't) being done by the primary key columns, but by some other id column (a nullable natural id).
I have 4 relevant entities here:
class EntityId implements Serializable {
int storeId;
long uniqueId;
}
#Entity
#IdClass(EntityId.class)
#Immutable
#Table(name = "parent")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(
discriminatorType = DiscriminatorType.STRING,
name = "disc_type"
)
public class Parent {
#Id
#Column(name = "store_id")
private int storeId;
#Id
#Column(name = "unique_id")
private long uniqueId;
// getters and setters omitted for brevity
}
#Entity
#DiscriminatorValue(value = "Extended")
public class ExtendedParent extends Parent {
#Column(name = "id", unique = true, nullable = false)
private Long id;
#OneToOne(mappedBy="parent")
#Fetch(FetchMode.JOIN)
private ExtendedChild child;
// getters and setters omitted for brevity
}
#Immutable
#MappedSuperclass
public abstract class AbstractChild {
#Id
#Column(name = "id", nullable = false)
private long id;
// getters and setters omitted for brevity
}
#Entity
#Table(name = "extended_child")
public class ExtendedChild extends AbstractChild {
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name="id", referencedColumnName = "id", insertable = false, updatable = false, nullable = false)
private ExtendedParent parent;
}
Unfortunately, whenever I try to start the application it fails with "Failed to load Application Context" with the following error:
Caused by: java.lang.ArrayIndexOutOfBoundsException: 1
at org.hibernate.sql.ANSIJoinFragment.addJoin(ANSIJoinFragment.java:81)
at org.hibernate.loader.plan.exec.internal.LoadQueryJoinAndFetchProcessor.addJoins(LoadQueryJoinAndFetchProcessor.java:281)
at org.hibernate.loader.plan.exec.internal.LoadQueryJoinAndFetchProcessor.renderEntityJoin(LoadQueryJoinAndFetchProcessor.java:184)
The problem is that it simply ignored the referencedColumnName and attempts to join via the primary key - visible when we investigate it further (breakpoint in the exception row - this is the root cause):
The right-hand side of the join in the left join (ignore the names of the columns) includes that are defined as the #Id of the Parent, instead of the "id" column defined in the ExtendedParent entity, as defined in the #JoinColumn in the ExtendedChild entity.
NOTE: I also tried setting the id column as a natural identifier with the #NaturalId annotation (moving it into the root entity Parent + adding the annotation there) but it made no difference - same error.
Ideas?
I have 3 tables in the db. I am trying to write the JPA entities. I am facing some issues with Association table entity. My entities are as follows,
Person.java
#Entity
#Table(name = "person")
public class Person {
#Id
#GeneratedValue
private Long id;
#Column(nullable = false)
private String firstName;
#Column(nullable = false)
private String lastName;
//setter and getter
}
Exam.java
#Entity
#Table(name = "exam")
public class Exam {
#Id
#GeneratedValue
private long examId;
#Column(nullable = false)
private String examName;
#Column(nullable = false)
private int marks;
//Setters and getters
}
The table structure for association table is,
create table person_exam (
personId BIGINT not null,
examId BIGINT not null,
primary key (personId, examId)
);
I tried the association table entity with #ManyToMany annotation for both the properties which is not giving me the result.
Can anyone please suggest me what should I need to use (ManyToMany/OneToOne/ManyToOne/OneToMany ) in my entity for the above person_exam table.
from the PRO JPA 2nd Ed. book:
the only way to implement a many-to-many relationship is with a separate join table. The consequence of not having any join columns in either of the entity tables is that there is no way to determine which side is the owner of the relationship. Because every bidirectional relationship has to have both an owning side and an inverse side, we must pick one of the two entities to be the owner.
So I chose the the Person entity. Applying the needed changes to your incomplete code:
#Entity
#Table(name = "person")
public class Person {
#Id
#GeneratedValue
private Long id;
#Column(nullable = false)
private String firstName;
#Column(nullable = false)
private String lastName;
/**
* we need to add some additional metadata to the Person designated
* as the owner of the relationship, also you must fully specify the names of
* the join table and its columns because you already provided a schema
* for the association table, otherwise the JPA provider would generate one.
*/
#ManyToMany
#JoinTable(name="person_exam",
joinColumns=#JoinColumn(name="personId"),
inverseJoinColumns=#JoinColumn(name="examId"))
private Collection<Exams> exams;
//setter and getter
}
#Entity
#Table(name = "exam")
public class Exam {
#Id
#GeneratedValue
private long examId;
#Column(nullable = false)
private String examName;
#Column(nullable = false)
private int marks;
//Setters and getters
/**
* As in every other bidirectional relationship,
* the inverse side must use the mappedBy element to identify
* the owning attribute.
*/
#ManyToMany(mappedBy="exams")
private Collection<Person> people;
}
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.
I'm having problems with generating primary keys with one-to-one relations that use shared primary key.
Here's code:
#Entity
#Table(name = "osoba")
public class Osoba implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "osoba_id")
private Integer osobaId;
#PrimaryKeyJoinColumn
#OneToOne(cascade = CascadeType.PERSIST)
public Pracownik pracownik;
...
and second class:
#Entity
#Table(name = "pracownik")
public class Pracownik
{
#OneToOne
#JoinColumn(name = "osoba_id")
#MapsId("osobaId")
private Osoba osoba;
#Id
#Column(name = "osoba_id")
private Integer osobaId;
...
I've been similar issues and I thought that i've done everything correctly but i still get
org.hibernate.id.IdentifierGenerationException: ids for this class must be manually assigned before calling save(): entity.Pracownik
when trying to persist Pracownik objects.
You need to follow example from #MapsId documentation (in your case, with #Id instead of #EmbeddedId):
#Entity
#Table(name = "pracownik")
public class Pracownik {
#Id
#Column(name = "oboba_id")
private Integer id;
#OneToOne
#MapsId
private Osoba osoba;
...
}
Inverse side of #OneToOne relationship should be mapped with mappedBy, as usually:
#Entity
#Table(name = "osoba")
public class Osoba implements Serializable {
...
#OneToOne(mappedBy = "osoba", cascade = CascadeType.PERSIST)
public Pracownik pracownik;
...
}
This old question but it works for me. Mayby it will help someone.
SQL Script (Oracle)
DROP TABLE HIBERNATE.PRACOWNIK;
DROP TABLE HIBERNATE.OSOBA;
DROP SEQUENCE HIBERNATE.OSOBA_SEQ;
CREATE TABLE HIBERNATE.OSOBA (
osoba_id NUMBER(15),
CONSTRAINT OSOBA_PK PRIMARY KEY (osoba_id)
);
CREATE TABLE HIBERNATE.PRACOWNIK (
pracownik_id NUMBER(15),
CONSTRAINT PRACOWNIK_PK PRIMARY KEY (pracownik_id),
CONSTRAINT PRACOWNIK_FK FOREIGN KEY (pracownik_id) REFERENCES OSOBA(osoba_id)
);
CREATE SEQUENCE HIBERNATE.OSOBA_SEQ START WITH 1 INCREMENT BY 1 NOCACHE NOCYCLE;
Osoba.java
#Entity
#Table(name = "osoba")
public #Data class Osoba {
#Id
#Column(name = "osoba_id")
#GeneratedValue(generator="osoba-generator", strategy = GenerationType.SEQUENCE)
#SequenceGenerator(name="osoba-generator", allocationSize = 1, sequenceName = "OSOBA_SEQ")
private Long osobaId;
#OneToOne(fetch=EAGER, mappedBy="osoba", cascade=ALL)
private Pracownik pracownik;
}
Pracownik.java
#Entity
#Table(name="pracownik")
public #Data class Pracownik {
#Id
#Column(name = "pracownik_id")
#GeneratedValue(generator="pracownik-generator")
#GenericGenerator(name="pracownik-generator", strategy="foreign", parameters=
#Parameter(name = "property", value = "osoba")
)
private Long pracownikId;
#OneToOne(fetch=FetchType.EAGER)
#PrimaryKeyJoinColumn
private Osoba osoba;
}
#Data is Lombok