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 JPAEntityManager and the HibernateSession (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;
}
@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!!
}
}
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;
}
@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
}
@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");
@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 managedstate, 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
It will result in following DB structure:
Student: | id | name | passport_id | <= owing a relationship
Passport: | id | number |
@OneToOne relationship fetch is always eager (fulfilled entity is returned, with opposite entity)
It will result in following DB structure:
Student: | id | name | passport_id |
Passport: | id | number | student_id |
This is bad because of data duplication.
It will result in following DB structure:
Student: | id | name | passport_id |
Passport: | id | number |