Paging with Spring Data

This blog shows by an example on how to use paging support in Spring Data. Let start with following repository without paging

 

public interface ProductRepository extends JpaRepository<Product, Long> {

 @Query("SELECT p FROM product p WHERE p.category= :category")
 List<Product> findByCategory(@Param("category") String category);

}

 

The above query will return all products with matching category. To change it to only return products required to fill in a single page,  the method is updated to:

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
...
public interface ProductRepository extends JpaRepository<Product, Long> {

 @Query("SELECT p FROM product p WHERE p.category= :category")
 Page<Product> findByCategory(@Param("category") String category, 
     Pageable pageable);

}

Note:

  1. The method now returns an object with class Page instead of List
  2. A new argument with type Pageable is added

The Pageable argument allows you to specify a few things including which page to return, the number of items in each page and properties used to sort the results. For example, to return the 2nd page where size of each page is 10, the method can be called with Pageable below

Pageable pageable = new PageRequest(1, 10);

Class PageRequest is a concrete implementation of Pageable provided by Spring Data. Note page number is 0-indexed.

To define how the results should be ordered, a Sort object can be supplied as the 3rd argument of the PageRequest constructor. For example, to return list of products sorted from highest price and then by name in alphabetical order:

Sort sort = new Sort(new Order(Direction.DESC, "price"), 
                     new Order(Direction.ASC, "name"));

Pageable pageable = new PageRequest(1, 10, sort);

Finally, note that the Page object returned from the method contains not just the list of products but also other values useful for implementing pagination, e.g. total number of results, number of pages. See the javadoc here for more detail.

 

 

Advertisements

Configuring Multiple JPA Entity Managers In Spring Boot

This blog will demonstrate how to setup multiple entity managers in Spring to connect to different data sources. The solution here also supports Spring Data.

Update Maven Pom file

Include Spring Boot dependency for Spring Data:

 <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-data-jpa</artifactId>
 </dependency>

Disable DataSourceAutoConfiguration

Since we are setting up the data sources, disable the auto configuration in Spring Boot

@Configuration
@ComponentScan
@EnableAutoConfiguration (exclude = {  DataSourceAutoConfiguration.class })
public class Application {
   ...

Configure Primary Entity Manager

Below is the Java configuration for the primary entity manager

@Profile("!test")          // 1
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "au.com.myblog.dao", entityManagerFactoryRef = "entityManager", transactionManagerRef = "transactionManager")        // 2
public class PrimaryMysqlDBConfiguration {
     @Bean(name = "dataSource")      // 3
     @Primary
     @ConfigurationProperties(prefix = "primary.datasource.mysql")
     public DataSource mysqlDataSource() {
          return DataSourceBuilder.create().build();
     }
    @PersistenceContext(unitName = "primary")   // 4
     @Primary
     @Bean(name = "entityManager")
     public LocalContainerEntityManagerFactoryBean mySqlEntityManagerFactory(EntityManagerFactoryBuilder builder) {
          return builder.dataSource(mysqlDataSource()).persistenceUnit("primary").properties(jpaProperties())
                    .packages("au.com.myblog.domain").build();
      }
     private Map<String, Object> jpaProperties() {
          Map<String, Object> props = new HashMap<>();
          props.put("hibernate.ejb.naming_strategy", new SpringNamingStrategy());
          return props;
      }
}

Note:

  1. @Profile annotation to use this configuration only for non test profile. This allows me to set a different datasource, e.g. H2, when running tests.
  2. @EnableJpaRepositories is used for Spring Data. Note we are using the default transaction manager setup in Spring
  3. Bean for primary datasource. The @ConfigurationProperties annotation specifies the prefix of the properties to use by this datasource. For the example here, the application.properties file should include properties like below:
    # DB Connection
    primary.datasource.mysql.url=jdbc:log4jdbc:mysql://localhost/bpmn_service
    primary.datasource.mysql.username=root
    primary.datasource.mysql.password=root
    primary.datasource.mysql.driver-class-name=net.sf.log4jdbc.DriverSpy
  4. Persistence unit name should be setup in the EntityManager bean as shown here.

