Building easy to use JPA classes
- JPA Entity classes
- A (usually single) Stateless Session Bean that handles the domain operations for the domain.
JPA Entity Classes
Let’s start out with some code for the JPA entity.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
@Entity public class Household { @Id @GeneratedValue private long householdId; private String address; public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Column(length=36, unique=true) private String uuid; public UUID getUuid() { return UUID.fromString(uuid); } public void setUuid(UUID uuid) { this.uuid = uuid.toString(); } private String date; public Date getDate() { try { return new MyDateFormat().parse(date); } catch (final ParseException e) { throw new RuntimeException(e); } } public void setDate(final Date date) { this.date = new MyDateFormat().format(date); } @Column(length = 64) private String hash; public byte[] getHash() { return DatatypeConverter.parseHexBinary(hash); } public void setHash(final byte[] hash) { this.hash = DatatypeConverter.printHexBinary(hash); } @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) @JoinColumn(name = "household_id") private final Set householdMembers = new HashSet(); public Set getHouseholdMembers() { return householdMembers; } } |
Some guidelines that you should gather from the above:
Never expose the primary key
The JPA package must not expose any of its primary keys to the outside. This allows the flexibility of having different types of primary keys used depending on the current code. Ideally, you’d only access the domain using a “business key” lookup. I typically use UUID for the business key as shown above. Using this rule actually helps make the decision whether a class should be part of the package or not. If you need the primary key then you are likely to be in the same domain. Don’t use efficiency as an excuse to use it.
Convert the data to and from the portable types
As noted in the previous article, we should only store data that can be portably persisted. In the above example, I have provided a few conversions for UUID, Date and a byte[] array. One thing to note is I store those types as Strings as String is a portably persisted data type.
It’s ok to expose your collection
Depending on the situation, for the most part return a modifiable list is okay, but it isn’t best practice because it allows clients to manipulate the data in an unrestricted fashion. So if you want to limit operations on the set you can create additional functions to wrap manipulation the set. However, if you want the other developers to have the flexibility of doing manipulations using the entire collections API then just return the actual set.
Stateless Session Bean
Let’s start out with some code for the SLSB.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
@Stateless public class Households { @PersistenceContext private EntityManager em; public Household householdByUuid(final UUID uuid) { return em.createNamedQuery("householdByUuid", Household.class) .setParameter("uuid", uuid.toString()).getSingleResult(); } public List householdsByHash(final byte[] hash) { return em.createNamedQuery("householdByHash", Household.class) .setParameter("hash", DatatypeConverter.printHexBinary(hash)) .getResultList(); } public void setEntityManager(final EntityManager em) { this.em = em; } } |
The SLSB is used so you get an entity manager and transaction given to you by the container so you don’t have to deal with it yourself. setEntityManager() is there for JUnit testing.
Primarily you create lookup methods using business keys and convert them to the proper parameters.
This is similar to the Transaction Script in that it hides the persistence details from the user.
Depending on the skill level of the users, you may or may not choose to wrap the “persist” and “merge” operations. In general, I don’t wrap these out of my own laziness, but also because it is a fundamental part of using JPA classes so the users should use them. However, if the users require a non-JPA API interface it isn’t too much extra work.
The there are two times I do wrap them:
- if the registration of the data is complicated, but that is just for the persist. I still expect updates and deletes to be done using the merge method.
- if I use a
@ManyToOnerelationship (sometimes I do because I may require JPA 1.0 compatibility)
Coming up in a couple of weeks, the final installment of the series.

Pingback: Trajano | The Ideal JPA Pattern