Spring JPA - how to allow duplicate entries for #Id - java

I am querying a view which joins three tables and returns the result like below:
select * from v_project_details
Project_ID Repo_Name Branch_Name
100 Repo1 Branch1
100 Repo1 Branch2
101 Repo2 Branch2
#Getter
#Setter
#Entity
#Table(name='v_project_details')
public class ProjectDetails{
#Id
#Column(name="Project_Id")
private int ProjectId
}
#Column(name="Repo_Name")
private String RepoName
}
#Column(name="Branch_Name")
private String BranchName
}
#Query(select p from v_project_details p)
List<ProjectDetails> findAll();
Results:
Project_ID Repo_Name Branch_Name
100 Repo1 Branch1
100 Repo1 Branch1 - I am expecting Branch2 here
101 Repo2 Branch2
when i query the table from spring jpa, i see three results but first row is repeated twice.
Looks like hibernate is not reinstantiating the object if #Id value is repeated more than once in the result set.
How do i force it to reinstantiate the object ? I do not have an unique identifier in the view as my view is joined from different tables.

#Id is used by JPA to identify the primary key. In your case, project_id as your primary key, which JPA understands that both row1 and row2 as same records. If project_id is not your unique identifier for your records but instead a combination of multiple fields like (eg: combination of project_id, repo_name and branch_name), you need to define a composite primary key based on those 3 fields. refer to below post by baledung to know more on composite primary keys.
Composite primary keys with JPA

JPA defines #Id as primary key field. Here is how w3scools defines primary key. Cite from https://www.w3schools.com/sql/sql_primarykey.ASP
The PRIMARY KEY constraint uniquely identifies each record in a table.
Primary keys must contain UNIQUE values, and cannot contain NULL values.
A table can have only ONE primary key; and in the table, this primary key can consist of single or multiple columns (fields).
So, primary key is always unique and prohibits null values.
JPA implementations can make use of it and store items effectively with assumption that #Id is always unique.
Your Project_ID can't be #Id since it's not unique, so you should remove this annotation and everything should work as expected. JPA requires entity to have primary key, so you can define composite key or include row number in your view, so you can make row number primary key.

Related

Create an Entity Class with two foreign keys - Spring JPA