Configure Secondary Entity Manager

Configuration of the secondary entity manager is very similar to that of the primary.The only thing is we have to define a new transaction manager. Make sure a different prefix is set in the configuration properties of the data source. Also, a different persistent unit name should be used.

@Profile("!test")
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "au.com.myblog.dao", entityManagerFactoryRef = "secondaryMySqlEntityManager", transactionManagerRef = "secondaryTransactionManager")
public class SecondaryMysqlDBConfiguration {
      @Bean
      @ConfigurationProperties(prefix = "secondary.datasource.mysql")
       public DataSource mysqlDataSource() {
            return DataSourceBuilder.create().build();
       }
      @PersistenceContext(unitName = "secondary")
      @Bean(name = "secondaryMySqlEntityManager")
      public LocalContainerEntityManagerFactoryBean mySqlEntityManagerFactory(EntityManagerFactoryBuilder builder) {
           return  builder.dataSource(mysqlDataSource()).properties(jpaProperties()).persistenceUnit("secondary").packages("au.com.myblog.domain").build();
       }
      @Bean(name = "secondaryTransactionManager")
       public PlatformTransactionManager transactionManager(EntityManagerFactoryBuilder builder) {
             JpaTransactionManager tm = new JpaTransactionManager();
             tm.setEntityManagerFactory(mySqlEntityManagerFactory(builder).getObject());
             tm.setDataSource(mysqlDataSource());
             return tm;
       }
       private Map<String, Object> jpaProperties() {
             Map<String, Object> props = new HashMap<>();
            // naming strategy to put underscores instead of camel case
            // as per auto JPA Configuration
            props.put("hibernate.ejb.naming_strategy", new SpringNamingStrategy());
            props.put("hibernate.hbm2ddl.auto", "update");
            return props;
       }

Note the setup above assumes the primary and secondary data sources are not used together in a single transaction. Hence we can use 2 independent transaction managers.

Specify which Entity Manager to use

Finally, make sure that you specify the name of the secondary transaction in the spring @Transactional annotation:

@Transactional(value = "secondaryTransactionManager")

Also, add the @PersistenceContext annotation with the unit name as defined in the configuration when injecting entity managers;

 @PersistenceContext(unitName = "secondary")
 private EntityManager entityManager;

That’s it!

Setup and Configure Spring Data MongoDB

I recently start working on creating a new app and have to design a database schema for loading data (in csv files) into a database. I end up using MongoDB instead of relational databases like MySQL as the Document database schema of MongoDB fits well with my data. MongoDB provides its own client driver but as always, there is a Spring framework, in this case Spring Data MongoDB, to make things better. I will outline the steps I took to setup and configure a project for developing application using Spring Data MongoDB.

Download and Install MongoDB

Download MongoDB here and following the installation and setup instructions.

Getting Spring Data MongoDB

Add the following Maven dependency:

<!– Spring Data MongoDB –>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>1.3.2.RELEASE</version>
</dependency>

Update Spring config

You can use either annotation or xml files to configure Spring Data MongoDB. Below is my spring config xml file.

<?xml version=”1.0″ encoding=”UTF-8″?>
<beans xmlns=”http://www.springframework.org/schema/beans&#8221;
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance&#8221; xmlns:mongo=”http://www.springframework.org/schema/data/mongo&#8221;
xmlns:context=”http://www.springframework.org/schema/context&#8221;
xsi:schemaLocation=”http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/data/mongo
http://www.springframework.org/schema/data/mongo/spring-mongo-1.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd”&gt;

<beans profile=”local”>
<context:property-placeholder location=”classpath:mongo.properties”/>
</beans>

<beans>
<mongo:mongo host=”${mongo.host}” port=”${mongo.port}”>
</mongo:mongo>

<bean id=”mongoTemplate” class=”org.springframework.data.mongodb.core.MongoTemplate”>
<constructor-arg ref=”mongo” />
<constructor-arg value=”${mongo.dbname}” />
</bean>

<mongo:repositories base-package=”com.rlee.blog.repository” />

</beans>

My mongo.properties file for a local environment looks like below

mongo.host=localhost
mongo.port=27017
mongo.dbname=testdb

Implement the domain objects

You develop your POJOs with annotation in a similar way as in JPA/Hibernate beans. For example (from Getting Started guide of Spring Data MongoDB),

@Document(collection=”mycustomer”)
public class Customer {

@Id
private String id;

private String firstName;
private String lastName;

Note Spring Data does most work by convention. You don’t need to add the annotation @Document to the class – it will map the entity into a collection of name customer. Here I add the annotation to redirect it to a different collection called mycustomer. Similarly, Spring Data will look for a property id for use as id of the collection and will create one if it could not find one and there is no @Id annotation found in the class. The properties firstName and lastName are mapped into properties of same names in the database.

Implement the Repository

Again from Spring Data MongoDB’s own getting start guide

public interface CustomerRepository extends MongoRepository<Customer, String> {

public Customer findByFirstName(String firstName);
public List<Customer> findByLastName(String lastName);

}

I define an interface here by extending MongoRepository. Together with my setup in Spring config (below), Spring Data will create the implementation class to support standard Crud operations as well as simple query methods like the ones defined above.

<mongo:repositories base-package=”com.rlee.blog.repository” />

Note the method signature in the interface is important:

