A Hibernate one-to-many mapping links one parent entity to many child entities, for example one bank account to all its payments. In the database, the link is a foreign key column in the child table (account_id in the Payment table), and in Java, the parent holds a collection of children annotated with @OneToMany. The recommended form is bidirectional, with @OneToMany(mappedBy) on the parent and @ManyToOne on the child, which we keep in sync with two helper methods.
We use a one-to-many mapping whenever one record owns a list of others, such as a customer and its addresses or a playlist and its songs.
The following example maps an account with many payments, and then saves, loads and removes payments.
// Account (parent)
@OneToMany(mappedBy = "account", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Payment> payments = new ArrayList<>();
public void addPayment(Payment payment) {
payments.add(payment);
payment.setAccount(this);
}
public void removePayment(Payment payment) {
payments.remove(payment);
payment.setAccount(null);
}
// Payment (child): owns the account_id foreign key
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "account_id")
private Account account;
Account account = new Account("Lokesh");
account.addPayment(new Payment("Rent", "1200.00"));
account.addPayment(new Payment("Groceries", "85.50"));
account.addPayment(new Payment("Coffee", "4.00"));
em.persist(account); // insert into Account (owner,id) values (?,default)
// 3 x insert into Payment (account_id,amount,description,id) ...
// in a later transaction
Account loaded = em.find(Account.class, id); // select ... from Account a1_0 where a1_0.id=?
int count = loaded.getPayments().size(); // 3: select ... from Payment p1_0 where p1_0.account_id=?
loaded.removePayment(loaded.findPayment("Coffee")); // delete from Payment where id=?
Notice that persist() on the account also inserts the three payments, and removing a payment from the list deletes its row.
Next, we map an account and its payments in all three ways JPA allows and compare the SQL each one sends. After that, we answer the common questions, such as null foreign keys and N+1 selects.
1. What Is a One-to-Many Mapping in Hibernate?
A one-to-many association means that one row of the parent table relates to many rows of the child table, and each child row belongs to at most one parent. So one account has many payments, but each payment is paid from one account only. Account and Payment are both entities (Java classes mapped to tables with @Entity).
The database stores the relationship in one place only, the account_id foreign key column in the Payment table. When both entities point to each other, Hibernate needs to know which Java field decides the value of that column.
- The owning side is the field whose value Hibernate writes to the foreign key. In a bidirectional one-to-many, it is always Payment.account (the @ManyToOne side).
- The inverse side is the collection marked with mappedBy. Hibernate fills Account.payments when it loads the account, but never writes account_id from it.

The value of mappedBy is the name of the Java field on the child, not a column name. For example, mappedBy = “account” points to Payment.account.
JPA offers three ways to map the same relationship. A bidirectional mapping has a field on both sides, whereas a unidirectional mapping has a field on the parent only, so the account knows its payments but a payment does not know its account. The three ways differ in which side owns the foreign key and in how many SQL statements Hibernate runs.
| Mapping | Annotations | Tables | Save 1 account + 3 payments | Remove 1 payment |
|---|---|---|---|---|
| Bidirectional (recommended) | @OneToMany(mappedBy) + @ManyToOne | Account, Payment | 4 inserts | 1 delete |
| Unidirectional with join column | @OneToMany + @JoinColumn | Account, Payment | 4 inserts + 3 updates | 1 update + 1 delete |
| Unidirectional with join table | @OneToMany only | Account, Account_Payment, Payment | 7 inserts | 1 delete + 2 inserts + 1 delete |