How can i create entity class for the below table which has two foreign keys of two different tables.
CREATE TABLE `flights_info` (
`airline_id` bigint(20) NOT NULL,
`flight_infoid` bigint(20) NOT NULL,
UNIQUE INDEX `UK_mnghyk14c0ufcb2gs2k6fab40`(`flight_infoid`) ,
INDEX `FKm5m2579nqtr1wele0bimvme8m`(`airline_id`) ,
CONSTRAINT `FKlda61sltnw69kxw7b0gx6sj5s` FOREIGN KEY (`flight_infoid`) REFERENCES `flight_info` (`flight_infoid`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `FKm5m2579nqtr1wele0bimvme8m` FOREIGN KEY (`airline_id`) REFERENCES `airline_info` (`airline_id`) ON DELETE RESTRICT ON UPDATE RESTRICT
);
my entity class:
#Entity
public class FlightsInfo {
#Id
#JoinTable(name="AirlineInfo", joinColumns=#JoinColumn(name="airline_id"))
private AirlineInfo airline_id;
#OneToOne
#JoinColumn(name="flight_infoid")
private FlightInfo flight_infoid;
}
The problem is that your table does not have a primary key. So it's hard to point the #Id annotation at the right column. JPA however accepts tables without PKs as long as you have a unique column: https://en.wikibooks.org/wiki/Java_Persistence/Identity_and_Sequencing#No_Primary_Key
Luckily you have a unique constraint on the flight_infoid column, so there you should try to point your #Id annotation.

JPA mapping with two ManyToOne using an intermediary table (middle entity)

I am trying to set a JPA mapping with JoinTable, and it seems to be ignored when Hibernate (my JPA implementation) is doing a query.
To explain the use case
Each time a user gets a page of my app, I insert a line in the USAGE_LOG table (with the id of the user and the id of the page).
Each page is related to a category (for instance: settings, orders, items, news...) and a type (for instance create, update, display, delete).
So, I have some kind of middle entity table, that links a page to: a category + a type. Like a triplet: (page, category, type)
My table structure
table USAGE_LOG (for information only, this one works well)
ID PrimaryKey
USER_ID Foreign key to column ID of table USER
USAGE_LOG_PAGE_ID Foreign key to column ID of table USER_LOG_PAGE
table USAGE_LOG_PAGE
ID PrimaryKey
URL VARCHAR
USER_ACTION_ID Foreign key to column ID of table USER_ACTION
table USER_ACTION
ID PrimaryKey
ACTION_CATEGORY_ID Foreign key to column ID of table ACTION_CATEGORY
ACTION_TYPE_ID Foreign key to column ID of table ACTION_CATEGORY
table ACTION_CATEGORY
ID PrimaryKey
NAME VARCHAR
table ACTION_TYPE
ID PrimaryKey
NAME VARCHAR
So the USER_ACTION table is a join table with the particularity that it links a USAGE_LOG_PAGE to a ACTION_CATEGORY and a ACTION_TYPE at the same time.
Also, I can have several USAGE_LOG_PAGE that are linked to the same ACTION_CATEGORY and ACTION_TYPE.
Unfortunately, I cannot change the database structure (it is legacy code).
I have tried the following Mappping on the Entity "UsageLogPage"
#ManyToOne
#JoinTable(name="action",
joinColumns=#JoinColumn(name="ID", referencedColumnName="USER_ACTION_ID"),
inverseJoinColumns=#JoinColumn(name="ACTION_CATEGORY_ID", referencedColumnName="ID"))
#Getter #Setter
private ActionCategory actionCategory;
#ManyToOne
#JoinTable(name="action",
joinColumns=#JoinColumn(name="ID", referencedColumnName="USER_ACTION_ID"),
inverseJoinColumns=#JoinColumn(name="ACTION_TYPE_ID", referencedColumnName="ID"))
#Getter #Setter
private ActionType actionType;
(I use Lombok for #Getter and #Setter)
This mapping compiles, but when I try to get data, I have the following exception:
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'usagelogpa0_.actionCategory' in 'field list'
Indeed, the Hibernate query is:
select usagelogpa0_.ID as ID1_80_0_,
usagelogpa0_.actionCategory as actionCa2_80_0_,
usagelogpa0_.actionType as actionTy3_80_0_,
usagelogpa0_.URL as URL5_80_0_
from usage_log_page usagelogpa0_
where usagelogpa0_.ID=?
(the key part is the "actionCategory" and "actionType" in the select)
This is not what I expect, Hibernate should do a join.
Have you any idea of what I did wrong?
Thanks !
After lots of investigations, I have found that:
it wasn't working as expected because I put the #ManyToOne and the #JoinTable annotations at the attribute level. I created a getter by hand and put the annotations on it, and they were taken into account
it still wasn't working correctly, because Hibernate didn't find the column "USER_ACTION_ID" on the USAGE_LOG_PAGE table, at run time. This column wasn't in the available fields, for a reason (that I coudn't find). When adding a field "usage_action_id" in the entity "UsageLogPage", it found the attribute, but refused to create the mapping because USAGE_ACTION_ID isn't a primary key.
At the end, even if I couldn't change the database, I could change the object model.
So I created the middle entity "UserAction", binded it with ManyToOne on the UsageLogPage entity, removed the attribute "actionCategory" and "actionType" from the UsageLogPage and added them as ManyToOne in the new UserAction entity.
If you have a table that acts as a middle entity for 2 different ManyToOne relationships, perhaps the best solution is to create the middle entity in your object model.

Hibernate - ManyToMany using unique property of one of the mapped tables

Hibernate doesn't let me do a join table with a unique field "docket", no matter if I specify the "referencedColumnName = "docket"" (i thought the idea of this property was to tell Hibernate which field to use, in case it is not the primary key).
Database tables:
student
---------------
id (PK) | docket (UNIQUE)
inscription
---------------
course_id | docket
inscription's PK is (course_id, docket)
course
---------------
id (PK)
The above problems raise with the following configuration:
On Student Entity:
#ManyToMany
#JoinTable(
name="inscription",
joinColumns=#JoinColumn(referencedColumnName = "docket", name="docket"),
inverseJoinColumns=#JoinColumn(name="course_id", referencedColumnName = "id")
)
private List<Course> studentCourses;
On Course Entity:
#ManyToMany(mappedBy = "studentCourses")
private List<Student> students;
What causes the problem is that, when project is deployed, Hibernate executes the statement:
alter table public.inscription add constraint FKp625s5r1hmlggpgeq4x2nju91 foreign key (docket) references public.student
which is (of course) incorrect, as it is not specifying that docket is a unique field.
What it should be doing is:
alter table public.inscription add constraint FKp625s5r1hmlggpgeq4x2nju91 foreign key (docket) references public.student(docket)
but I don't know how can I tell it to do so.
Any help?
Thanks in advance.
Found the answer at the official documentation.
From JPA 2.0 documentation: http://download.oracle.com/otndocs/jcp/persistence-2.0-fr-oth-JSpec/:
11.1.21 JoinColumn Annotation
The JoinColumn annotation is used to specify a column for joining an entity association or element
collection.
...
The name annotation element defines the name of the foreign key column. The remaining annotation
elements (other than referencedColumnName) refer to this column and have the same semantics as
for the Column annotation.
If the referencedColumnName element is missing, the foreign key is assumed to refer to the primary
key of the referenced table.
Support for referenced columns that are not primary key columns of the referenced table is optional.
Applications that use such mappings will not be portable.
So, perhaps, what was going on was that Hibernate does not have this feature implemented, as it is not mandatory.
What I did to fix it was to modify the inscription table, replacing each field by the corresponding primary key.
(i thought the idea of this property was to tell Hibernate which field to use, in case it is not the primary key)
Your assumption is in contradiction with the JPA 2.0 specification provided you are using Hibernate as an implementation of the JPA because the following extract states that you have to join on primary keys. It doesn't say anything about unique fields:
2.10.4 Bidirectional ManyToMany Relationships
Assuming that:
Entity A references a collection of Entity B.
Entity B references a collection of Entity A.
Entity A is the owner of the relationship.
The following mapping defaults apply:
Entity A is mapped to a table named A.
Entity B is mapped to a table named B.
There is a join table that is named A_B (owner name first). This join table has two foreign key columns. One foreign key column refers to table A and has the same type as the primary key of table A. The name of this foreign key column is formed as the concatenation of the following:
the name of the relationship property or field of entity B; "_"; the name of the primary key column in table A.
The other foreign key column refers to table B and has the same type as the primary key of table B. The name of this foreign key column is formed as the concatenation of the following: the name of the relationship property or field of entity A; "_"; the name of the primary key column in table B.
(I added the format; the text is the original quotation from the specification.)
docket is not primary key in your case and therefore you cannot join on it.

