I'm facing several problems setting up a One to one associations.
Can you help me set up the relationship using annotations for the following table.
Login -> (id, username, password)
Members -> (member_id, name, address)
Here member_id is same as id in login.
Here's the example that the Hibernate documentation provides:
#Entity
class MedicalHistory implements Serializable {
#Id Integer id;
#MapsId #OneToOne
#JoinColumn(name = "patient_id")
Person patient;
}
#Entity
class Person {
#Id #GeneratedValue Integer id;
}
Replace Person with Login, and MedicalHistory with Member, add the fields you need, and you have your answer.
Related
In my spring data project, there is an entity that looks like:
public class Employee {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
#Column(name="id")
private Integer id;
#Column(name="category")
private Category category;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="element_id")
private Department department;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="agency_id")
private Agency agency;
public Employee() {}
// routine getters and setters
}
In my EmployeeRepository, I find that I can derive a method, findEmployeeById() that seems to work in exactly the same way as the usual findById() (but I'm not sure). Can someone explain the difference between these two methods?
Optional<Employee> findEmployeeById (Integer id);
vs
Optional<Employee> findById (Integer id);
I "discovered" this by accident via autocomplete in my side (IntelliJ).
The difference is that while findEmployeeById() searches - as it states - employee by its field named id, findById searches by field annotated as #Id disregarding what is the name of the entity's id field.
In your case - as with many else's - the thing is that the #Id field happens to be named id so the result is the same.
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.
This title is closest I could get to what I am trying to do. :)
Let's start with how it should look in database (tables with columns):
Email
- id : PK
- email : String
CompanyEmail
- email_id : FK
- company_id : FK
PersonEmail
- email_id : FK
- person_id : FK
Company
- id : PK
Person
- id : PK
Now let's look at model:
#Entity
public class Company
{
#Id
#GeneratedValue
private Long id;
#OneToMany
private List<CompanyEmail> emails = new ArrayList<>();
}
#Entity
public class Person
{
#Id
#GeneratedValue
private Long id;
#OneToMany
private List<PersonEmail> emails = new ArrayList<>();
}
// Should this be #Entity? Maybe #MappedSuperclass? What strategy to use to be able to extend it?
public class Email // This is base email class/entity
{
#Id
#GeneratedValue
private Long id;
#javax.validation.constraints.Email
private String email;
}
#Entity
public class PersonEmail // This needs to somehow extend Email, so all Email data is also accessible here.
{
#ManyToOne
private Person person;
}
#Entity
public class ComanyEmail // This needs to somehow extend Email, so all Email data is also accessible here.
{
#ManyToOne
private Company company;
}
Now my question is - is it possible in Hibernate (latest) to achieve such structure?
Key points when designing above (what drove me):
Keep ALL emails in one table, for sanity checks (uniqueness).
Have smallest database footprint - above gives just one table for Email and then 2 join tables with 2 FK.
Still be able to keep the model design Hibernate-friendly (basically don't use any special queries, just JPA annotations). This means that both Company and Person can easily LAZY load their specific emails (subclasses), and also those subclassed emails can correspond to them (PersonEmail to Person, and CompanyEmail to Company) - making model bidirectional.
Note: I've also considered creating something like Contactable base class for Comapny and Person which would have Email list, but that doesn't suit my needs (neither class per table, nor same table designs).
My question is - is it possible? Even if I don't get answer with example based on classes I gave, just the fact will give me hope and I will find it.
Edit 1
Should I maybe use discriminator in Email table and then keep FK there?
Email
- id : PK
- email : String
- companyOrPerson : FK
- discriminator : Decides what FK points at
Company
- id : PK
Person
- id : PK
Here I am grabbing straws - I have no idea if such thing is possible, can discriminator decide about parent table (companyOrPerson)? Does bidirectional work here (like I mentioned, maybe I should make some base class Contactable)?
I am open to suggestions on how to do it well.
Favour composition over inheritence. It tends to simplify things.
If you had main entities of
Person
Company
Email
Then a couple of composites
PersonEmail (contains a Person and an Email)
CompanyEmail (contains a Company and an Email)
Would that not fit what you need?
e.g.
#Entity
public class Person {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
#OneToMany
#JoinColumn(name="person_id")
private List<PersonEmail> emails = new ArrayList<>();
}
#Entity
public class Company {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
#OneToMany()
#JoinColumn(name="company_id")
private List<CompanyEmail> emails = new ArrayList<>();
}
#Entity
public class Email {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
#javax.validation.constraints.Email
private String email;
}
#Entity
public class PersonEmail {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name="person_id", referencedColumnName="id")
private Person person;
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name="email_id", referencedColumnName="id")
private Email email;
}
#Entity
public class CompanyEmail
{
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name="company_id", referencedColumnName="id")
private Company company;
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name="email_id", referencedColumnName="id")
private Email email;
}
Hibernate Mapping
How to implement such a code?
Each company has two properties, they are company name and estimated annual earnings.
There are two types of companies: 1- Main company, 2 - Subsidiary company.
The company can belong only to one company but can have a few child companies.
public class Company {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String companyName;
private double estimatedAnnualEarnings;
private Company company; // here need to do a #OneToOne
private List<Company> subsidiaryCompany; // here need to do a #OneToMany
}
In your Implementation you should use :
The #Entity annotation in your class level, so the entity can be persisted to database.
The #Column annotation with the companyName and estimatedAnnualEarnings properties, so they can be persisted as columns in the database.
#ManyToOne annotation with the company field, so it can be mapped with a self-reference relationship.
The same goes with the subsidiaryCompany List which needs to be mapped with #OneToMany annotation to have a relationship too.
This is how should be your code:
#Entity
public class Company {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column
private String companyName;
#Column
private double estimatedAnnualEarnings;
#ManyToOne(cascade={CascadeType.ALL})
#JoinColumn(name="mainCompanyId")
private Company mainCompany;
#OneToMany(mappedBy="mainCompany")
private List<Company> subsidiaryCompanies;
//getters and setters goes here
}
Note
I changed the name of company field to mainCompany and
subsidiaryCompaniy to subsidiaryCompanies for better readability
and to make it fit the logic better.
If you want to give your entity a different name in the database you
should use #Table(name="differentName") in the class level with
#Entity annotation, the smae thing with the columns you can add
name property to the #Column annotation i.e
#Column(name="company_name") if you want different names.
I'm using JPA Hibernate/Spring boot to build a web server with MySQL database, and I'm trying to extend a POJO Entity that looks like this, with additional OneToMany Lists.
#Entity
#Table(name="user")
public class User {
#Id
#GeneratedValue
private Integer id;
#Column(nullable=false)
private String name;
....Constructors, getters and setters....
}
with this basic user entity, I just wanna make a UserInfo entity with additional information about the user's careers.
#Entity
public class UserInfo extends User {
#OneToMany(cascade= CascadeType.ALL, fetch= FetchType.EAGER)
#JoinColumn(name="user_id", referencedColumnName = "id")
private List<Career> careers;
....Constructors, getters, setters......
}
And I'm quite confused which inheritance strategy I should choose. I don't think its necessary to make another column or table for this.
Or should I just query twice..?
I'm kinda new to JPA so not sure which is considered as the best practice or design..
Edit:
This is how Career entity looks like. Just in case..
#Entity
#Table(name="career")
public class Career {
#Id
#GeneratedValue
private Integer id;
#Column(nullable=false)
private Integer user_id;
#Column(nullable=false)
private String name;
#Column(nullable=false)
private String description;
....Constructors, getters and setters....
}
Since extending User table was meaningless(just in my case), I changed the User class like this.
#Table(name="user")
public class User {
#Id
#GeneratedValue
private Integer id;
#Column(nullable=false)
private String name;
#OneToMany(fetch= FetchType.LAZY)
#JoinColumn(name="user_id", referencedColumnName = "id")
private List<Career> careers;
....Constructors, getters, setters......
}
Now I'm trying this with Spring Data JPA, and when I try to show the list of Users with their Careers, it is now querying more than 40 times taking about a minute to show the result.
Is this the N+1 problem..? how can I solve this?
In my opinion the error lies within the model itself. Why should UserInfo extend User? I cannot imagine which attributes or methods the UserInfo should inherit from a User. Typical inheritances would be "Developer" or "Administrator".
Why don't you add UserInfo as a 1:1 relation in your User entity? Another option is to omit UserInfo and put the Careers as a 1:n relation right into your User.
To prevent possible n+1 issues on a growing number of Careers you might want to change the fetch mode. See below
#OneToMany(fetch=FetchType.LAZY,mappedBy="user")
#Fetch(FetchMode.SUBSELECT)
private Set<Career> careers = new HashSet<>();