The coding guidelines for this package has been slightly modified from the the general coding guidelines. Here is a short list with the changes.
Inside a class, attributes and methods should be organised in related groups, ie. the private attribute is together with the getter and setter methods that uses that attribute. This makes it easy to re-use existing code with copy-and-paste operations.
public static int long MAX_ADDRESS_LENGTH = 255; private String address; /** @hibernate.property column="`address`" type="string" length="255" not-null="false" */ public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } private int row; /** @hibernate.property column="`row`" type="int" */ public int getRow() { return row; } public void setRow(int row) { this.row = row; }
Class names should follow the general guidelines, but should in most
cases end with Data
.
public class SampleData extends CommonData implements DiskConsumableData { ... }
Each data-class must inherit from one of the already existing abstract base classes.
They contain code that is common to all classes, for example implementations of
the equals()
and hashCode()
methods or how to link with the owner of an item. For information about
which classes/interfaces that can be used see Section 29.2.1, “Basic classes and interfaces”.
Always define a public a no-argument constructor. No other constructors are needed. If we want to use other persistence mechanisms or serializability in the future this type of constructor is probably the most compatible. The constructor should be empty and not contain any code. Do not initialise properties or create new objects for internal use. Most of the time the object is loaded by Hibernate and Hibernate will ensure that it is properly initialised by calling all setter methods.
For example, a many-to-many relation usually has a Set
or a Map
to hold the links to the other objects. Do not
create a new HashSet
or HashMap
in the constructor. Wait until the get method is called and only create a new
object if Hibernate hasn't already called the setter method with it's own object.
See the code example below. There is also more information about this in
the section called “Many-to-many and one-to-many mappings”.
// From GroupData.java public GroupData() {} private Set<UserData> users; public Set<UserData> getUsers() { if (users == null) users = new HashSet<UserData>(); return users; }
See also:
"Hibernate in action", chapter 3.2.3 "Writing POJOs", page 67-69
Hibernate user documentation: 4.1.1. Implement a no-argument constructor
We use database identity to compare objects, ie. two objects are considered
equal if they are of the same class and have the same id, thus representing the
same database row. All this stuff is implemented by the BasicData class. Therefore
it is required that all classes are subclasses of this class. It is recommended
that the equals()
or hashCode()
methods are not overridden by any of the subclasses. We would have liked to make
them final, but then the proxy feature of Hibernate would not work.
Avoid mixing saved and unsaved objects | |
---|---|
The approch used for object identity may give us a problem if we mix objects
which hasn't been saved to the database, with objects loaded from the database.
Our recommendation is to avoid that, and save any objects to the database before
adding them to sets, maps or any other structure that uses the
To be more specific, the problem arises because the following two rules for hascodes are contradicting when the hashcode is based on the database id:
For objects in the database, the hash code is based on the id. For new objects,
which doesn't have an id yet, we fall back to the system hash code. But, what
happens when we save the new object to the database? If nobody has asked for
the hash code it is safe to use the id, otherwise we must stick with the system
hash code. Now, imagine that we load the same object from the database in
another Hibernate session. What will now happen? The loaded object will have
it's hash code based on the id but the original object is still using the
system hash code, which most likely is not the same as the id. Yet, the
|
See also:
"Hibernate in action", chapter 3.4 "Understanding object identity", page 87-90
"Hibernate in action", chapter 4.1.4 "The scope of object identity", page 119-121
"Hibernate in action", chapter 4.1.6 "Implementing equals() and hashCode(), page 122-126
Hibernate user documentation: 4.3. Implementing equals() and hashCode()
No methods should be tagged with the final
keyword. This is a
requirement to be able to use the proxy feature of Hibernate, which we need for
performance reasons.
See also:
Hibernate user documentation: 4.1.3. Prefer non-final classes
Hibernate user documentation: 19.1.3. Single-ended association proxies
To gain performance we use the second-level cache of Hibernate. It is a transparent
feature that doesn't affect the code in any way. The second-level cache is configured
in the hibernate.cfg.xml
and ehcache.xml
files and not in the individual class mapping files. BASE is shipped with a standard
configuration, but different deployment scenarios may have to fine-tune the cache
settings for that particular hardware/software setup. It is beyond the scope of
this document to discuss this issue.
The second-level cache is suitable for objects that are rarely modified but
are often needed. For example, we do not expect the user information represented
by the UserData
It is required that one thinks a bit of the usage of a class before coming up with a good caching strategy. We have to answer the following questions:
Should objects of this class be cached at all?
How long timeout should we use?
How many objects should we keep in memory or on disk?
The first question is the most important. Good candidates are classes with few
objects that change rarely, but are read often. Also, objects which are linked
to by many other objects are good candidates. The UserData
LabelData
BioMaterialEventData
BioMaterialData
The answer to the second question depends on how often an object is modified. For most objects this time is probably several days or months, but we would not gain much by keeping objects in the cache for so long. Suddenly, the information has changed and we won't risk that old information is kept that long. We have set the timeout to 1 hour for all classes so far, and we don't recommend a longer timeout. The only exception is for immutable objects, that cannot be changed at all, which may have an infinite timeout.
The answer to the third question depends a lot on the hardware (available memory). With lots of memory we can afford to cache more objects. Caching to disk is not really necessary if the database is on the same machine as the web server, but if it is on another machine we have to consider the network delay to connect to the database versus the disk access time. The default configuration does not use disk cache.
See also:
"Hibernate in action", chapter 5.3 "Caching theory and practice", page 175-194.
Hibernate user documentation: 19.2. The Second Level Cache
Proxies are also used to gain performance, and they may have some impact on
the code. Proxies are created at runtime (by Hibernate) as a subclass of the
actual class but are not populated with data until some method of the object
is called. The data is loaded from the database the first time a method other
than getId()
is called. Thus, we can avoid loading
data that is not needed at a particular time.
There can be a problem with using the instanceof
operator with proxies
and the table-per-class-hierarchy mapping. For example, if we have the abstract
class Animal
and subclasses Cat
and Dog
. The proxy of an Animal
is a
runtime generated subclass of Animal
, since we do not know if it
is a Cat
or Dog
. So,
x instanceof Dog
and x instanceof Cat
would both return
false. If we hadn't used a proxy, at least one of them would always be true.
Proxies are only used when a not-null object is linked with many-to-one or one-to-one from another object. If we ask for a specific object by id, or by a query, we will never get a proxy. Therefore, it only makes sense to enable proxies for classes that can be linked from other classes. One-to-one links on the primary key where null is allowed silently disables the proxy feature, since Hibernate doesn't know if there is an object or not without querying the database.
The goal of a proxy and the second-level cache are the same: to avoid hitting the database. It is perfectly possible to enable both proxies and the cache for a class. Then we would start with a proxy and as soon as a method is called Hibernate would look in the second-level cache. Only if it is not there it would be loaded from the database. But, do we really need a proxy in the first place? Well, I think it might be better to use only the cache or only proxies. But, this also makes it even more important that the cache is configured correctly so there is a high probability that the object is already in the cache.
If a class has been configured to use the second-level cache, we recommend that proxies are disabled. For child objects in a parent-child relationship proxies should be disabled, since they have no other links to them than from the parent. If a class can be linked as many-to-one from several other classes it makes sense to enable proxies. If we have a long chain of many-to-one relations it may also make sense to enable proxies at some level, even if the second-level cache is used. In that case we only need to create one proxy instead of looking up several objects in the cache. Also, think about how a particular class most commonly will be used in a client application. For example, it is very common to display the name of the owner of an item, but we are probably not interested in displaying quota information for that user. So, it makes sense to put users in the second-level cache and use proxies for quota information.
Here is a table which summarises different settings for the second-level cache, proxies, batch fetching and many-to-one links. Batch fetching and many-to-one links are discussed later in this document.
First, decide if the second-level cache should be enabled or not. Then, if proxies should be enabled or not. The table then gives a reasonable setting for the batch size and many-to-one mappings. NOTE! The many-to-one mappings are the links from other classes to this one, not links from this class.
The settings in this table are not absolute rules. In some cases there might be a good reason for another combination. Please, write a comment about why the recommendations were not followed.
Table 31.1. Choosing cache and proxy settings
Global configuration | Class mapping | Many-to-one mapping | |
---|---|---|---|
Cache | Proxy | Batch-size | Outer-join |
no | no* | yes | true |
yes | no* | no | false |
no | yes | yes | false |
yes | yes | no | false |
* = Do not use this setting for classes which are many-to-one linked from a batchable class.
See also:
"Hibernate in action", chapter 4.4.6 "Selecting a fetching strategy in mappings", page 146-147
"Hibernate in action", chapter 6.4.1 "Polymorphic many-to-one associations", page 234-236
Hibernate user documentation: 19.1.3. Single-ended association proxies
We use Javadoc tags to specify the database mapping needed by Hibernate. The tags are processed by XDoclet at build time which generates the XML-based Hibernate mapping files.
XDoclet doesn't support all mappings | |
---|---|
The XDoclet that we use was developed to generate mapping files for Hibernate 2.x. Since then, Hibernate has released several 3.x versions, and the mapping file structure has changed. Some changes can be handled by generating a corresponding 2.x mapping and then converting it to a 3.x mapping at build time using simple search-and-replace operations. One such case is to update the DTD reference to the 3.0 version instead of the 2.0 version. Other changes can't use this approach. Instead we have to provide extra mappings inside an XML files. This is also needed if we need to use some of the new 3.x features that has no 2.x counterpart. |
/** This class holds information about any data... @author Your name @version 2.0 @hibernate.class table="`Anys`" lazy="false" batch-size="10" @base.modified $Date: 2007-08-17 09:18:29 +0200 (Fri, 17 Aug 2007) $ */ public class AnyData extends CommonData { // Rest of class code... }
The class declaration must contain a @hibernate.class
Javadoc entry
where Hibernate can find the name of the table where items of this type are stored.
The table name should generally be the same as the class name, without the ending
Data
and in a plural form. For example UserData
Users
. The back-ticks (`) around the table name tells Hibernate
to enclose the name in whatever the actual database manager uses for such things
(back-ticks in MySQL, quotes for an ANSI-compatible database).
Specify a value for the lazy attribute | |
---|---|
The lazy attribute enables/disables proxies for the class. Do not forget
to specify this attribute since the default value is true. If proxies are
enabled, it may also make sense to specify a batch-size attribute. Then
Hibernate will load the specified number of items in each SELECT statement
instead of loading them one by one. It may also make sense to specify a
batch size when proxies are disabled, but then it would probably be even
better to use eager fetching by setting
Classes that are linked with a many-to-one association from a batchable
class must specify |
Remember to enable the second-level cache | |
---|---|
Do not forget to configure settings for the second-level cache if this
should be enabled. This is done in the |
See also:
"Hibernate in action", chapter 3.3 "Defining the mapping metadata", page 75-87
Hibernate user documentation: 5.1.3. class
Properties such as strings, integers, dates, etc. are mapped with
the @hibernate.property
Javadoc tag. The main purpose
is to define the database column name. The column names should
generally be the same as the get/set method name without the get/set prefix,
and with upper-case letters converted to lower-case and an underscore inserted.
Examples:
getAddress()
--> column="`address`"
getLoginComment()
--> column="`login_comment`"
The back-ticks (`) around the column name tells Hibernate to enclose the name in whatever the actual database manager uses for such things (back-ticks in MySQL, quotes for an ANSI-compatible database).
public static int long MAX_STRINGPROPERTY_LENGTH = 255; private String stringProperty; /** Get the string property. @hibernate.property column="`string_property`" type="string" length="255" not-null="true" */ public String getStringProperty() { return stringProperty; } public void setStringProperty(String stringProperty) { this.stringProperty = stringProperty; }
Do not use a greater value than 255 for the length attribute. Some databases
has that as the maximum length for character columns (ie. MySQL). If you need
to store longer texts use type="text"
instead. You can then skip
the length attribute. Most databases will allow up to 65535 characters or more
in a text field. Do not forget to specify the not-null
attribute.
You should also define a public constant MAX_STRINGPROPERTY_LENGTH
containing the maximum allowed length of the string.
private int intProperty; /** Get the int property. @hibernate.property column="`int_property`" type="int" not-null="true" */ public int getIntProperty() { return intProperty; } public void setIntProperty(int intProperty) { this.intProperty = intProperty; }
It is also possible to use Integer
, Long
or Float
objects instead of int
,
long
and float
. We have only used it
if null values have some meaning.
private boolean booleanProperty; /** Get the boolean property. @hibernate.property column="`boolean_property`" type="boolean" not-null="true" */ public boolean isBooleanProperty() { return booleanProperty; } public void setBooleanProperty(boolean booleanProperty) { this.booleanProperty = booleanProperty; }
It is also possible to use a Boolean
object instead of
boolean
. It is only required if you absolutely need
null values to handle special cases.
private Date dateProperty; /** Get the date property. Null is allowed. @hibernate.property column="`date_property`" type="date" not-null="false" */ public Date getDateProperty() { return dateProperty; } public void setDateProperty(Date dateProperty) { this.dateProperty = dateProperty; }
Hibernate defines several other date and time types. We have decided to use
the type="date"
type when we are only interested in the date and
the type="timestamp"
when we are interested in both the date
and time.
See also:
"Hibernate in action", chapter 3.3.2 "Basic property and class mappings", page 78-84
"Hibernate in action", chapter 6.1.1 "Built-in mapping types", page 198-200
Hibernate user documentation: 5.1.9. property
Hibernate user documentation: 5.2.2. Basic value types
private OtherData other; /** Get the other object. @hibernate.many-to-one column="`other_id`" not-null="true" outer-join="false" */ public OtherData getOther() { return other; } public void setOther(OtherData other) { this.other = other; }
We create a many-to-one mapping with the @hibernate.many-to-one
tag.
The most important attribute is the column
attribute which specifies the name of
the database column to use for the id of the other item. The back-ticks (`)
around the column name tells Hibernate to enclose the name in whatever the
actual database manager uses for such things (back-ticks in MySQL, quotes for
an ANSI-compatible database).
We also recommend that the not-null
attribute is specified. Hibernate
will not check for null values, but it will generate table columns that allow
or disallow null values. See it as en extra safety feature while debugging.
It is also used to determine if Hibernate uses LEFT JOIN
or
INNER JOIN
in SQL statements.
The outer-join
attribute is important and affects how the
cache and proxies are used. It can take three values: auto
,
true
or false
. If the value is
true
Hibernate will always use a join to load the linked
object in a single select statement, overriding the cache and proxy settings.
This value should only be used if the class being linked has disabled both
proxies and the second-level cache, or if it is a link between a child
and parent in a parent-child relationship. A false value is best when
we expect the associated object to be in the second-level cache or proxying
is enabled. This is probably the most common case. The auto setting uses a
join if proxying is disabled otherwise it uses a proxy. Since we always
know if proxying is enabled or not, this setting is not very useful. See
Table 31.1, “Choosing cache and proxy settings” for the
recommended settings.
See also:
"Hibernate in action", chapter 3.7 "Introducing associations", page 105-112
"Hibernate in action", chapter 4.4.5-4.4.6 "Fetching strategies", page 143-151
"Hibernate in action", chapter 6.4.1 "Polymorphic many-to-one associations", page 234-236
Hibernate user documentation: 5.1.10. many-to-one
There are many variants of mapping many-to-many or one-to-many, and it is
not possible to give examples of all of them. In the code these mappings
are represented by Set
:s, Map
:s,
List
:s, or some other collection object. The most
important thing to remember is that (in our application) the collections
are only used to maintain the links between objects. They are not used
for returning objects to client applications, as is the case with the
many-to-one mapping.
For example, if we want to find all members of a group we do not use the
GroupData.getUsers()
method, instead we will execute a database query
to retrieve them. The reason for this design is that the logged in user may
not have access to all users and we must add a permission checking filter
before returning the user objects to the client application. Using a query
will also allow client applications to specify sorting and filtering options
for the users that are returned.
// RoleData.java private Set<UserData> users; /** Many-to-many from roles to users @hibernate.set table="`UserRoles`" lazy="true" @hibernate.collection-key column="`role_id`" @hibernate.collection-many-to-many column="`user_id`" class="net.sf.basedb.core.data.UserData" */ public Set<UserData> getUsers() { if (users == null) users = new HashSet<UserData>(); return users; } void setUsers(Set<UserData> users) { this.users = users; }
As you can see this mapping is a lot more complicated than what we have
seen before. The most important thing is the lazy
attribute.
It tells Hibernate to delay the loading of the related objects until the set
is accessed. If the value is false or missing, Hibernate will load all objects
immediately. There is almost never a good reason to specify something other
than lazy="true"
.
Another important thing to remember is that the get method must always return
the same object that Hibernate passed to the set method. Otherwise, Hibernate
will not be able to detect changes made to the collection and as a result
will have to delete and then recreate all links. To ensure that the collection
object is not changed we have made the setUsers()
method
package private, and the getUsers()
will create a
new HashSet
for us only if Hibernate didn't pass one
in the first place.
Let's also have a look at the reverse mapping:
// UserData.java private Set<RoleData> roles; /** Many-to-many from users to roles @hibernate.set table="`UserRoles`" lazy="true" @hibernate.collection-key column="`user_id`" @hibernate.collection-many-to-many column="`role_id`" class="net.sf.basedb.core.data.RoleData" */ Set<RoleData> getRoles() { return roles; } void setRoles(Set<RoleData> roles) { this.roles = roles; }
The only real difference here is that both the setter and the getter methods
are package private. This is required because Hibernate will get confused if
we modify both ends. Thus, we are forced to always add/remove users to/from
the set in the GroupData
RoleData
So, why do we need the second collection at all? It is never accessed except by Hibernate, and since it is lazy it will always be "empty". The answer is that we want to use the relation in HQL statements. For example:
SELECT ... FROM GroupData grp WHERE grp.users ... SELECT ... FROM UserData usr WHERE usr.groups ...
Without the inverse mapping, it would not have been possible to execute the second HQL statement. The inverse mapping is also important in parent-child relationships, where it is used to cascade delete the children if a parent is deleted.
Do not use the inverse="true" setting | |
---|---|
Hibernate defines an In the "Hibernate in action" book they have a very different design where they recommend that changes are made in both collections. We don't have to do this since we are only interested in maintaining the links, which is always done in one of the collections. |
When one or more objects are tightly linked to some other object we talk
about a parent-child relationship. This kind of relationship becomes important
when we are about to delete a parent object. The children cannot exist
without the parent so they must also be deleted. Luckily, Hibernate can
do this for us if we specify a cascade="delete"
option for the link.
This example is a one-to-many link between client and help texts.
// ClientData.java private Set<HelpData> helpTexts; /** This is the inverse end. @see HelpData#getClient() @hibernate.set lazy="true" inverse="true" cascade="delete" @hibernate.collection-key column="`client_id`" @hibernate.collection-one-to-many class="net.sf.basedb.core.data.HelpData" */ Set<HelpData> getHelpTexts() { return helpTexts; } void setHelpTexts(Set<HelpData> helpTexts) { this.helpTexts = helpTexts; } // HelpData.java private ClientData client; /** Get the client for this help text. @hibernate.many-to-one column="`client_id`" not-null="true" update="false" outer-join="false" unique-key="uniquehelp" */ public ClientData getClient() { return client; } public void setClient(ClientData client) { this.client = client; }
This show both sides of the one-to-many mapping between parent and children.
As you can see the @hibernate.set
doesn't specify a table,
since it is given by the class
attribute of the
@hibernate.collection-one-to-many
tag.
In a one-to-many mapping, it is always the "one" side that handles the
link so the "many" side should always be mapped with inverse="true"
.
Another type of many-to-many mapping uses a Map
for the collection. This kind of mapping is needed when the association between
two objects needs additional data to be kept as part of the association.
For example, the permission (stored as an integer value) given to users that
are members of a project. Note that you should use a Set
for mapping the inverse end.
// ProjectData.java private Map<UserData, Integer> users; /** Many-to-many mapping between projects and users including permission values. @hibernate.map table="`UserProjects`" lazy="true" @hibernate.collection-key column="`project_id`" @hibernate.index-many-to-many column="`user_id`" class="net.sf.basedb.core.data.UserData" @hibernate.collection-element column="`permission`" type="int" not-null="true" */ public Map<UserData, Integer> getUsers() { if (users == null) users = new HashMap<UserData, Integer>(); return users; } void setUsers(Map<UserData, Integer> users) { this.users = users; } // UserData.java private Set<ProjectData> projects; /** This is the inverse end. @see ProjectData#getUsers() @hibernate.set table="`UserProjects`" lazy="true" @hibernate.collection-key column="`user_id`" @hibernate.collection-many-to-many column="`project_id`" class="net.sf.basedb.core.data.ProjectData" */ Set<ProjectData> getProjects() { return projects; } void setProjects(Set<ProjectData> projects) { this.projects = projects; }
See also:
"Hibernate in action", chapter 3.7 "Introducing associations", page 105-112
"Hibernate in action", chapter 6.2 "Mapping collections of value types", page 211-220
"Hibernate in action", chapter 6.3.2 "Many-to-many associations", page 225-233
Hibernate user documentation: Chapter 6. Collection Mapping
Hibernate user documentation: Chapter 21. Example: Parent/Child
A one-to-one mapping can come in two different forms, depending on if both objects should have the same id or not. We start with the case were the objects can have different id:s and the link is done with an extra column in one of the tables. The example is from the mapping between hybridizations and arrayslides.
// HybridizationData.java private ArraySlideData arrayslide; /** Get the array slide @hibernate.many-to-one column="`arrayslide_id`" not-null="false" unique="true" */ public ArraySlideData getArraySlide() { return arrayslide; } public void setArraySlide(ArraySlideData arrayslide) { arrayslide.setHybridization(this); this.arrayslide = arrayslide; } // ArraySlideData.java private HybridizationData hybridization; /** Get the hybridization @hibernate.one-to-one property-ref="arraySlide" */ public HybridizationData getHybridization() { return hybridization; } void setHybridization(HybridizationData hybridization) { this.hybridization = hybridization; }
As you can see, we use the many-to-one mapping on with a unique="true"
option for the hybridization. This will force the database to only allow the
same array slide to be linked once. Also note that since, not-null="false"
,
null values are allowed and it doesn't matter which end of the relation that
is inserted first into the database.
For the array slide end we use a @hibernate.one-to-one
mapping and specify the name of the property on the other end that we are
linking to. Also, note that the we can only change the link with the
HybridizationData.setArraySlide()
method, and that
this method also updates the other end.
The second form of a one-to-one mapping is used when both objects must have the same id (primary key). The example is from the mapping between users and passwords.
// UserData.java /** @hibernate.id column="`id`" generator-class="foreign" @hibernate.generator-param name="property" value="password" */ public int getId() { return super.getId(); } private PasswordData password; /** Get the password. @hibernate.one-to-one class="net.sf.basedb.core.data.PasswordData" cascade="all" outer-join="false" constrained="true" */ public PasswordData getPassword() { if (password == null) { password = new PasswordData(); password.setUser(this); } return password; } void setPassword(PasswordData user) { this.password = password; } // PasswordData.java private UserData user; /** Get the user. @hibernate.one-to-one class="net.sf.basedb.core.data.UserData" */ public UserData getUser() { return user; } void setUser(UserData user) { this.user = user; }
In this case, we use the @hibernate.one-to-one
mapping
in both classes. The constrained="true"
tag in UserData
UserData.getId()
method, which uses the foreign
id generator. This generator will look
at the password property, ie. call getPassword().getId()
to find the id for the user. Also note the initialisation code and cascade="all"
tag in the UserData.getPassword()
method. This is need
to avoid NullPointerException
:s and to make sure everything
is created and deleted properly.
See also:
"Hibernate in action", chapter 6.3.1 "One-to-one association", page 220-225
Hibernate user documentation: 5.1.11. one-to-one
The data layer code needs documentation. A simple approach is used for Javadoc documentation
The documentation for the class doesn't have to be very lengthy. A single sentence is usually enough. Provide tags for the author, version, last modification date and a reference to the corresponding class in the net.sf.basedb.core package.
/** This class holds information about any items. @author Your name @version 2.0 @see net.sf.basedb.core.AnyItem @base.modified $Date: 2007-08-17 09:18:29 +0200 (Fri, 17 Aug 2007) $ @hibernate.class table="`Anys`" lazy="false" */ public class AnyData extends CommonData { ... }
Write a short one-sentence description for all public getter methods. You do not have document the parameters or the setter methods, since it would just be a repetition. Methods defined by interfaces are documented in the interface class. You should not have to write any documentation for those methods.
For the inverse end of an association, which has only package private methods, write a notice about this and provide a link to to non-inverse end.
// UserData.java private String address; /** Get the address for the user. @hibernate.property column="`address`" type="string" length="255" */ public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } private Set<GroupData> groups; /** This is the inverse end. @see GroupData#getUsers() @hibernate.set table="`UserGroups`" lazy="true" inverse="true" @hibernate.collection-key column="`user_id`" @hibernate.collection-many-to-many column="`group_id`" class="net.sf.basedb.core.data.GroupData" */ Set<GroupData> getGroups() { return groups; } void setGroups(Set<GroupData> groups) { this.groups = groups; }
Write a short one-sentence description for public static final
fields. Private fields does not have to be documented.
/** The maximum length of the name of an item that can be stored in the database. @see #setName(String) */ public static int MAX_NAME_LENGTH = 255;
Groups of related classes should be included in an UML-like diagram to show how they are connected and work together. For example we group together users, groups, roles, etc. into an authentication UML diagram. It is also possible that a single class may appear in more than one diagram. For more information about how to create UML diagrams see Section 30.2, “Create UML diagrams with MagicDraw”.