JPA composite primary key [duplicate] - java

This question already has answers here:
Mapping ManyToMany with composite Primary key and Annotation:
(2 answers)
Closed 7 years ago.
I have the following classes in my JPA model (getters, setters, and irrelevant fields omitted):
#Entity #Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Currency {
#Id
private Integer ix;
}
#Entity #Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Product {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
}
I need to define a class Price, such that when the DDL is generated from the classes, the primary key of the corresponding table is composed of the keys for Product and Currency. I've tried the following:
#Entity #Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
#IdClass(PricePK.class)
public class Price {
#Id #ManyToOne(optional = false)
private Product product;
#Id
#ManyToOne(optional = false)
private Currency currency;
}
#Embeddable
public class PricePK implements Serializable {
Integer product;
Integer currency;
}
But this generates the following for the PRICE table:
create table PRICE (
currency_id int null,
product_id int null,
primary key (currency_id, product_id)
);
Notice that both currency_id and product_id are nullable, which causes the following error when I try to load the DDL into SQL Server
Cannot define PRIMARY KEY constraint on nullable column in table 'PRICE'
I don't understand why these are nullable, because in the domain model they are annotated
#ManyToOne(optional = false)
The DDL is generated using the org.hibernate.dialect.SQLServerDialect SQL dialect.

Recently I created ManyToMany relation using Composite Primary key and annotation as bi directional #OneToMany. This code works flawless. Maybe it will help:
Mapping ManyToMany with composite Primary key and Annotation:

Since you are using #IdClass, the PricePK class need not be marked with the #Embeddable
annotation. An example is given in http://www.java2s.com/Code/Java/JPA/SetIdClassforCompoundKey.htm
I tried your code removing the #Embeddable on PricePK class, and the price table generated in MYSQL database with not null fields.
Following is how you could use #EmbeddedId to achieve the required result:
(getters and setters omitted)
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Price {
#EmbeddedId
PricePK pricePk;
}
#Embeddable
public class PricePK implements Serializable {
#ManyToOne(optional = false)
private Product product;
#ManyToOne(optional = false)
private Currency currency;
}

Related

#PrimaryKeyJoinColumn of JPA InheritanceType does not work correctly in COUNT query

There is some class like this:
#Entity
#Table(name="person")
#Inheritance(strategy=InheritanceType.JOINED)
public class Person {
#Id
#GeneratedValue
private int personId;
and the its child class is:
#Entity
#Table(name="employee")
#PrimaryKeyJoinColumn(name="employee_id")
public class Employee extends Person {
[Something else fields]
It is necessary to mention that there are some else objects that are extended from Person such as Student that has its own Fk field(such as student_id) to Person entity.
The generated query of the following HQL:
select count(e)
from Employee e
is:
select count(e.id)
from employee e
inner join person p
on e.employee_id = p.id
while the correct generated query must be:
select count(e.employee_id)
from employee e
inner join person p
on e.employee_id = p.id
So the invalid identifier exception is raised.
Where is wrong and what I have to do for solving this problem?
UPDATE
The table of these Entities are:
person:
id and others files
------------------------------------
and the employee table is:
employee_id[fk to person table] others fields
----------------------------------------------------
Although Hibernate allows such kind of inheritance, but it is usually avoid as the result / behavior is less controllable by developer.
Instead, through my previous experience and IMHO, I would suggest to follow below practice which would gives you more control on achieving your expected result by using #JoinColumn since JPA 2.0:
#Entity
#Table(name="person")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="person_id")
private Long personId;
}
#Entity
#Table(name="employee")
public class Employee{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="employee_id")
private Long employeeId;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn (name="person_id")
private Person person;
}
UPDATE 1
If using #PrimaryKeyJoinColumn is more preferred, then there is one thing need to rectify in the original code (at least need to assign a name to the PK to give you more control:
#Entity
#Table(name="person")
#Inheritance(strategy=InheritanceType.JOINED)
public class Person {
#Id
#GeneratedValue
#Column(name="person_id")
private int personId;
}
#Entity
#Table(name="employee")
#PrimaryKeyJoinColumn(name="person_id")
public class Employee extends Person {
#GeneratedValue
#Column(name="employee_id")
private int employeeId;
}
It is because #PrimaryKeyJoinColumn is supposed to let the current secondary table to know which primary key of primary table it needs to reference with.
You can refer to below section of JPA 2.0 specifiction:
11.1.40 PrimaryKeyJoinColumn Annotation
The PrimaryKeyJoinColumn annotation specifies a primary key column that is used as a foreign key to join to another table.
The PrimaryKeyJoinColumn annotation is used to join the primary table
of an entity subclass in the JOINED mapping strategy to the primary
table of its superclass; it is used within a SecondaryTable annotation
to join a secondary table to a primary table; and it may be used in a
OneToOne mapping in which the primary key of the referencing entity is
used as a foreign key to the referenced entity[108].
To expand garykwwong's answer, you could:
use explicit personId column name (person_id and employee_id will be used as column names in person and employee tables):
#Entity
#Table(name="person")
#Inheritance(strategy=InheritanceType.JOINED)
public class Person {
#Id
#GeneratedValue
#Column(name="person_id")
private int personId;
}
#Entity
#Table(name="employee")
#PrimaryKeyJoinColumn(name = "employee_id", referencedColumnName = "person_id")
public class Employee extends Person {
// Explicit employeeId field is NOT required in the class
}
not use personId column name at all and let JPA do the linking for you (personId and employee_id will be used as column names in person and employee tables):
#Entity
#Table(name="person")
#Inheritance(strategy=InheritanceType.JOINED)
public class Person {
#Id
#GeneratedValue
private int personId;
}
#Entity
#Table(name="employee")
#PrimaryKeyJoinColumn(name = "employee_id")
public class Employee extends Person {
// Explicit employeeId field is NOT required in the class
}
not use explicit names at all (personId will be used as column name in both person and employee tables):
#Entity
#Table(name="person")
#Inheritance(strategy=InheritanceType.JOINED)
public class Person {
#Id
#GeneratedValue
private int personId;
}
#Entity
#Table(name="employee")
#PrimaryKeyJoinColumn
public class Employee extends Person {
}
Here is a demo project demonstrating the first option.

