Part 3 of creating the siark.com Gallery involves adding the keywords. This requires a new domain object, Keyword and a many to many relationship between the gallery object and the keyword object with the gallery object being the ‘owner’ or the relationship.
The Keyword class.
package com.siark.igallery.domain; import java.util.HashSet; import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.ManyToMany; import javax.persistence.Table; @Entity @Table(name = "keyword") public class Keyword extends DomainBase { private static final long serialVersionUID = 3747792588358317008L; private String text; private Set<Gallery> galleries = new HashSet<Gallery>(); /** * */ public Keyword() { } /** * Construct a new keyword with the keyword text. A keyword can include spaces. * The keyword text is not case sensitive and blank spaces are removed. * * @param text the keyword */ public Keyword(String text) { this.text = text.toLowerCase().trim(); } /** * Get the text of the keyword. * * @return the keyword text */ @Column(name="text") public String getText() { return text; } /** * Set the text of the keyword. A keyword can include spaces. * The keyword text is not case sensitive and blank spaces at the end are removed. * * @param text the keyword text */ public void setText(String text) { this.text = text; } /** * Get the galleries associated with this keyword. * * @return galleries associated with this keyword */ @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, mappedBy = "keywords", targetEntity = Gallery.class) public Set<Gallery> getGalleries() { return galleries; } /** * Set the galleries associated with this keyword. * * @param galleries the galleries associated with this keyword. */ public void setGalleries(Set<Gallery> galleries) { this.galleries = galleries; } /** * */ @Override public String toString() { StringBuffer buffer = new StringBuffer(); buffer.append(super.toString()); buffer.append("Text: " + text + ";"); return buffer.toString(); } /** * */ @Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof Keyword)) return false; Keyword keyword = (Keyword) o; if ((this.id == keyword.getId()) && (this.text == null) ? (keyword.getText() == null) : keyword.getText().equals(this.text)) { return true; } else { return false; } } /** * */ @Override public int hashCode() { int result = 17; result = 31 * result + this.id; result = 31 * result + (this.text == null ? 0 : this.text.hashCode()); return result; } }
The Gallery class must now be updated to include keywords. I am also including the notion of a gallery being a leaf. That is a leaf gallery may not be the parent of any other galleries.
package com.siark.igallery.domain; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; import javax.persistence.Table; import org.hibernate.annotations.Cascade; import org.hibernate.annotations.ForeignKey; import org.hibernate.validator.constraints.NotEmpty; @Entity @Table(name = "gallery") public class Gallery extends DomainBase { private static final long serialVersionUID = -3449742178300288206L; private String title; private Gallery parent; private List<Gallery> children = new ArrayList<Gallery>(); private Set<Keyword> keywords = new HashSet<Keyword>(); boolean leaf = false; /** * Construct a new gallery with the display name of the gallery. * Blank spaces at the end of the title are removed. * * @param title the display name of the gallery */ public Gallery(String title) { this.title = title.trim(); } /** * */ public Gallery() { } /** * Returns the display name of the gallery. * * @return title the display name of the gallery */ @Column(name = "title") @NotEmpty public String getTitle() { return title; } /** * Sets the display name of the gallery. * Blank spaces at the end of the title are removed. * * @param title the display name of the gallery */ public void setTitle(String title) { this.title = title.trim(); } /** * Returns the gallery that is the parent to this gallery. * A gallery has only one parent. * * @return the gallery that is this galleries parent */ @ManyToOne @JoinColumn(name="parent_fk") @ForeignKey(name="to_parent_fk") public Gallery getParent() { return this.parent; } /** * Sets this galleries parent. * * @param the parent gallery */ public void setParent(Gallery parent) { this.parent = parent; } /** * If this gallery is a non-leaf gallery it can have zero or more child galleries. * * @return a list of this galleries children */ @OneToMany @JoinColumn(name="parent_fk") public List<Gallery> getChildren() { return children; } /** * Child galleries can only be added to this gallery if this gallery is a non-leaf gallery. * * @param the list of this galleries child galleries */ public void setChildren(List<Gallery> children) throws GalleryException { if (!this.leaf || children.isEmpty() || (children == null)) this.children = children; else throw new GalleryException(); } /** * Get the keywords associated with this gallery. * * @return a set of keywords */ @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, targetEntity = Keyword.class) @JoinTable(name = "gallery_keyword", joinColumns = @JoinColumn(name = "gallery_fk"), inverseJoinColumns = @JoinColumn(name="keyword_fk")) @ForeignKey(name = "to_gallery_fk", inverseName = "to_keyword_fk") @Cascade(org.hibernate.annotations.CascadeType.SAVE_UPDATE) public Set<Keyword> getKeywords() { return keywords; } /** * Set the keywords associated with this gallery. A gallery may have zero or more keywords, * but no duplicate keywords. * * @param keywords a set of keywords */ public void setKeywords(Set<Keyword> keywords) { this.keywords = keywords; } /** * A leaf gallery may not be the parent of any other galleries. * * @return true if this gallery is a leaf otherwise false */ @Column(name="leaf") public boolean isLeaf() { return leaf; } /** * The value of leaf can only be set to true if there are no children. * * @param leaf */ public void setLeaf(boolean leaf) { this.leaf = leaf; } /** * */ @Override public String toString() { StringBuffer buffer = new StringBuffer(); buffer.append(super.toString()); buffer.append("Title: " + this.title + "; "); buffer.append(this.parent == null ? "Parent: null ; " : "Parent: " + this.parent.getId() + "; "); buffer.append("Children: "); for (Iterator<Gallery> i = this.children.iterator(); i.hasNext();) { buffer.append(i.next().getTitle()); buffer.append(i.hasNext() ? ", " : "; "); } buffer.append("Keywords: "); for (Iterator<Keyword> i = this.keywords.iterator(); i.hasNext();) { buffer.append(i.next()); buffer.append(i.hasNext() ? ", " : "; "); } buffer.append("isLeaf: " + this.leaf + ";"); return buffer.toString(); } /** * */ @Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof Gallery)) return false; if (getClass() != o.getClass()) return false; Gallery gallery = (Gallery) o; if ((this.id == gallery.getId()) && (this.title == null) ? (gallery.getTitle() == null) : gallery.getTitle().equals(this.title) && (this.parent == null) ? (gallery.getParent() == null) : gallery.getParent().equals(this.parent) && this.children.equals(gallery.getChildren()) && this.keywords.equals(gallery.getKeywords()) && this.leaf == gallery.leaf) { return true; } else { return false; } } /** * */ @Override public int hashCode() { int result = 17; result = 31 * result + this.id; result = 31 * result + (this.title == null ? 0 : this.title.hashCode()); result = 31 * result + (this.parent == null ? 0 : this.parent.hashCode()); result = 31 * result + this.children.hashCode(); result = 31 * result + this.keywords.hashCode(); result = 31 * result + (this.leaf ? 1 : 0); return result; } }
I now need to create a dao for the keyword class.
package com.siark.igallery.repository; import com.siark.igallery.domain.Keyword; public interface KeywordDao { public void persistKeyword(Keyword keyword); public Keyword findKeywordByKeyword(String keyword); public Keyword findKeywordById(int id); }
package com.siark.igallery.repository; import javax.inject.Inject; import javax.inject.Named; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hibernate.SessionFactory; import com.siark.igallery.domain.Keyword; @Named("KeywordDao") public class HibernateKeywordDao implements KeywordDao { protected final Log logger = LogFactory.getLog(getClass()); private SessionFactory sessionFactory; /** * Save a new keyword or update an existing keyword. * * @param keyword a new or existing keyword */ public void persistKeyword(Keyword keyword) { this.sessionFactory.getCurrentSession() .saveOrUpdate(keyword); } /** * Find the keyword with a specific id or null if a keyword with that id does not exist. * * @param id the id of the keyword to find * @return a keyword with the id specified or null */ public Keyword findKeywordById(int id) { return (Keyword) this.sessionFactory.getCurrentSession() .createQuery("from Keyword as keyword where keyword.id = :id") .setInteger("id", id) .uniqueResult(); } /** * Find the keyword with the specified text. * * @param text the text the keyword must contain * @return the keyword containing the specified text */ public Keyword findKeywordByKeyword(String text) { return (Keyword) this.sessionFactory.getCurrentSession() .createQuery("from Keyword as keyword where text = :text") .setString("text", text) .uniqueResult(); } /** * */ @Inject public void setSessionFactory(SessionFactory sessionFactory) { this.sessionFactory = sessionFactory; } }
Now I need a service.
package com.siark.igallery.service; import java.util.Set; import com.siark.igallery.domain.Keyword; public interface KeywordService { public void persistKeyword(Keyword keyword); public Keyword getKeyword(String Keyword); public Set<Keyword> getKeywords(Set<String> Keywords); public Keyword findKeywordById(int id); public Keyword findKeywordByKeyword(String keyword); }
package com.siark.igallery.service; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import javax.inject.Inject; import javax.inject.Named; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import com.siark.igallery.domain.Keyword; import com.siark.igallery.repository.KeywordDao; @Named("KeywordService") @Transactional(propagation=Propagation.SUPPORTS, readOnly=true) public class KeywordServiceImpl implements KeywordService { protected final Log logger = LogFactory.getLog(getClass()); @Inject @Named("KeywordDao") private KeywordDao keywordDao; /** * * * @param keyword */ @Transactional(propagation=Propagation.REQUIRED, readOnly=false) public void persistKeyword(Keyword keyword) { keywordDao.persistKeyword(keyword); } /** * * * @param id * @return */ public Keyword findKeywordById(int id) { return keywordDao.findKeywordById(id); } /** * * * @param keyword * @return */ public Keyword findKeywordByKeyword(String keyword) { return keywordDao.findKeywordByKeyword(keyword); } /** * * * @param keywordStr * @return */ public Keyword getKeyword(String keywordStr) { Keyword keyword = keywordDao.findKeywordByKeyword(keywordStr); if (keyword == null) { keyword = new Keyword(keywordStr); keywordDao.persistKeyword(keyword); return keywordDao.findKeywordByKeyword(keywordStr); } return keyword; } /** * * * @param keywordsStrs * @return */ public Set<Keyword> getKeywords(Set<String> keywordStrs) { Set<Keyword> keywords = new HashSet<Keyword>(); for (Iterator<String> i = keywordStrs.iterator(); i.hasNext();) { String keywordStr = i.next(); keywords.add(getKeyword(keywordStr)); } return keywords; } /** * * * @param keywordDao */ public void setKeywordDao(KeywordDao keywordDao) { this.keywordDao = keywordDao; } /** * * * @return */ public KeywordDao getKeywordDao() { return keywordDao; } }
I’ve added Spring transactions to the service classes so I have to add transactional support to the db-config.xml file.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>classpath:jdbc.properties</value> <value>classpath:igallery.properties</value> </list> </property> </bean> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="${jdbc.driverClassName}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> </bean> <!-- Hibernate SessionFactory --> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="packagesToScan" value="com.siark.igallery.domain" /> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect">org.hibernate.dialect.MySQLInnoDBDialect</prop> </props> </property> </bean> <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <tx:annotation-driven /> </beans>
I have also removed the flushModeName property from the mvc-config.xml file.