dinsdag 2 oktober 2007

jsf Solutions for many2many

Introduction many to many

A challenging and interesting usecase for me has been the implementation of a many-to-many relations in a user interface. I havent found too much documentation or examples on the internet, so I thought I just publish my own efforts here. Note that here is quite some documentation on the ejb (jpa) modelling, but the actual gui implementation is missing in most cases.
Please note that this example is part of my "project" accountancy for sportclubs, see earliest blog for more details.

Technology


The versions used in this example: jboss 4.2 (ejb3), seam 1.2.1, mysql 5.0m icefaces 1.6.0 and jsf-ri (?.?)

Use case description & data model



I have a subject (which may be of interest to our club: a person, store selling us volleybals, the room landlord etc) Furter I have roles, some of the roles will be connected to posts on the balance sheets (like credit, trainer, rent or debitor: compition player, recreational player). A subject performing a role becomes an actor. Of course a role maybe played by many actors, and a subject may fulfill many roles.
The actor is a period based entity, during a season, the next season he may give up his role as trainer, or give up his role as competition member.

In ejb3 the possibility exists to directly indicate a many-to-many relation ship using an annotation with the same name and indicating a join-table. However I'm not convinced this is the way to go to map many-to-many relations. You loose control over your join table (in my use case, an actor exists for a certain period, for a new period the actor must be cloned), see also this blog, where the author introduces some life in the join table. However in the end it turns out to be dead meat (see question 6 and 7).

So instead of modeling a many2many relationship I modeled 2 one-to-many relationships. I'm not completely sure this is the right approach but for me it worked well. The 2 one-2-many relationships are intuitively mapped in the entities Subject, Actor and Role. See code below.

Gui


We're approaching our final goal: how to display this in a gui. I have chosen for a master detail view: A list of all subjects, clicking on one subject presents you the subject in focus and the possibility to edit the subject's attributes. I'll leave vanilla attributes out of scope, and only look on how to select the roles the user wishes the subject to fulfill (role creation is left out of this use case). For this list I have tried two different implementations a ice:selectManyListbox and an ice:dataTable toegether with ice:rowSelector. Both having pros and cons which will be explained in (earlier) posts below and summarized in the last section. For now have fun with the generated entity classes.

Thanks to


This blog wouldn't definitely have made it without the jboss seam and iceface guys, Thanks! Also thanks to the contributors of both forums who have helped me out on numerous occasions. Thanks also to my colleagues Bertram and Faizal who have helped me out on the "elegant" query. And finally to Ellen who has withstood all my swearing and cursing on the css and the blogspot templates. And was patient enough to let me it all figure out...
Jeroen.

Subject.java

001 package nl.jeroen.testdb.persist;
002 // Generated Dec 23, 2006 5:43:17 PM by Hibernate Tools 3.2.0.snapshotb9
003
004 import java.util.ArrayList;
024 import ....;
024 import org.jboss.seam.log.Log;
025
026 /**
027 * Subject generated by hbm2java
028 */
029 @Name("subject")
030 @Entity
031 @Scope(ScopeType.SESSION)
032 @Table(name = "subject", catalog = "seam", uniqueConstraints = {})
033 public class Subject implements java.io.Serializable {
034
035 // Fields
036 @org.jboss.seam.annotations.Logger
037 private Log logger;
038 private int subjectid;
039 private String info;
040 private String firstname;
041 private String lastname;
042 private String email;
043 private List<Actor> actors = new ArrayList<Actor>(0);
044
119 @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "subject")
120 public List<Actor> getActors() {
121 return this.actors;
122 }
123
124 public void setActors(List<Actor> actors) {
125 this.actors = actors;
126 }
127
128 }


Actor.java

001 package nl.jeroen.testdb.persist;
002
003 import java.util.HashSet;
015 import ..;
016 import org.hibernate.validator.NotNull;
017
018 /**
019 * Actor generated by hbm2java
020 */
021 @Entity
022 @Table(name = "actor", catalog = "seam", uniqueConstraints = {@UniqueConstraint(columnNames = {
023 "roleid", "subjectid", "periodid"})})
024 public class Actor implements java.io.Serializable {
025
026 // Fields
027
028 private int actorid;
029 private Period period;
030 private Role role;
031 private Subject subject;
032 private Set<Transaction> transactionsForDestinationid = new HashSet<Transaction>(
033 0);
034 private Set<Transaction> transactionsForSourceid = new HashSet<Transaction>(
035 0);
036
037 // Constructors
038
039 /** default constructor */
040 public Actor() {
041 }
042
043 /** minimal constructor */
044 public Actor(int actorid, Period period, Role role, Subject subject) {
045 this.actorid = actorid;
046 this.period = period;
047 this.role = role;
048 this.subject = subject;
049 }
050 /** full constructor */
051 public Actor(int actorid, Period period, Role role, Subject subject,
052 Set<Transaction> transactionsForDestinationid,
053 Set<Transaction> transactionsForSourceid) {
054 this.actorid = actorid;
055 this.period = period;
056 this.role = role;
057 this.subject = subject;
058 this.transactionsForDestinationid = transactionsForDestinationid;
059 this.transactionsForSourceid = transactionsForSourceid;
060 }
061
062 // Property accessors
063 @Id @GeneratedValue
064 @Column(name = "actorid", unique = true, nullable = false)
065 @NotNull
066 public int getActorid() {
067 return this.actorid;
068 }
069
070 public void setActorid(int actorid) {
071 this.actorid = actorid;
072 }
073 @ManyToOne(fetch = FetchType.LAZY)
074 @JoinColumn(name = "periodid", nullable = false)
075 @NotNull
076 public Period getPeriod() {
077 return this.period;
078 }
079
080 public void setPeriod(Period period) {
081 this.period = period;
082 }
083 @ManyToOne(fetch = FetchType.LAZY)
084 @JoinColumn(name = "roleid", nullable = false)
085 @NotNull
086 public Role getRole() {
087 return this.role;
088 }
089
090 public void setRole(Role role) {
091 this.role = role;
092 }
093 @ManyToOne(fetch = FetchType.LAZY)
094 @JoinColumn(name = "subjectid", nullable = false)
095 @NotNull
096 public Subject getSubject() {
097 return this.subject;
098 }
099
100 public void setSubject(Subject subject) {
101 this.subject = subject;
102 }
103 @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "actorByDestinationid")
104 public Set<Transaction> getTransactionsForDestinationid() {
105 return this.transactionsForDestinationid;
106 }
107
108 public void setTransactionsForDestinationid(
109 Set<Transaction> transactionsForDestinationid) {
110 this.transactionsForDestinationid = transactionsForDestinationid;
111 }
112 @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "actorBySourceid")
113 public Set<Transaction> getTransactionsForSourceid() {
114 return this.transactionsForSourceid;
115 }
116
117 public void setTransactionsForSourceid(
118 Set<Transaction> transactionsForSourceid) {
119 this.transactionsForSourceid = transactionsForSourceid;
120 }
121
122 }


Role.java

001 package nl.jeroen.testdb.persist;
002 // Generated Dec 23, 2006 5:43:17 PM by Hibernate Tools 3.2.0.snapshotb9
003
004 import java.util.HashSet;
012 import ..;
018 import org.hibernate.validator.NotNull;
019
020 /**
021 * Role generated by hbm2java
022 */
023 @Entity
024 @Table(name = "role", catalog = "seam", uniqueConstraints = {})
025 public class Role implements java.io.Serializable {
026 /**
027 *
028 */
029 private static final long serialVersionUID = -1996329923962792444L;
030
031 private static final org.apache.commons.logging.Log logger = org.apache.commons.logging.LogFactory
032 .getLog(Role.class);
033 // Fields
034
035 private int roleid;
036 private String info;
037 private Integer parentid;
038 private String name;
039 private Set<Actor> actors = new HashSet<Actor>(0);
040
041 // Constructors
103 @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "role")
104 public Set<Actor> getActors() {
105 return this.actors;
106 }
107
108 public void setActors(Set<Actor> actors) {
109 this.actors = actors;
110 }
111
112 @Override
113 public int hashCode() {
114 return roleid;
115 }
116
117 @Override
118 public boolean equals(Object obj) {
119 final String methodName = "equals";
120 if (logger.isTraceEnabled()) {
121 logger.trace(LogUtil.getLogMessage(LogUtil.VERSION, methodName
122 ,"start '" + "'"));
123 }
124 if (obj == null) {
125 return false;
126 }
127 if (this == obj) {
128 return true;
129 }
130 if (obj instanceof Role
131 && ((Role)obj).getRoleid() == roleid) {
132 return true;
133 }
134 return false;
135 }
136 }

1 opmerking:

Anoniem zei

The most strange and amusing images and pictures online.
Counterfoil out
[url=http://www.funnyimgz.blogspot.com]funny pics[/url]