diff --git a/core/src/main/java/google/registry/model/reporting/Spec11ThreatMatchDao.java b/core/src/main/java/google/registry/model/reporting/Spec11ThreatMatchDao.java
new file mode 100644
index 000000000..92531a8a1
--- /dev/null
+++ b/core/src/main/java/google/registry/model/reporting/Spec11ThreatMatchDao.java
@@ -0,0 +1,52 @@
+// Copyright 2020 The Nomulus Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package google.registry.model.reporting;
+
+import com.google.common.collect.ImmutableList;
+import google.registry.persistence.transaction.JpaTransactionManager;
+import org.joda.time.LocalDate;
+
+/**
+ * Data access object for {@link google.registry.model.reporting.Spec11ThreatMatch}.
+ *
+ *
A JpaTransactionManager is passed into each static method because they are called from a BEAM
+ * pipeline and we don't know where it's coming from.
+ */
+public class Spec11ThreatMatchDao {
+
+ /** Delete all entries with the specified date from the database. */
+ public static void deleteEntriesByDate(JpaTransactionManager jpaTm, LocalDate date) {
+ jpaTm.assertInTransaction();
+ jpaTm
+ .getEntityManager()
+ .createQuery("DELETE FROM Spec11ThreatMatch WHERE check_date = :date")
+ .setParameter("date", date.toString())
+ .executeUpdate();
+ }
+
+ /** Query the database and return a list of domain names with the specified date. */
+ public static ImmutableList loadEntriesByDate(
+ JpaTransactionManager jpaTm, LocalDate date) {
+ jpaTm.assertInTransaction();
+ return ImmutableList.copyOf(
+ jpaTm
+ .getEntityManager()
+ .createQuery(
+ "SELECT match FROM Spec11ThreatMatch match WHERE match.checkDate = :date",
+ Spec11ThreatMatch.class)
+ .setParameter("date", date)
+ .getResultList());
+ }
+}
diff --git a/core/src/test/java/google/registry/model/reporting/Spec11ThreatMatchDaoTest.java b/core/src/test/java/google/registry/model/reporting/Spec11ThreatMatchDaoTest.java
new file mode 100644
index 000000000..56875304d
--- /dev/null
+++ b/core/src/test/java/google/registry/model/reporting/Spec11ThreatMatchDaoTest.java
@@ -0,0 +1,106 @@
+// Copyright 2020 The Nomulus Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package google.registry.model.reporting;
+
+import static com.google.common.truth.Truth.assertThat;
+import static google.registry.model.ImmutableObjectSubject.immutableObjectCorrespondence;
+import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import google.registry.model.EntityTestCase;
+import google.registry.model.reporting.Spec11ThreatMatch.ThreatType;
+import org.joda.time.LocalDate;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/** Unit tests for {@link Spec11ThreatMatchDao}. */
+public class Spec11ThreatMatchDaoTest extends EntityTestCase {
+
+ private static final LocalDate TODAY = new LocalDate(2020, 8, 4);
+ private static final LocalDate YESTERDAY = new LocalDate(2020, 8, 3);
+
+ @BeforeEach
+ void setUp() {
+ jpaTm()
+ .transact(
+ () -> {
+ jpaTm().saveAllNew(getThreatMatchesToday());
+ jpaTm().saveAllNew(getThreatMatchesYesterday());
+ });
+ }
+
+ @Test
+ void testDeleteEntriesByDate() {
+ // Verify that all entries with the date TODAY were removed
+ jpaTm()
+ .transact(
+ () -> {
+ Spec11ThreatMatchDao.deleteEntriesByDate(jpaTm(), TODAY);
+ ImmutableList persistedToday =
+ Spec11ThreatMatchDao.loadEntriesByDate(jpaTm(), TODAY);
+ assertThat(persistedToday).isEmpty();
+ });
+
+ // Verify that all other entries were not removed
+ jpaTm()
+ .transact(
+ () -> {
+ ImmutableList persistedYesterday =
+ Spec11ThreatMatchDao.loadEntriesByDate(jpaTm(), YESTERDAY);
+ assertThat(persistedYesterday)
+ .comparingElementsUsing(immutableObjectCorrespondence("id"))
+ .containsExactlyElementsIn(getThreatMatchesYesterday());
+ });
+ }
+
+ @Test
+ void testLoadEntriesByDate() {
+ jpaTm()
+ .transact(
+ () -> {
+ ImmutableList persisted =
+ Spec11ThreatMatchDao.loadEntriesByDate(jpaTm(), TODAY);
+
+ assertThat(persisted)
+ .comparingElementsUsing(immutableObjectCorrespondence("id"))
+ .containsExactlyElementsIn(getThreatMatchesToday());
+ });
+ }
+
+ private ImmutableList getThreatMatchesYesterday() {
+ return ImmutableList.of(
+ createThreatMatch("yesterday.com", YESTERDAY),
+ createThreatMatch("yesterday.org", YESTERDAY));
+ }
+
+ private ImmutableList getThreatMatchesToday() {
+ return ImmutableList.of(
+ createThreatMatch("today.com", TODAY), createThreatMatch("today.org", TODAY));
+ }
+
+ private Spec11ThreatMatch createThreatMatch(String domainName, LocalDate date) {
+ Spec11ThreatMatch threatMatch =
+ new Spec11ThreatMatch()
+ .asBuilder()
+ .setThreatTypes(ImmutableSet.of(ThreatType.MALWARE))
+ .setCheckDate(date)
+ .setDomainName(domainName)
+ .setRegistrarId("Example Registrar")
+ .setDomainRepoId("1-COM")
+ .build();
+ return threatMatch;
+ }
+}