Hibernate

Hibernate uses PreparedStatement exclusively, so not only it protect against SQL injection, but the data access layer can better take advantage of server-side and client-side statement caching as well.

💡 When you open a transaction => you have a Persistence context, where retrieved entities are kept (entities manipulated by EntityManager). When transaction is closed => persistence context is also closed. Session = Persistence context.

Write-behind cache

The Persistence Context acts as a transactional write-behind cache, deferring entity state flushing up until the last possible moment.

Because every modifying DML statement requires locking (to prevent dirty writes), the write behind cache can reduce the database lock acquisition interval, therefore increasing concurrency.

But caches introduce consistency challenges, and the Persistence Context requires a flush prior to executing any JPQL or native SQL query (as otherwise it might break the read- your-own-write consistency guarantee).

Session flush()

session in-memory state -> database

Happens automatically for cases:

  • before query execution

  • when transaction is committed

JPA entity state

The Persistence Context captures entity state changes, and, during flushing, it translates them to SQL statements. The JPA EntityManager and the Hibernate Session (which includes additional methods for moving an entity from one state to the other) interfaces are gateways towards the underlying Persistence Context, and they define all the entity state transition operations.

Hibernate entity state

Relationship

One to one

Which table has a foreign key => this table owning a relation

@Entity
class Student {

    @Id @GeneratedValue
    private Long id;
    
    private String name;
    
    @OneToOne
    private Passport passport;
    // Student is owning a relation
}

@Entity
class Passport {

    @Id @GeneratedValue
    private Long id;
    
    private String number;
}    

It will result in following DB structure: Student: | id | name | passport_id | <= ⚠️ owing a relationship Passport: | id | number |

@Transactional
@Repository
class StudentRepository {

    @Autowired
    EntityManager manager;
    
    public void saveStudentWithPassport() {
        Passport pass = new Passport("N123");
        manager.persist(pass); // !! otherwise student cannot be stored
        
        Student stud = new Student("John");
        stud.setPassport(pass);
        manager.persist(student);
    }                
    
    public void retrieveStudent() {
        manager.find(Student.class, "123");
        assertThat(student.getPassword).isNotNull();
        // eager fetch!!
    }
    
}    

ℹ️ @OneToOne relationship fetch is always eager (fulfilled entity is returned, with opposite entity)

Lazy fetch

@Entity
class Student {

    @Id @GeneratedValue
    private Long id;
    
    private String name;
    
    @OneToOne(fetch = FetchType.Lazy)
    private Passport passport;
    // only when you call student.getPassport()
    // query to Passport table is sent
    // i.e. you get details when you explicitly need
}

@Entity
class Passport {

    @Id @GeneratedValue
    private Long id;
    
    private String number;
}    

Bidirectional relation

@Entity
class Student {

    @Id @GeneratedValue
    private Long id;
    
    private String name;
    
    @OneToOne(fetch = FetchType.LAZY)
    private Passport passport;
    // only when you call student.getPassport()
    // query to Passport table is sent
    // i.e. you get details when you explicitly need
}

@Entity
class Passport {

    @Id @GeneratedValue
    private Long id;
    
    private String number;
    
    @OneToOne(fetch = FetchType.LAZY)
    private Student;
}    

It will result in following DB structure: Student: | id | name | passport_id | Passport: | id | number | student_id | 🚫 This is bad because of data duplication.

@Entity
class Student {

    @Id @GeneratedValue
    private Long id;
    
    private String name;
    
    @OneToOne(fetch = FetchType.LAZY)
    private Passport passport;
    // Student is still owning a relation
}

@Entity
class Passport {

    @Id @GeneratedValue
    private Long id;
    
    private String number;
    
    @OneToOne(fetch=FetchType.LAZY, mappedBy="passport"))
    private Student;
    // this is a NON owning side of the relation
}    

👍 It will result in following DB structure: Student: | id | name | passport_id | Passport: | id | number |

@Transactional
@Repository
class StudentRepository {

    @Autowired
    EntityManager manager;          
    
    public void retrievePassword() {
        manager.find(Passport.class, "123");
        assertThat(passport.getStudent).isNotNull();
    }
    
}    

Many to one

@Entity
class Course {
    
    @OneToMany(mappedBy = "course")
    private List<Review> reviews = new ArrayList<>();
    // think from course perspective:
    // course has many reviews
    // you have a mappedBy because course does not own a relation
}

