Implementing DAO Design Pattern in Jedis Using Java API
In this article, we are going to learn the DAO design pattern and the implementation in the Jedis java client.
Join the DZone community and get the full member experience.
Join For FreeIn this article, we are going to learn the DAO design pattern and the implementation in the Jedis java client. The DAO pattern is implemented as a layer between the Client application and the Database. The client application need not depend on the underlying database interaction API (Low-level). The data being stored in the Redis database is modeled as a Domain object (POJO class). It will have only getter and setter methods. The client application knows only the domain object and the high-level API.
Prerequisites
- Eclipse (any version) with Maven capabilities
- Java 8+
- Junit
- Redis and Jedis
Installing Redis Server on Windows
- Click on the link: https://github.com/dmajkic/redis/downloads
- Download Redis-2.4.5-win32-win64.zip file
- Unzip the file and go to the 64bit folder. There you can find redis-server.exe
- To start the Redis server, execute the redis-server.exe file.
Installing Eclipse-IDE on Windows
- Click on the link: https://www.eclipse.org/downloads/download.php?file=/oomph/epp/2020-09/R/eclipse-inst-jre-win64.exe
- Download the eclipse-inst-jre-win64.exe file and run the eclipse installer.
3. Select Eclipse IDE for Eclipse committers and install
Creating a Maven Project in Eclipse IDE
1. Open the Eclipse IDE
2. Go to File > New > Project
3. Go to Maven -> Maven Project and click Next.
4. Select your workspace location and click Next
5. Select quick start maven archetype and click Next
6. Enter Group Id, Artifact Id, and package name.
- Group Id: Fill a groupId for the project of your choice.
- Artifact Id: Fill artifactId for the project of your choice.
- Package: java package structure of your choice
7. The above process will create a project structure like below.
8. Create packages like com.example.demo.dao, com.example.demo.model and com.example.demo.util under src/test/java folder
9. Place the SiteDaoRedisImplTest.java file in com.example.demo package.
package com.example.demo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertEquals;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import com.example.demo.dao.SiteDaoRedisImpl;
import com.example.demo.model.Site;
import com.example.demo.util.HostPort;
import com.example.demo.util.RedisSchema;
import com.example.demo.util.TestKeyManager;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class SiteDaoRedisImplTest {
private static JedisPool jedisPool;
private static Jedis jedis;
private static TestKeyManager keyManager;
private Set<Site> sites;
public static void setUp() throws Exception {
String password = HostPort.getRedisPassword();
if (password.length() > 0) {
jedisPool = new JedisPool(new JedisPoolConfig(), HostPort.getRedisHost(), HostPort.getRedisPort(), 2000, password);
} else {
jedisPool = new JedisPool(HostPort.getRedisHost(), HostPort.getRedisPort());
}
jedis = new Jedis(HostPort.getRedisHost(), HostPort.getRedisPort());
if (password.length() > 0) {
jedis.auth(password);
}
keyManager = new TestKeyManager("test");
}
public static void tearDown() {
jedisPool.destroy();
jedis.close();
}
public void flush() {
keyManager.deleteKeys(jedis);
}
public void generateData() {
sites = new HashSet<>();
sites.add(new Site(1, 4.5, 3, "123 Willow St.",
"Oakland", "CA", "94577" ));
sites.add(new Site(2, 3.0, 2, "456 Maple St.",
"Oakland", "CA", "94577" ));
sites.add(new Site(3, 4.0, 3, "789 Oak St.",
"Oakland", "CA", "94577" ));
}
/**
* Challenge #0 Part 1. This challenge is explained in
* the video "How to Solve a Sample Challenge"
*/
public void findByIdWithExistingSite() {
SiteDaoRedisImpl dao = new SiteDaoRedisImpl(jedisPool);
Site site = new Site(4L, 5.5, 4, "910 Pine St.",
"Oakland", "CA", "94577");
dao.insert(site);
Site storedSite = dao.findById(4L);
System.out.println(storedSite);
System.out.println(site);
assertThat(storedSite, is(site));
}
/**
* Challenge #0 Part 2. This challenge is explained in
* the video "How to Solve a Sample Challenge"
*/
public void findByIdWithMissingSite() {
SiteDaoRedisImpl dao = new SiteDaoRedisImpl(jedisPool);
System.out.println(dao.findById(4L));
assertThat(dao.findById(4L), is(nullValue()));
}
/**
* Challenge #1 Part 1. Use this test case to
* implement the challenge in Chapter 1.
*/
public void findAllWithMultipleSites() {
SiteDaoRedisImpl dao = new SiteDaoRedisImpl(jedisPool);
// Insert all sites
for (Site site : sites) {
dao.insert(site);
System.out.println(site);
}
System.out.println("----------------------------------");
assertThat(dao.findAll(), is(sites));
}
/**
* Challenge #1 Part 2. Use this test case to
* implement the challenge in Chapter 1.
*/
public void findAllWithEmptySites() {
SiteDaoRedisImpl dao = new SiteDaoRedisImpl(jedisPool);
assertThat(dao.findAll(), is(empty()));
}
public void insert() {
SiteDaoRedisImpl dao = new SiteDaoRedisImpl(jedisPool);
Site site = new Site(4, 5.5, 4, "910 Pine St.",
"Oakland", "CA", "94577");
dao.insert(site);
Map<String, String> siteFields = jedis.hgetAll(RedisSchema.getSiteHashKey(4L));
System.out.println(siteFields);
System.out.println(site.toMap());
assertEquals(siteFields, site.toMap());
assertThat(jedis.sismember(RedisSchema.getSiteIDsKey(), RedisSchema.getSiteHashKey(4L)),
is(true));
}
}
10. Add the SiteDao.java file in com.example.demo.dao package.
xxxxxxxxxx
package com.example.demo.dao;
import java.util.Set;
import com.example.demo.model.Site;
public interface SiteDao {
void insert(Site site);
Site findById(long id);
Set<Site> findAll();
}
11. Add the SiteDaoRedisImpl.java file in com.example.demo.dao package
xxxxxxxxxx
package com.example.demo.dao;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.example.demo.model.Site;
import com.example.demo.util.RedisSchema;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
public class SiteDaoRedisImpl implements SiteDao {
private final JedisPool jedisPool;
public SiteDaoRedisImpl(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
// When we insert a site, we set all of its values into a single hash.
// We then store the site's id in a set for easy access.
public void insert(Site site) {
try (Jedis jedis = jedisPool.getResource()) {
String hashKey = RedisSchema.getSiteHashKey(site.getId());
String siteIdKey = RedisSchema.getSiteIDsKey();
jedis.hmset(hashKey, site.toMap());
jedis.sadd(siteIdKey, hashKey);
}
}
public Site findById(long id) {
try(Jedis jedis = jedisPool.getResource()) {
String key = RedisSchema.getSiteHashKey(id);
Map<String, String> fields = jedis.hgetAll(key);
if (fields == null || fields.isEmpty()) {
return null;
} else {
return new Site(fields);
}
}
}
// Challenge #1
public Set<Site> findAll() {
try (Jedis jedis = jedisPool.getResource()) {
Set<String> keys = jedis.keys("test:sites:info:*");
Set<Site> sites = new HashSet<>(keys.size());
for (String key : keys) {
Map<String, String> site = jedis.hgetAll(key);
if (!site.isEmpty()) {
sites.add(new Site(site));
}
}
System.out.println(sites);
return sites;
}
}
}
12. Add HostPort.java,KeyHelper.java,MetricUnit.java,RedisSchema.java and TaskKeyManager.java in com.example.demo.util package
xxxxxxxxxx
package com.example.demo.util;
public class HostPort {
final private static String defaultHost = "localhost";
final private static Integer defaultPort = 6379;
final private static String defaultPassword = "";
public static String getRedisHost() {
return defaultHost;
}
public static Integer getRedisPort() {
return defaultPort;
}
public static String getRedisPassword() {
return defaultPassword;
}
}
xxxxxxxxxx
package com.example.demo.util;
public class KeyHelper {
final private static String defaultPrefix = "app";
private static String prefix = null;
public static void setPrefix(String keyPrefix) {
prefix = keyPrefix;
}
public static String getKey(String key) {
return getPrefix() + ":" + key;
}
public static String getPrefix() {
if (prefix != null) {
return prefix;
} else {
return defaultPrefix;
}
}
}
xxxxxxxxxx
package com.example.demo.util;
public enum MetricUnit {
WHGenerated("whG"),
WHUsed("whU"),
TemperatureCelsius("tempC");
private final String shortName;
MetricUnit(String shortName) {
this.shortName = shortName;
}
public String getShortName() {
return shortName;
}
}
xxxxxxxxxx
package com.example.demo.util;
import java.time.ZonedDateTime;
/**
* Methods to generate key names for Redis
* data structures. These key names are used
* by the RedisDaoImpl classes. This class therefore
* contains a reference to all possible key names
* used by this application.
*/
public class RedisSchema {
// sites:info:[siteId]
// Redis type: hash
public static String getSiteHashKey(long siteId) {
return KeyHelper.getKey("sites:info:" + siteId);
}
// sites:ids
// Redis type: set
public static String getSiteIDsKey() {
return KeyHelper.getKey("sites:ids");
}
// sites:stats:[year-month-day]:[siteId]
// Redis type: sorted set
public static String getSiteStatsKey(Long siteId, ZonedDateTime dateTime) {
return KeyHelper.getKey("sites:stats:" +
getYearMonthDay(dateTime) + ":" +
String.valueOf(siteId));
}
// limiter:[name]:[duration]:[maxHits]
// Redis type: string of type integer
static String getRateLimiterKey(String name, int minuteBlock,
long maxHits) {
return KeyHelper.getKey("limiter:" +
name + ":" +
String.valueOf(minuteBlock) + ":" +
String.valueOf(maxHits));
}
// sites:geo
// Redis type: geo
static String getSiteGeoKey() {
return KeyHelper.getKey("sites:geo");
}
// sites:capacity:ranking
// Redis type: sorted set
static String getCapacityRankingKey() {
return KeyHelper.getKey("sites:capacity:ranking");
}
// metric:[unit-name]:[year-month-day]:[siteId]
// Redis type: sorted set
static String getDayMetricKey(Long siteId, MetricUnit unit,
ZonedDateTime dateTime) {
return KeyHelper.getPrefix() +
":metric:" +
unit.getShortName() +
":" +
getYearMonthDay(dateTime) +
":" +
String.valueOf(siteId);
}
// sites:feed
// Redis type: stream
static String getGlobalFeedKey() {
return KeyHelper.getKey("sites:feed");
}
// sites:feed:[siteId]
// Redis type: stream
static String getFeedKey(long siteId) {
return KeyHelper.getKey("sites:feed:" + String.valueOf(siteId));
}
// Return the year and month in the form YEAR-MONTH-DAY
private static String getYearMonthDay(ZonedDateTime dateTime) {
return String.valueOf(dateTime.getYear()) + "-" +
String.valueOf(dateTime.getMonthValue()) + "-" +
String.valueOf(dateTime.getDayOfMonth());
}
// sites:ts:[siteId]:[unit]
// Redis type: RedisTimeSeries
static String getTSKey(Long siteId, MetricUnit unit) {
return KeyHelper.getKey("sites:ts:" + String.valueOf(siteId) + ":" + unit.toString());
}
}
xxxxxxxxxx
package com.example.demo.util;
import redis.clients.jedis.Jedis;
import java.util.Set;
// Provides a consistent key prefix for tests and
// a method for cleaning up these keys.
public class TestKeyManager {
private String prefix;
public TestKeyManager(String prefix) {
KeyHelper.setPrefix(prefix);
this.prefix = prefix;
}
public void deleteKeys(Jedis jedis) {
Set<String> keysToDelete = jedis.keys(getKeyPattern());
for (String key : keysToDelete) {
jedis.del(key);
}
}
private String getKeyPattern() {
return prefix + ":*";
}
}
13. Replace the pom.xml with the below content.
xxxxxxxxxx
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<prerequisites>
<maven>3.0.0</maven>
</prerequisites>
<groupId>JedisDAODemo</groupId>
<artifactId>JedisDAODemo</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<name>JedisDAODemo</name>
<properties>
<redis.host>localhost</redis.host>
<redis.port>6379</redis.port>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<dropwizard.version>1.3.8</dropwizard.version>
<mainClass>com.example.demo.RediSolarApplication</mainClass>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-bom</artifactId>
<version>${dropwizard.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>4.2.2</version>
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-core</artifactId>
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-assets</artifactId>
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-testing</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.1.0-m3</version>
</dependency>
<dependency>
<groupId>com.redislabs</groupId>
<artifactId>jredistimeseries</artifactId>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.2.11</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.2.11</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.2.11</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>properties-maven-plugin</artifactId>
<version>1.0.0</version>
<executions>
<execution>
<phase>initialize</phase>
<goals>
<goal>read-project-properties</goal>
</goals>
<configuration>
<files>
<file>pom.xml</file>
</files>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.1</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>${mainClass}</mainClass>
</transformer>
</transformers>
<!-- exclude signed Manifests -->
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>${mainClass}</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-source-plugin</artifactId>
<version>3.0.1</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<reporting>
<plugins>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>2.8.1</version>
<configuration>
<dependencyLocationsEnabled>false</dependencyLocationsEnabled>
<dependencyDetailsEnabled>false</dependencyDetailsEnabled>
</configuration>
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.10.3</version>
</plugin>
</plugins>
</reporting>
</project>
Running the Code
- Build the Maven project and run the test case as shown below.
2. Check the test result.
Now, we learned the DAO pattern to perform database operations without knowing the low-level API through the Jedis java client application.
Further, this use case can be adapted according to your requirements.
Feel free to ask any questions.
Opinions expressed by DZone contributors are their own.
Comments