|

Access Strategies in JPA and Hibernate – Which is better, field or property access?


Take your skills to the next level!

The Persistence Hub is the place to be for every Java developer. It gives you access to all my premium video courses, monthly Java Persistence News, monthly coding problems, and regular expert sessions.


The access strategy defines how your JPA implementation, e.g., Hibernate or EclipseLink, access your entity attributes. You can choose between field-based access and property-based access:

  • If you use field-based access, your JPA implementation uses reflection to read or write your entity attributes directly. It also expects that you place your mapping annotations on your entity attributes.
  • If you use property-based access, you need to annotate the getter methods of your entity attributes with the required mapping annotations. Your JPA implementation then calls the getter and setter methods to access your entity attributes.

That might sound like a minor, internal difference. But as I will show you in this article, the access strategy has a noticeable impact on your entity implementation and can also affect your business code. So, the question is: Which one should you prefer?

How to specify the access strategy

But before we dive into the differences between the two, let me quickly explain how to configure an access strategy.

Default configuration of your access strategy

By default, you specify the access strategy implicitly by annotating your primary key attribute or its getter method with an @Id annotation. If you annotate the attribute itself, Hibernate uses field-based access.

@Entity
public class Review {
 
    @Id
    protected Long id;
 
    @Enumerated
    private Rating rating;
 
    private ZonedDateTime postedAt;
 
    @Version
    private int version;


	public Long getId() {
		return id;
	}

	public int getVersion() {
		return version;
	}

	public Rating getRating() {
		return rating;
	}

	public void setRating(Rating rating) {
		this.rating = rating;
	}

	public ZonedDateTime getPostedAt() {
		return postedAt;
	}

	public void setPostedAt(ZonedDateTime postedAt) {
		this.postedAt = postedAt;
	}

	@Override
	public String toString() {
		return "BaseReview [id=" + id + ", rating=" + rating + ", postedAt="
				+ postedAt + ", version=" + version + "]";
	}
}

And if you annotate a getter method with the @Id annotation, Hibernate uses property-based access to set and read the attributes of this entity.

@Entity
public class Review {
 
    protected Long id;
 
    private Rating rating;
 
    private ZonedDateTime postedAt;
 
    private int version;

	@Id
	public Long getId() {
		return id;
	}
	
	public void setId(Long id) {
		this.id = id;
	}
	
	@Version
	public int getVersion() {
		return version;
	}
	
	public void setVersion(int version) {
		this.version = version;
	}
	
	@Enumerated
	public Rating getRating() {
		return rating;
	}

	public void setRating(Rating rating) {
		this.rating = rating;
	}

	public ZonedDateTime getPostedAt() {
		return postedAt;
	}

	public void setPostedAt(ZonedDateTime postedAt) {
		this.postedAt = postedAt;
	}

	@Override
	public String toString() {
		return "BaseReview [id=" + id + ", rating=" + rating + ", postedAt="
				+ postedAt + ", version=" + version + "]";
	}
}

Override the default access strategy

If you want to mix both access strategies within one entity or a hierarchy of entities, you need to override the default strategy with an @Access annotation. Otherwise, the JPA specification defines the behavior as undefined:

The behavior of applications that mix the placement of annotations on fields and properties within an entity hierarchy without explicitly specifying the Access annotation is undefined.
JSR 338: JavaTM Persistence API, Version 2.2

I use the @Access in the following example to change the access strategy for version attribute from property-based to field-based access.

@Entity
public class Review {
 
    protected Long id;
 
    private Rating rating;
 
    private ZonedDateTime postedAt;
    
    @Version
    @Access(AccessType.FIELD)
    private int version;

	@Id
	public Long getId() {
		return id;
	}
	
	public void setId(Long id) {
		this.id = id;
	}
	
	public int getVersion() {
		return version;
	}
	
	public void setVersion(int version) {
		this.version = version;
	}
	
	@Enumerated
	public Rating getRating() {
		return rating;
	}

	public void setRating(Rating rating) {
		this.rating = rating;
	}

	public ZonedDateTime getPostedAt() {
		return postedAt;
	}

	public void setPostedAt(ZonedDateTime postedAt) {
		this.postedAt = postedAt;
	}

	@Override
	public String toString() {
		return "BaseReview [id=" + id + ", rating=" + rating + ", postedAt="
				+ postedAt + ", version=" + version + "]";
	}
}

5 reasons why you should use field-based access

As explained earlier, you most often specify your access strategy by annotating the attributes or getter methods of your entity. But which strategy should you choose? Does it make a real difference?

Yes, it does make a difference. I always recommend using field-based access. Here are 5 reasons why field-based access is the better access strategy.

Reason 1: Better readability of your code

Writing readable code is important. You should always consider how a specific implementation visually affects your code. And because the access strategy defines where you can place your mapping annotations, it has a significant impact on readability.

If you use field-based access, you annotate your entity attributes with your mapping annotations. By placing the definition of all entity attributes at the top of your class, you get a relatively compact view of all attributes and their mappings.

@Entity
public class Review {
 
    @Id
    protected Long id;
 
    @Enumerated
    private Rating rating;
 
    private ZonedDateTime postedAt;
 
    @Version
    private int version;

    ...
}

You can’t achieve that with property-based access because the implementation of your getter methods spreads over multiple lines. This requires you to scroll through more code, and this makes it much harder to get an overview of an entity and its mapping.