@Entity
class Review {
    
    @ManyToOne
    private Course course;
    // this is an owning side of the relation
}    

It will result in following DB structure: Course : | id | name | Review: | id | rating | course_id |

Many to many

// is not important which entity owning a relationship
@Entity
class Course {
    
    @ManyToMany(mappedBy="courses")
    private List<Student> students = new ArrayList<>();
}

@Entity
class Student {
    
    @ManyToMany
    @JoinTable(name="STUDENT_COURSE",
        joinColumns = @JoinColumn(name="STUDENT_ID"),
        inverseJoinColumns = @JoinColumn(name="COURSE_ID"))
    private List<Course> courses = new ArrayList<>();    
    // in this case Student is the owning side of the relation
    // because Course has "mappedBy"
    
    // in the entity of owing side you may specify annotations
    // to configure join table (name, column names)
}  
@Transactional
@Repository
class StudentRepository {

    @Autowired
    EntityManager manager;
    
    public void getStudent() {
        Student stu = manager.find(Student.class, "123");
        // at this point only student will be queried
        // his courses will not be queried
        // LAZY fetch by default!

        logger.info(student.getCourses());
        // at this point inner join between
        // student_course and course table will happen
        // to retrieve courses by student id
    } 
    
    public void insertStudent() {
        Student student = new Student("Jack");
        Course course = new Course("Algo");
        manager.persist(student);
        manager.persist(course);
        
        student.addCourse(course);
        course.addStudent(student);
        manager.persist(student); // not sure that this last step is needed
    }
}  

Inheritance

@Entity
abstract class Employee {

    @Id @GeneratedValue
    private Long id;
    private String name;
}

class PartTimeEmployee extends Employee {
    private BigDecimal hourlyWage;
}

class FullTimeEmployee extends Employee {
    private BigDecimal salary;
}
@Transactional
@Repository
class EmployeeRepository {

    @Autowired
    EntityManager manager;
    
    public void insertEmployee(Employee emp) {
        manager.pesist(emp);
    } 
    
    public void retrieveAllEmployee() {
        manager.createQuery("select e from Employee e",
            Employee.class)
            .getResultList();
    }
}  

Single table

@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
// this is default strategy
abstract class Employee {

    @Id @GeneratedValue
    private Long id;
    private String name;
}

class PartTimeEmployee extends Employee {
    private BigDecimal hourlyWage;
}

class FullTimeEmployee extends Employee {
    private BigDecimal salary;
}
  • it is good that it is single table (better for performance!!)

  • it is bad that there are many nullable columns

Table per class

@Entity
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
abstract class Employee {

    @Id @GeneratedValue
    private Long id;
    private String name;
}

class PartTimeEmployee extends Employee {
    private BigDecimal hourlyWage;
}

class FullTimeEmployee extends Employee {
    private BigDecimal salary;
}
  • many tables may be created because of many subclasses

  • Not a good choice (c)

Joined

@Entity
@Inheritance(strategy=InheritanceType.JOINED)
abstract class Employee {

    @Id @GeneratedValue
    private Long id;
    private String name;
}

class PartTimeEmployee extends Employee {
    private BigDecimal hourlyWage;
}

class FullTimeEmployee extends Employee {
    private BigDecimal salary;
}
  • fields from super class are stored into EMPLOYEE table

  • subclass columns are stored in separate tables

  • db design is clear

  • performance may be poor in fetching all kinds of employees (many joins will happen)

  • Better for data integrity

Mapped super class

JPQL

@Entity
class Course {
    
    @ManyToMany(mappedBy="courses")
    private List<Student> students = new ArrayList<>();
}

@Entity
class Student {
    
    @ManyToMany
    private List<Course> courses = new ArrayList<>();
    
    @OneToOne
    private Passport passport;    
}  

@Entity
class Passport {

    @Id @GeneratedValue
    private Long id;
    
    private String number;
}   
// examples of JPQL queries
Query q = entityManager.createQuery("select c from course c");

TypedQuery<Course> = EntityManager.createQuery(
    "select c from Course c", 
    Course.class);
    
TypedQuery<Course> = EntityManager.createQuery(
    "select c from Course c where name like '%bla' ",
    Course.class);
    
TypedQuery<Course> = EntityManager.createQuery(
    "select c from Course c where c.students is empty",
    Course.class);

