DEV Community

nileshborole
nileshborole

Posted on

3 1

Repository Pattern

Repository Pattern is a part of Domain-Driven Design, It abstract out the way to access data from the database. It mediates between the domain and data mapping layers using a Repository interface for accessing domain objects. In this article, we will understand what is a Repository pattern, Query Object, Mapping Metadata and how it can be implemented.

Repository interface exposes methods for adding, updating, deleting and finding records from the database. Repository implementation encapsulates the database connection and way to execute a query on the database.

The Repository takes the help of Query Object to construct the database query and map the result of the query after execution to an entity (or domain object) using Mapping Metadata.

Query Object

Query Object is an in-memory (programming object) representation of database query. The query object is implemented as an interpreter pattern and abstracts logic to create a database query string. Query objects de-couples the database schema and domain object as we refer to in-memory objects (classes and fields). This gives us the flexibility to form database queries from program objects (Query Object).

Mapping Metadata

Once the database query is executed by Repository, Query Object needs to know how to map a resultset row to domain object (or entity). This can be done by providing Mapping Metadata to the Query Object.

Mapping Metadata holds object-relational mapping details as metadata. Mapping Metadata can be provided by mean of Mapper object (like in a Spring framework we use RowMapper or ResultSetMapper), it can be held in a metadata file (example an XML or JSON file like a Mybatis ResultMap XML tag in mapper file) or can be annotated on the class field.

Let’s look into the implementation details of the Repository Pattern. We are going to implement a repository pattern simply.

  • Expose Repository interface with CRUD methods
  • Implement a Query Object
  • Implement a Mapper Metadata

Repository Interface

Repository interface exposes CRUD methods: Create, Retrieve(Find), Update, Delete.

package org.persistence;
import java.util.List;
public interface Repository {
Object create(SqlQuery query) throws RepositoryException;
int update(SqlQuery query) throws RepositoryException;
int delete(SqlQuery query) throws RepositoryException;
<T> List<T> find(SqlFinderQuery<T> query) throws RepositoryException;
}
view raw Repository.java hosted with ❤ by GitHub

Query Object

Two types of query objects: Here we are using a simple implementation of query object which will encapsulate string SQL query and query parameters.

  1. Query: SqlQuery wraps the SQL database query string and parameters.
    package org.persistence;
    import java.util.List;
    public class SqlQuery {
    private final String query;
    private final List params;
    public SqlQuery(String query, List params) {
    this.query = query;
    this.params = params;
    }
    public String getQuery() {
    return query;
    }
    public List getParams() {
    return params;
    }
    }
    view raw SqlQuery.java hosted with ❤ by GitHub
  2. FinderQuery: SqlFinderQuery is an extension of SqlQuery and encapsulates the way to map database resultset to domain objects using RowMapper object.
package org.persistence;
import java.util.List;
public class SqlFinderQuery<T> extends SqlQuery {
private final SqlRowMapper<T> rowMapper;
public SqlFinderQuery(String query, List params, SqlRowMapper<T> rowMapper) {
super(query, params);
this.rowMapper = rowMapper;
}
public SqlRowMapper<T> getRowMapper() {
return rowMapper;
}
}

A SqlRowMapper is an interface to map resultset to domain objects or entities.

package org.persistence;
import java.sql.ResultSet;
public interface SqlRowMapper<T> {
T mapTo(ResultSet resultSet);
}

SQL Repository Implementation

A SqlRepositoryImpl is an implementation of a Repository interface that encapsulates JDBC DataSource and implements CRUD (Create, Retrieve, Update & Delete)methods.

package org.persistence;
import javax.sql.DataSource;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class SqlRepositoryImpl implements Repository {
private final DataSource dataSource;
public SqlRepositoryImpl(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
*
* @param query is a Query Object wraps the insert SQL query string and parameters.
* @return auto generated value of column, in case of bulk insert returns records inserted count.
* @throws RepositoryException
*/
public Object create(SqlQuery query) throws RepositoryException{
try(Connection connection = getConnection()){
try(PreparedStatement statement = connection.prepareStatement(query.getQuery())){
int index = 0;
for(Object param : query.getParams()){
statement.setObject(++index, param);
}
int res = statement.executeUpdate();
if(res == 1){
ResultSet resultSet = statement.getGeneratedKeys();
if(resultSet.next())
return resultSet.getObject(1);
}
return res;
}
}catch (SQLException e){
throw new RepositoryException(e);
}catch (Exception e){
throw new RepositoryException(-1, "UNKNOWN ERROR");
}
}
/**
*
* @param query is a Query Object wraps the update SQL query string and parameters
* @return
* @throws RepositoryException
*/
public int update(SqlQuery query) throws RepositoryException{
return executeUpdate(query);
}
/**
*
* @param query is a Query Object wraps the delete SQL query string and parameters
* @return
* @throws RepositoryException
*/
public int delete(SqlQuery query) throws RepositoryException{
return executeUpdate(query);
}
/**
* Executes select SQL queries.
* @param query is a SqlFinderQuery&lt;T&gt;
* @param <T>
* @return List of domain object after mapping ResultSet row to domain object or entity
* @throws RepositoryException
*/
public <T> List<T> find(SqlFinderQuery<T> query) throws RepositoryException{
List<T> result = new ArrayList<>();
try(Connection connection = getConnection()){
try(PreparedStatement statement = connection.prepareStatement(query.getQuery())){
int index = 0;
for(Object param : query.getParams()){
statement.setObject(++index, param);
}
ResultSet resultSet = statement.executeQuery();
while (resultSet.next()){
result.add(query.getRowMapper().mapTo(resultSet));
}
}
}catch (SQLException e){
throw new RepositoryException(e);
}catch (Exception e){
throw new RepositoryException(-1, "UNKNOWN ERROR");
}
return result;
}
/**
* re-usable method to execute SqlQuery using Connection object.
* @param query
* @return
* @throws RepositoryException
*/
private int executeUpdate(SqlQuery query) throws RepositoryException{
try(Connection connection = getConnection()){
try(PreparedStatement statement = connection.prepareStatement(query.getQuery())){
int index = 0;
for(Object param : query.getParams()){
statement.setObject(++index, param);
}
return statement.executeUpdate();
}
}catch (SQLException e){
throw new RepositoryException(e);
}catch (Exception e){
throw new RepositoryException(-1, "UNKNOWN ERROR");
}
}
private Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
}

Heres a class diagram of above code:

Repository Pattern

When to use Repository Pattern

If you are dealing with multiple entities or domain objects, and have to write many database queries then a Repository Pattern will be helpful to reduce the redundant code, reduce the amount of code needed to implement for all queries.

Repository pattern is also beneficial when connecting to multiple data sources. Repository abstract out the implementation of how to access the database, database client (can be a JDBC client in case of a relational database) and takes care of all data access operations. With this abstraction, you can easily switch the target database for domain objects.

Please visit GitHub repository Persistence, which is a persistence framework implemented using a repository pattern.


References:
  1. https://deviq.com/repository-pattern/
  2. https://martinfowler.com/eaaCatalog/repository.html
  3. Object-Relational Metadata Mapping Patterns from Patterns of Enterprise Application Architecture
  4. Specification Pattern

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

Top comments (0)

Billboard image

Create up to 10 Postgres Databases on Neon's free plan.

If you're starting a new project, Neon has got your databases covered. No credit cards. No trials. No getting in your way.

Try Neon for Free →

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay