@Table(name="TB_SEASON")
public class Season implements Serializable {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private long seasonId;
@Column(name="season_year")
private int year;
@OneToMany(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
@OrderBy("trainingBegin DESC")
private List<Unit> trainingUnits;
//lot of code omitted
}
So you can store BikingUnit:
public class BikingUnit extends Unit {
private int avgCadence;
@Enumerated(EnumType.STRING)
@Column(name="t_type")
private TrainingType type;
@ManyToOne(cascade=CascadeType.ALL)
private Bike bike;
//some code omitted
}
or RunningUnit in it:
@Entity
@NamedQuery(name=RunningUnit.ALL_UNITS,query="Select r From RunningUnit r")
public class RunningUnit extends Unit {
@ManyToOne(cascade=CascadeType.ALL)
private Shoes shoes;
//some code omitted
}
with their specific states and behaviors.
So far, everything works really well. You can manage your units conveniently. Beyond that this solution is extendible, because you could easily introduce just another entity which inherits from the abstract Unit (I got already some request from triathletes for swimming....).
However I just wanted to provide really basic report functionality and compute the average speed. In that particular case it the average speed of Biking and Running Units should be computed separetely (it is not very impressive to have 25km avg speed on a bike, but is noteworthy to be able to run such fast).
To achieve this, I have to filter out all Running/Biking units first in the Season entity:
public List<RunningUnit> getRunningUnits() {
List<RunningUnit> retVal = new ArrayList<RunningUnit>();
for (Unit unit : getTrainingUnits()) {
if (unit instanceof RunningUnit)
retVal.add((RunningUnit) unit);
}
Collections.sort(retVal, new UnitComparator());
return retVal;
}
It works fine, because you will hardly have more, than 356 units a year - so it is not a huge problem. In case we would have several thousands units - we would run into performance / memory issue with this approach. In that case it would be more appropriate to query the database and not traverse the objects. ...but to access the database, we would need an EntityManager - but it cannot be injected yet. In all of my projects we stored it somehow, mostly in a ThreadLocal.
In real world projects, we needed the access to the EntityManager because of some historization, searches and filtering (mostly because of performance optimization). I always had a bad feeling - because obtaining a reference from an JPA-Entities through a ThreadLocal seemed always a "hack" to me.
Other bloggers ran in the issue as well. In Frank Cornelis wish list, he suggest a query extension:
@Entity
@NamedQueries(@NamedQuery(name="byName", query="FROM MyEntity WHERE name = :name"))
public class MyEntity {
@Id
String name;
// ...
public static Query createQueryWhereName(EntityManager em, String name) {
Query query = em.createNamedQuery("byName");
query.setParameter("name", name);
return query;
}
}The question remains -> how the EntityManager is going to be obtained in his case?
I try to push this issue in my work as EG in JPA 2.0 (JSR-317) -> so if you had similar issues just leave a comment here or write a feedback to the group. You can download the whole application from http://p4j5.dev.java.net (from SVN). The project name is: RunAndBikeApplication (the EAR), and RunAndBikeDB (both Netbeans 6.0 projects, tested on Glassfish v2). The application is available online as well (but slow because of bandwidth).
One solution could be the introduction of a specific LifecycleListener and Lifecycle Call Back methods like:
@PostAttach
public void postAttach(EntityManager manager){
}
@PreDetach
public void preDetach(){}
I will cover the issue of Rich Domain Models and JPA in one of the upcoming issues of the german magazine JavaSPEKTRUM in more detail as well. You can dowload the part 2 for free.
Any thoughts?