Skip to content

12. [TD]: Implementation of the [DAO] layer of the TD using [Spring Data]

Keywords: multi-layer architecture, Spring, dependency injection, JPA (Java Persistence API), Spring Data.

We will follow the same approach as before to implement the [DAO] layer of the TD.

12.1. Support

  • In [1], the [support / chap-12] folder contains the Eclipse project for this chapter;

12.2. The Eclipse project

The Eclipse project will be as follows:

  
  • [elections.dao.config]: contains the Spring project configuration class;
  • [elections.dao.entities]: contains the JPA entities as well as the project’s exception class;
  • [elections.dao.repositories]: contains the [CrudRepository] interfaces for the [CONF] and [LISTES] tables;
  • [elections.dao.service]: contains the implementation of the [DAO] layer. This is what we need to write;
  • [elections.dao.console]: contains a [console] test class;
  • [pom.xml]: the Maven project configuration file;

This project implements the following architecture:

The [DAO] layer only sees the layer implemented by [Spring Data].

12.3. Maven Configuration


Task: Build the project's [pom.xml] file.


12.4. The entities of the [JPA] layer

  
  • [ElectionsConfig] is the object model associated with a row in the [CONF] table;
  • [VoterList] is the object model associated with a row in the [LISTS] table;
  • [AbstractEntity] is the parent class of the two preceding classes. It factors out the [id, version] fields common to both classes;
  • [ElectionsException] is the project’s exception class;

12.4.1. The [ElectionsException] class

  

The [ElectionsException] class is the unhandled exception described in section 4.3.

12.4.2. The [AbstractEntity] class

  

The [AbstractEntity] class is as follows:


package elections.dao.entities;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.Version;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

@MappedSuperclass
public abstract class AbstractEntity implements Serializable {
    private static final long serialVersionUID = 1L;

    // properties
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ID")
    protected Long id;
    @Version
    @Column(name = "VERSION")
    protected Long version;

    // constructors
    public AbstractEntity() {

    }

    public AbstractEntity(Long id, Long version) {
        this.id = id;
        this.version = version;
    }

    // override [equals] and [hashCode]
    @Override
    public int hashCode() {
        return (id != null ? id.hashCode() : 0);
    }

    @Override
    public boolean equals(Object entity) {
        if (!(entity instanceof AbstractEntity)) {
            return false;
        }
        String class1 = this.getClass().getName();
        String class2 = entity.getClass().getName();
        if (!class2.equals(class1)) {
            return false;
        }
        AbstractEntity other = (AbstractEntity) entity;
        return id != null && this.id == other.id;
    }

    // JSON signature
    public String toString() {
        ObjectMapper mapper = new ObjectMapper();
        try {
            return mapper.writeValueAsString(this);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return null;
        }
    }

    // getters and setters
...
}

This is the class described in section 11.3.5.2, without the JSON filters. Here, the [CONF] and [LISTES] tables are not linked by a foreign key relationship. However, it is the existence of such a relationship with the [lazy loading] mode that necessitates JSON filters.

12.4.3. The [ElectionsConfig] class

  

The [ElectionsConfig] class is the JPA entity associated with the [CONF] table;


Task: Implement the [ElectionsConfig] class.


12.4.4. The [VoterList] class

  

The [VoterList] class is the JPA entity associated with the [LISTS] table;


Task: Implement the [VoterList] class.


12.5. The [Spring Data] layer

  

Task: Write the two [Spring Data] interfaces to manage the two tables [CONF] and [LISTES];


12.6. The [DAO] layer

  

The [IElectionsDao] interface of the [DAO] layer is as follows:


package dao.service;

import dao.entities.ElectionsConfig;
import dao.entities.VoterList;

public interface IElectionsDao {

    // election configuration
    public ElectionsConfig getElectionsConfig();

    // candidate lists
    public VoterList[] getVoterLists();

    // update candidate lists
    public void setCandidateLists(CandidateList[] candidateLists);
}

Task: Write the [ElectionsDaoJpa] implementation of the [IElectionsDao] interface.


12.7. Spring Project Configuration

  

The [DaoConfig] class configures the Spring project.


