Hibernate: joining with one of the keys of a multi-keyed table - java

I've got a table Category and a table TranslatableText. The category is like this
create table Category (
id int not null,
parent_id int default 0,
TranslatableDescriptionId int default 1,
primary key(id));
create table TranslatableText (
id int not null,
lang enum ('NO','EN','FR'),
text mediumtext,
primary key(id, lang));
In my Category entity I've defined a mapping:
#Fetch(FetchMode.SUBSELECT)
#Cache(usage=CacheConcurrencyStrategy.READ_ONLY)
#OneToMany(fetch=FetchType.LAZY)
#JoinColumn(name="TranslatableDescriptionId")
#ForeignKey(name="FK_TranslatableTextId")
private Set<TranslatableText> translatableText;
But when it executes, it tries to access TranslatableDescriptionId, not id. Even if the TranslatableText entity has defined
#Id
#Column(name = "id", nullable = false)
private Integer id;
#Id
#Column(name = "lang", nullable = false)
#Enumerated(EnumType.STRING)
private String lang;
#Column(name = "text", length = 400, nullable = false)
private String text;
The query with the incorrect name selected:
select translatab0_.TranslatableDescriptionId as Translat4_13_1_, translatab0_.id as id1_, translatab0_.lang as Lang1_, translatab0_.id as id22_0_, translatab0_.lang as Lang22_0_, translatab0_.text as Text22_0_ from tblTranslateableText translatab0_ where translatab0_.TranslatableDescriptionId in ('126', '119', '103', '116', '121', '107', '113', '101', '109', '105', '123', '106', '125', '124', '114')
If I change the mappings #JoinColumn to read
#JoinColumn(name="TranslatableDescriptionId", referencedColumnName="id")
I get the following error when loading my app:
org.hibernate.MappingException: Unable to find column with logical name: id in org.hibernate.mapping.Table(Category) and its related supertables and secondary tables
For good measure I also tried:
#JoinColumn(name="id", referencedColumnName="TranslatableDescriptionId")
That gave me the error:
org.hibernate.MappingException: Unable to find column with logical name: TranslatableDescriptionId in org.hibernate.mapping.Table(Category) and its related supertables and secondary tables
Any suggestions to what I should do? I really want Category's translateableText to contain all the translations for its description, so I really want to join Category.TranslatableDescriptionId==TranslatableText.id
UPDATE1:
TranslatableText is used by many entities, so putting in a categoryId in it and reversing the relationship is not an option.
UPDATE2:
I was able to load it saying #JoinColumn(name="id"), but this led to a ClassCastException in Hibernate where it, instead of having an Integer as a key, has an Array containing a single Integer as a key. This fails to be made into a String and thus proper SQL. So it's probably still not the mapping I want
Cheers
Nik

This kind of mapping is possible, but not very convenient because you'll have to manage identity of TranslatableTexts manually (that's why Hibernate complains about non-mapped column TranslatableDescriptionId):
public class Category implements Serializable {
...
private Long translatableDescriptionId;
#OneToMany
#JoinColumn(name="id", referencedColumnName="TranslatableDescriptionId")
private Set<TranslatableText> translatableText;
...
}
So, you need to manually assign unique translatableDescriptionIds to all "targets" of TranslatableText (categories, items, folders as you say) and manually set this values as id of TranslatableText before persisting it (you can't just add TranslatableText into the Set).
--
However, the more convenient design is to introduce an intermediate entity to keep the identity of all transalations attatched to a specific target:
public class Category {
...
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "targetId")
private TranslationTarget target;
}
public class TranslationTarget {
#Id #GeneratedValue
private Long id;
#OneToMany
#JoinColumn(name = "targetId")
private Set<TranslatableText> texts;
}
-
create table Category (
targetId int,
...);
create table TranslationTargets (
id int primary key
);
create table TranslatableText (
targetId int not null,
lang enum ('NO','EN','FR'),
text mediumtext,
primary key(targetId, lang));

Related

How do I add a constraint to check if value is present in another column within the same table?

