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
@EntityclassStudent { @Id @GeneratedValueprivateLong id;privateString name; @OneToOneprivatePassport passport;// Student is owning a relation}@EntityclassPassport { @Id @GeneratedValueprivateLong id;privateString number;}
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)
Lazy fetch
@EntityclassStudent { @Id @GeneratedValueprivateLong id;privateString name; @OneToOne(fetch =FetchType.Lazy)privatePassport passport;// only when you call student.getPassport()// query to Passport table is sent// i.e. you get details when you explicitly need}@EntityclassPassport { @Id @GeneratedValueprivateLong id;privateString number;}
Bidirectional relation
@EntityclassStudent { @Id @GeneratedValueprivateLong id;privateString name; @OneToOne(fetch =FetchType.LAZY)privatePassport passport;// only when you call student.getPassport()// query to Passport table is sent// i.e. you get details when you explicitly need}@EntityclassPassport { @Id @GeneratedValueprivateLong id;privateString 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.
@EntityclassStudent { @Id @GeneratedValueprivateLong id;privateString name; @OneToOne(fetch =FetchType.LAZY)privatePassport passport;// Student is still owning a relation}@EntityclassPassport { @Id @GeneratedValueprivateLong id;privateString 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 |
@EntityclassCourse { @OneToMany(mappedBy ="course")privateList<Review> reviews =newArrayList<>();// think from course perspective:// course has many reviews// you have a mappedBy because course does not own a relation}@EntityclassReview { @ManyToOneprivateCourse 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@EntityclassCourse { @ManyToMany(mappedBy="courses")privateList<Student> students =newArrayList<>();}@EntityclassStudent { @ManyToMany @JoinTable(name="STUDENT_COURSE", joinColumns = @JoinColumn(name="STUDENT_ID"), inverseJoinColumns = @JoinColumn(name="COURSE_ID"))privateList<Course> courses =newArrayList<>(); // 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@RepositoryclassStudentRepository { @AutowiredEntityManager manager;publicvoidgetStudent() {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 } publicvoidinsertStudent() {Student student =newStudent("Jack");Course course =newCourse("Algo");manager.persist(student);manager.persist(course);student.addCourse(course);course.addStudent(student);manager.persist(student); // not sure that this last step is needed }}
// examples of JPQL queriesQuery 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);// JOINSQuery q =entityManager.createQuery("select c, s from Course c JOIN c.students s");
@TransactionalpublicvoidregisterNewAccount() {// 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 rollbackuserTransaction.rollback(); throw e;}
Redundant save() invocation
@TransactionalpublicvoidchangeName(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