Joining a legacy table with no PK with Hibernate

I have 2 legacy tables:
CREATE TABLE A (
ID NUMBER PRIMARY KEY ,
DATA NUMBER
)
CREATE TABLE A_CONF (
A_ID NUMBER, // FK to A
INFO VARCHAR2(256)
)
Creating the JPA entity for A is straightforward. Yet, what can I do retrieve the multiple INFO fields that can be associated to an instance of A since there is no PK in A_CONF and therefore cannot create an entity for it?
Thanks for helping.
Seems like you are looking for what JPA calls an "element collection":
#Entity
public class A {
#Id
private Long id;
private Long data;
#ElementCollection
#CollectionTable(name="A_CONF", joinColumns=#JoinColumn(name="A_ID")) // A_ID would be the default join column
#Column(name="INFO")
private Set<String> infos; // using Set assuming unique values
}
You can define a primary key in your model class even if your table doesn't have one, just pick one or some columns in your model and put them as ids.

JPA composite key #OneToMany

I have the following existing DB schema, which I'd like to recreate with Java and plain JPA annotations (using hibernate as provider, so hibernate specific annotations would work as a last resort):
CREATE TABLE users (
user_id NUMBER NOT NULL -- pk
);
CREATE TABLE userdata_keys (
userdata_key_id NUMBER NOT NULL, -- pk
key VARCHAR2(128) NOT NULL
);
CREATE TABLE users_userdata (
user_id NUMBER NOT NULL, -- fk users.user_id
userdata_key_id NUMBER NOT NULL, -- fk userdata_keys.userdata_key_id
value VARCHAR2(256)
);
I've thus created the following classes and annotations:
class User {
#Id
Long id;
#OneToMany
Set<Userdata> userdata;
}
class UserdataKey {
#Id
Long id;
String key;
}
class Userdata {
String value;
#EmbeddedId
UserdataId userdataId;
}
#Embeddable
class UserdataId {
User user;
UserdataKey userdataKey;
}
I left out columnName attributes and other attributes of the entities here.
It does however not quite work as intended. If I do not specify a mappedBy attribute for User.userdata, hibernate will automatically create a table USERS_USERS_USERDATA, but as far as I've seen does not use it. It does however use the table which I specified for the Userdata class.
Since I'm rather new to Java and hibernate as well, all I do to test this currently is looking at the DB schema hibernate creates when persisting a few sample entries.
As a result, I'm entirely puzzled as to whether I'm doing this the right way at all. I read the hibernate documentation and quite a bunch of Google results, but none of them seemed to deal with what I want to do (composite key with "subclasses" with their own primary key).
The mappedBy attribute is mandatory at one of the sides of every bidirectional association. When the association is a one-to-many, the mappedBy attribute is placed ot the one- side (i.e. on the User's userdata field in your case).
That's because when an association is bidirectional, one side of the association is always the inverse of the other, so there's no need to tell twice to Hibernate how the association is mapped (i.e. which join column or join table to use).
If you're ready to recreate the schema, I would do it right (and easier), and use a surrogate auto-generated key in users_userdata rather than a composite one. This will be much easier to handle, in all the layers of your application.

Categories

Resources