TypedQuery<Course> = EntityManager.createQuery(
    "select c from course c where c.students >=2",
    Course.class);

TypedQuery<Course> = EntityManager.createQuery(
    "select c from course c order by size(c.students) desc",
    Course.class);

TypedQuery<Student> = EntityManager.createQuery(
    "select s from Student s where s.passport.number like '%123%'",
    Student.class);

// JOINS
Query q = entityManager.createQuery(
    "select c, s from Course c JOIN c.students s");

Criteria queries

// select all courses
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Course> cq = cb.createQuery(Course.class);
Root<Course> courseRoot = cq.from(Course.class);

TypedQuery<Course> query = entityManager.createQuery(
    cq.select(courseRoot));
    
// select courses having 100 steps in name
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Course> cq = cb.createQuery(Course.class);
Root<Course> courseRoot = cq.from(Course.class);
Predicate like100Steps = cb.like(courseRoot.get("name"), "%100 Steps");
cq.where(like100Steps);

TypedQuery<Course> query = entityManager.createQuery(
    cq.select(courseRoot));

// select courses without students
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Course> cq = cb.createQuery(Course.class);
Root<Course> courseRoot = cq.from(Course.class);
Predicate studentsIsEmpty = cb.empty(courseRoot.get("students"), "%100 Steps");
cq.where(studentsIsEmpty)

TypedQuery<Course> query = entityManager.createQuery(
    cq.select(courseRoot));

// join course on students
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Course> cq = cb.createQuery(Course.class);
Root<Course> courseRoot = cq.from(Course.class);
Join<Object,Object> join = courseRoot.join("students");
// Join<Object,Object> join = courseRoot.join("students", JoinType.Left);

TypedQuery<Course> query = entityManager.createQuery(
    cq.select(courseRoot));

Transactions

Atomicity, Concurrency, Isolation, Durability

Dirty Read

Non Repeatable Read

Phantom Read

Read Uncommited

possible

possible

possible

Read Commited

(lock on specific value in row)

SOLVED

possible

possible

Repeatable Read

(whole row is locked)

SOLVED

SOLVED

possible

Serializable

(any row matching constraint is locked)

SOLVED

SOLVED

SOLVED

Spring Transactional

@Transactional
public void registerNewAccount() {
   // business code
}

is logically equal to

UserTransaction userTransaction = entityManager.getTransaction();
try {
  // begin a new transaction if expected
  // (depending on the current transaction context and/or propagation mode setting)
   userTransaction.begin(); 

   registerNewAccount(); 

   userTransaction.commit();
} catch(RuntimeException e) {   // Runtime!!! checked exceptions do not cause rollback
   userTransaction.rollback(); 
   throw e;
}

Redundant save() invocation

@Transactional
public void changeName(long id, String name) {
  User user = userRepository.getById(id);
  user.setName(name);
//  userRepository.save(user);
// above call is not needed
}

When a method is transactional, then entities retrieved within this transaction are in the managed state, which means that all changes made to them will be populated to the database automatically at the end of the transaction. Therefore either the save() call is redundant

Ignored Transactional annotation

For Spring in order to take an action on transactions two expected criteria:

  • The method visibility can’t be any other than public.

  • The invocation must come from outside of the bean.

This is due to how Spring proxying work by default (using CGLIB proxies).

When you auto-wire a bean of type Sample, Spring in fact doesn’t provide you exactly with an instance of Sample. Instead, it injects a generated proxy class that extends Sample (yes, that’s the reason why you can’t make your spring bean classes final) and overrides its public methods to be able to add extra behaviour (like transactional support).

Solution 1

  • Extract the method to another class and make it public.

Solution 2

  • Use AspectJ weaving instead of default proxy-based Spring AOP. AspectJ is capable of working with both: non-public methods and self-invocations.

Solution 3 (only for self-invocation)

  • Disclaimer: I wouldn’t use this “solution” in the production code.

Rollbacks

The rule when transaction rollbacks are triggered automatically is very simple but worth reminding: by default, a transaction will be rolled back if any unchecked exception is thrown within it, whereas checked exceptions don’t trigger rollbacks.

We can customize this behaviour with parameters:

  • noRollbackFor - to specify runtime exception, which shouldn’t cause a rollback

  • rollbackFor - to indicate which checked exception should trigger rollbacks

Last updated