@Entity
public class Review {
 
    ...

	@Id
	public Long getId() {
		return id;
	}
	
	public void setId(Long id) {
		this.id = id;
	}
	
	@Version
	public int getVersion() {
		return version;
	}
	
	public void setVersion(int version) {
		this.version = version;
	}
	
	@Enumerated
	public Rating getRating() {
		return rating;
	}

	public void setRating(Rating rating) {
		this.rating = rating;
	}

	public ZonedDateTime getPostedAt() {
		return postedAt;
	}

	public void setPostedAt(ZonedDateTime postedAt) {
		this.postedAt = postedAt;
	}

	@Override
	public String toString() {
		return "BaseReview [id=" + id + ", rating=" + rating + ", postedAt="
				+ postedAt + ", version=" + version + "]";
	}
}

Reason 2: Omit getter or setter methods that shouldn’t be called by your application

Another advantage of field-based access is that your persistence provider, e.g., Hibernate or EclipseLink, doesn’t use the getter and setter methods of your entity attributes. That means that you don’t need to provide any method that shouldn’t be used by your business code. This is most often the case for setter methods of generated primary key attributes or version columns. Your persistence provider manages the values of these attributes, and you should not set them programmatically.

@Entity
public class Review {
 
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "review_seq")
    @SequenceGenerator(name = "review_seq", sequenceName = "review_seq")
    protected Long id;
 
    @Enumerated
    private Rating rating;
 
    private ZonedDateTime postedAt;
 
    @Version
    private int version;


	public Long getId() {
		return id;
	}

	public int getVersion() {
		return version;
	}

    ...
}

You can also provide utility methods that enable you to add or remove elements from a to-many association and prevent direct access to the underlying List or Set. This makes the implementation of your business code more comfortable and is a general best practice for bi-directional many-to-many associations.

@Entity
public class Book {

    @ManyToMany
    Set<Author> authors;
 
    public void addAuthor(Author a) {
        this.authors.add(a);
        a.getBooks.add(this);
    }
 
    public void removeAuthor(Author a) {
        this.authors.remove(a);
        a.getBooks().remove(this);
    }
 
    …
}

As you can see, field-based access gives you the flexibility to implement only the getter and setter methods that should be used by your business code. That makes your entities much easier to use and helps you to prevent bugs.

Reason 3: Flexible implementation of getter and setter methods

Because your persistence provider doesn’t call the getter and setter methods, they are not forced to fulfill any external requirements. You can implement these methods in any way you want. That enables you to implement business-specific validation rules, to trigger additional business logic or to convert the entity attribute into a different data type.

I use that in the following example to wrap an optional association in a Java Optional. Even so, JPA and Hibernate don’t support Optional as an attribute type, field-based access enables you to use it as the return type of a getter method.

@Entity
public class Book {

    @ManyToOne
    Publisher publisher;
 
    public void setPublisher(Publisher p) {
        this.publisher = p;
    }
 
    public Optional<Publisher> getPublisher() {
        return Optional<Publisher>.ofNullable(this.publisher);
    }
 
    …
}

Reason 4: No need to mark utility methods as @Transient

Another benefit of the field-based access strategy is that you don’t need to annotate your utility methods with @Transient. This annotation tells your persistence provider that a method or attribute is not part of the entity persistent state. And because with field-type access the persistent state gets defined by the attributes of your entity, your JPA implementation ignores all methods of your entity.

Reason 5: Avoid bugs when working with proxies

Hibernate uses proxies for lazily fetched to-one associations so that it can control the initialization of these associations. That approach works fine in almost all situations. But it introduces a dangerous pitfall if you use property-based access.

If you use property-based access, Hibernate initializes the attributes of the proxy object when you call the getter method. That’s always the case if you use the proxy object in your business code. But quite a lot of equals and hashCode implementations access the attributes directly. If this is the first time you access any of the proxy attributes, these attributes are still uninitialized.

@Entity
public class Book {

    @NaturalId
    String isbn;

    ...
 
    @Override
    public int hashCode() {
	return Objects.hashCode(isbn);
    }

    @Override
    public boolean equals(Object obj) {
	if (this == obj)
		return true;
	if (obj == null)
		return false;
	if (getClass() != obj.getClass())
		return false;
	Book other = (Book) obj;
	return Objects.equals(isbn, other.isbn);
    }
}

You can easily avoid this pitfall by using the field-based access strategy.

Conclusion

All JPA implementations, like Hibernate and EclipseLink, support 2 access strategies that define how your persistence provider reads and sets the attributes of your entity. The field-based access strategy uses reflection and the property-based access strategy uses the getter and setter methods of your entity.

This might seem like a slight, internal difference. But as we have discussed in this article, the access strategy affects how you can implement your entity and might even help you to avoid bugs. That’s why I always recommend using the field-based access strategy.

4 Comments

  1. Avatar photo Binh Thanh Nguyen says:

    Thanks, nice explanation

  2. Thank you very much for detailed explanation

  3. Avatar photo Giuseppe Cusenza says:

    Another impaccable, cristal clear article on JPA! I bought your book and I plan to enroll in your online classes as I progress in the subject.
    Thanks a lot Janssen!

    1. Avatar photo Thorben Janssen says:

      Thank you, Guiseppe!

Comments are closed.