From 4efa18f9e66b586b145de966a350b9f9e379d646 Mon Sep 17 00:00:00 2001 From: Athou Date: Thu, 4 Jul 2013 23:40:25 +0200 Subject: [PATCH] materialize the manytomany relationship to have better control over queries --- .../com/commafeed/backend/dao/FeedDAO.java | 12 ---- .../commafeed/backend/dao/FeedEntryDAO.java | 19 ++++--- .../backend/dao/FeedEntryStatusDAO.java | 19 ++++--- .../com/commafeed/backend/model/Feed.java | 32 ++++------- .../commafeed/backend/model/FeedEntry.java | 25 ++++---- .../backend/model/FeedFeedEntry.java | 57 +++++++++++++++++++ .../backend/services/FeedUpdateService.java | 14 ++++- src/main/resources/META-INF/orm.xml | 2 +- 8 files changed, 110 insertions(+), 70 deletions(-) create mode 100644 src/main/java/com/commafeed/backend/model/FeedFeedEntry.java diff --git a/src/main/java/com/commafeed/backend/dao/FeedDAO.java b/src/main/java/com/commafeed/backend/dao/FeedDAO.java index 8cd53359..43b64d6d 100644 --- a/src/main/java/com/commafeed/backend/dao/FeedDAO.java +++ b/src/main/java/com/commafeed/backend/dao/FeedDAO.java @@ -88,18 +88,6 @@ public class FeedDAO extends GenericDAO { return null; } - public Feed findByIdWithEntries(Long feedId, int offset, int limit) { - CriteriaQuery query = builder.createQuery(getType()); - Root root = query.from(getType()); - - query.where(builder.equal(root.get(Feed_.id), feedId)); - root.fetch(Feed_.entries, JoinType.LEFT); - - TypedQuery q = em.createQuery(query); - limit(q, offset, limit); - return q.getSingleResult(); - } - public List findByTopic(String topic) { return findByField(Feed_.pushTopicHash, DigestUtils.sha1Hex(topic)); } diff --git a/src/main/java/com/commafeed/backend/dao/FeedEntryDAO.java b/src/main/java/com/commafeed/backend/dao/FeedEntryDAO.java index 8584e381..6c17f53d 100644 --- a/src/main/java/com/commafeed/backend/dao/FeedEntryDAO.java +++ b/src/main/java/com/commafeed/backend/dao/FeedEntryDAO.java @@ -18,7 +18,8 @@ import org.slf4j.LoggerFactory; import com.commafeed.backend.model.Feed; import com.commafeed.backend.model.FeedEntry; import com.commafeed.backend.model.FeedEntry_; -import com.commafeed.backend.model.Feed_; +import com.commafeed.backend.model.FeedFeedEntry; +import com.commafeed.backend.model.FeedFeedEntry_; import com.commafeed.backend.services.ApplicationSettingsService; import com.google.common.collect.Iterables; @@ -33,11 +34,11 @@ public class FeedEntryDAO extends GenericDAO { public static class EntryWithFeed { public FeedEntry entry; - public Feed feed; + public FeedFeedEntry ffe; - public EntryWithFeed(FeedEntry entry, Feed feed) { + public EntryWithFeed(FeedEntry entry, FeedFeedEntry ffe) { this.entry = entry; - this.feed = feed; + this.ffe = ffe; } } @@ -52,7 +53,7 @@ public class FeedEntryDAO extends GenericDAO { EntryWithFeed result = null; List list = q.getResultList(); for (EntryWithFeed ewf : list) { - if (ewf.entry != null && ewf.feed != null) { + if (ewf.entry != null && ewf.ffe != null) { result = ewf; break; } @@ -66,9 +67,9 @@ public class FeedEntryDAO extends GenericDAO { public List findByFeed(Feed feed, int offset, int limit) { CriteriaQuery query = builder.createQuery(getType()); Root root = query.from(getType()); - SetJoin feedsJoin = root.join(FeedEntry_.feeds); + SetJoin feedsJoin = root.join(FeedEntry_.feedRelationships); - query.where(builder.equal(feedsJoin.get(Feed_.id), feed.getId())); + query.where(builder.equal(feedsJoin.get(FeedFeedEntry_.feed), feed)); query.orderBy(builder.desc(root.get(FeedEntry_.updated))); TypedQuery q = em.createQuery(query); limit(q, offset, limit); @@ -94,9 +95,9 @@ public class FeedEntryDAO extends GenericDAO { CriteriaQuery query = builder.createQuery(getType()); Root root = query.from(getType()); - SetJoin join = root.join(FeedEntry_.feeds, + SetJoin join = root.join(FeedEntry_.feedRelationships, JoinType.LEFT); - query.where(builder.isNull(join.get(Feed_.id))); + query.where(builder.isNull(join.get(FeedFeedEntry_.feed))); TypedQuery q = em.createQuery(query); q.setMaxResults(max); diff --git a/src/main/java/com/commafeed/backend/dao/FeedEntryStatusDAO.java b/src/main/java/com/commafeed/backend/dao/FeedEntryStatusDAO.java index 14efb91a..42ef0740 100644 --- a/src/main/java/com/commafeed/backend/dao/FeedEntryStatusDAO.java +++ b/src/main/java/com/commafeed/backend/dao/FeedEntryStatusDAO.java @@ -16,7 +16,6 @@ import javax.persistence.criteria.Path; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import javax.persistence.criteria.Selection; -import javax.persistence.criteria.SetJoin; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; @@ -31,6 +30,8 @@ import com.commafeed.backend.model.FeedEntryContent_; import com.commafeed.backend.model.FeedEntryStatus; import com.commafeed.backend.model.FeedEntryStatus_; import com.commafeed.backend.model.FeedEntry_; +import com.commafeed.backend.model.FeedFeedEntry; +import com.commafeed.backend.model.FeedFeedEntry_; import com.commafeed.backend.model.FeedSubscription; import com.commafeed.backend.model.FeedSubscription_; import com.commafeed.backend.model.Feed_; @@ -139,8 +140,8 @@ public class FeedEntryStatusDAO extends GenericDAO { CriteriaQuery query = builder.createTupleQuery(); Root root = query.from(FeedEntry.class); - SetJoin feedJoin = root.join(FeedEntry_.feeds); - SetJoin subJoin = feedJoin + Join feedJoin = root.join(FeedEntry_.feedRelationships).join(FeedFeedEntry_.feed); + Join subJoin = feedJoin .join(Feed_.subscriptions); Join contentJoin = root .join(FeedEntry_.content); @@ -231,8 +232,8 @@ public class FeedEntryStatusDAO extends GenericDAO { CriteriaQuery query = builder.createTupleQuery(); Root root = query.from(FeedEntry.class); - SetJoin feedJoin = root.join(FeedEntry_.feeds); - SetJoin subJoin = feedJoin + Join feedJoin = root.join(FeedEntry_.feedRelationships).join(FeedFeedEntry_.feed); + Join subJoin = feedJoin .join(Feed_.subscriptions); Selection entryAlias = root.alias("entry"); @@ -317,8 +318,8 @@ public class FeedEntryStatusDAO extends GenericDAO { CriteriaQuery query = builder.createQuery(FeedEntry.class); Root root = query.from(FeedEntry.class); - SetJoin feedJoin = root.join(FeedEntry_.feeds); - SetJoin subJoin = feedJoin + Join feedJoin = root.join(FeedEntry_.feedRelationships).join(FeedFeedEntry_.feed); + Join subJoin = feedJoin .join(Feed_.subscriptions); List predicates = Lists.newArrayList(); @@ -388,8 +389,8 @@ public class FeedEntryStatusDAO extends GenericDAO { CriteriaQuery query = builder.createTupleQuery(); Root root = query.from(FeedEntry.class); - SetJoin feedJoin = root.join(FeedEntry_.feeds); - SetJoin subJoin = feedJoin + Join feedJoin = root.join(FeedEntry_.feedRelationships).join(FeedFeedEntry_.feed); + Join subJoin = feedJoin .join(Feed_.subscriptions); Selection entryAlias = root.alias("entry"); diff --git a/src/main/java/com/commafeed/backend/model/Feed.java b/src/main/java/com/commafeed/backend/model/Feed.java index db514129..5a0fcbd6 100644 --- a/src/main/java/com/commafeed/backend/model/Feed.java +++ b/src/main/java/com/commafeed/backend/model/Feed.java @@ -4,11 +4,10 @@ import java.util.Date; import java.util.Set; import javax.persistence.Cacheable; +import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; -import javax.persistence.ManyToMany; import javax.persistence.OneToMany; -import javax.persistence.PreRemove; import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; @@ -17,8 +16,6 @@ import javax.persistence.Transient; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; -import com.google.common.collect.Sets; - @Entity @Table(name = "FEEDS") @SuppressWarnings("serial") @@ -111,8 +108,8 @@ public class Feed extends AbstractModel { @Column(length = 40) private String lastContentHash; - @ManyToMany(mappedBy = "feeds") - private Set entries = Sets.newHashSet(); + @OneToMany(mappedBy = "feed", cascade = CascadeType.REMOVE) + private Set entryRelationships; @OneToMany(mappedBy = "feed") private Set subscriptions; @@ -145,13 +142,6 @@ public class Feed extends AbstractModel { @Transient private boolean urgent; - @PreRemove - private void removeEntriesFromFeed() { - for (FeedEntry entry : entries) { - entry.getFeeds().remove(this); - } - } - public Feed() { } @@ -176,14 +166,6 @@ public class Feed extends AbstractModel { this.lastUpdated = lastUpdated; } - public Set getEntries() { - return entries; - } - - public void setEntries(Set entries) { - this.entries = entries; - } - public String getMessage() { return message; } @@ -344,4 +326,12 @@ public class Feed extends AbstractModel { this.normalizedUrlHash = normalizedUrlHash; } + public Set getEntryRelationships() { + return entryRelationships; + } + + public void setEntryRelationships(Set entryRelationships) { + this.entryRelationships = entryRelationships; + } + } diff --git a/src/main/java/com/commafeed/backend/model/FeedEntry.java b/src/main/java/com/commafeed/backend/model/FeedEntry.java index 8dbe11fa..ac56a48d 100644 --- a/src/main/java/com/commafeed/backend/model/FeedEntry.java +++ b/src/main/java/com/commafeed/backend/model/FeedEntry.java @@ -9,8 +9,6 @@ import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.JoinColumn; -import javax.persistence.JoinTable; -import javax.persistence.ManyToMany; import javax.persistence.OneToMany; import javax.persistence.OneToOne; import javax.persistence.Table; @@ -20,8 +18,6 @@ import javax.persistence.TemporalType; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; -import com.google.api.client.util.Sets; - @Entity @Table(name = "FEEDENTRIES") @SuppressWarnings("serial") @@ -35,9 +31,8 @@ public class FeedEntry extends AbstractModel { @Column(length = 40, nullable = false) private String guidHash; - @ManyToMany - @JoinTable(name = "FEED_FEEDENTRIES", joinColumns = { @JoinColumn(name = "FEEDENTRY_ID", nullable = false, updatable = false) }, inverseJoinColumns = { @JoinColumn(name = "FEED_ID", nullable = false, updatable = false) }) - private Set feeds = Sets.newHashSet(); + @OneToMany(mappedBy = "entry", cascade = CascadeType.REMOVE) + private Set feedRelationships; @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false) @JoinColumn(nullable = false, updatable = false) @@ -82,14 +77,6 @@ public class FeedEntry extends AbstractModel { this.updated = updated; } - public Set getFeeds() { - return feeds; - } - - public void setFeeds(Set feeds) { - this.feeds = feeds; - } - public Set getStatuses() { return statuses; } @@ -130,4 +117,12 @@ public class FeedEntry extends AbstractModel { this.author = author; } + public Set getFeedRelationships() { + return feedRelationships; + } + + public void setFeedRelationships(Set feedRelationships) { + this.feedRelationships = feedRelationships; + } + } diff --git a/src/main/java/com/commafeed/backend/model/FeedFeedEntry.java b/src/main/java/com/commafeed/backend/model/FeedFeedEntry.java new file mode 100644 index 00000000..593fd373 --- /dev/null +++ b/src/main/java/com/commafeed/backend/model/FeedFeedEntry.java @@ -0,0 +1,57 @@ +package com.commafeed.backend.model; + +import java.io.Serializable; + +import javax.persistence.Cacheable; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; + +@Entity +@Table(name = "FEED_FEEDENTRIES") +@SuppressWarnings("serial") +@Cacheable +@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL) +public class FeedFeedEntry implements Serializable { + + @Id + @ManyToOne + @JoinColumn(name = "FEED_ID") + private Feed feed; + + @Id + @ManyToOne + @JoinColumn(name = "FEEDENTRY_ID") + private FeedEntry entry; + + public FeedFeedEntry() { + + } + + public FeedFeedEntry(Feed feed, FeedEntry entry) { + this.feed = feed; + this.entry = entry; + } + + public Feed getFeed() { + return feed; + } + + public void setFeed(Feed feed) { + this.feed = feed; + } + + public FeedEntry getEntry() { + return entry; + } + + public void setEntry(FeedEntry entry) { + this.entry = entry; + } + +} diff --git a/src/main/java/com/commafeed/backend/services/FeedUpdateService.java b/src/main/java/com/commafeed/backend/services/FeedUpdateService.java index a592b986..27ce54ee 100644 --- a/src/main/java/com/commafeed/backend/services/FeedUpdateService.java +++ b/src/main/java/com/commafeed/backend/services/FeedUpdateService.java @@ -5,6 +5,8 @@ import java.util.List; import javax.ejb.Stateless; import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; import com.commafeed.backend.MetricsBean; import com.commafeed.backend.cache.CacheService; @@ -17,12 +19,16 @@ import com.commafeed.backend.model.Feed; import com.commafeed.backend.model.FeedEntry; import com.commafeed.backend.model.FeedEntryContent; import com.commafeed.backend.model.FeedEntryStatus; +import com.commafeed.backend.model.FeedFeedEntry; import com.commafeed.backend.model.FeedSubscription; import com.commafeed.backend.model.User; import com.google.common.collect.Lists; @Stateless public class FeedUpdateService { + + @PersistenceContext + protected EntityManager em; @Inject FeedSubscriptionDAO feedSubscriptionDAO; @@ -46,6 +52,7 @@ public class FeedUpdateService { entry.getUrl(), feed.getId()); FeedEntry update = null; + FeedFeedEntry ffe = null; if (existing == null) { entry.setAuthor(FeedUtils.truncate(FeedUtils.handleContent( entry.getAuthor(), feed.getLink(), true), 128)); @@ -56,11 +63,11 @@ public class FeedUpdateService { feed.getLink(), false)); entry.setInserted(new Date()); - entry.getFeeds().add(feed); + ffe = new FeedFeedEntry(feed, entry); update = entry; - } else if (existing.feed == null) { - existing.entry.getFeeds().add(feed); + } else if (existing.ffe == null) { + ffe = new FeedFeedEntry(feed, existing.entry); update = existing.entry; } @@ -78,6 +85,7 @@ public class FeedUpdateService { cache.invalidateUserData(users.toArray(new User[0])); feedEntryDAO.saveOrUpdate(update); feedEntryStatusDAO.saveOrUpdate(statusUpdateList); + em.persist(ffe); metricsBean.entryUpdated(statusUpdateList.size()); } } diff --git a/src/main/resources/META-INF/orm.xml b/src/main/resources/META-INF/orm.xml index 5d080cf2..7a23bbe5 100644 --- a/src/main/resources/META-INF/orm.xml +++ b/src/main/resources/META-INF/orm.xml @@ -10,7 +10,7 @@ - select new com.commafeed.backend.dao.FeedEntryDAO$EntryWithFeed(e, f) FROM FeedEntry e LEFT JOIN e.feeds f WITH f.id = :feedId where e.guidHash = :guidHash and e.url = :url + select new com.commafeed.backend.dao.FeedEntryDAO$EntryWithFeed(e, f) FROM FeedEntry e LEFT JOIN e.feedRelationships f WITH f.feed.id = :feedId where e.guidHash = :guidHash and e.url = :url