Spring Data JPA - Basics
Overview
Introduction to Spring JPA with examples. Create domain classes that map to database & write JPA queries to fetch the data.
Github: https://github.com/gitorko/project82
Spring Data JPA
Spring Data JPA provides an abstraction layer over the Java Persistence API (JPA) Spring Data JPA offers a repository abstraction that allows developers to interact with their data models using a repository pattern, which includes out-of-the-box implementations for common CRUD (Create, Read, Update, Delete) operations
Features
- Repository Abstraction: Provides a high-level abstraction over the data access layer, allowing developers to define repositories with minimal code.
- Automatic Query Generation: Generates queries based on method names defined in repository interfaces.
- Pagination and Sorting: Supports pagination and sorting out of the box.
- Auditing: Supports auditing of entity changes (e.g., tracking created/modified dates and users).
- Custom Query Methods: Allows custom JPQL (Java Persistence Query Language) and SQL queries.
- Integration with Spring: Seamlessly integrates with the Spring Framework, including transaction management and dependency injection.
Annotation | Description |
---|---|
@ManyToOne | Most natural way to map a foreign key relation. Default to FETCH.EAGER |
@OneToMany | Parent entity to map collection of child entities. If bi-directional then child entity has @ManyToOne. If child entities can grow then will affect performance. Use only when child entities are few |
@OneToOne | Creates a foreign key in parent table that refers to the primary key of child table. |
@MapsId | Single key acts as primary key & foreign key, with single key you can now fetch data from both table with same key. |
@ManyToMany | Two parents on one child, avoid doing CascadeType.ALL, dont do orphan removal. |
mappedBy | Present in parent, Tells hibernate that the child side is in charge of handling bi-directional association. mappedBy & @JoinColumn cant be present in the same class. |
For bi-directional associations where the child is in charge of handling association, you must still write setter methods in parent to sync both sides. Otherwise, you risk very subtle state propagation issues.
Spring JPA determines of an object is new based on @Version
annotation, you can also accomplish the same by implementing Persistable
interface.
Spring JPA uses dirty checking mechanism to determine if something has changed and then auto saves the data to database.
Dirty checking default all columns as updated, if you want avoid it use the annotation @DynamicUpdate
on the class.
Locking & Transaction Isolation
Locking ensures that the row is not concurrently updated by 2 different threads which might corrupt the data.
Problem:
Thread A: Reads row with amount 100$ in Transaction T1 Thread B: Reads row with amount 100$ in Transaction T2 Thread A: Adds 10$, new amount is 110$ Thread B: Adds 10$, new amount is still 110$ instead of 120$.
Solution 1 (Optimistic Locking):
Thread A: Reads row with amount 100$ in Transaction T1 Thread B: Reads row with amount 100$ in Transaction T2 Thread A: Adds 10$, new amount is 110$ Thread B: Adds 10$ and tries to save but sees that the record is not the same record that it read. So fails & does retry.
Solution 2 (Pessimistic Locking):
Thread A: Reads row with amount 100$ in Transaction T1, it holds a row level lock. Thread B: Reads row in Transaction T2 but is blocked as T1 holds a lock, So it waits till timeout happens & retry. Thread A: Adds 10$, new amount is 110$ Thread B: Reads row with updated amount 110$ and updates to 120$
Types of locking
- Pessimistic Locking - Locks held at row level or table level. Not ideal of high performance & cant scale.
- Optimistic Locking - Version field is added to the table, JPA ensures that version check is done before saving data, if the version has changed then update will throw Error. Ideal for high performance & can scale.
Pessimistic locking
LockModeType.PESSIMISTIC_READ
- Rows are locked and can be read by other transactions, but they cannot be deleted or modified. PESSIMISTIC_READ guarantees repeatable reads.LockModeType.PESSIMISTIC_WRITE
- Rows are locked and cannot be read, modified or deleted by other transactions. For PESSIMISTIC_WRITE no phantom reads can occur and access to data must be serialized.LockModeType.PESSIMISTIC_FORCE_INCREMENT
- Rows are locked and cannot be read, modified or deleted by other transactions. it forces an increment of the version attribute
Lock the row being read to avoid the same row from being updated by 2 different transactions
select * from table FOR SHARE
- This clause locks the selected rows for read, other threads can read but cant modify.
select * from table FOR UPDATE
- This clause locks the selected rows for update. This prevents other transactions from reading/modifying these rows until the current transaction is completed (committed or rolled back)
select * from table FOR UPDATE SKIP LOCKED
clause - This clause tells the database to skip rows that are already locked by another transaction. Instead of waiting for the lock to be released
Optimistic locking
LockModeType.OPTIMISTIC
- Checks the version attribute of the entity before committing the transaction to ensure no other transaction has modified the entity.LockModeType.OPTIMISTIC_FORCE_INCREMENT
- Forces a version increment of the entity, even if the entity has not been modified during the update.
Transaction Isolation
Transaction isolation levels in JPA define the degree to which the operations within a transaction are isolated from the operations in other concurrent transactions JPA, typically using the underlying database and JDBC settings
Isolation.READ_UNCOMMITTED
Read Uncommitted - The lowest level of isolation. Transactions can read uncommitted changes made by other transactions.Isolation.READ_COMMITTED
Read Committed - Transactions can only read committed changes made by other transactions.Isolation.REPEATABLE_READ
Repeatable Read - If a transaction reads a row, it will get the same data if it reads the row again within the same transaction.Isolation.SERIALIZABLE
Serializable - The highest level of isolation. Transactions are completely isolated from one another.
Data Consistency
- Dirty reads: read UNCOMMITED data from another transaction.
- Non-repeatable reads: read COMMITTED data from an UPDATE query from another transaction.
- Phantom reads: read COMMITTED data from an INSERT or DELETE query from another transaction.
Dirty Read
NAME | AGE |
---|---|
Bob | 35 |
TRANSACTION T1 | TRANSACTION T2 |
---|---|
select age from table where name = 'Bob'; (35) | |
update table set age = 40 where name = 'Bob'; | |
select age from table where name = 'Bob'; (40) | |
commit; |
Non-Repeatable Read
NAME | AGE |
---|---|
Bob | 35 |
TRANSACTION T1 | TRANSACTION T2 |
---|---|
select age from table where name = 'Bob'; (35) | |
update table set age = 40 where name = 'Bob'; | |
commit; | |
select age from table where name = 'Bob'; (40) |
Phantom Read
NAME | AGE |
---|---|
Bob | 35 |
TRANSACTION T1 | TRANSACTION T2 |
---|---|
select count(*) from table where age = 35; (1) | |
insert into table values ('jack', 35); | |
commit; | |
select count(*) from table where age = 35; (2) |
Behaviour of Isolation Levels
Isolation Level | Dirty | Non-Repeatable Reads | Phantom Reads |
---|---|---|---|
Read Uncommitted | Yes | Yes | Yes |
Read Committed | No | Yes | Yes |
Read Committed | No | No | Yes |
Serializable | No | No | No |
1spring:
2 jpa:
3 properties:
4 hibernate:
5 connection:
6 isolation: 2
1@Transactional(isolation = Isolation.SERIALIZABLE)
1SHOW default_transaction_isolation;
Transaction Propagation
When one transaciton functions calls another in the same class boundary then the parent transaction level is applied. You need to move the function to a different public class if you want its transaction to be enforced. When nested calls happen on transaction boundary then the transaction is suspended.
@Transactional(readOnly = true)
- transaction is readonly and now updates can happen.@Transactional(propagation = Propagation.REQUIRES_NEW)
- creates a new transaction.@Transactional(propagation = Propagation.REQUIRED)
- default, spring will create a new transaction if not present.@Transactional(propagation = Propagation.MANDATORY)
- will throw exception if transaction doesn't exist.@Transactional(propagation = Propagation.SUPPORTS)
- if existing transaction present then it will be used, else operation will happen without any transaction.@Transactional(propagation = Propagation.NOT_SUPPORTED)
- operation will have with no transaction.@Transactional(propagation = Propagation.NOT_SUPPORTED)
- will throw an exception if transaction present.
You can define which exception call the rollback and which don't.
1@Transactional(noRollbackFor = {CustomException.class}, rollbackFor = {RuntimeException.class})
To track transactions
1logging:
2 level:
3 root: info
4 org.springframework.orm.jpa.JpaTransactionManager: DEBUG
Spring keeps the transaction open till the controller returns the response.
This is because it thinks that the object may be accessed later in the HTML (web mvc templates).
We don't use this, so we will set the below property to false that way transaction is closed after @Transactional
function ends.
1spring:
2 jpa:
3 open-in-view: false
By setting auto-commit to false spring won't commit immediately but will commit when the transaction ends.
1spring:
2 datasource:
3 hikari:
4 auto-commit: false
You can also use TransactionTemplate
to control transactions if you dont want to use @Transactional
and want more control.
Try to the transaction boundary small. External calls need to be done outside the transaction context.
1transactionTemplate.executeWithoutResult()
2transactionTemplate.execute()
Code
1package com.demo.project82;
2
3import static org.junit.jupiter.api.Assertions.assertEquals;
4import static org.junit.jupiter.api.Assertions.assertNotNull;
5import static org.junit.jupiter.api.Assertions.assertNull;
6
7import java.math.BigDecimal;
8import java.nio.charset.StandardCharsets;
9import java.time.LocalDate;
10import java.util.Date;
11import java.util.HashMap;
12import java.util.List;
13import java.util.Map;
14import java.util.Optional;
15import java.util.concurrent.CountDownLatch;
16import java.util.concurrent.ExecutorService;
17import java.util.concurrent.Executors;
18import java.util.concurrent.TimeUnit;
19
20import com.demo.project82._00_constraints.Student00;
21import com.demo.project82._00_constraints.repo.Student00Repository;
22import com.demo.project82._01_one2one_unidirectional.Contact01;
23import com.demo.project82._01_one2one_unidirectional.Student01;
24import com.demo.project82._01_one2one_unidirectional.repo.Contact01Repository;
25import com.demo.project82._01_one2one_unidirectional.repo.Student01Repository;
26import com.demo.project82._02_one2one_unidirectional_mapsid.Contact02;
27import com.demo.project82._02_one2one_unidirectional_mapsid.Student02;
28import com.demo.project82._02_one2one_unidirectional_mapsid.repo.Contact02Repository;
29import com.demo.project82._02_one2one_unidirectional_mapsid.repo.Student02Repository;
30import com.demo.project82._03_one2one_unidirectional_no_cascade.Contact03;
31import com.demo.project82._03_one2one_unidirectional_no_cascade.Student03;
32import com.demo.project82._03_one2one_unidirectional_no_cascade.repo.Contact03Repository;
33import com.demo.project82._03_one2one_unidirectional_no_cascade.repo.Student03Repository;
34import com.demo.project82._04_one2one_bidirectional.Contact04;
35import com.demo.project82._04_one2one_bidirectional.Student04;
36import com.demo.project82._04_one2one_bidirectional.repo.Contact04Repository;
37import com.demo.project82._04_one2one_bidirectional.repo.Student04Repository;
38import com.demo.project82._05_one2one_bidirectional_nplus1_fixed.Student05;
39import com.demo.project82._05_one2one_bidirectional_nplus1_fixed.repo.Student05Repository;
40import com.demo.project82._06_one2many_3tables_unidirectional_wrong.Student06;
41import com.demo.project82._06_one2many_3tables_unidirectional_wrong.repo.Student06Repository;
42import com.demo.project82._07_one2many_unidirectional.Course07;
43import com.demo.project82._07_one2many_unidirectional.Student07;
44import com.demo.project82._07_one2many_unidirectional.repo.Student07Repository;
45import com.demo.project82._08_one2many_unidirectional_nplus1_fixed.Student08;
46import com.demo.project82._08_one2many_unidirectional_nplus1_fixed.repo.Student08Repository;
47import com.demo.project82._09_one2many_mappedby_wrong.Student09;
48import com.demo.project82._09_one2many_mappedby_wrong.repo.Student09Repository;
49import com.demo.project82._10_one2many_many2one_bidirectional_mappedby.Course10;
50import com.demo.project82._10_one2many_many2one_bidirectional_mappedby.Student10;
51import com.demo.project82._10_one2many_many2one_bidirectional_mappedby.repo.Course10Repository;
52import com.demo.project82._10_one2many_many2one_bidirectional_mappedby.repo.Student10Repository;
53import com.demo.project82._11_many2one_unidirectional.Course11;
54import com.demo.project82._11_many2one_unidirectional.Student11;
55import com.demo.project82._11_many2one_unidirectional.repo.Course11Repository;
56import com.demo.project82._11_many2one_unidirectional.repo.Student11Repository;
57import com.demo.project82._12_one2many_elementcollection_unidirectional.Phone12;
58import com.demo.project82._12_one2many_elementcollection_unidirectional.Student12;
59import com.demo.project82._12_one2many_elementcollection_unidirectional.repo.Student12Repository;
60import com.demo.project82._13_many2many_bidirectional.Student13;
61import com.demo.project82._13_many2many_bidirectional.Teacher13;
62import com.demo.project82._13_many2many_bidirectional.repo.Student13Repository;
63import com.demo.project82._13_many2many_bidirectional.repo.Teacher13Repository;
64import com.demo.project82._14_many2many_unidirectional.Student14;
65import com.demo.project82._14_many2many_unidirectional.Teacher14;
66import com.demo.project82._14_many2many_unidirectional.repo.Student14Repository;
67import com.demo.project82._14_many2many_unidirectional.repo.Teacher14Repository;
68import com.demo.project82._15_many2many_jointable_bidirectional.Student15;
69import com.demo.project82._15_many2many_jointable_bidirectional.Teacher15;
70import com.demo.project82._15_many2many_jointable_bidirectional.repo.Student15Repository;
71import com.demo.project82._15_many2many_jointable_bidirectional.repo.Teacher15Repository;
72import com.demo.project82._16_one2many_jointable_unidirectional.Course16;
73import com.demo.project82._16_one2many_jointable_unidirectional.Student16;
74import com.demo.project82._16_one2many_jointable_unidirectional.repo.Student16Repository;
75import com.demo.project82._17_one2many_jointable_mapkey.Course17;
76import com.demo.project82._17_one2many_jointable_mapkey.Student17;
77import com.demo.project82._17_one2many_jointable_mapkey.repo.Student17Repository;
78import com.demo.project82._18_one2one_jointable_unidirectional.Contact18;
79import com.demo.project82._18_one2one_jointable_unidirectional.Student18;
80import com.demo.project82._18_one2one_jointable_unidirectional.repo.Student18Repository;
81import com.demo.project82._19_one2many_unidirectional.Course19;
82import com.demo.project82._19_one2many_unidirectional.Student19;
83import com.demo.project82._19_one2many_unidirectional.repo.Course19Repository;
84import com.demo.project82._19_one2many_unidirectional.repo.Student19Repository;
85import com.demo.project82._20_enum_lob.Student20;
86import com.demo.project82._20_enum_lob.StudentType;
87import com.demo.project82._20_enum_lob.repo.Student20Repository;
88import com.demo.project82._21_audit.Student21;
89import com.demo.project82._21_audit.repo.Student21Repository;
90import com.demo.project82._22_unique_constraints.Student22;
91import com.demo.project82._22_unique_constraints.repo.Student22Repository;
92import com.demo.project82._23_nartual_id.Student23;
93import com.demo.project82._23_nartual_id.repo.Student23Repository;
94import com.demo.project82._24_composite_key.Student24;
95import com.demo.project82._24_composite_key.Student24Identity;
96import com.demo.project82._24_composite_key.repo.Student24Repository;
97import com.demo.project82._25_map.Student25;
98import com.demo.project82._25_map.repo.Student25Repository;
99import com.demo.project82._26_embeddable.Address;
100import com.demo.project82._26_embeddable.Student26;
101import com.demo.project82._26_embeddable.Teacher26;
102import com.demo.project82._26_embeddable.repo.Student26Repository;
103import com.demo.project82._26_embeddable.repo.Teacher26Repository;
104import com.demo.project82._27_inheritance.Student27;
105import com.demo.project82._27_inheritance.repo.Student27Repository;
106import com.demo.project82._28_projections.Student28;
107import com.demo.project82._28_projections.Student28DTO;
108import com.demo.project82._28_projections.Student28Pojo;
109import com.demo.project82._28_projections.Student28View;
110import com.demo.project82._28_projections.repo.Student28Repository;
111import com.demo.project82._29_pessimistic_locking.repo.Student29Repository;
112import com.demo.project82._29_pessimistic_locking.service.Student29Service;
113import com.demo.project82._30_optimistic_locking.Student30;
114import com.demo.project82._30_optimistic_locking.repo.Student30Repository;
115import com.demo.project82._30_optimistic_locking.service.Student30Service;
116import com.demo.project82._31_java_records.Student31Record;
117import com.demo.project82._31_java_records.repo.Student31Converter;
118import com.demo.project82._31_java_records.service.Student31Service;
119import com.demo.project82._32_transaction.Student32;
120import com.demo.project82._32_transaction.repo.Student32Repository;
121import com.demo.project82._32_transaction.service.Student32Service;
122import com.demo.project82._33_query_by_example.Student33;
123import com.demo.project82._33_query_by_example.repo.Student33Repository;
124import com.demo.project82._33_query_by_example.service.Student33Service;
125import com.demo.project82._34_proxy.Course34;
126import com.demo.project82._34_proxy.Student34;
127import com.demo.project82._34_proxy.repo.Course34Repository;
128import com.demo.project82._34_proxy.repo.Student34Repository;
129import com.demo.project82._35_json.Student35;
130import com.demo.project82._35_json.repo.Student35Repository;
131import jakarta.persistence.EntityManager;
132import jakarta.persistence.PersistenceContext;
133import org.junit.jupiter.api.Test;
134import org.springframework.beans.factory.annotation.Autowired;
135import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
136import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
137import org.springframework.context.annotation.Import;
138import org.springframework.data.domain.Page;
139import org.springframework.data.domain.PageRequest;
140import org.springframework.data.domain.Sort;
141import org.springframework.orm.ObjectOptimisticLockingFailureException;
142import org.springframework.transaction.support.TransactionTemplate;
143import org.testcontainers.junit.jupiter.Testcontainers;
144
145@Testcontainers
146@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
147@DataJpaTest
148@Import({Student29Service.class, Student30Service.class, Student31Service.class, Student32Service.class,
149 Student33Service.class, Student31Converter.class})
150public class StudentTest extends BaseTest {
151
152 final ExecutorService threadPool = Executors.newFixedThreadPool(2);
153
154 @Autowired
155 Contact01Repository contact01Repository;
156
157 @Autowired
158 Contact02Repository contact02Repository;
159
160 @Autowired
161 Contact03Repository contact03Repository;
162
163 @Autowired
164 Contact04Repository contact04Repository;
165
166 @Autowired
167 Student00Repository student00Repository;
168
169 @Autowired
170 Student01Repository student01Repository;
171
172 @Autowired
173 Student02Repository student02Repository;
174
175 @Autowired
176 Student03Repository student03Repository;
177
178 @Autowired
179 Student04Repository student04Repository;
180
181 @Autowired
182 Student05Repository student05Repository;
183
184 @Autowired
185 Student06Repository student06Repository;
186
187 @Autowired
188 Student07Repository student07Repository;
189
190 @Autowired
191 Student08Repository student08Repository;
192
193 @Autowired
194 Student09Repository student09Repository;
195
196 @Autowired
197 Student10Repository student10Repository;
198
199 @Autowired
200 Student11Repository student11Repository;
201
202 @Autowired
203 Student12Repository student12Repository;
204
205 @Autowired
206 Student13Repository student13Repository;
207
208 @Autowired
209 Student14Repository student14Repository;
210
211 @Autowired
212 Student15Repository student15Repository;
213
214 @Autowired
215 Student16Repository student16Repository;
216
217 @Autowired
218 Student17Repository student17Repository;
219
220 @Autowired
221 Student18Repository student18Repository;
222
223 @Autowired
224 Student19Repository student19Repository;
225
226 @Autowired
227 Student20Repository student20Repository;
228
229 @Autowired
230 Student21Repository student21Repository;
231
232 @Autowired
233 Student22Repository student22Repository;
234
235 @Autowired
236 Student23Repository student23Repository;
237
238 @Autowired
239 Student24Repository student24Repository;
240
241 @Autowired
242 Student25Repository student25Repository;
243
244 @Autowired
245 Student26Repository student26Repository;
246
247 @Autowired
248 Student27Repository student27Repository;
249
250 @Autowired
251 Student28Repository student28Repository;
252
253 @Autowired
254 Student29Repository student29Repository;
255
256 @Autowired
257 Student30Repository student30Repository;
258
259 @Autowired
260 Student32Repository student32Repository;
261
262 @Autowired
263 Student33Repository student33Repository;
264
265 @Autowired
266 Student34Repository student34Repository;
267
268 @Autowired
269 Student35Repository student35Repository;
270
271 @Autowired
272 Teacher26Repository teacher26Repository;
273
274 @Autowired
275 Course10Repository course10Repository;
276
277 @Autowired
278 Course11Repository course11Repository;
279
280 @Autowired
281 Course19Repository course19Repository;
282
283 @Autowired
284 Course34Repository course34Repository;
285
286 @Autowired
287 Teacher13Repository teacher13Repository;
288
289 @Autowired
290 Teacher14Repository teacher14Repository;
291
292 @Autowired
293 Teacher15Repository teacher15Repository;
294
295 @Autowired
296 TransactionTemplate transactionTemplate;
297
298 @Autowired
299 Student31Service student31Service;
300
301 @Autowired
302 Student33Service student33Service;
303
304 @PersistenceContext
305 EntityManager entityManager;
306
307 @Test
308 public void test_00_constraints_entityManager() {
309 transactionTemplate.executeWithoutResult(status -> {
310 String photo = "photo";
311 Student00 student = Student00.builder()
312 .studentName("Jack")
313 .userName("jack")
314 .dob(new Date())
315 .registered_on(LocalDate.now())
316 .age(40)
317 .email("email@email.com")
318 .gpaScore(BigDecimal.valueOf(9.9))
319 .notes("something about student")
320 .blob(photo.getBytes(StandardCharsets.UTF_8))
321 .build();
322 entityManager.persist(student);
323 entityManager.flush();
324 entityManager.clear();
325 System.out.println("Student: " + student);
326 });
327 }
328
329 @Test
330 public void test_00_constraints() {
331 String photo = "photo";
332 Student00 student = Student00.builder()
333 .studentName("Jack")
334 .userName("jack")
335 .dob(new Date())
336 .registered_on(LocalDate.now())
337 .age(40)
338 .email("email@email.com")
339 .gpaScore(BigDecimal.valueOf(9.9))
340 .notes("something about student")
341 .blob(photo.getBytes(StandardCharsets.UTF_8))
342 .build();
343 Student00 savedStudent = student00Repository.save(student);
344 System.out.println("Student: " + student);
345 assertNotNull(savedStudent.getId());
346 assertNotNull(savedStudent.getDob());
347 assertNotNull(savedStudent.getRegistered_on());
348 }
349
350 @Test
351 public void test_01_one2one_unidirectional() {
352 Contact01 contact = Contact01.builder().address("Bangalore").build();
353 Student01 student = Student01.builder().studentName("Jack").contact(contact).build();
354 Student01 savedStudent = student01Repository.save(student);
355 assertNotNull(savedStudent.getId());
356 assertNotNull(savedStudent.getContact().getId());
357 }
358
359 @Test
360 public void test_02_one2one_unidirectional_mapsid() {
361 Contact02 contact = Contact02.builder().address("Bangalore").build();
362 Student02 student = Student02.builder().studentName("Jack").contact(contact).build();
363 Student02 savedStudent = student02Repository.save(student);
364 //No cascade but contact still saved.
365 assertNotNull(savedStudent.getId());
366 assertNotNull(savedStudent.getContact().getId());
367 }
368
369 @Test
370 public void test_03_one2one_unidirectional_no_cascade() {
371 Contact03 contact = Contact03.builder().address("Bangalore").build();
372 Student03 student = Student03.builder().studentName("Jack").contact(contact).build();
373 Student03 savedStudent = student03Repository.save(student);
374 //no cascade so contact is not saved.
375 assertNotNull(savedStudent.getId());
376 assertNull(savedStudent.getContact().getId());
377 }
378
379 @Test
380 public void test_04_one2one_bidirectional() {
381 Contact04 contact1 = Contact04.builder().address("Bangalore").build();
382 Student04 student1 = Student04.builder().studentName("Jack").contact(contact1).build();
383
384 Student04 savedStudent = student04Repository.save(student1);
385 assertNotNull(savedStudent.getContact().getId());
386 assertNotNull(savedStudent.getId());
387 Optional<Student04> studentOptional = student04Repository.findById(savedStudent.getId());
388 assertNotNull(studentOptional.get().getContact().getId());
389
390 Student04 student2 = Student04.builder().studentName("Jack").build();
391 Contact04 contact2 = Contact04.builder().address("Bangalore").student(student2).build();
392
393 Contact04 savedContact = contact04Repository.save(contact2);
394 assertNotNull(savedContact.getStudent().getId());
395 assertNotNull(savedContact.getId());
396 Optional<Contact04> contactOptional = contact04Repository.findById(savedContact.getId());
397 assertNotNull(contactOptional.get().getStudent().getId());
398 }
399
400 @Test
401 public void test_04_one2one_bidirectional_nplus1() {
402 //creates the N+1 problem
403 Iterable<Student04> studentList = student04Repository.findAll();
404 //Even though student contact is not required it is loaded as the relation is @OneToOne
405 studentList.forEach(e -> {
406 assertNotNull(e.getId());
407 });
408 }
409
410 @Test
411 public void test_05_one2one_bidirectional_nplus1_fixed() {
412 //N+1 problem solved
413 Iterable<Student05> studentList = student05Repository.findAll();
414 studentList.forEach(e -> {
415 assertNotNull(e.getId());
416 });
417 }
418
419 @Test
420 public void test_06_one2many_3tables_unidirectional_wrong() {
421 //Wrong way to map relation don't use this
422 Iterable<Student06> studentList = student06Repository.findAll();
423 studentList.forEach(e -> {
424 assertNotNull(e.getId());
425 assertEquals(3, e.getCourses().size());
426 });
427 }
428
429 @Test
430 public void test_07_one2many_unidirectional() {
431 Iterable<Student07> studentList = student07Repository.findAll();
432 studentList.forEach(e -> {
433 assertNotNull(e.getId());
434 assertEquals(3, e.getCourses().size());
435 });
436 }
437
438 @Test
439 public void test_07_one2many_unidirectional_save() {
440 Course07 course = Course07.builder()
441 .courseName("chemistry")
442 .build();
443 Student07 student = Student07.builder()
444 .studentName("Jack")
445 .courses(List.of(course))
446 .build();
447 Student07 savedStudent = student07Repository.save(student);
448 assertNotNull(savedStudent.getId());
449 assertEquals(1, savedStudent.getCourses().size());
450 }
451
452 @Test
453 public void test_08_one2many_unidirectional_nplus1_fixed() {
454 Iterable<Student08> studentList = student08Repository.findAll();
455 studentList.forEach(e -> {
456 assertNotNull(e.getId());
457 assertEquals(3, e.getCourses().size());
458 });
459 }
460
461 @Test
462 public void test_09_one2many_mappedby_wrong() {
463 //Wrong way to map relation don't use this
464 Iterable<Student09> studentList = student09Repository.findAll();
465 studentList.forEach(e -> {
466 assertNotNull(e.getId());
467 assertEquals(3, e.getCourses().size());
468 });
469 }
470
471 @Test
472 public void test_10_one2many_many2one_bidirectional_mappedby_1() {
473 Iterable<Student10> student10List = student10Repository.findAll();
474 student10List.forEach(e -> {
475 assertNotNull(e.getId());
476 System.out.println("Student Name: " + e.getStudentName());
477 assertEquals(3, e.getCourses().size());
478 });
479 }
480
481 @Test
482 public void test_10_one2many_many2one_bidirectional_mappedby_2() {
483 List<Course10> history = course10Repository.findAllByCourseName("history");
484 assertEquals(2, history.size());
485 history.forEach(e -> {
486 System.out.println("Student Name: " + e.getStudent().getStudentName());
487 assertNotNull(e.getStudent().getId());
488 });
489 }
490
491 @Test
492 public void test_10_one2many_many2one_bidirectional_mappedby_3() {
493 Course10 historyCourse = Course10.builder()
494 .courseName("history")
495 .build();
496 Course10 physicsCourse = Course10.builder()
497 .courseName("physics")
498 .build();
499 Student10 student = Student10.builder()
500 .studentName("Jack")
501 .build();
502 student.addCourse(historyCourse);
503 student.addCourse(physicsCourse);
504 Student10 savedStudent = student10Repository.save(student);
505
506 List<Course10> courses = course10Repository.findAllByStudent(savedStudent);
507 assertEquals(2, courses.size());
508 }
509
510 @Test
511 public void test_10_one2many_many2one_bidirectional_mappedby_4() {
512 Course10 historyCourse = Course10.builder()
513 .courseName("history")
514 .build();
515 Course10 physicsCourse = Course10.builder()
516 .courseName("physics")
517 .build();
518 Student10 student = Student10.builder()
519 .studentName("Jack")
520 .build();
521 student.addCourse(historyCourse);
522 student.addCourse(physicsCourse);
523 Course10 savedHistoryCourse = course10Repository.save(historyCourse);
524 Course10 savedPhysicsCourse = course10Repository.save(physicsCourse);
525 Student10 savedStudent = savedHistoryCourse.getStudent();
526 assertNotNull(savedStudent.getId());
527
528 List<Course10> courses = course10Repository.findAllByStudent(savedStudent);
529 assertEquals(2, courses.size());
530 }
531
532 @Test
533 public void test_11_many2one_unidirectional() {
534 //Get all the students & for each student get all the courses
535 Iterable<Student11> studentList = student11Repository.findAll();
536 studentList.forEach(e -> {
537 assertNotNull(e.getId());
538 System.out.println("Student Name: " + e.getStudentName());
539 List<Course11> courses = course11Repository.findAllByStudent(e);
540 courses.forEach(c -> {
541 System.out.println("Course: " + c.getCourseName());
542 });
543 });
544 }
545
546 @Test
547 public void test_11_many2one_unidirectional_save() {
548 Student11 student = Student11.builder()
549 .studentName("Jack")
550 .build();
551 Course11 historyCourse = Course11.builder()
552 .courseName("history")
553 .student(student)
554 .build();
555 Course11 physicsCourse = Course11.builder()
556 .courseName("physics")
557 .student(student)
558 .build();
559 Course11 savedHistory = course11Repository.save(historyCourse);
560 Course11 savedPhysics = course11Repository.save(physicsCourse);
561 Student11 savedStudent = student11Repository.findById(savedHistory.getStudent().getId()).orElseGet(null);
562
563 List<Course11> courses = course11Repository.findAllByStudent(savedStudent);
564 assertEquals(2, courses.size());
565 }
566
567 @Test
568 public void test_12_one2many_elementcollection_unidirectional() {
569 Phone12 phone1 = Phone12.builder()
570 .phone("999-999-9999")
571 .build();
572 Student12 student = Student12.builder()
573 .studentName("Jack")
574 .phones(List.of(phone1))
575 .build();
576 Student12 savedStudent12 = student12Repository.save(student);
577 assertNotNull(savedStudent12.getId());
578 }
579
580 @Test
581 public void test_12_one2many_elementcollection_unidirectional_find() {
582 Iterable<Student12> listOfStudents = student12Repository.findAll();
583 listOfStudents.forEach(e -> {
584 assertEquals(2, e.getPhones().size());
585 });
586 }
587
588 @Test
589 public void test_13_many2many_bidirectional() {
590 Teacher13 teacher1 = Teacher13.builder()
591 .teacherName("Mr. Adam")
592 .build();
593 Teacher13 teacher2 = Teacher13.builder()
594 .teacherName("Mr. Smith")
595 .build();
596 Student13 student1 = Student13.builder()
597 .studentName("Jack")
598 .build();
599 Student13 student2 = Student13.builder()
600 .studentName("David")
601 .build();
602
603 Student13 savedStudent1 = student13Repository.save(student1);
604 Student13 savedStudent2 = student13Repository.save(student2);
605
606 Teacher13 savedTeacher1 = teacher13Repository.save(teacher1);
607 Teacher13 savedTeacher2 = teacher13Repository.save(teacher2);
608
609 savedTeacher1.addStudent(student1);
610 teacher13Repository.save(savedTeacher1);
611
612 savedStudent2.addTeacher(savedTeacher2);
613 student13Repository.save(savedStudent2);
614 }
615
616 @Test
617 public void test_14_many2many_unidirectional_save() {
618 Teacher14 teacher1 = Teacher14.builder()
619 .teacherName("Mr. Adam")
620 .build();
621 Teacher14 teacher2 = Teacher14.builder()
622 .teacherName("Mr. Smith")
623 .build();
624 Student14 student1 = Student14.builder()
625 .studentName("Jack")
626 .build();
627 Student14 student2 = Student14.builder()
628 .studentName("David")
629 .build();
630
631 Student14 savedStudent1 = student14Repository.save(student1);
632 Student14 savedStudent2 = student14Repository.save(student2);
633
634 Teacher14 savedTeacher1 = teacher14Repository.save(teacher1);
635 Teacher14 savedTeacher2 = teacher14Repository.save(teacher2);
636
637 savedStudent1.addTeacher(savedTeacher1);
638 savedStudent2.addTeacher(savedTeacher2);
639 student14Repository.save(savedStudent1);
640 student14Repository.save(savedStudent2);
641 }
642
643 @Test
644 public void test_14_many2many_unidirectional_delete() {
645
646 Student14 savedStudent1 = student14Repository.findById(100l).orElseGet(null);
647 Student14 savedStudent2 = student14Repository.findById(101l).orElse(null);
648
649 Teacher14 savedTeacher1 = teacher14Repository.findById(200l).orElse(null);
650 Teacher14 savedTeacher2 = teacher14Repository.findById(201l).orElse(null);
651
652 savedStudent1.removeTeacher(savedTeacher1);
653 student14Repository.save(savedStudent1);
654
655 savedStudent1 = student14Repository.findById(100l).orElseGet(null);
656 assertEquals(1, savedStudent1.getTeachers().size());
657
658 savedStudent2 = student14Repository.findById(101l).orElseGet(null);
659 assertEquals(2, savedStudent2.getTeachers().size());
660 }
661
662 @Test
663 public void test_15_many2many_jointable_bidirectional() {
664 Teacher15 teacher1 = Teacher15.builder()
665 .teacherName("Mr. Adam")
666 .build();
667 Teacher15 teacher2 = Teacher15.builder()
668 .teacherName("Mr. Smith")
669 .build();
670 Student15 student1 = Student15.builder()
671 .studentName("Jack")
672 .build();
673 Student15 student2 = Student15.builder()
674 .studentName("David")
675 .build();
676
677 Student15 savedStudent1 = student15Repository.save(student1);
678 Student15 savedStudent2 = student15Repository.save(student2);
679
680 Teacher15 savedTeacher1 = teacher15Repository.save(teacher1);
681 Teacher15 savedTeacher2 = teacher15Repository.save(teacher2);
682
683 savedTeacher1.addStudent(student1);
684 teacher15Repository.save(savedTeacher1);
685
686 savedStudent2.addTeacher(savedTeacher2);
687 student15Repository.save(savedStudent2);
688 }
689
690 @Test
691 public void test_16_one2many_jointable_unidirectional() {
692 Course16 physicsCourse = Course16.builder()
693 .courseName("physics")
694 .build();
695 Course16 chemistryCourse = Course16.builder()
696 .courseName("chemistry")
697 .build();
698 Student16 student = Student16.builder()
699 .studentName("Jack")
700 .courses(List.of(physicsCourse, chemistryCourse))
701 .build();
702 Student16 savedStudent = student16Repository.save(student);
703 assertNotNull(savedStudent.getId());
704 }
705
706 @Test
707 public void test_17_one2many_jointable_mapkey() {
708 Course17 physicsCourse = Course17.builder()
709 .courseName("physics")
710 .build();
711 Course17 chemistryCourse = Course17.builder()
712 .courseName("chemistry")
713 .build();
714 Student17 student = Student17.builder()
715 .studentName("Jack")
716 .build();
717 Map<String, Course17> courseMap = new HashMap<>();
718 courseMap.put("physics", physicsCourse);
719 courseMap.put("chemistry", chemistryCourse);
720 student.setCourseMap(courseMap);
721 Student17 savedStudent = student17Repository.save(student);
722 assertNotNull(savedStudent.getId());
723 }
724
725 @Test
726 public void test_18_one2one_jointable_unidirectional() {
727 Contact18 contact = Contact18.builder()
728 .address("Bangalore")
729 .build();
730 Student18 student = Student18.builder()
731 .studentName("Jack")
732 .contact(contact)
733 .build();
734 Student18 savedStudent = student18Repository.save(student);
735 assertNotNull(savedStudent.getId());
736 assertNotNull(savedStudent.getContact().getId());
737 }
738
739 @Test
740 public void test_19_one2many_unidirectional_save() {
741 Student19 student = Student19.builder()
742 .studentName("Jack")
743 .build();
744 Course19 course = Course19.builder()
745 .courseName("physics")
746 .student(student)
747 .build();
748 Course19 savedCourse = course19Repository.save(course);
749 assertNotNull(savedCourse.getId());
750 assertNotNull(savedCourse.getStudent().getId());
751 }
752
753 @Test
754 public void test_19_one2many_unidirectional_find() {
755 Iterable<Student19> students = student19Repository.findAll();
756 students.forEach(e -> {
757 System.out.println("Student: " + e);
758 List<Course19> courses = course19Repository.findAllByStudent(e);
759 assertEquals(3, courses.size());
760 courses.forEach(c -> {
761 System.out.println("Student: " + e + ", Course: " + c);
762 assertNotNull(c.getId());
763 });
764 });
765 }
766
767 @Test
768 public void test_20_enum_lob() {
769 Student20 student = Student20.builder()
770 .studentName("Jack")
771 .studentType(StudentType.FULL_TIME)
772 .build();
773 Student20 savedStudent = student20Repository.save(student);
774 assertNotNull(savedStudent.getId());
775 assertEquals(StudentType.FULL_TIME, savedStudent.getStudentType());
776 }
777
778 @Test
779 public void test_21_audit() {
780 Student21 student = Student21.builder()
781 .studentName("Jack")
782 .build();
783 Student21 savedStudent = student21Repository.save(student);
784 assertNotNull(savedStudent.getId());
785 assertNotNull(savedStudent.getCreatedAt());
786 assertNotNull(savedStudent.getUpdatedAt());
787 }
788
789 @Test
790 public void test_22_unique_constraints() {
791 Student22 student = Student22.builder()
792 .studentName("Jack")
793 .userName("user01")
794 .email("email@email.com")
795 .build();
796 Student22 savedStudent = student22Repository.save(student);
797 assertNotNull(savedStudent.getId());
798 }
799
800 @Test
801 public void test_23_nartual_id() {
802 Student23 student = Student23.builder()
803 .studentName("Jack")
804 .email("email@email.com")
805 .build();
806 Student23 savedStudent = student23Repository.save(student);
807 assertNotNull(savedStudent.getId());
808 }
809
810 @Test
811 public void test_24_composite_key() {
812 Student24 student = Student24.builder()
813 .student24Identity(Student24Identity.builder()
814 .registrationId("R-568")
815 .studentId("S-457")
816 .build())
817 .studentName("Jack")
818 .build();
819 Student24 savedStudent = student24Repository.save(student);
820 assertNotNull(savedStudent);
821 }
822
823 @Test
824 public void test_25_map() {
825 Map<String, Object> attributes = new HashMap<>();
826 attributes.put("address", "123 Main Street");
827 attributes.put("zipcode", 12345);
828
829 Student25 student = Student25.builder()
830 .studentName("jack")
831 .attributes(attributes)
832 .build();
833 Student25 savedStudent25 = student25Repository.save(student);
834 assertNotNull(savedStudent25);
835
836 Student25 findStudent25 = student25Repository.findById(savedStudent25.getId()).orElseThrow();
837 assertEquals(12345, findStudent25.getAttributes().get("zipcode"));
838 }
839
840 @Test
841 public void test_26_embeddable() {
842 Student26 student = Student26.builder()
843 .studentName("Jack")
844 .addresses(Address.builder()
845 .addressLine("5th street")
846 .city("Bangalore")
847 .country("India")
848 .zipCode("570021")
849 .build())
850 .build();
851 Teacher26 teacher = Teacher26.builder()
852 .teacherName("Mr. Adams")
853 .addresses(Address.builder()
854 .addressLine("9th street")
855 .city("Bangalore")
856 .country("India")
857 .zipCode("570015")
858 .build())
859 .build();
860 Student26 savedStudent = student26Repository.save(student);
861 Teacher26 savedTeacher = teacher26Repository.save(teacher);
862 assertNotNull(savedStudent.getId());
863 assertNotNull(savedTeacher.getId());
864 }
865
866 @Test
867 public void test_27_inheritance() {
868 Student27 student = Student27.builder()
869 .studentName("Jack")
870 .build();
871 Student27 savedStudent = student27Repository.save(student);
872 assertNotNull(savedStudent.getId());
873 }
874
875 @Test
876 public void test_28_projections() {
877 Student28 student = Student28.builder()
878 .studentName("Jack")
879 .notes("something about student")
880 .monthlySalary(5000)
881 .build();
882 Student28 savedStudent = student28Repository.save(student);
883 assertNotNull(savedStudent.getId());
884
885 Student28View student27View = student28Repository.getStudent27View(savedStudent.getStudentName());
886 assertEquals(60000, student27View.getAnnualSalary());
887
888 Student28DTO student27Dto = student28Repository.getStudent27Dto(savedStudent.getStudentName());
889 assertEquals(60000, student27Dto.annualSalary());
890
891 Student28Pojo student27Pojo = student28Repository.getStudent27Pojo(savedStudent.getStudentName());
892 assertEquals(60000, student27Pojo.getAnnualSalary());
893 }
894
895 @Test
896 public void test_30_optimistic_locking_multi_thread() throws InterruptedException {
897 CountDownLatch latch = new CountDownLatch(2);
898 modifyStudent30(100l, latch);
899 modifyStudent30(100l, latch);
900 latch.await(5, TimeUnit.SECONDS);
901 Student30 student = student30Repository.findById(100l).orElseThrow();
902 System.out.println("Student: " + student);
903 }
904
905 private void modifyStudent30(Long id, CountDownLatch latch) {
906 threadPool.submit(() -> {
907 try {
908 Student30 student = student30Repository.findById(id).orElseThrow();
909 student.setStudentName(student.getStudentName() + "_" + Thread.currentThread().getName());
910 //Delay so that version is updated before next thread saves.
911 TimeUnit.SECONDS.sleep(5);
912 student30Repository.save(student);
913 } catch (ObjectOptimisticLockingFailureException | InterruptedException ex) {
914 ex.printStackTrace();
915 assertEquals(ObjectOptimisticLockingFailureException.class, ex.getClass());
916 } finally {
917 latch.countDown();
918 }
919 });
920 }
921
922 @Test
923 public void test_31_java_records() {
924 Student31Record student = new Student31Record(null, "jack");
925 Student31Record savedStudent = student31Service.save(student);
926 assertNotNull(savedStudent.id());
927 }
928
929 @Test
930 public void test_32_transaction() {
931 /**
932 * Tests do transaction rollback after the test is completed.
933 */
934 Student32 student = Student32.builder()
935 .studentName("jack")
936 .build();
937 Student32 savedStudent = student32Repository.save(student);
938 assertNotNull(savedStudent.getId());
939 }
940
941 @Test
942 public void test_32_dynamic_update() {
943 //Check the SQL update statement they will contain on the columns that changed as we used @DynamicUpdate
944 //Since tests don't persist the data you will not see this in the test logs.
945 Student32 student = Student32.builder()
946 .studentName("jack")
947 .build();
948 Student32 savedStudent = student32Repository.save(student);
949 assertNotNull(savedStudent.getId());
950 }
951
952 @Test
953 public void test_33_queryByExample() {
954 Student33 exampleStudent = Student33.builder()
955 .studentName("jack")
956 .build();
957 Student33 student = student33Service.findByOneExample1(exampleStudent);
958 assertNotNull(student.getId());
959
960 student = student33Service.findByOneExample2(exampleStudent);
961 assertNotNull(student.getId());
962
963 List<Student33> students = student33Service.findAllExample1(exampleStudent);
964 assertEquals(1, students.size());
965
966 students = student33Service.findAllExample2(exampleStudent);
967 assertEquals(1, students.size());
968
969 Page<Student33> page = student33Service.findAllByPage(PageRequest.of(0, 10, Sort.by("studentName")));
970 assertEquals(5, page.getTotalElements());
971
972 page = student33Service.findAllByPageSort(PageRequest.of(0, 10));
973 assertEquals(5, page.getTotalElements());
974
975 students = student33Service.findByNameAndAgeIndex("raj", 34);
976 assertEquals(1, students.size());
977
978 students = student33Service.findByNameAndAgeParam("raj", 34);
979 assertEquals(1, students.size());
980 }
981
982 @Test
983 public void test_34_proxy() {
984 //by using getReferenceById we get a proxy object instead of the real object.
985 //with just the proxy object we are able to get the courses.
986 //drawback is that there can be constraint violation if the object doesn't really exist as we never checked the db.
987 Student34 student = student34Repository.getReferenceById(100l);
988 List<Course34> courses = course34Repository.findAllByStudent(student);
989 assertEquals(3, courses.size());
990 }
991
992 @Test
993 public void test_35_json() {
994 String payload = "{\"city\": \"bangalore\"}";
995 Student35 student = Student35.builder()
996 .studentName("jack")
997 .payload(payload)
998 .build();
999 Student35 savedStudent35 = student35Repository.save(student);
1000 assertNotNull(savedStudent35);
1001
1002 Student35 findStudent35 = student35Repository.findById(savedStudent35.getId()).orElseThrow();
1003 assertEquals(payload, findStudent35.getPayload());
1004
1005 List<Student35> studentList = student35Repository.findByCity("bangalore");
1006 assertEquals(1, studentList.size());
1007 }
1008
1009}
Setup
1# Project82
2
3Spring Data JPA Essentials
4
5### Version
6
7Check version
8
9```bash
10$java --version
11openjdk version "21.0.3" 2024-04-16 LTS
12```
13
14### Postgres DB
15
16```
17docker run -p 5432:5432 --name pg-container -e POSTGRES_PASSWORD=password -d postgres:14
18docker ps
19docker exec -it pg-container psql -U postgres -W postgres
20CREATE USER test WITH PASSWORD 'test@123';
21CREATE DATABASE "test-db" WITH OWNER "test" ENCODING UTF8 TEMPLATE template0;
22grant all PRIVILEGES ON DATABASE "test-db" to test;
23
24docker stop pg-container
25docker start pg-container
26```
27
28There is a bug in spring data jpa where `jakarta.persistence.lock.timeout` is not working for postgres.
29Hence set the timeout at database level to 10 seconds.
30
31```bash
32ALTER DATABASE "test-db" SET lock_timeout=10000;
33```
34
35To look at isolation level
36
37```bash
38SHOW default_transaction_isolation;
39ALTER DATABASE "test-db" SET default_transaction_isolation = 'read committed'
40```
41
42### Dev
43
44To run the backend in dev mode.
45
46```bash
47./gradlew clean build
48./gradlew bootRun
49```
References
https://vladmihalcea.com/blog/
https://thorben-janssen.com/ultimate-guide-association-mappings-jpa-hibernate/
https://docs.spring.io/spring-data/jpa/reference/repositories/projections.html