Task: Write the [DaoConfig] class


12.8. The [console] layer

  

The [Main] class is the following executable class:


package dao.console;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import dao.config.AppConfig;
import dao.entities.ElectionsConfig;
import dao.entities.VoterList;
import dao.service.IElectionsDao;

public class Main {

    // data source
    private static IElectionsDao dao;

    public static void main(String[] args) {
        // retrieve the Spring context
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
        // Retrieve the data source
        dao = ctx.getBean(IElectionsDao.class);
        // Contents of the two tables
        ElectionsConfig electionsConfig = dao.getElectionsConfig();
        VoterList[] lists = dao.getVoterLists();
        // display
        System.out.println(String.format("Number of seats to be filled: %d", electionsConfig.getNbSiegesAPourvoir()));
        System.out.println(String.format("Electoral threshold: %5.2f", electionsConfig.getSeuilElectoral()));
        System.out.println("Candidate lists----------------");
        for (ElectionList list : lists) {
            System.out.println(list);
        }
        // Close Spring context
        ctx.close();
    }

}

The [Test01] class is a JUnit test:


package dao.tests;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import dao.config.AppConfig;
import dao.entities.ElectionsConfig;
import dao.entities.VoterList;
import dao.service.IElectionsDao;

@SpringApplicationConfiguration(classes = AppConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class Test01 {

    // [DAO] layer
    @Autowired
    private IElectionsDao electionsDao;

    @Before
    public void init() {
        // clear the [LISTS] table
        // competing lists
        ElectoralLists[] lists = electionsDao.getElectoralLists();
        // Set votes and seats to 0 and set "eliminated" to false
        int votes = 0;
        int seats = 0;
        boolean eliminated = false;
        for (VoterList list : lists) {
            list.setVotes(votes);
            list.setSeats(seats);
            list.setEliminated(eliminated);
        }
        // We make this data persistent using the [DAO] layer
        electionsDao.setVoterLists(lists);
    }

    @Test
    public void testElections01() {
        System.out.println("testElections01-------------------------------------");
        // retrieve the election configuration
        ElectionsConfig electionsConfig = electionsDao.getElectionsConfig();
        int numSeatsToBeFilled = electionsConfig.getNumSeatsToBeFilled();
        double electoralThreshold = electionsConfig.getSeuilElectoral();
        Assert.assertEquals(6, nbSiegesAPourvoir);
        Assert.assertEquals(0.05, electoralThreshold, 1E-6);

        // competing lists
        ElectionLists[] lists = electionsDao.getElectionLists();
        // display read values
        System.out.println("Number of seats to be filled: " + nbSiegesAPourvoir);
        System.out.println("Electoral threshold: " + electoralThreshold);
        System.out.println("Lists in the race ---------------------");
        for (int i = 0; i < lists.length; i++) {
            System.out.println(lists[i]);
        }

        // assign votes and seats to the lists
        int votes = 0;
        int seats = 0;
        boolean eliminated = false;
        for (VoterList list : lists) {
            list.setVotes(votes);
            list.setSeats(seats);
            list.setEliminated(eliminated);
            votes += 10;
            seats += 1;
            eliminated = !eliminated;
        }

        // We make this data persistent using the [DAO] layer
        electionsDao.setVoterLists(lists);

        // we read the data again
        VoterList[] voterLists2 = electionsDao.getVoterLists();
        // we verify the retrieved data
        Assert.assertEquals(7, electoralLists2.length);
        votes = 0;
        seats = 0;
        eliminated = false;
        for (VoterList list : lists) {
            Assert.assertEquals(votes, list.getVotes());
            Assert.assertEquals(votes, list.getVotes());
            Assert.assertEquals(eliminated, list.isEliminated());
            votes += 10;
            seats += 1;
            eliminated = !eliminated;
        }
        System.out.println("Competing lists ---------------------");
        for (int i = 0; i < lists.length; i++) {
            System.out.println(lists[i]);
        }
    }
}

Task: Run the [console] and [JUnit] tests on your [DAO] layer.


12.9. Generating the project's Maven archive

Following the example in Section 11.3.12, generate the project's Maven archive.