Guice-repository simple example

If you like Spring DATA-JPA’s repository pattern, but you want to use it in a guice project,
there is a library for that called guice-repository. The only drawback of using this library is that it pulls almost the entire spring framework as a dependency since it uses it’s classes.

This simple example project has:

  • Dependency Injection through Guice(@Inject),
  • JPA Repositories(Spring’s JpaRepository interface),
  • Annotated transactional methods (Spring’s @Transactional),
  • Runnable as using in-memory db.

I used in-memory database for this example to simplify, so no need for any database setup (but you can change it to any other database in the persistence.xml). This is a standalone application to minimize all dependencies for this example
NOTE: You can not use guice-persist and guice-repository in the same project, this example is about how to use guice-repository.

The project structure:

structure

We go through these files one by one:

1. Maven config

We will first look into our pom.xml file for all the required dependencies.

pom.xml

<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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>hu.daniel.hari.learn</groupId>
    <artifactId>GuiceRepositories</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <build>
    </build>

    <dependencies>
        <!-- TEST -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
            <scope>test</scope>
        </dependency>

        <!-- GUICE for dependency management -->
        <dependency>
            <groupId>com.google.inject</groupId>
            <artifactId>guice</artifactId>
            <version>3.0</version>
        </dependency>

        <!-- Guice JPA Repositories -->
        <dependency>
            <groupId>com.google.code.guice-repository</groupId>
            <artifactId>guice-repository</artifactId>
            <version>2.1.0</version>
        </dependency>

        <!-- JPA provider -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>4.1.1.Final</version>
        </dependency>

        <!-- IN MEMORY JDBC -->
        <dependency>
            <groupId>org.hsqldb</groupId>
            <artifactId>hsqldb</artifactId>
            <version>2.2.8</version>
        </dependency>

    </dependencies>

</project>

1. JPA config

We define a persistence unit as in-memory database and Hibernate JPA provider.

persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">

    <persistence-unit name="testDB" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>

        <properties>
            <property name="hibernate.connection.driver_class" value="org.hsqldb.jdbcDriver"/>
            <property name="hibernate.connection.url" value="jdbc:hsqldb:mem:testDB"/>

            <property name="hibernate.show_sql" value="true" />
            <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect" />
            <property name="hibernate.hbm2ddl.auto" value="update" />

        </properties>
    </persistence-unit>
</persistence>

2. Model

Let’s define an entity.

Product.java

package hu.daniel.hari.learn.guice.model;

import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class Product {

    @Id
    private Long id;
    private String name;

    public Product() {
    }
    public Product(Long id, String name) {
        super();
        this.id = id;
        this.name = name;
    }

    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Product [id=" + id + ", name=" + name + "]";
    }

}

3. Repository interface (the focus of this tutorial)

Repository is the data access layer in this project instead of DAO Classes. It is an interface, so no any implementation needed, only method names and annotations.  We inherit JpaRepository that has many CRUD and other JPA operations defined already, that we can use without defining them, but we can define more specific operations like below.

  • We haven’t need to define findAll(), because that is inherited.
  • We defined a query by fluent keywords method name : findByNameContainingIgnoreCase(). spring-data-jpa translates this to a query automatically.
  • We defined a query by @Query annotation.

(For supported keywords check this link)

ProductRepository.java

package hu.daniel.hari.learn.guice.repository;

import java.util.List;

import hu.daniel.hari.learn.guice.model.Product;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

/** 
 * Guice-repository uses Spring Spring DATA-JPA's JpaRepository interface, so you can
 * use it exactly the same way as Spring DATA-JPA. 
 */
public interface ProductRepository extends JpaRepository<Product, Long> {

    /** You can define JPA queries **/
    @Query("select p from Product p where p.name = :name")
    public List<Product> findByNameIs(@Param("name") String name);

    /** No need to define @Query here, Spring DATA-JPA supports 
     *  resolution of keywords inside method names. **/
    public List<Product> findByNameContainingIgnoreCase(String searchString);

}

4. Services

Let’s define a service.
Note that repository is injected in service.

ProductService.java

package hu.daniel.hari.learn.guice.service;

import hu.daniel.hari.learn.guice.model.Product;
import hu.daniel.hari.learn.guice.repository.ProductRepository;

import java.util.Collection;
import java.util.List;

import javax.inject.Inject;

import org.springframework.transaction.annotation.Transactional;

public class ProductService {

    private ProductRepository productRepository;

    @Inject
    public ProductService(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }

    public Product get(long id) {
        return productRepository.findOne(id);
    }

    public List<Product> listAll() {
        return productRepository.findAll();
    }

    public List<Product> filter(String subString) {
        return productRepository.findByNameContainingIgnoreCase(subString);
    }

