I have an Employee and Address with one-to-one bi-directional mapping:
#Entity
public class Employee {
#Id
#Column(name = "EMP_ID")
private long id;
private String firstName;
private String lastName;
private double salary;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ADDRESS_ID")
private Address address;
}
Below is my address entity:
#Entity
public class Address {
#Id
#Column(name = "ADDRESS_ID")
private long id;
private String street;
private String city;
private String province;
private String country;
private String pinCode;
#OneToOne(fetch = FetchType.LAZY, mappedBy = "address")
private Employee owner;
}
In Address I have set Fetch type as Lazy. So if I get an address then I am expecting hibernate to run select query on address only, but I see in logs that it is trying to get Employee also.
Below is my HQL query:
List<Address> emps = session.createQuery("from Address where id=20").list();
These are the queries run by Hibernate:
Hibernate:
/*
from
Address
where
id=20 */ select
address0_.ADDRESS_ID as ADDRESS_1_0_,
address0_.city as city2_0_,
address0_.country as country3_0_
from
Address address0_
where
address0_.ADDRESS_ID=20
Hibernate:
/* load Employee */ select
employee0_.EMP_ID as EMP_ID1_1_0_,
employee0_.ADDRESS_ID as ADDRESS_5_1_0_,
employee0_.firstName as firstNam2_1_0_,
employee0_.lastName as lastName3_1_0_
from
Employee employee0_
where
employee0_.ADDRESS_ID=?
Why hibernate loads Employee eagerly even when I set its fetching strategy as LAZY.
This great article describes the problem and a possible solution:
https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibernate/
Possible solution: It must be a one-directional relationship from child to parent. The parent cannot have a #OneToOne field to access the child because:
"For every managed entity, the Persistence Context requires both the entity type and the identifier, so the child identifier must be known when loading the parent entity, and the only way to find the associated {child} primary key is to execute a secondary query."
Second solution: Use #OneToMany instead. Don't use #OneToOne because it has this complicated, subtle, quirky problem. You can alter the code to only allow one-to-one access and optionally add a unique key to enforce 1-1.
Lazy loading on one-to-one mapping is possible either by
Setting optional=false (If its not nullable) or
JoinColumn (not on PK and might require schema change)
You can refer to this link for moreinfo.
Explanation : You can refer to explanation link for detailed explanation about this.
Newer version of hibernate can't use trick such as optional=false.
Checkout the updated solution best way to map a onetoone relationship
Related
i have the empty database in mysql, and two java entites. One of those have unidirectional relation. When hibernate tryes to create tables, i got the error:
Error executing DDL "alter table entry add constraint FK6ov2k83sx3crs9v3q8nvjuf1j foreign key (category_name) references category (name)" via JDBC Statement
There are my entites:
#Entity
public class Entry {
#Id
#GeneratedValue( strategy = GenerationType.IDENTITY)
private int id;
#Column
private String myfio;
private String descr;
#OneToOne(cascade = CascadeType.ALL)
private Category category;
}
And the second:
#Entity
#Table(name="category")
public class Category {
#Id
#Column
private String name;
}
How to create tables without errors?
OneToOne relationship shares the same id. So it should be the same type, but the first one is int (actually it should be Integer to allow null value for the transient (not stored) entities) and the second one is String. It seems you simply missed a line. Also, it worths to mention Vlad Mihalchea’s article https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibernate/
I'm working with Spring Boot 2.3, Spring Data and Hibernate.
I've the following entities
#Entity
#Getter
#Setter
#EqualsAndHashCode(of = "id")
public class User {
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Id
private Long id;
private String name;
#OneToOne(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
private Address address;
#Version
private Long version;
}
#Entity
#Getter
#Setter
#EqualsAndHashCode(of = "id")
public class Address {
#Id
private Long id;
private String fullAddress;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "id")
#MapsId
private User user;
#Version
private Long version;
}
When the following code is executed, any query related to the user repository is performed (and for me it is the expected behavior).
Address addressFromDb = addressRepository.findAll().get(0);
log.info("" + addressFromDb.getUser().getId());
// select address0_.id as id1_0_, address0_.full_address as full_add2_0_, address0_.version as version3_0_ from address address0_
but when I execute the following code, then there are multiple queries and I don't understanding why. Apparently the FetchType.LAZY from user to address is not honored.
User userFromDb = userRepository.findAll().get(0);
// select user0_.id as id1_4_, user0_.name as name2_4_, user0_.version as version3_4_ from user user0_
// select address0_.id as id1_0_0_, address0_.full_address as full_add2_0_0_, address0_.version as version3_0_0_ from address address0_ where address0_.id=?
What am I missing?
In order to be more helpful and more clear I've created the following github repo
Hibernate (or more specifically PersistenceContext) needs to know, whether the entity exists or not, so that it can decide, whether to provide a proxy for the entity or null. This does not apply for XToMany relationships, because the whole collection can be wrapped in a proxy and in special case it will be empty.
It is also important to point out, that FetchType is just a suggestion for the JPa implementation and there is no guarantee, that in every case it will be fulfilled. You can read more about #OneToOne here, especially in terms of fetching strategy:
While the unidirectional #OneToOne association can be fetched lazily, the parent-side of a bidirectional #OneToOne association is not. Even when specifying that the association is not optional and we have the FetchType.LAZY, the parent-side association behaves like a FetchType.EAGER relationship. And EAGER fetching is bad.
Even if the FK is NOT NULL and the parent-side is aware about its non-nullability through the optional attribute (e.g. #OneToOne(mappedBy = "post", fetch = FetchType.LAZY, optional = false)), Hibernate still generates a secondary select statement.
For every managed entity, the Persistence Context requires both the entity type and the identifier,
so the child identifier must be known when loading the parent entity, and the only way to find the associated post_details primary key is to execute a secondary query.
Bytecode enhancement is the only viable workaround. However, it only works if the parent side is annotated with #LazyToOne(LazyToOneOption.NO_PROXY) and the child side is not using #MapsId.
I am trying to join to Hibernate Entities in a OneToOne Mapping. I am able to fetch the data for a given primary key from the Main Entity, the joining entity, however, returns null. I am new to hibernate and any help will be appreciated.
I have two Tables,
PT_CORE
Primary Key: ptId - Integer;
Foreign Key: stId(ST_AUX) - Integer;
Columns: ptId, ptName
ST_AUX
Primary Key: stId;
Columns: stId, stName
The two tables get populated by other applications and mine is a read-only operation.
Below is my first Entity class(PtCore.java)
#Entity
#Table(name="PT_CORE")
public class PtCore implements Serializable{
#Id
#Column(name="ptId", nullable = false)
private int id;
#Column(nullable=false)
private int stId; //The Foreign key column
#OneToOne
#JoinTable( name = "core_aux", joinColumns = {#JoinColumn(Name="ptId")},
inverseJoinColumns = {#JoinColumn(Name="stId")}
)
private StAux staux;
//Getters, setters and toString() for above
}
StAux is another Entity, defined as below,
#Entity
#Table(name="ST_AUX")
public class StAux implements Serializable {
#Id
#Column(nullable=false)
private Integer stId;
#OneToOne
private PtCore ptcore;
#Column
private String stName;
//Getters, Setters and toString follow.
}
I do below in the Service method:
PtCore obj = (PtCore) session.get(PtCore.class,1);
System.out.println(obj);
In the Results, I get the value of ptName, but the stAux class variables are null, Indicating that the join does not work as expected.
First of all you have the mapping information existing in your PT_CORE. And I assume it is something like FOREIGN KEY (stid) REFERENCES (stid). If you want to use existing schema and existing data I guess there is no mapping table core_aux really existing. At least you did not mention it. However it is visible as #JoinTable annotation but still there is this above mentioned foreign key which seems to be the real mapping (so again not the join table).
I suggest the following
remove this
#Column(nullable=false)
private int stId; //The Foreign key column
from your PtCore. I think it is not needed. Also in PtCore, remove the #JoinTable (because what I told above) and add mapping informaiion to #OneToOne annotation, like:
#OneToOne
#JoinColumn(name = "stid")
private StAux staux;
from your PT_CORE.
Then in StAux alter also a bit:
#Id
#Column(name = "stid") // this might not be needed but if there is like "st_id"...
private Integer stId; // so just for sure
#OneToOne(mappedBy = "staux")
private PtCore ptcore;
Because you have existing tables and constraints there might raise errors if hibernate tries to auto-generate those again by JPA instructions.
Check this for example for more information.
UPDATE: just realized also that in your title is #OneToMany but in your code is #OneToOne.
So you might want to elaborate your question and/or title a bit.
In your relation, the owning side is PtCore, the inverse side is StAux.
In bidirectional OneToOne relations, the inverse side has to have the mappedBy attribute. Actually, the mappedBy attribute contains the name of the association-field on the owning side.
So, you must change your inverse side code (StAux Entity). You have to add mappedBy attribute to #OneToOne in StAux class:
#OneToOne(mappedBy="staux")
private PtCore ptcore;
I am trying to better familiarize myself with JPA so I created a very simple project. I have a User Class and an Address class. It appears that I have to persist both even though I am adding Address to my User class?
User:
import javax.persistence.*;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
#Entity
#Table(name = "usr") // #Table is optional, but "user" is a keyword in many SQL variants
#NamedQuery(name = "User.findByName", query = "select u from User u where u.name = :name")
public class User {
#Id // #Id indicates that this it a unique primary key
#GeneratedValue // #GeneratedValue indicates that value is automatically generated by the server
private Long id;
#Column(length = 32, unique = true)
// the optional #Column allows us makes sure that the name is limited to a suitable size and is unique
private String name;
// note that no setter for ID is provided, Hibernate will generate the ID for us
#OneToMany(fetch=FetchType.LAZY, mappedBy="user")
private List<Address> addresses;
Address:
#Entity
public class Address {
#Id // #Id indicates that this it a unique primary key
#GeneratedValue // #GeneratedValue indicates that value is automatically generated by the server
private Long id;
#Column(length=50)
private String address1;
#ManyToOne
#JoinColumn(name="user_id")
private User user;
EntityManager:
EntityManager entityManager = Persistence.createEntityManagerFactory("tutorialPU").createEntityManager();
entityManager.getTransaction().begin();
User user = new User();
user.setName("User");
List<Address> addresses = new ArrayList<Address>();
Address address = new Address();
address.setAddress1("Address1");
addresses.add(address);
user.setAddresses(addresses);
entityManager.persist(user);
entityManager.persist(address);
entityManager.getTransaction().commit();
entityManager.close();
Probably doing something wrong...just not sure what it is?
Any suggestions would be appreciated.
Thanks,
S
Try the cascade element for the annotation.
#OneToMany(fetch=FetchType.LAZY, mappedBy="user", cascade=CascadeType.PERSIST)
private List<Address> addresses;
The documentation says that by default no operation is cascaded. It also states that the cascade operation is optional, so it really depends on the implementation that you are using.
Also, while setting the relationship, make sure you set both sides of the relationship. Set addresses to the user and user to the addresses.
What you're talking about it's called Cascading. That means doing the same action to nested objects, such as an Address in your User. By default, there is no cascading at all if you don't specify any CascadeType.
You can define various cascade types at once
#OneToMany(fetch=FetchType.LAZY, mappedBy="user", cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE})
private List<Address> addresses;
or just tell JPA to cascade every operation:
#OneToMany(fetch=FetchType.LAZY, mappedBy="user", cascade = CascadeType.ALL)
private List<Address> addresses;
Both ways will result in, for example, a persisted Address while persisting an User or the deletion of the associated Address when an User is removed.
But!... if you remove CascadeType.REMOVE from the first example, removing an User won't remove its associated Address (The removal operation won't be applied to nested objects).
You are using a oneToMany annotation. From my understanding you have to persist the parent class (the USer) when you want to add a child class (Address) to it.
By persisting the User class, you do let know JPA know which row to update.
So the situation is as follows: there is an entity which needs to be connected with a dictionary. Imagine a following structure
create table Address (
addressId bigint not null,
addressLine1 varchar(255),
city varchar(255),
country varchar(255),
state varchar(255),
zipCode varchar(255),
primary key (addressId)
)
create table STATES_DICT (
state_code varchar(255),
state_fullname varchar(255),
primary key (state_code)
)
I want to map both ADDRESS and STATE_DICTIONARY into a single entity.
#Entity
#Table(name = "ADDRESS")
public class Address implements Serializable {
#Id
#Column(name = "ADDRESSID")
private int addressId;
#Column(name = "ADDRESSLINE1")
private String addressLine1;
#Column(name = "STATE")
private String state;
//??? annotations
private String fullStateName;
#Column(name = "ZIPCODE")
private String zipCode;
#Column(name = "CITY")
private String city;
#Column(name = "COUNTRY")
private String country;
//... getters and setters
}
For a pure SQL I'll run
select a.ADDRESSID, a.ADDRESSLINE1, a.CITY, a.ZIPCODE, a.STATE,
d.STATE_FULLNAME, a.COUNTRY
from ADDRESS a, STATES_DICT d where a.STATE = d.STATE_CODE
but I'm having severe problems with mapping it with JPA.
I cannot use #SecondaryTable because the tables are not mapped by primary keys
The best I could get was:
#ElementCollection
#JoinTable(name="STATES_DICT",
joinColumns=#JoinColumn(name="STATE_CODE", referencedColumnName="STATE"))
#Column(name = "STATE_FULLNAME")
private Collection<String> fullStateName;
Downside is - the mapping is always one-to-one and the Collection brings confusion and the relation is more of one-to-one (many-to-one) not one-to-many.
Any ideas? Is there an equivalent of #ElementCollection for one-to-one mappings?
Dropping the #ElementCollection does not help. fullStateName field is expected to be in ADDRESS column - which is not the case.
Some notes:
* I need those two to keep together in a single entity.
* I'm extending existing solution, need to add just this dictionary column
* The entity is processed later on by some other service which runs through primitive types only. I'd rather not change the service, that's why adding a #OneToOne relation is not preferable
Many thanks
I'm extending the question with #SecondaryTable example - which didn't work for me.
#Entity
#Table(name = "ADDRESS")
#SecondaryTable(name="STATES_DICT",
pkJoinColumns=#PrimaryKeyJoinColumn(columnDefinition="STATE_CODE", referencedColumnName="STATE"))
public class Address implements Serializable {
#Id
#Column(name = "ADDRESSID")
private int addressId;
#Column(name = "ADDRESSLINE1")
private String addressLine1;
#Column(name = "STATE")
private String state;
#Column(table="STATES_DICT", name = "STATE_FULLNAME")
private String fullStateName;
#Column(name = "ZIPCODE")
private String zipCode;
#Column(name = "CITY")
private String city;
#Column(name = "COUNTRY")
private String country;
//... getters and setters
}
That caused a nasty exception of:
Caused by: org.hibernate.AnnotationException: SecondaryTable JoinColumn cannot reference a non primary key
For the record - I couldn't find a way to do this (and assumed it's simply not the way it should be done). I've gone with relation annotations (#ManyToOne, #OneToOne) and #JoinColumn - so the proper way. I've adjusted the further processing logic to treat #JoinColumn annotations in the same way it's working with #Column. It worked.
The further processing is a security feature which suppresses values based on user roles and original database column names. That's why it was so important for me to stick with the #Column annotation
Answer from user2601805 is correct using
#PrimaryKeyJoinColumn(name="STATE_CODE", referencedColumnName="STATE")
In your case, this should suffice as all you want is a property from STATES_DICT.
I also asked a question relating to JPA that shows an example for using #SecondaryTable and #Embeddable to achieve something similar to #ElementCollection but for #OneToOne
Also see this blog for example http://www.bagdemir.com/2013/03/03/mapping-embeddable-objects-whichve-no-identities-using-multiple-tables-with-jpahibernate/
You should be able to create an #Embeddable State class that maps the relationship, but provide a delegate method to expose the state name as a property. Would this accomplish what you're trying to do?
In #pkJoinColumns you should use:
#PrimaryKeyJoinColumn(name="STATE_CODE", referencedColumnName="STATE")
I have same problem and your hint to use #SecondaryTable solved my problem. See Mapping one entity to several tables for more information.