How to stop Hibernate from eagerly fetching a relationship when it is mapped using a column (referencedColumnName) different than the primary key?

I'm mapping a relationship that does not use the entity's primary key. Using "referencedColumnName" with a column different than the primary key causes hibernate to eagerly fetch the association, by issuing an extra select, even when it's tagged with FetchType.LAZY.
My goal is to make it behave like a regular mapping, meaning it wouldn't issue an extra query every time I need to query the main entity.
I have already tried using #LazyToOne(LazyToOneOption.NO_PROXY), which sorts out the problem, but it does not operate well with Jackson's (JSON parsing library) module "jackson-datatype-hibernate5", which skips hibernate lazy proxies when serializing the results.
Here is a scenario almost like the one I have that causes the problem:
Entities:
#Entity(name = "Book")
#Table(name = "book")
public class Book
implements Serializable {
#Id
#GeneratedValue
private Long id;
private String title;
private String author;
#NaturalId
private String isbn;
//Getters and setters omitted for brevity
}
#Entity(name = "Publication")
#Table(name = "publication")
public class Publication {
#Id
#GeneratedValue
private Long id;
private String publisher;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(
name = "isbn",
referencedColumnName = "isbn"
)
private Book book;
#Column(
name = "price_in_cents",
nullable = false
)
private Integer priceCents;
private String currency;
//Getters and setters omitted for brevity
}
Repository (Spring-Data, but you could try directly with the EntityManager):
#Repository
public interface PublicationRepository extends JpaReadRepository <Publication, Long>
{
#Query ("SELECT d FROM Publication d WHERE d.publisher = ?1 ")
Optional <Publication> findByPublisher (String isbn);
}
Thanks
The only way to achieve what you are looking for is by moving the annotatation #Id to the isbn property.
You can leave the #GeneratedValue on the autoincrement property.
Notes:
1 - Make sure that your equals/hc are following the OID(Object ID) on your domain case the "NaturalId" ISBN.
2 - It will be good to ensure if possible on DB level that your natural ID has unique contraint on it.

How can I join 3 entities JPA?

I have 3 entities: Aluno, Turma and Modalidade. Now I need create Matricula, this entity Matricula will contain all ids of Aluno, Turma and Modalidade with others attribute.
one Matricula can have one Aluno and can have many Turma and can have many Modalidade.
Entity Matricula, can have:
OneToOne Aluno
OneToMany Turma
OneToMany Modalidade
I hope can yours understand.
How to I do that ?
I have a tutorial that goes into a fair bit of detail about how you set up various relationships using Hibernate annotations. You can find it here.
I'm going to assume that you'd want bi-directional relationships using a foreign key mapping (as shown in the tutorial, if this is wrong, you can find the uni-directional configurations there), you can basically just declare your classes like this:
#Entity
#Table
public class Matricula {
#Id
private long matriculaId;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "alunoId")
private Aluno aluno;
#OneToMany(mappedBy="turma")
private List<Turma> turmas;
#OneToMany(mappedBy="modalidade")
private List<Modalidade> modalidades;
}
#Entity
#Table
public class Turma {
//Put a unique ID here to be used as PK
#ManyToOne
#JoinColumn(name="matriculaId)
private Matricula matricula;
}
#Entity
#Table
public class Modalidade {
//Put a unique ID here to be used as PK
#ManyToOne
#JoinColumn(name="matriculaId)
private Matricula matricula;
}
#Entity
#Table
public class Aluno {
//Put a unique ID here to be used as PK
#OneToOne(mappedBy="aluno")
private Matricula matricula;
}
Please note that this is assuming that your column names will match, and that your database is correctly set up.
Hope it goes well

