I'm facing some problems with unidirectional onetomany mapping.
I got tables Users and Rubrica:
User (
scode double precision NOT NULL,
...
CONSTRAINT utenti_pkey PRIMARY KEY (scode)
)
Rubrica (
id serial NOT NULL,
id_owner integer NOT NULL,
id_contact integer NOT NULL,
CONSTRAINT rubrica_pkey PRIMARY KEY (id ),
CONSTRAINT rubrica_fk01 FOREIGN KEY (id_owner)
REFERENCES users (scode) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT rubrica_fk02 FOREIGN KEY (id_contact)
REFERENCES users (scode) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
Don't mind about Users double PKey. It's a customer's table and I can't modify it. Rubrica store relation between its owner, a User, and contacts, a set of User too.
User is mapped as follow:
#SuppressWarnings("serial")
#Entity
#Table(name = "utenti", schema = "public")
public class User implements Serializable {
#Id
#Column(name = "scode", unique = true, nullable = false)
private Integer scode;
...
}
Ok. Here is when problems come. If I map Rubrica like this:
public class Rubrica2 implements Serializable {
#Id
#Column(name = "id", nullable = false, unique = true)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "id_owner", nullable = false, unique = true, referencedColumnName = "scode")
private User owner;
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name = "id_contact", nullable = false, updatable = false, insertable = true, referencedColumnName = "scode")
private Set<User> relations = new HashSet<User>();
...
}
JBoss gave me this exception at deploy:
Caused by: org.hibernate.MappingException: Unable to find column with logical name: scode in org.hibernate.mapping.Table(public.rubrica) and its related
supertables and secondary tables
If I map Rubrica this way:
public class Rubrica2 implements Serializable {
#Id
#Column(name = "id", nullable = false, unique = true)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "id_owner", nullable = false, unique = true, referencedColumnName = "scode")
private User owner;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "scode")
private Set<User> relations = new HashSet<User>();
...
}
I got bad behavior at runtime. If I run this code
r = new Rubrica2();
q2.setParameter("id", ownerID);
User owner = (User) q2.getSingleResult();
r.setOwner(owner);
q2.setParameter("id", contactID);
User u = (User) q2.getSingleResult();
r.getRelations().add(u);
entityManager.persist(r);
I got this exception:
Hibernate: insert into public.rubrica (id_owner) values (?)
11:08:19,440 DEBUG [org.hibernate.engine.jdbc.spi.SqlExceptionHelper] (EJB default - 7)
ERROR: null values in column "id_contact" violates not-null constraint [n/a]: org.postgresql.util.PSQLException:
ERROR: null values in column "id_contact" violates not-null constraint
I follow theory indicated here about onetomany unidirectional.
I'm using JPA2.0, Hibernate4 (as provided by JBoss7.1.1.Final) and PostgresSQL.
This mapping, or the database design, makes no sense. If you want one Rubrica to have many contacts, you can't have a foreign key to the relation in the rubrica table. A foreign key can only reference one contact, not many.
To map such a one-to-many association, you would need a foreign key in user to rubrica (all the users having the same rubrica_id would be the contacts of this rubrica), or a join table between both tables.
Related
Let's imagine that we have two entities in the database. We have a simple Server with a unique Id:
PostgreSQL:
CREATE TABLE public.servers (
id bigint NOT NULL,
name character varying(64) NOT NULL
);
ALTER TABLE ONLY public.servers
ADD CONSTRAINT servers_pkey PRIMARY KEY (id);
Class entity:
#Entity
#Table(name = "servers")
public class Server {
#Id
private long id;
private String name;
#OneToMany(mappedBy = "server", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private List<Channel> channels;
public Server() {}
public Server(String name) {
this.name = name;
channels = new ArrayList<>();
}
// Getters and Setters...
}
Each Server can have several Channels that also have a unique id and belong to a server:
PostgreSQL:
CREATE TABLE public.channels (
id bigint NOT NULL,
server_id bigint NOT NULL,
name character varying NOT NULL
);
ALTER TABLE ONLY public.channels
ADD CONSTRAINT channels_pkey PRIMARY KEY (id);
ALTER TABLE ONLY public.channels
ADD CONSTRAINT fkey_channel_server FOREIGN KEY (server_id) REFERENCES public.servers(id);
Class entity:
#Entity
#Table (name = "channels")
public class Channel {
#Id
private long id;
private String name;
#ManyToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "server_sn")
private Server server;
public Channel() {}
public Channel(Server server, String name) {
this.server = server;
this.name = name;
}
// Getters and Setters...
}
Next, I create a View in PostgreSQL:
CREATE VIEW public.summary AS
SELECT servers.s_snowflake AS server_id, channels.c_snowflake AS channel_id, channels.name FROM servers
JOIN channels ON servers.s_snowflake = channels.server_sn;
I want to get data from this view but I'm facing a problem on how to implement the class. I have tried something like this:
#Embeddable
class SummaryPK implements Serializable {
private long server_id;
private long channel_id;
}
#Entity
#Immutable
#Subselect("SELECT * FROM summary")
public class Summary {
#EmbeddedId
private SummaryPK summaryPK;
#MapsId("server_id")
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Server.class)
#JoinColumn(name = "id", insertable = false, updatable = false)
private Server server;
#MapsId("channel_id")
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Channel.class)
#JoinColumn(name = "id", insertable = false, updatable = false)
private Channel channel;
private String name;
// Getters and Setters
}
In the program, I want to receive a list of Entity, for example, by passing the Server ID. Any idea how to implement a working structure here?
Fixed the problem, there were incorrect id in JoinColumn:
#JoinColumn(name = "server_id", insertable = false, updatable = false)
#JoinColumn(name = "channel_id", insertable = false, updatable = false)
I have 2 tables:
CREATE TABLE user_profile (
id VARCHAR(36) PRIMARY KEY,
first_name VARCHAR(255) NOT NULL
);
CREATE TABLE user_phone (
id VARCHAR(36) PRIMARY KEY,
user_id VARCHAR(36),
phone_number VARCHAR(255) NOT NULL,
code VARCHAR(10) NOT NULL,
validated_at DATETIME NULL
);
ALTER TABLE user_phone ADD CONSTRAINT user_phone_FK FOREIGN KEY (user_id) REFERENCES user_profile(id) ON DELETE CASCADE;
so child has ID reference to his parent, because if I will delete parent I want this child to be deleted also.
In User class I have:
#Entity
#Table(name = "user_profile")
public class UserProfile {
...
#OneToOne
#JoinColumn(name = "id", referencedColumnName = "user_id")
private UserPhone phoneNumber;
but then when I run it I see in JPA logs:
from
user_profile userprofil0_
where
userprofil0_.id=?
why it looks there by ID ? should be by user_id field. Or I missunderstood smth there..
thanks!
One day later and finally I found the solution.
Its pretty tricky in spring and not maybe so intuitive.
So - from user_phone table Ive removed additional ID column, and now user_id is there primary key, only 1 change in sql.
Then in spring:
in UserProfile:
#OneToOne(mappedBy = "userProfile", cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private UserPhone userPhone;
and in UserPhone:
#Id
#Column(name = "user_id", nullable = false)
#Type(type = "uuid-char")
private UUID id;
#OneToOne(fetch = FetchType.LAZY)
#MapsId
#JoinColumn(name = "user_id")
private UserProfile userProfile;
#PrimaryKeyJoinColumn annotation indicates that the primary key of the UserProfile is used as the foreign key value for the associated UserPhone.
class Employee {
#EmbeddedId
private EmployeeId id; // composite key with two fileds companyId and employeeNumber
#OneToMany(mappedBy = "employee", cascade = CascadeType.ALL)
private List<Phone> phoneList;
}
class Phone {
#EmbeddedId
private PhoneId phoneId; // composite key with three fileds companyId , employeeNumber and phoneNumber
#ManyToOne
#JoinColumns({
#JoinColumn(name = "COMPANY_ID", referencedColumnName = "COMPANY_ID", insertable = false,
updatable = false),
#JoinColumn(name = "EMPLOYEE_NUMBER_NEW", referencedColumnName = "EMPLOYEE_NUMBER",
insertable = false, updatable = false)})
private Employee employee;
}
In above example, Employee table gets generated with column employeeId, employeeNumber. And Phone table gets generated with employeeId, employeeNumber, phoneNumber, employee_nuber_new.
When I am creating employee object and phone object, at the end employeeId and employeeNumber values in Phone table gets populated from Employee object ( as part of FK ) but employee_number_new column still have "null" value. I want to updated employee_number_new column with employeeNumber from Employee table.
Tried lot of ways including JoinFormula and all but no success. Can anybody throw some light on this ?
Thanks...
The reason you are getting a null value in the Phone table because you are using insertable = false in your JoinColumn definition. If you could set this to true then your problem would be solved.
Hibernate doesn't support the mixing of insertable and non-insertable columns. So you have to make companyId insertable too. But that leads to the possibility of your primary key being changed in the Phone table which you don't want.
So, we are left with two options, if the EMPLOYEE_NUMBER is the same in both tables don't create the new column EMPLOYEE_NUMBER_NEW column and use EMPLOYEE_NUMBER.
But if EMPLOYEE_NUMBER is not the same in both tables then and you need to save the employee number from employee object, create another insertable column it can be COMPANY_ID_EMP and make updateable false. That way when you save a phone object both EMPLOYEE_NUMBER_NEW and COMPANY_ID_EMP will be saved. But in practice COMPANY_ID_EMP and COMPANY_ID will be the same value.
The following code worked for me
#ManyToOne
#JoinColumns({
#JoinColumn(name = "companyIdEmp", referencedColumnName = "companyId", updatable = false),
#JoinColumn(name = "employeeNumberNew", referencedColumnName = "employeeNumber", updatable = false)})
private Employee employee;
I am trying to map a non primary key column between 2 tables using one-to-one mapping through JPA.
The OneToOne is not performing the join on the mentioned column rather it is picking up the Id field.
Below is the table structure:
Person table
id (PK)
name
college
College table
id (PK)
clg_name
location
Location table
id (PK)
loc_name
I need to provide OneToOne mapping between College and Location using the columns location and loc_name respectively. I have tried using the #NaturalId, #MapsId and by providing the reference column name. Still it uses the id field
//person
#Entity
#Table(name = "PERSON", schema = "DETAILS")
#SecondaryTables({
#SecondaryTable(name = "COLLEGE", schema = "DETAILS")
})
class Person{
Person(){
this.college = new College();
}
#Id
#Column(name = "ID", nullable = false)
private Long id;
#Column(name = "NAME", nullable = false)
private String name;
#Column(name = "COLLEGE_NAME", table = "COLLEGE", nullable = false)
private String college;
#OneToOne(cascade = { CascadeType.ALL }, fetch = FetchType.EAGER, mappedBy = "person")
#JoinColumn(name = "ID")
private College college;
//getter setters
}
//college
#Entity
#Table(name = "COLLEGE", schema = "DETAILS")
class College{
College(){
}
#Id
#MapsId
#OneToOne()
#JoinColumn(name = "ID")
private Person person;
#OneToOne(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER, mappedBy = "college")
#JoinColumn(referencedColumnName = "LOC_NAME")
private Location location;
#Column(name = "LOCATION", nullable = false)
private String loc;
//getter setters
}
//location
#Entity
#Table(name = "LOCATION", schema = "DETAILS")
class Location{
Location(){}
#Id
#Column(name = "ID")
private Long collegeId;
#MapsId
#OneToOne()
#JoinColumn(name = "LOC_NAME", referencedColumnName ="LOCATION", nullable = false, unique = true)
private College college;
#Column(name = "LOC_NAME", nullable = false)
private String locName;
//getter setters
}
In the above code, I am facing in OneToOne mapping issue using the location name columns. I am querying the Person object from the JPA repository by querying "from Person p where p.id = :id".
The generated JPA queries in logs for 1to1 mapping appears to be
select from details.college college0_ left outer join details.location location1_ on college0_.id=location1_.locName where college0_.id=?
Error:
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessResourceUsageException: could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet] with root cause
Caused by: java.sql.SQLSyntaxErrorException: ORA-01722: invalid number
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:447)
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:396)
at oracle.jdbc.driver.T4C8Oall.processError(T4C8Oall.java:951)
at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:513)
If I remove #MapsId from Location then I get below error:
org.hibernate.AnnotationException: A Foreign key refering has the wrong number of column. should be 0
I have two table:
CREATE TABLE [LeTYPE](
[LeNAME] [varchar](100) NOT NULL,
[Le_DESC] [varchar](500) NULL,
[LeFOR] [varchar](50) NOT NULL,
CONSTRAINT [PK_LeTYPE] PRIMARY KEY CLUSTERED
(
[LeNAME] ASC
)
)
CREATE TABLE [Le](
[SN] [int] IDENTITY(1,1) NOT NULL,
[LeNAME_FK] [varchar](100) NOT NULL,
[Le_SN] [int] NULL,
[LOWERRANGE] [float] NOT NULL,
[UPPERRANGE] [float] NOT NULL,
[Le_DESC] [varchar](500) NULL,
[COLOR] [varchar](45) NULL,
CONSTRAINT [Le_pk] PRIMARY KEY CLUSTERED
(
[SN] ASC
))
GO
ALTER TABLE [Le] WITH CHECK ADD CONSTRAINT [FK_Le_LeTYPE] FOREIGN KEY([LeNAME_FK])
REFERENCES [LeTYPE] ([LeNAME])
ON UPDATE CASCADE
ON DELETE CASCADE
GO
ALTER TABLE [Le] CHECK CONSTRAINT [FK_Le_LeTYPE]
GO
One tuple in LETYPE will have many LE.
JPA Entity generated by netbeans:
public class Letype implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 100)
#Column(nullable = false, length = 100)
private String Lename;
#Size(max = 500)
#Column(name = "Le_DESC", length = 500)
private String LeDesc;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 50)
#Column(nullable = false, length = 50)
private String Lefor;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "LenameFk", fetch = FetchType.LAZY)
private List<Le> LeList;
}
public class Le implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#NotNull
#Column(nullable = false)
private Integer sn;
#Column(name = "Le_SN")
private Integer LeSn;
#Basic(optional = false)
#NotNull
#Column(nullable = false)
private double lowerrange;
#Basic(optional = false)
#NotNull
#Column(nullable = false)
private double upperrange;
#Size(max = 500)
#Column(name = "Le_DESC", length = 500)
private String LeDesc;
#Size(max = 45)
#Column(length = 45)
private String color;
#JoinColumn(name = "LeNAME_FK", referencedColumnName = "LeNAME", nullable = false)
#ManyToOne(optional = false, fetch = FetchType.LAZY)
private Letype LenameFk;
}
Now, What I wanted was if I add a LETYPE from JSF view I would like to add multiple LE also at the same time.
LETYPE
-LE1
-LE2
-LE3
The structure of entities generated by netbean has letypeList with in Le and not the opposite.
Is my database structure wrong or How to do it right?
Yes i feel like you got wrong relationship in database between LE and LETYPE.
Current relation from JPA perspective is treated as One LE can have Many LETYPE that is why you see below code in LE entity class.
#OneToMany(mappedBy = "LeFk")
private List<Letype> letypeList;
But it is wrong, you need Reverse relation i.e, One LETYPE can have Many LE (One-To-Many).
Basically your database structure is wrong I guess, you have to maintain FK column in LE table, where as currently you maintaining in LETYPE.
In database world, FK column always resides in Many side , in your case i.e. LE table.
What to do now
Remove FK column in LETYPE table
Add FK column in LE table referring to LETYPE table primary key
Generate JPA Entity again
Then you see right JPA code with right relation ship.
I hope then you should be able to do what you need.
"What I wanted was if I add a LETYPE from JSF view I would like to add multiple LE also at the same time" means that the LETYPE can be seen as the component class, and the LE as the composite class, so you should reverse the mapping annotations. To illustrate this and what #Jayasagar well explained in addition, the two classes's forms look like :
Letype.java :
public class Letype implements Serializable {
...
#OneToMany(mappedBy="letype")
private List<Le> les;
}
Le.java :
public class Le implements Serializable {
...
#ManyToOne
#JoinColumn(name = "Letype_FK", referencedColumnName = "LENAME")
private Letype letype;
}