  • The first method findByFirstName will return the first customer found while the other findByLastName will return all customers with input lastName.
  • The method name is used to define the query to use. It is possible to search using more than one properties. For example, to search customer with given first and last name:

Customer findByFirstNameAndLastName(String firstName, String lastName);

  • You can also sort results by adding an input argument with class Sort. For example,

public List<Customer> findByLastName(String lastName, Sort sort);

Test

Now we are all set for running some tests.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(“classpath:app-config.xml”)
@ActiveProfiles(“local”)
public class CustomerRepositoryTest {

     @Autowired
protected CustomerRepository repository;

               @Before
public void before() {
repository.deleteAll();
// save a couple of customers
repository.save(new Customer(“Alice”, “Smith”));
repository.save(new Customer(“Bob”, “Smith”));
}

@Test
public void testGetByLastName() {
List<Customer> customers = repository.findByLastName(“Smith”);
assertEquals(2, customers.size());
}

}

One last thing. Turn on the debug mode in log4j, When running the above, you should see something like below

DEBUG: 17 Nov 2013 12:35:58,916 (main) org.springframework.data.mongodb.core.MongoDbUtils – Getting Mongo Database name=[testdb]
DEBUG: 17 Nov 2013 12:35:59,109 (main) org.springframework.data.mongodb.core.MongoTemplate – Remove using query: { } in collection: mycustomer.
DEBUG: 17 Nov 2013 12:35:59,116 (main) org.springframework.data.mongodb.core.MongoTemplate – Saving DBObject containing fields: [_class, _id, firstName, lastName]
DEBUG: 17 Nov 2013 12:35:59,117 (main) org.springframework.data.mongodb.core.MongoDbUtils – Getting Mongo Database name=[testdb]
DEBUG: 17 Nov 2013 12:35:59,119 (main) org.springframework.data.mongodb.core.MongoTemplate – Saving DBObject containing fields: [_class, _id, firstName, lastName]
DEBUG: 17 Nov 2013 12:35:59,119 (main) org.springframework.data.mongodb.core.MongoDbUtils – Getting Mongo Database name=[testdb]
DEBUG: 17 Nov 2013 12:35:59,138 (main) org.springframework.data.mongodb.repository.query.MongoQueryCreator – Created query Query: { “lastName” : “Smith”}, Fields: null, Sort: null
DEBUG: 17 Nov 2013 12:35:59,145 (main) org.springframework.data.mongodb.core.MongoTemplate – find using query: { “lastName” : “Smith”} fields: null for class: class com.madman.abs.domain.Customer in collection: mycustomer

That’s it.