    /** @throws IllegalStateException if the entity exists. */
    @Transactional
    public void add(Product product) {
        boolean exists = productRepository.exists(product.getId());
        if(exists)
            throw new IllegalStateException("Product with id:" + product.getId() + " already exists");
        productRepository.save(product);
    }

    /** @throws IllegalStateException if one of the entities exists. */
    @Transactional
    public void addAll(Collection<Product> products) {
        for (Product product : products) {
            add(product);
        }
    }

}

Please note that @Transactional annotations used in service layer’s write operations.

5. Guice config

Let’s configure the guice module.

GuiceModule.java

package hu.daniel.hari.learn.guice;

import com.google.code.guice.repository.configuration.ScanningJpaRepositoryModule;
import com.google.inject.AbstractModule;

public class GuiceModule extends AbstractModule {

    private String persistenceUnitName;
    private String repositoriesBasePackageName;

    public GuiceModule(String persistenceUnitName, String repositoriesBasePackageName) {
        this.persistenceUnitName = persistenceUnitName;
        this.repositoriesBasePackageName = repositoriesBasePackageName;
    }

    @Override
    protected void configure() {
        //Repository classes auto-scanned by package name
        install(new ScanningJpaRepositoryModule(repositoriesBasePackageName, persistenceUnitName));
    }

}

6. Tester

Define a tester class for our module.

ProductITTest.java

package hu.daniel.hari.learn.guice;

import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import hu.daniel.hari.learn.guice.model.Product;
import hu.daniel.hari.learn.guice.repository.ProductRepository;
import hu.daniel.hari.learn.guice.service.ProductService;

import java.util.Arrays;
import java.util.List;

import org.junit.Before;
import org.junit.Test;

import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Stage;

public class ProductServiceITTest {
    private static final String TEST_PERSISTENCE_UNIT_NAME = "testDB";
    private static final String REPOSITORIES_BASE_PACKAGE_NAME = ProductRepository.class.getPackage().getName();

    private ProductService productService;
    private ProductRepository productRepository;

    public ProductServiceITTest() {
        Injector injector = createInjector();
        productService = injector.getInstance(ProductService.class);
        productRepository = injector.getInstance(ProductRepository.class);
    }

    private static Injector createInjector() {
        return Guice.createInjector(Stage.DEVELOPMENT,
                new GuiceModule(TEST_PERSISTENCE_UNIT_NAME, REPOSITORIES_BASE_PACKAGE_NAME));
    }

    @Before
    public void clear() {
        productRepository.deleteAll();
    }

    @Test
    public void testAdd() {
        productService.add(new Product(1l, "Ligth bulb"));
        productService.add(new Product(2l, "Dijon Mustarde"));

        assertNotNull(productService.get(1l));
        assertNotNull(productService.get(2l));
        assertNull(productService.get(3l));

    }

    @Test
    public void testFilter() {
        productService.add(new Product(1l, "Apple"));
        productService.add(new Product(2l, "Strawberry"));
        productService.add(new Product(3l, "Blue Berry"));
        productService.add(new Product(4l, "Lemon"));

        List<Product> filteredProducts = productService.filter("berry");
        assertTrue(filteredProducts.size() == 2);
        assertTrue(filteredProducts.get(0).getId() == 2l);
        assertTrue(filteredProducts.get(1).getId() == 3l);
    }

    @Test
    public void testAddAll() {
        productService.addAll(Arrays.asList(
                new Product(1l, "Book"), 
                new Product(2l, "Towel"), 
                new Product(3l, "Chair")
                ));

        assertNotNull(productService.get(1l));
        assertNotNull(productService.get(2l));
        assertNotNull(productService.get(3l));

    }

    @Test
    public void testAddAllRollback() {
        try {
            productService.addAll(Arrays.asList(
                    new Product(1l, "Bear"), 
                    new Product(2l, "Cat"), 
                    new Product(1l, "Tiger")
                    ));
            fail("Expected exception for id duplication");
        } catch (RuntimeException e) {
            //Expected rollback done
            assertNull(productService.get(1l));
            assertNull(productService.get(2l));
        }
    }

}

We use an in-memory db for testing.

  • Please note, that this is an integration test, since we don’t want to test classes each by each now, rather we want to test the module as it is.
  • ProductRepository is needed to be accessible from test class only because we have to clear the entities before each test methods to provide independence. Parallel running of test methods is not possible for this test.
  • We also test a rollback of a transactional service method call.

7. Run the tester

junit

References:

https://code.google.com/p/guice-repository

You can download the final project from below link and play around with it to learn more.

Written by Dániel Hári

Dániel Hári is the founder of log4jtester.com, cleancodejava.com. Who is an enthusiastic Java developer who enhances his knowledge by continously searching for best practices and modern technologies. Engaged to clean coding, and honors the engineering profession as releasing quality work.