2. One-to-Many Mapping Example
We map a bank account and its payments with Hibernate 7.4, Java 25 and an H2 in-memory database, and run each mapping from the table in section 1.
2.1. Domain Model
An account has an owner (“Lokesh”) and a list of payments. Each payment has a description, an amount and the account it was paid from, and a banking app shows these payments as the account statement.
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String owner;
@OneToMany(mappedBy = "account", cascade = CascadeType.ALL, orphanRemoval = true)
@OrderBy("amount desc")
private List<Payment> payments = new ArrayList<>();
// addPayment() and removePayment() as shown above
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String description;
private BigDecimal amount; // new Payment("Rent", "1200.00")
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "account_id")
private Account account;
The helper methods addPayment() and removePayment() change both sides in one call. Without them, we can update the collection and forget the owning field, which is the field Hibernate saves (see FAQ 3.1). The project also has a small findPayment(description) helper that returns a payment from the list by its description. Each attribute in the mapping controls one part of the behavior.
| Attribute | Where | What it does |
|---|---|---|
| mappedBy = “account” | @OneToMany | Marks the collection as the inverse side of Payment.account |
| cascade = CascadeType.ALL | @OneToMany | persist(), merge(), remove() and the other operations on the account also apply to its payments |
| orphanRemoval = true | @OneToMany | A payment removed from the list is deleted from the table |
| @OrderBy(“amount desc”) | Collection | Adds order by to the SQL that loads the payments |
| fetch = FetchType.LAZY | @ManyToOne | Loads the account of a payment only when we use it (the default is EAGER) |
| @JoinColumn(name = “account_id”) | @ManyToOne | Names the foreign key column in the Payment table |
Hibernate creates this schema from the two entities.
create table Account (id bigint generated by default as identity, owner varchar(255), primary key (id))
create table Payment (amount numeric(38,2), account_id bigint, id bigint generated by default as identity, description varchar(255), primary key (id))
alter table if exists Payment add constraint FKjiyk2ty9k4v31oo83q8sxo5ka foreign key (account_id) references Account
2.2. Saving an Account With Its Payments
We call persist() only for the account. CascadeType.ALL passes the call on to every payment in the list, and each payment already knows its account, so its insert contains the foreign key.
emf.runInTransaction(em -> {
Account account = new Account("Lokesh");
account.addPayment(new Payment("Rent", "1200.00"));
account.addPayment(new Payment("Groceries", "85.50"));
account.addPayment(new Payment("Coffee", "4.00"));
em.persist(account);
});
insert into Account (owner,id) values (?,default)
insert into Payment (account_id,amount,description,id) values (?,?,?,default)
insert into Payment (account_id,amount,description,id) values (?,?,?,default)
insert into Payment (account_id,amount,description,id) values (?,?,?,default)
Without a cascade, we must call em.persist() for each payment ourselves. The other JPA cascade types pass other operations, such as merge() and remove(), from the account to its payments in the same way.
2.3. Loading the Payments of an Account
A @OneToMany collection is LAZY by default. The call em.find() loads only the account, and Hibernate reads the payments when we first use the list. The annotation @OrderBy sorts them in the same query.
Account account = em.find(Account.class, accountId);
// select a1_0.id,a1_0.owner from Account a1_0 where a1_0.id=?
int count = account.getPayments().size(); // 3
// select p1_0.account_id,p1_0.id,p1_0.amount,p1_0.description from Payment p1_0
// where p1_0.account_id=? order by p1_0.amount desc
List<Payment> payments = account.getPayments(); // [Rent 1200.00, Groceries 85.50, Coffee 4.00]
The second query runs inside the same transaction, because reading the list after the transaction has ended throws LazyInitializationException (see FAQ 3.4). The same lazy loading applies to the @ManyToOne field, because we set fetch = FetchType.LAZY on it.
2.4. Removing a Payment
The helper removePayment() takes the payment out of the list and clears its account field. Because the mapping has orphanRemoval = true, Hibernate deletes the row at commit. For example, the owner cancels the Coffee payment before the bank books it.
emf.runInTransaction(em -> {
Account account = em.find(Account.class, accountId);
account.removePayment(account.findPayment("Coffee"));
}); // delete from Payment where id=?
Without orphanRemoval, the same code only sets account_id to NULL, so the payment row stays in the table as an orphan that belongs to no account.
2.5. Unidirectional One-to-Many With @JoinColumn
When the payment has no account field, the parent becomes the owning side. The annotation @JoinColumn on the collection tells Hibernate to keep the foreign key in the Payment table, so the tables stay the same as in section 2.1.
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "account_id")
private List<Payment> payments = new ArrayList<>();
account.getPayments().add(new Payment("Rent", "1200.00")); // no helper needed
The payment does not know its account, so Hibernate inserts it without the foreign key and fills account_id afterwards with one UPDATE per payment.
insert into Account (owner,id) values (?,default)
insert into Payment (amount,description,id) values (?,?,default)
insert into Payment (amount,description,id) values (?,?,default)
insert into Payment (amount,description,id) values (?,?,default)
update Payment set account_id=? where id=?
update Payment set account_id=? where id=?
update Payment set account_id=? where id=?
update Payment set account_id=null where account_id=? and id=?
delete from Payment where id=?
2.6. Unidirectional One-to-Many With a Join Table
A @OneToMany without mappedBy and without @JoinColumn makes Hibernate create a join table, a third table that holds only pairs of foreign keys.
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
private List<Payment> payments = new ArrayList<>();
create table Account_Payment (Account_id bigint not null, payments_id bigint not null unique)
alter table if exists Account_Payment add constraint FKsei2xjt62nf2atbumltybmko4 foreign key (payments_id) references Payment
alter table if exists Account_Payment add constraint FKrtncxgwqnc46edxgyguo9vwmk foreign key (Account_id) references Account
The default names follow the JPA rules. The table is Account_Payment (owner entity first). The column Account_id is the entity name plus its key column, and payments_id is the collection field name plus the key column of Payment. The unique constraint on payments_id keeps a payment in one account only. We can rename all three with @JoinTable.
Saving the same account writes three extra rows into the join table.
insert into Account (owner,id) values (?,default)
insert into Payment (amount,description,id) values (?,?,default)
insert into Payment (amount,description,id) values (?,?,default)
insert into Payment (amount,description,id) values (?,?,default)
insert into Account_Payment (Account_id,payments_id) values (?,?)
insert into Account_Payment (Account_id,payments_id) values (?,?)
insert into Account_Payment (Account_id,payments_id) values (?,?)
Removing one payment costs even more. A List without an index column is a bag for Hibernate, and its rows have no position or key that identifies one link. So Hibernate deletes every join table row of the account and inserts the remaining ones again.
delete from Account_Payment where Account_id=?
insert into Account_Payment (Account_id,payments_id) values (?,?)
insert into Account_Payment (Account_id,payments_id) values (?,?)
delete from Payment where id=?
Loading the payments also needs a join of two tables instead of one.
select p1_0.Account_id,p1_1.id,p1_1.amount,p1_1.description
from Account_Payment p1_0 join Payment p1_1 on p1_1.id=p1_0.payments_id where p1_0.Account_id=?
A join table makes sense for a many-to-many mapping, which needs one, but for one-to-many we prefer the bidirectional mapping from section 2.1.
The complete project on GitHub runs all three mappings and prints the SQL, and 11 JUnit tests check every statement count from section 2 (mvn -q compile exec:java, mvn test).
3. One-to-Many Mapping FAQs
3.1. Why Is the Foreign Key Column NULL After Saving?
We added the child to the parent’s collection but did not set the child’s @ManyToOne field. Hibernate writes account_id from the owning side only, so the payment is saved without its account.
// Wrong: only the inverse side is set
account.getPayments().add(new Payment("Books", "30.00"));
// insert into Payment (account_id,amount,description,id) ... account_id = null
// Right: the helper sets both sides
account.addPayment(new Payment("Books", "30.00"));
The cascade still inserts the payment, so there is no error. The row is saved with account_id = NULL, and the next time we load the account, the payment is missing from the list.
3.2. Is @OneToMany Lazy by Default?
Yes. Annotations that map a collection default to LAZY, and annotations that map a single entity default to EAGER.
| Annotation | Default fetch type |
|---|---|
| @OneToMany | LAZY |
| @ManyToMany | LAZY |
| @ManyToOne | EAGER |
| @OneToOne | EAGER |
We set fetch = FetchType.LAZY on every @ManyToOne, so loading a payment does not also load its account. We keep the collection LAZY too, because with EAGER, Hibernate loads all payments of every account it loads, even when we only need the owner name.
3.3. How Do I Fix the N+1 Select Problem?
The N+1 problem appears when we load N parents with one query and then read each parent’s lazy collection in a loop, because Hibernate runs one more query per parent. With three accounts, the loop runs 1 + 3 = 4 queries. For example, an admin page that lists 50 accounts with their payment totals runs 51 queries.
List<Account> accounts = em.createQuery("from Account", Account.class).getResultList();
// select a1_0.id,a1_0.owner from Account a1_0
accounts.forEach(a -> a.getPayments().size());
// select ... from Payment p1_0 where p1_0.account_id=? order by p1_0.amount desc (3 times)
A join fetch query loads the accounts and their payments in one query. We use left join fetch so that accounts without payments are also returned.
List<Account> accounts = em.createQuery(
"from Account a left join fetch a.payments", Account.class).getResultList();
// select a1_0.id,a1_0.owner,p1_0.account_id,p1_0.id,p1_0.amount,p1_0.description
// from Account a1_0 left join Payment p1_0 on a1_0.id=p1_0.account_id order by p1_0.amount desc
Another fix is the hibernate.default_batch_fetch_size setting. It keeps the collections lazy, but loads the payments of up to 10 accounts with one IN query.

