Hibernate/JPA – Transparent Persistence II

I wanted to add another nice example about Transparent Persistence (explained in a previous post), to insist on the beauty of this. This time, adding JPA mappings in the classes that represent the domain model of my application. Additionally, I will mention how Hibernate/JPA behaves in this case, and how to change that if necessary, to ensure your application perform smoothly.

Suppose I’m implementing an application that allows you to create different competitions and let people (participants) enrol to them. Participants are allowed to enrol the competition only during a period of time, defined within the competition. It will also need validate if the participant is not already enrolled.

As I have been saying in my other post, implement your business requirements inside your domain model, don’t be tempted to put business logic inside Facades, DAOs, Repositories, Controllers, etc. This temptation occurs specially once you have to persist your objects. Think in a way that your memory is persistent, implement your requirements in the domain model and then use Transparent Persistent. Only when the ORM tool you are using does not provide the features to make the transparent persistence perform smoothly, then there is when you are forced to make changes to your objects.

So, lets implement this in the domain model. The domain model with JPA annotations looks like the one below:

@Entity
public class Competition {

	@Id
	@GeneratedValue(generator = "system-uuid")
	@GenericGenerator(name = "system-uuid", strategy = "uuid")
	private String id;
	@Column
	private String name;
	@Column
	private LocalDate startDate;
	@Column
	private LocalDate endDate;

	@OneToMany(mappedBy = "competition")
	private Collection<Participant> participants
						= new ArrayList<>(); 	        
	//just for Hibernate
	protected Competition() { }

	public Competition(String name, LocalDate startDate, 
							LocalDate endDate) {
		this.name = name;
		this.startDate = startDate;
		this.endDate = endDate;
	}

	public void enrol(Participant p) {
		checkEnrolInPeriod();
		if (!this.participants.contains(p)) {
			participants.add(p);
			p.setCompetition(this);
		}
	}

	private void checkEnrolInPeriod() {
		LocalDate today = LocalDate.now();
		if (!(!today.isBefore(this.startDate) 
				&& !today.isAfter(this.endDate))) {
			throw new RuntimeException("You can't "
							+ "enrol outside the period...");
		}
	}
}

@Entity
public class Participant {

	@Id
	@GeneratedValue(generator = "system-uuid")
	@GenericGenerator(name = "system-uuid", strategy = "uuid")
	private String id;

	@Column
	private String name;
	
	@ManyToOne
	@JoinColumn(name = "competition_id")
	private Competition competition; 
	
	//just for Hibernate
	protected Participant() { }
	
	public Participant(String name) {
		this.name = name;
	}
    
	...
}

Now, in the client code of my domain model, in this case, a Facade that is used by my Front-End, we wrap in a persistent context the following sentences:

	public void enrol(idCompetition, idParticipant) {
		//start persistence context

		Competition aCompetition = // retrieving from the storage by Id
		Participant aParticipant = // retrieving from the storage by Id
		aCompetition.enrol(aParticipant);

		//close persistence context
	}

What happen if we execute the enrol(idCompetition, idParticipant) method ?

  • The first line will retrieve from the persistent storage the aCompetition instance, executing the following SQL statement:

        select
            id,
            competition_id,
            name,
        from
            Competition
        where
            id = idCompetition;
    

    Note that there is no retrieval at all from Participants, which means that the Competition#participants collections is not initialized. This is because by default in JPA the one-to-many relationship is lazy. Lazy means that only if you need somenthing from that collection, then at that point the collection will be initialized.

  • The second line will retrieve from the persistent storage the aParticipant instance, executing the following SQL statement:

        select 
            participan0_.id, 
            participan0_.competition_id, 
            participan0_.name,
            competitio1_.id,
            competitio1_.endDateForInscription,
            competitio1_.name,
            competitio1_.startDateForInscription
        from Participant participan0_ 
                left outer join Competition competitio1_ on   
                participan0_.competition_id = competitio1_.id 
        where participan0_.id = idParticipant;
    

    Note that there is a join with Competition due to in JPA the many-to-one relationship that exists here between Participant and Competition, is eager by default.

  • The third (and last) invoke the business method aCompetition.enrol(aParticipant). Looking at the code of that method:
    • The first sentence: checkEnrolInPeriod(); will check if aParticipant is on time to be enrolled in aCompetition. There is no SQL statement generated here as the instances loaded before has everything needed to execute this check.
    • After that, the sentence this.participants.contains(p) will make Hibernate/JPA to initialize the participants collection. This will produce a SQL statement like the following:
    •     select
              name,        
              id,
              competition_id
          from
              Participant
          where
              competition_id = idCompetition;
      

      After executing that SQL and initializing the collection, the Collection#contains is executed. This is normal Java behaviour, it will compare instances using the equals method, and with that will decide if aParticipant is already enrolled or not.

    • Following that, if the condition evaluates to true, then it will add aParticipant to the participants collection. Generating the following SQL statment
          update 
              Participant
          set 
              competition_id=?, 
          where 
              id = idParticipant;
      

      Note that the SQL statement is generating the relationship between Competition and Participant in the relational model.

It is important to highlight, one more time, that our Competition#enrol(Participant) method has been written without thinking in the underlying persistent storage (in this case a relational model). And in a Transparent way, the persistent storage has been modified to accommodate the changes in the objects that belongs to the domain model.

Performance Improvement

As showed before, the sentence this.participants.contains(p) is bringing to memory all the participants from the persistent storage, in order to compare if aParticipant is already enrolled or not. This works perfect for small collections, but for large collections might not work.

Hibernate/JPA provides an additional annotation for this case called Extra Lazy. The extra lazy mapping will produce that the methods Collection#size and Collection#contains won’t trigger the initialization. Lets make that addition to our class.

@Entity
public class Competition {
	...

	@OneToMany(mappedBy = "competition")
	@LazyCollection(LazyCollectionOption.EXTRA)
	private Collection<Participant> participants
						= new ArrayList<>(); 	        
	...

}

With this in place, our explanation above about what occurs when this.participants.contains(p) is invoked will change. Now, instead of initialize the collection (bring all the participants into memory), it will perform the next SQL statement to let the persistent storage to answer if aParticipant has been enrolled or not.

        select  
                1 
        from 
                Participant 
        where 
                competition_id = idCompetition and 
                id = idParticipant;

Looks good right?

This post presented another example or use case where you are allowed to develop Object Oriented software without having to break how you model your objects or without being forced to break your object model due to persistence. Enjoy Transparent Persistence.

Posted in Persistence Tagged with: , ,