1. @Transactional Annotation Issues
Problem: Changes are not reflected in the database despite using @Transactional.
Reason 1: Calling from the Same Class
- Explanation: Spring uses proxy-based AOP for transaction management. If a @Transactional method is called from the same class, proxy is bypassed.
- Example:
@Service
public class MyService {
@Transactional
public void transactionalMethod() {
// Database update logic
}
public void callTransactional() {
transactionalMethod(); // No transaction applied
}
}
- Solution: Move the
transactionalMethod()to another Spring-managed bean.
Reason 2: Wrong Propagation Type
- Explanation: Using
Propagation.NEVERorNOT_SUPPORTEDdisables transactional behavior. - Solution: Use
Propagation.REQUIREDunless there’s a specific need.
@Transactional(propagation = Propagation.REQUIRED)
Reason 3: No Exception Thrown (or Checked Exception)
- Explanation: By default, Spring only rolls back on unchecked exceptions.
- Example:
@Transactional
public void updateData() throws IOException {
// Some DB logic
throw new IOException("Checked exception"); // Transaction won't roll back
}
- Solution:
@Transactional(rollbackFor = IOException.class)
Reason 4: No Explicit Flush (rare in Spring Boot)
- Spring handles flush implicitly at commit time, but older setups might not.
- Solution: Call
entityManager.flush()manually if needed.
Reason 5: Incorrect Isolation Levels
- Explanation:
READ_UNCOMMITTEDmay result in dirty reads. - Solution:
@Transactional(isolation = Isolation.READ_COMMITTED)
Reason 6: Read-Only Transaction
- Explanation: Marking transaction as
readOnly = truewill block write operations. - Solution: Remove the readOnly flag for write logic.
Reason 7: @EnableTransactionManagement Not Enabled
- Explanation: Required in non-Spring Boot apps.
- Solution:
@Configuration
@EnableTransactionManagement
public class AppConfig {}
2. ID Generation Gaps
Problem: Gaps in ID generation like 1, 10, 51
Reason 1: Hibernate’s Batch Pre-Allocation
- Hibernate pre-allocates sequences in memory.
- Example: Sequence allocationSize = 50, uses 1–50 in memory. If restart happens at 10, next allocation is 51–100.
Reason 2: Transaction Rollbacks
- Rollback discards IDs.
Reason 3: Multiple App Instances
- Each instance may pre-allocate separate ID batches.
Reason 4: Database-Specific Behavior
- MySQL: Uses auto-increment.
- PostgreSQL/Oracle: Uses sequences with caching.
Solutions:
- Use
GenerationType.IDENTITYfor sequential IDs (MySQL-friendly). - Disable batch insert if strict order is needed.
- Configure sequences:
CREATE SEQUENCE my_seq INCREMENT BY 1 CACHE 1;
- Use a custom table to generate and track IDs manually.
3. N+1 Query Problem
Problem:
- One query for parent, and N additional queries for child collections.
- Example: Fetching all
Departments and their lazy-loadedEmployees → 1 (Department) + N (Employee).
Solutions:
1. Join Fetch
@Query("SELECT d FROM Department d JOIN FETCH d.employees")
List<Department> findAllDepartmentsWithEmployees();
2. Entity Graph
@NamedEntityGraph(name = "Department.employees", attributeNodes = @NamedAttributeNode("employees"))
@Entity
public class Department { ... }
@EntityGraph(value = "Department.employees")
List<Department> findAll();
3. Batch Fetching
@OneToMany(fetch = FetchType.LAZY) @BatchSize(size = 10) private List<Employee> employees; @Fetch(FetchMode.SUBSELECT)
4. Eager Loading
- Avoid unless absolutely necessary. Can lead to performance issues.
4. Soft Delete in JPA/Hibernate
Concept:
- Logically delete records instead of removing from DB. Useful for audit and recovery.
Method 1: Boolean Flag + @Where
@Column(name = "is_deleted")
private boolean isDeleted = false;
@Where(clause = "is_deleted = false")
@Entity
public class User { ... }
Method 2: @SQLDelete
@SQLDelete(sql = "UPDATE user SET is_deleted = true WHERE id = ?")
@Entity
public class User { ... }
Caveats:
- Cache might still hold deleted data.
- Manual handling needed for cascade deletions.
Best Practice:
- Use both
@SQLDeleteand@Wherefor safe and consistent soft deletion behavior.