| Approach | Queries for 3 accounts |
|---|---|
| Lazy collections read in a loop | 4 (1 + N) |
| left join fetch a.payments | 1 |
| hibernate.default_batch_fetch_size = 10 | 2 (account_id in (?,?,…)) |
3.4. Why Do I Get a LazyInitializationException When Reading the Collection?
The collection was never loaded, and the transaction that loaded the account has ended, so Hibernate has no open session to run the query.
Account account = emf.callInTransaction(em -> em.find(Account.class, accountId));
int count = account.getPayments().size(); // LazyInitializationException
org.hibernate.LazyInitializationException: Cannot lazily initialize collection of role 'com.howtodoinjava.hibernate.onetomany.bidirectional.Account.payments' with key '1' (no session)
We load the payments while the transaction is still open.
- Fetch them in the query with left join fetch (see FAQ 3.3).
- Read the list inside the transaction, for example with getPayments().size().
- Call Hibernate.initialize() on the collection.
3.5. When Should I Use a Unidirectional One-to-Many?
Rarely, because both unidirectional forms run extra SQL (see section 1), and the bidirectional form costs only one field and two helper methods. Each option fits a different case.
- Bidirectional mappedBy with @ManyToOne is the default choice. It writes each row once, and we can go from an account to its payments and back.
- A @ManyToOne on the child without any collection fits an account with thousands of payments. We load them with a query such as select p from Payment p where p.account = :account, which also supports paging.
- A unidirectional @OneToMany with @JoinColumn is acceptable for small collections when the child class must not reference the parent.
- A unidirectional @OneToMany with a join table adds a table and rewrites it on every removal, so we avoid it for one-to-many.
4. Conclusion
A one-to-many mapping stores the relationship in one foreign key column of the child table. We map it with @OneToMany(mappedBy) on the parent and a lazy @ManyToOne on the child, and keep both sides in sync with helper methods. When we need the children of many parents, we use join fetch. The unidirectional forms work, but they run extra UPDATE statements or need a join table.
5. References
- Jakarta Persistence 3.2 specification: Bidirectional ManyToOne / OneToMany Relationships
- OneToMany JavaDoc (Jakarta Persistence 3.2)
- ManyToOne JavaDoc (Jakarta Persistence 3.2)
- OrderBy JavaDoc (Jakarta Persistence 3.2)
- Hibernate ORM 7.4 User Guide: @OneToMany
- Hibernate ORM 7.4 User Guide: Fetching
Happy Learning !!
Spring cannot create beans like this anymore. I tried to create foreign key relationship to another table and it resulted in error like:
“`
org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘entityManagerFactory’ defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is org.hibernate.AnnotationException: Associations marked as mappedBy must not define database mappings like @JoinTable or @JoinColumn: it.akademija.canteen.Canteen.menus
“`
It is not a Spring related error. Try this solution else share your code.
Hi Lokesh
There is no table structure are added in source code so could you add?
I had 4 tables and they are related like this:
1st Table : Columns are username, password, is active. It has a primary key username
2nd Table : Column are user_role_id, username, role. User_role_id is primary key and username has a foreign key constraint with 1st table (username)
3rd Table: Columns are id, role_id, comp_id. id is primary key and role_id and comp_id has foreign key constraints with 2nd (user_role_id) and 4th table (id)
4th Table: columns are id, component_name, component_path. id is primary key
My problem is I want to retrieve component_name and Component_path based on username.
How to do it in Hibernate. Plz help. Stuck for many days on this.
Hi Lokesh,
Thanks for your wonderful tutorial!!
I have some doubts –
1. The foreign key association example in this post is unidirectional association or a bidirectional one. I feel it is bidirectional as
both the entities have reference to each other.
2. In case it is bidirectional then why have we not made the Account entity as the owner of the
relation. Why is “mappedBy” attribute not used?
Thanks in advance,
Rohit
Hi Lokesh Gupta,
Thanks for the post.
This work good for save,Update,delete,get(EmployeeEntity.class,id), but in Query.list() gives Error like this
Hibernate: select employeeen0_.ID as ID0_0_, employeeen0_.EMAIL as EMAIL0_0_, employeeen0_.FIRST_NAME as FIRST3_0_0_, employeeen0_.LAST_NAME as LAST4_0_0_ from Employee employeeen0_ where employeeen0_.ID=?
org.hibernate.hql.ast.QuerySyntaxException: EmployeeEntity is not mapped [from EmployeeEntity]
can help me to fatch all Records from EmployeeEntity Table with Account details.
Hi Lokesh,
nice explanation!!
I have a doubt regarding account class.Nowhere you are mentioning save account object then how it is saving the account object.
please clarify!!
@OneToMany(cascade=CascadeType.ALL)
Hi,
I am not quite clear why did you use Set accounts; on EmployeeEntity? everybody uses set on OneToMany relationship is there a specific reason for it?
thanks
we can use any collection implementation like set or list, no limitations to set , but we generally use set because set takes no duplicate values so if any account is added twice it might change the entire concept so here used SET.
Hi, I have seen at different places that the annotations are placed above the getter methods. Does it make a difference if we place the annotations above the getter methods?
Read more : Field vs Property vs Mixed Access modes
Thanks for the great article.
When would be the behaivor if i add the mappedBy annotation to the end of one entiy ?
With reference to implementation of REST Api using Spring, In above given link, there are some test call say “TestForeignKeyAssociation” and “TestJoinTable”. Where do I mention this code in my application? How shall I pass two objects as request body? Do I really need to send two objects as request body? What approach shall I follow?
Can you please elaborate what you are doing.. or what you want to do… in little more detail??
[1] created two entity class called address and location (where LocationId is foreign key)
[2] applied relationship in address class as “@OneToOne(cascade = CascadeType.ALL)…@JoinColumn(name = “LocationId”)…private Location location;
[3] Now, How do I send JSON input for address and location values to controller which will further send to service and DAO class to persist?
Sir, if possible kindly provide the same example using Spring REST.
Here is an example of Spring REST. Create a POST method with request body like below:
UserLocation { "address": { "street":"data", "city": "data"}, "location": { "field1":"data", "field2": "data"}, }In controller, parse this object and populate separate Location and Address entities, and store them using hibernate.
To parse this object, do I need to create a separate POJO (consist both address and location variables) for the same or do I just parse it as JSON and fetch it using JSONObject?
I will suggest to create separate POJO.
Thanks !! Anyways nice post.
Can you provide some reference to implement REST API using spring security? (like, user login, limited access of information, etc…)
I will try to update.
Thanks in advance.
How do I apply the same logic for Spring REST api application. Like, logic implemented in HibernateStandAlone will be applied at where? How do I take two objects as an Input? Do I really need to take input of two object or one will be sufficient?
Hi Lokesh,
I am little confused after reading the above post. I guess this is related to Unidirectional association only. Do you have any good link for bidirectional OneToMany association?
Thanks,
Anurag.
I will update soon.
Hi ….Lokesh ,
I Dont know anout hibernate i am create database with one to many and many to one mapped table
Tbl_FileMaster (File_id,filename,filepath) joincolumn fileid
Tbl_Header_Master(id,header,(fileid)—/joincolumn) created now i want to use inner join on it so is it possiable to create such query in this senario
select header with max(fileid) in Filemaster table …..
Need Help
Thanks !
Hi Lokesh,
Can you please explain more about inverse property.
I have idea how to define in entity hbm.XML file, but I am confused in case of annotations.
I want to know how we decide about the Relationship owned in case of annotations based config.
Thanks in advance.
Hi Lokesh, Thanks for the nice post. I have some query regarding, I have to make composite primary key in table B which is combination of B_id ,first_id without using Embedded Id.So,Is there any way to do this without creating any extra class.
——-
TABLE_A
——-
A_id (pk)
[other fields]
——-
TABLE_B
——-
B_id
first_id (fk TABLE_A.A_id)
PRIMARY KEY (B_id, first_id)
[other fields]
@id
@manytoone(“your column name”)
declare your referenced class object like “Device device”
@id
@manytoone(“anothe column”)
object anothe class that u have to mapped
it just simple its work for me
Hi Lokesh,
First of all thanks for putting the effort for creating the various tutorials on java.
Now onto the question. How to get EmployeeEntity associated with AccountEntity when using jointable. Would I be able to do a @ManyToOne annotation on a field inside AccountEntity like in the example using foreign key association?
Thanks,
-aru
I do not see any delete calls in your test code.
I executed sql delete statement
I don’t understand as to why following entries were added in hibernate.cfg.xml file
We have already annotated the entity with mapping information.
Are you asking about? Put the xml code in after converting < >
Hi Lokesh,
Finally,I have completed your Hibernate tutorial.I am very grateful to you.I need another assistance from you.Please provide some examples with real time scenarios struts with Hibernate integration using log4j,ANT tools..
I will work on it on priority.
Great stuff
Dear Lokesh..I worked out your example(Using a join table) its works great…iam totally new to hibernate..i used hibernate tools to generate annotated files..but its totally different…any way i over come that issue.my only question is
how can i generate annotated file like you(specified in above example) ?pls reply iam very much comfortable with your example…
I am really unaware of auto code generation tools related to hibernate. Let me take some time. Probably i will be able to answer your query.
The closest thing I found to code generation tools is HEDL. You can find it at:
Dear Lokesh
I am getting following ExceptionInInitializerError
what should I do?
Initial SessionFactory creation failed.java.lang.NoClassDefFoundError: org/apache/commons/logging/LogFactory
Exception in thread “main” java.lang.ExceptionInInitializerError
at hibernate.test.HibernateUtil.buildSessionFactory(HibernateUtil.java:22)
at hibernate.test.HibernateUtil.(HibernateUtil.java:10)
at hibernate.test.oneToMany.TestJoinTable.main(TestJoinTable.java:17)
Caused by: java.lang.NoClassDefFoundError: org/apache/commons/logging/LogFactory
at org.hibernate.cfg.Configuration.(Configuration.java:110)
at hibernate.test.HibernateUtil.buildSessionFactory(HibernateUtil.java:15)
… 2 more
Hibernate has internal dependency on this class for logging support. Add required dependency from here.
https://commons.apache.org/proper/commons-logging/
Hi, Lokesh
Thanks for the post.
If i want to update the employee entity by adding new account(‘b’) for an employee.how to go about it, in case of using join table.
What happens to the records(‘a’ account for employee e1) in Account table. How do we delete them.
let me reply based on what i understood.
you want to assign a new account to employee. so do like this:
1) read an employee entity from session.
2) add newly created account object to set of accounts.
3) finally use saveOrUpdate() method from session.
there is no need to delete any previous record. hibernate is smart enough to differentiate between attached and detached entities.