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

  1. Repository Abstraction: Provides a high-level abstraction over the data access layer, allowing developers to define repositories with minimal code.
  2. Automatic Query Generation: Generates queries based on method names defined in repository interfaces.
  3. Pagination and Sorting: Supports pagination and sorting out of the box.
  4. Auditing: Supports auditing of entity changes (e.g., tracking created/modified dates and users).
  5. Custom Query Methods: Allows custom JPQL (Java Persistence Query Language) and SQL queries.
  6. Integration with Spring: Seamlessly integrates with the Spring Framework, including transaction management and dependency injection.
AnnotationDescription
@ManyToOneMost natural way to map a foreign key relation. Default to FETCH.EAGER
@OneToManyParent 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
@OneToOneCreates a foreign key in parent table that refers to the primary key of child table.
@MapsIdSingle key acts as primary key & foreign key, with single key you can now fetch data from both table with same key.
@ManyToManyTwo parents on one child, avoid doing CascadeType.ALL, dont do orphan removal.
mappedByPresent 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

  1. Pessimistic Locking - Locks held at row level or table level. Not ideal of high performance & cant scale.
  2. 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

  1. 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.
  2. 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.
  3. 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

  1. LockModeType.OPTIMISTIC - Checks the version attribute of the entity before committing the transaction to ensure no other transaction has modified the entity.
  2. 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

  1. Isolation.READ_UNCOMMITTED Read Uncommitted - The lowest level of isolation. Transactions can read uncommitted changes made by other transactions.
  2. Isolation.READ_COMMITTED Read Committed - Transactions can only read committed changes made by other transactions.
  3. 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.
  4. Isolation.SERIALIZABLE Serializable - The highest level of isolation. Transactions are completely isolated from one another.

Data Consistency

  1. Dirty reads: read UNCOMMITED data from another transaction.
  2. Non-repeatable reads: read COMMITTED data from an UPDATE query from another transaction.
  3. Phantom reads: read COMMITTED data from an INSERT or DELETE query from another transaction.

Dirty Read

NAMEAGE
Bob35
TRANSACTION T1TRANSACTION 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

NAMEAGE
Bob35
TRANSACTION T1TRANSACTION 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

NAMEAGE
Bob35
TRANSACTION T1TRANSACTION 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 LevelDirtyNon-Repeatable ReadsPhantom Reads
Read UncommittedYesYesYes
Read CommittedNoYesYes
Read CommittedNoNoYes
SerializableNoNoNo
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.

  1. @Transactional(readOnly = true) - transaction is readonly and now updates can happen.
  2. @Transactional(propagation = Propagation.REQUIRES_NEW) - creates a new transaction.
  3. @Transactional(propagation = Propagation.REQUIRED) - default, spring will create a new transaction if not present.
  4. @Transactional(propagation = Propagation.MANDATORY) - will throw exception if transaction doesn't exist.
  5. @Transactional(propagation = Propagation.SUPPORTS) - if existing transaction present then it will be used, else operation will happen without any transaction.
  6. @Transactional(propagation = Propagation.NOT_SUPPORTED) - operation will have with no transaction.
  7. @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

comments powered by Disqus