JPA/Hibernate: mapping a single element in an entity with an one-to-many relationship

I have the following tables:
TABLE Orders (
ID BIGINT NOT NULL PRIMARY KEY,
... other columns ...
)
TABLE Order_States (
ID BIGINT NOT NULL PRIMARY KEY,
Order_ID BIGINT NOT NULL FOREIGN KEY REFERENCES Orders(ID),
State_Type VARCHAR(30) NOT NULL,
State_Date DATETIME NOT NULL,
... other columns ...
)
And the following mappings:
#Entity
#Table(name = "orders")
class Order {
#Id
#GeneratedValue
private Long id;
#OneToMany(mappedBy = "order")
#OrderBy("stateDate DESC")
private List<OrderState> orderStates
public OrderState getCurrentState() {
return orderStates.get(0);
}
public void setCurrentState(OrderState state) {
state.setStateDate(new Date());
orderStates.add(state);
}
... other members ...
}
#Entity
#Table(name = "order_states")
class OrderState {
#Id
#GeneratedValue
private Long id;
#ManyToOne
#JoinColumn(name = "order_id")
private Order order;
#Enumerated(EnumType.STRING)
#Column(name = "state_type")
private StateType stateType;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "state_date")
private Date stateDate;
... other members ...
}
I don't want to have all order's states in my mappings (they are just for historical reasons), but just the current state of the order, which is determined by the latest state's date.
Is there are any annotations for this? For example (I guess):
#Entity
#Table(name = "orders")
class Order {
...
#OneToMany(mappedBy = "order")
#OrderBy("stateDate DESC")
#TakeFirst
private OrderState orderState
... getter and setter for orderState ...
...
}
The feature you are talking about is called soft delete. Here is an example of doing such stuff by using Hibernate's annotations #SQLDelete and #Where.
However it would be difficult to make following code work
#TakeFirst
private OrderState orderState
it would be easier to code it like this
#TakeFirst
private Set<OrderState> orderState // this will be a set containing one entry

HIbernate one-to-one annotation isn't generating foreign key GerericGenerator in dependent table

I am trying to create OneToOne relation between a Person and Auth table. The problem is when the DB table "Auth" is generated, I'm not seeing the foreign key in the AUTH table that should reference Person. The object is to have the Auth table use the same Primary Key of the Person Table.
#MappedSuperclass
public abstract class DomainBase {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#Version
#Column(name="OPLOCK")
private Integer version;
}
#Entity
#Table(name = "person")
public class Person extends DomainBase {
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name="auth_id")
private Auth auth;
}
#Entity
public class Auth {
#Id
#GeneratedValue(generator="foreign")
#GenericGenerator(name="foreign", strategy = "foreign", parameters={
#Parameter(name="property", value="person")
})
#Column(name="person_id")
private int personId;
---------------------------------
#OneToOne(cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private Person person;
}
My Database scripts after hibernate DB generation.
CREATE TABLE auth
(
person_id integer NOT NULL,
activate boolean,
activationid character varying(255),
last_login_attempt_date timestamp without time zone,
last_login_attempt_timezone character varying(255),
last_login_date timestamp without time zone,
last_login_timezone character varying(255),
nonlocked boolean,
num_login_attempts integer,
CONSTRAINT auth_pkey PRIMARY KEY (person_id),
CONSTRAINT uk_d68auh3xsosyrjw3vmwseawvt UNIQUE (activationid)
)
WITH (
OIDS=FALSE
);
ALTER TABLE auth
OWNER TO postgres;
It seems that the problem is you declare twice the #OneToOne annotation between "person" table and "auth" table, without specify the relation between them. Take a look at the hibernate documentation, at the point 2.2.5.1, there is some examples about using one-to-one association.
For me, the best way is to set up the association in one table, the one that declare the foreing key column, and to use the mappedBy parameter in the other object. In your code, this will be :
#Entity
#Table(name = "person")
public class Person extends DomainBase {
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name="auth_id")
private Auth auth;
}
#Entity
public class Auth {
#Id
#GeneratedValue(generator="foreign")
#GenericGenerator(name="foreign", strategy = "foreign", parameters={
#Parameter(name="property", value="person")
})
#Column(name="person_id")
private int personId;
#OneToOne(mappedBy = "auth")
private Person person;
....
}
This is the second example in the hibernate documentation, introduce just after the sentence "In the following example, the associated entities are linked through an explicit foreign key column". I tested this code, and the "auth_id" column appeared.

Categories

Resources