I have an entity like this,
#Entity
#Table(name = "Persons", schema="PU")
public class Persons {
#Basic
#Column(name = "PERSON_ID")
private String personId
#Basic
#Column(name = "PERSON_NAME")
private String personName
#Basic
#Column(name = "PERSON_NAME_COPY")
private String personNameCopy
}
I want to add a constraint in the personNameCopy column such that whatever value is being inserted in that column should be present in the personName column.
Is there any way to achieve this?
DROP TABLE IF EXISTS dbo.Person;
CREATE TABLE dbo.Person
(
PERSON_ID INT NOT NULL PRIMARY KEY,
PERSON_NAME VARCHAR(50)NOT NULL,
PERSON_NAME_COPY VARCHAR(50)NOT NULL,
CONSTRAINT CHK_PERSON_NAME_PERSON_NAME_COPY CHECK (PERSON_NAME=PERSON_NAME_COPY)
)
GO
INSERT dbo.Person(PERSON_ID,PERSON_NAME,PERSON_NAME_COPY)
VALUES(1,'KUMAR','KUMAR')
--THIS ROW IS TO BE REJECTED
INSERT dbo.Person(PERSON_ID,PERSON_NAME,PERSON_NAME_COPY)
VALUES(2,'JOHN','KUMAR')
SELECT *FROM DBO.Person;
if I understand your question correctly you can implement check constraint (example is above)
Please also have look https://learn.microsoft.com/en-us/sql/relational-databases/tables/create-check-constraints?view=sql-server-ver16

Why isn't the foreign key field of a Many-to-One relationship being set on insert?

My Spring web application allows users to update "Employee" records to change the fields or add new "Phone" records related to this "Employee" record. However, when the "Employee" record is submitted for update after adding a new "Phone" record, it's throwing a SQL error exception.
The problem is that the "employee_id" foreign key on "Phone" table to the "Employee" table isn't set in the eventual SQL insert statement submitted to the database. However, in the "PhoneEntity" JPA entity object that is referenced by the updated/merged "EmployeeEntity" object, the property associated with the employee_id database field isn't null, it's set to the "EmployeeEnity" Object being updated/merged.
From my understanding of JPA, having the entity property associated with a database field should set it when the insert statement for the entity's record is submitted to the database, but in this case it isn't which is causing this error.
I've tried stepping through with a debugger, and I have verified that the created PhoneEntity object is a member of EmployeeEntity's phones property, and that the same PhoneEntity's employee property is set to the same EmployeeEntity object (with the same object IDs) in a bidirectional relationship.
I've also set the hibernate.show_sql=true to see the SQL statement being submitted to the database and it includes the statement (with the ellipses being more fields):
Hibernate:
insert
into
phone
(id, employee_id, ...)
values
(?, ?, ...)
Which means that it is inserting a new phone for the new PhoneEntity object.
After trying to running this insert statement it gives the SQL error "Column 'employee_id' cannot be null". However like I said before, I've checked with the debugger and the employee property is indeed set to the EmployeeEntity object.
this is a simplified example of what my code looks like:
#Entity
#Table(name = "employee")
public class EmployeeEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#OneToMany(mappedBy="employee", cascade = {CascadeType.PERSIST})
private Set<PhoneEntity> phones = new HashSet<>();
...
}
#Entity
#Table(name = "phone")
public class PhoneEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#ManyToOne
#JoinColumn(name = "employee_id", nullable = false)
private EmployeeEntity employee;
...
}
With tables that have the structure created by the following SQL statements.
CREATE TABLE employee (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
...
);
CREATE TABLE phone (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
employee_id INT NOT NULL,
...
FOREIGN KEY(employee_id) REFERENCES employee(id)
);
And the following is where it actually submits the updates to the entity manager to make updates to the database.
public void update(EmployeeDomain employee) {
EmployeeEntity entity = employeeDomainToEntity.transform(employee)
getEntityManager().merge(entity);
}
The EmployeeEntity and PhoneEntity objects are created by converting similar domain objects that were in turn deserialized from a http request. I'd include more of this section of the code but, as I've mentioned, I've already confirmed with my debugger that the actual entity objects being submitted to the merge are already in the form that we expected with the phones fields and employee fields being set correctly, so the end entities should be correct.
In the official JPA specification document (version 2.1) in section "3.2.7.1 Merging Detached Entity State" (page 85) we find:
For all entities Y referenced by relationships from X having the cascade element value cascade=MERGE or cascade=ALL, Y is merged recursively as Y'. For all such Y referenced by X, X' is set to reference Y'. (Note that if X is managed then X is the same object as X'.)
This explains that you are lacking cascade=MERGE for the annotation of the phones field.
As proposed in thanh ngo's answer, the aforementioned definition (or: explanation) thus translates to:
#OneToMany(mappedBy="employee", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private Set<PhoneEntity> phones = new HashSet<>();
Alternatively, you could also make use of cascade=CascadeType.ALL. However, this would also include operations such as CascadeType.REMOVE which might not always be intended.
Hope it helps.
I think the problem is that you are using merge.
The cascade type setting for the entity should be:
#OneToMany(mappedBy="employee", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private Set<PhoneEntity> phones = new HashSet<>();

