JPA Tutorial: Mapping Entities – Part 3
In my last article I showed two different ways to read/write persistent entity state – field and property. When field access mode is used, JPA directly reads the state values from the fields of an entity using reflection. It directly translates the field names into database column names if we do not specify the column names explicitly. In case of property access mode, the getter/setter methods are used to read/write the state values. In this case we annotate the getter methods of the entity states instead of the fields using the same annotations. If we do not explicitly specify the database column names then they are determined following the JavaBean convention, that is by removing the “get” portion from the getter method name and converting the first letter of the rest of the method name to lowercase character.
We can specify which access mode to use for an entity by using the @Access annotation in the entity class declaration. This annotation takes an argument of type AccessType (defined in the javax.persistence package) enum, which has two different values corresponding to two different access modes – FIELD and PROPERTY. As an example, we can specify property access mode for the Address entity in the following way:
@Entity @Table(name = "tbl_address") @Access(AccessType.PROPERTY) public class Address { private Integer id; private String street; private String city; private String province; private String country; private String postcode; private String transientColumn; @Id @GeneratedValue @Column(name = "address_id") public Integer getId() { return id; } public Address setId(Integer id) { this.id = id; return this; } public String getStreet() { return street; } public Address setStreet(String street) { this.street = street; return this; } public String getCity() { return city; } public Address setCity(String city) { this.city = city; return this; } public String getProvince() { return province; } public Address setProvince(String province) { this.province = province; return this; } public String getCountry() { return country; } public Address setCountry(String country) { this.country = country; return this; } public String getPostcode() { return postcode; } public Address setPostcode(String postcode) { this.postcode = postcode; return this; } }
Couple of points to note about the above example:
- As discussed before, we are now annotating the getter method of the entity id with the @Id, @GeneratedValue and @Column annotations.
- Since now column names will be determined by parsing the getter methods, we do not need to mark the transientColumn field with the @Transient annotation anymore. However if Address entity had any other method whose name started with “get”, then we needed to apply @Transient on it.
If an entity has no explicit access mode information, just like our Address entity that we created in the first part of this series, then JPA assumes a default access mode. This assumption is not made at random. Instead, JPA first tries to figure out the location of the @Id annotation. If the @Id annotation is used on a field, then field access mode is assumed. If the @Id annotation is used on a getter method, then property access mode is assumed. So even if we remove the @Access annotation from the Address entity in the above example the mapping will still be valid and JPA will assume property access mode:
@Entity @Table(name = "tbl_address") public class Address { private Integer id; private String street; private String city; private String province; private String country; private String postcode; private String transientColumn; @Id @GeneratedValue @Column(name = "address_id") public Integer getId() { return id; } // Rest of the class........
Some important points to remember about the access modes:
- You should never declare a field as public if you use field access mode. All fields of the entity should have either private (best!), protected or default access type. The reason behind this is that declaring the fields as public will allow any unprotected class to directly access the entity states which could defeat the provider implementation easily. For example, suppose that you have an entity whose fields are all public. Now if this entity is a managed entity (which means it has been saved into the database) and any other class changes the value of its id, and then you try to save the changes back to the database, you may face unpredictable behaviors (I will try to elaborate on this topic in a future article). Even the entity class itself should only manipulate the fields directly during initialization (i.e., inside the constructors).
- In case of property access mode, if we apply the annotations on the setter methods rather than on the getter methods, then they will simply be ignored.
It’s also possible to mix both of these access types. Suppose that you want to use field access mode for all but one state of an entity, and for that one remaining state you would like to use property access mode because you want to perform some conversion before writing/after reading the state value to and from the database. You can do this easily by following the steps below:
- Mark the entity with the @Access annotation and specify AccessType.FIELD as the access mode for all the fields.
- Mark the field for which you do not like to use the field access mode with the @Transient annotation.
- Mark the getter method of the property with the @Access annotation and specify AccessType.PROPERTY as the access mode.
The following example demonstrates this approach as the postcode has been changed to use property access mode:
@Entity @Table(name = "tbl_address") @Access(AccessType.FIELD) public class Address { @Id @GeneratedValue @Column(name = "address_id") private Integer id; private String street; private String city; private String province; private String country; /** * postcode is now marked as Transient */ @Transient private String postcode; @Transient private String transientColumn; public Integer getId() { return id; } public Address setId(Integer id) { this.id = id; return this; } public String getStreet() { return street; } public Address setStreet(String street) { this.street = street; return this; } public String getCity() { return city; } public Address setCity(String city) { this.city = city; return this; } public String getProvince() { return province; } public Address setProvince(String province) { this.province = province; return this; } public String getCountry() { return country; } public Address setCountry(String country) { this.country = country; return this; } /** * We are now using property access mode for reading/writing * postcode */ @Access(AccessType.PROPERTY) public String getPostcode() { return postcode; } public Address setPostcode(String postcode) { this.postcode = postcode; return this; } }
The important thing to note here is that if we do not annotate the class with the @Access annotation to explicitly specify the field access mode as the default one, and we annotate both the fields and the getter methods, then the resultant behavior of the mapping will be undefined. Which means the outcome will totally depend on the persistence provider i.e., one provider might choose to use the field access mode as default, one might use property access mode, or one might decide to throw an exception!
That’s it for today. If you find any problems/have any questions, please do not hesitate to comment!
Until next time.
Reference: | JPA Tutorial: Mapping Entities – Part 3 from our JCG partner Sayem Ahmed at the Codesod blog. |