JPA readonly mapping

Toplink can use read-only mappings when multiple attributes in an object map to the same fields in the database but only one of the mappings can write to the field.
Does JPA has such feature, how to write annotation? I have one #ManyToOne and one #Column annotation which need to map to same field in database.
#ManyToOne(optional=false, fetch=FetchType.LAZY)
#JoinColumn(name="USR_ID", referencedColumnName="USER_ID", nullable=false)
private User user;
/** #generated **/
#Column(name="USER_ID", nullable=false, length=30)
private String userId;
From here
The Column annotation and XML element defines insertable and updatable options. These allow for this column, or foreign key field to be omitted from the SQL INSERT or UPDATE statement. These can be used if constraints on the table prevent insert or update operations. They can also be used if multiple attributes map to the same database column, such as with a foreign key field through a ManyToOne and Id or Basic mapping. Setting both insertable and updatable to false, effectively mark the attribute as read-only.
So
#Column(name="USER_ID", nullable=false, length=30,
updatable=false, insertable=false)
private String userId;
should do it
updatable=false, insertable=false might not be enough. Hibernate exposes additional requirement:
org.hibernate.DuplicateMappingException:
Table [passenger] contains physical column name [parent_id]
referred to by multiple logical column names: [parent_id], [parentId]
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl$TableColumnNameBinding.bindPhysicalToLogical(InFlightMetadataCollectorImpl.java:1055)
The logical name parent_id comes from:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(insertable = false, updatable = false)
private Passenger parent;
while parentId comes from:
private Long parentId;
To harmonize logical names correct one (or set both) explicitly:
#Column(name = "parent_id")
private Long parentId;
Requirements for updatable=false, insertable=false comes from:
for ( Selectable columnOrFormula : value.getSelectables() ) {
if ( !columnOrFormula.isFormula() ) {
Column col = (Column) columnOrFormula;
if ( !distinctColumns.add( col.getName() ) ) {
throw new MappingException(
"Column '" + col.getName()
+ "' is duplicated in mapping for entity '" + getEntityName()
+ "' (use '#Column(insertable=false, updatable=false)' when mapping multiple properties to the same column)"
located:
at org.hibernate.mapping.PersistentClass.checkColumnDuplication(PersistentClass.java:1009)
at org.hibernate.mapping.PersistentClass.checkPropertyColumnDuplication(PersistentClass.java:1027)
at org.hibernate.mapping.PersistentClass.checkColumnDuplication(PersistentClass.java:1055)
at org.hibernate.mapping.PersistentClass.validate(PersistentClass.java:720)
at org.hibernate.mapping.RootClass.validate(RootClass.java:283)
at org.hibernate.boot.internal.MetadataImpl.validate(MetadataImpl.java:376)

Hibernate Exception: Missing Column (column exists)

Okay, so within the database we have a table called distributionCompanies, created like so:
CREATE TABLE `distributionCompanies` (
`distributionCompanyID` INT(11) NOT NULL,
`distributionCompanyName` VARCHAR(255) NOT NULL,
PRIMARY KEY (distributionCompanyID)
);
I'm trying to map this table to a class using Hibernate:
#Entity
#Table(name = "distributionCompanies")
public class DistributionCompany implements DatabaseObject {
#Id
#GeneratedValue
#Column(name = "distributionCompanyID", length = 11, unique = true, nullable = false)
private int distributionCompanyID;
....
However, when running, I hit this issue:
Initial SessionFactory creation failedorg.hibernate.HibernateException: Missing column: distributionCompanyID_distributionCompanyID in database2.distributionCompanies
This isn't the only table in the database, and I've managed to map other classes successfully using the same method, so I'm a little stumped as to why this is causing an issue.
Thank you for your time,
Samuel Smith
EDIT: In response to Xavi's comment, I temporarily removed another mapping for the column, and the error went away, so the bad-egg probably lays in the following code:
#ManyToOne(targetEntity = DistributionCompany.class)
#JoinTable(name = "distributionCompanies", joinColumns = { #JoinColumn(name = "distributionCompanyID", nullable = false) })
private int distributionCompanyID;
Hibernate is looking for a column named distributionCompanyID_distributionCompanyID in your distributionCompanies table.
This is probably due to a ToOne association mapping towards this table without #JoinColum.
From Hibernate Documentation:
The #JoinColumn attribute is optional, the default value(s) is like in one to one, the concatenation of the name of the relationship in the owner side, _ (underscore), and the name of the primary key column in the owned side. In this example company_id because the property name is company and the column id of Company is id.
If you've got a #ManyToOne or #OneToOne association mapping in another entity, this would explain why Hibernate is looking for such a column.
EDIT Seeing the association mapping you posted, it looks like it should be:
#ManyToOne(targetEntity = DistributionCompany.class)
#JoinColumn(name = "distributionCompanyID")
private DistributionCompany distributionCompany;
The #JoinTable annotation is used to specify a join table (that means an intermediate table used to model many-to-many associations). And the point of mapping an association would be to dispose of the mapped object instance (in this case a DistributionCompany, not just a distributionCompanyId).

Hibernate: Where do insertable = false, updatable = false belong in composite primary key constellations involving foreign keys?

When implementing composite primary keys in Hibernate or other ORMs there are up to three places where to put the insertable = false, updatable = false in composite primary key constellations that use identifying relationships (FKs that are part of the PK):
Into the composite PK class' #Column annotation (#Embeddable classes only) or
Into the entity class' association #JoinColumn/s annotation or
Into the entity class' redundant PK property's #Column annotation (#IdClass classes only)
The third is the only way to do with #IdClass and JPA 1.0 AFAIK. See http://en.wikibooks.org/wiki/Java_Persistence/Identity_and_Sequencing#Primary_Keys_through_OneToOne_Relationships. I will consider only cases 1. and 2.
Q:
Which way is the preferred place to put the "insertable = false, updatable = false" to generally?
I have experienced problems with Hibernate concerning this question. For example, Hibernate 3.5.x will complain about the Zips table
CREATE TABLE Zips
(
country_code CHAR(2),
code VARCHAR(10),
PRIMARY KEY (country_code, code),
FOREIGN KEY (country_code) REFERENCES Countries (iso_code)
)
with:
org.hibernate.MappingException: Repeated column in mapping for entity: com.kawoolutions.bbstats.model.Zip column: country_code (should be mapped with insert="false" update="false")
org.hibernate.mapping.PersistentClass.checkColumnDuplication(PersistentClass.java:676)
org.hibernate.mapping.PersistentClass.checkPropertyColumnDuplication(PersistentClass.java:698)
...
As you can see the country_code column is both PK and FK. Here are its classes:
Entity class:
#Entity
#Table(name = "Zips")
public class Zip implements Serializable
{
#EmbeddedId
private ZipId id;
#ManyToOne
#JoinColumn(name = "country_code", referencedColumnName = "iso_code")
private Country country = null;
...
}
Composite PK class:
#Embeddable
public class ZipId implements Serializable
{
#Column(name = "country_code", insertable = false, updatable = false)
private String countryCode;
#Column(name = "code")
private String code;
...
}
When putting the insertable = false, updatable = false into the entity class association's #JoinColumn all exceptions disappear and everything work fine. However, I don't see why the above code should not be working. It might be Hibernate having problems with this. Is the described a Hibernate bug, as it doesn't seem to evaluate #Column "insertable = false, updatable = false"?
In essence, what's the standard JPA way, the best practice, or preference where to put "insertable = false, updatable = false"?
Let me answer step by step.
1. When do you need ` insertable = false, updatable = false`?
Let's look at the below mapping,
public class Zip {
#ManyToOne
#JoinColumn(name = "country_code", referencedColumnName = "iso_code")
private Country country = null
#Column(name = "country_code")
private String countryCode;
}
Here we are referring to the same column in the table using two different properties. In the below code,
Zip z = new Zip();
z.setCountry(getCountry("US"));
z.setCountryCode("IN");
saveZip(z);
What will Hibernate do here??
To prevent these kind of inconsistency, Hibernate is asking you to specify the update point of relationships. Which means you can refer to the same column in the table n number of times but only one of them can be used to update and all others will be read only.
2. Why is Hibernate complaining about your mapping?
In your Zip class you are referring to the Embedded id class ZipId that again contains the country code. As in the above scenario now you have a possibility of updating the country_code column from two places. Hence the error given by Hibernate is proper.
3. How to fix it in your case?
No. Ideally you want your ZipId class to generate the id, so you should not add insertable = false, updatable = false to the countryCode inside the ZipId. So the fix is as below modify the country mapping in your Zip class as below,
#ManyToOne
#JoinColumn(name = "country_code", referencedColumnName = "iso_code",
insertable = false, updatable = false)
private Country country;
Hope this helps your understanding.
You can also solve this problem by using #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.
If no PrimaryKeyJoinColumn annotation is specified for a subclass in the JOINED mapping strategy, the foreign key columns are assumed to have the same names as the primary key columns of the primary table of the superclass.

Categories

Resources