Vamos aprender como configurar o Spring Data em conjunto com o Spring MVC, utilizar anotações para controle transacional, usar spectations no Spring Data para termos uma DSL de nossas consultas

Nesse projeto utilizaremos a versão 4.X.X do Spring, Maven para resolução de dependências, e o Tomcat como container

Não vamos entrar na criação do projeto Maven, suponho que já possua o conhecimento, e já tenha o projeto criado, caso não pode dar uma olhada aqui

As dependências que vamos utilizar são as seguintes

<dependency>
	<groupId>javax.servlet</groupId>
	<artifactId>javax.servlet-api</artifactId>
	<version>3.1.0</version>
	<scope>provided</scope>
</dependency>

<dependency>
	<groupId>javax.servlet</groupId>
	<artifactId>jstl</artifactId>
	<version>1.2</version>
</dependency>

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-webmvc</artifactId>
	<version>4.3.4.RELEASE</version>
</dependency>

<dependency>
	<groupId>com.fasterxml.jackson.core</groupId>
	<artifactId>jackson-databind</artifactId>
	<version>2.6.1</version>
</dependency>

<dependency>
	<groupId>org.springframework.data</groupId>
	<artifactId>spring-data-jpa</artifactId>
	<version>1.10.5.RELEASE</version>
</dependency>

<dependency>
	<groupId>com.h2database</groupId>
	<artifactId>h2</artifactId>
	<version>1.2.125</version>
</dependency>

<dependency>
	<groupId>org.hibernate</groupId>
	<artifactId>hibernate-entitymanager</artifactId>
	<version>5.2.2.Final</version>
</dependency>

<dependency>
	<groupId>log4j</groupId>
	<artifactId>log4j</artifactId>
	<version>1.2.17</version>
</dependency>

Usaremos o Jackson para serialização de nossa entidade em JSON, o hibernate como implementação do JPA, o banco de dados será em memória utilizando o H2

Vamos começar configurando nosso projeto, vamos criar a classe SpringConfig, no pacote "me.efraimgentil.springmvcanddata.config", essa classe irá contér as configurações básicas de beans do Spring

package me.efraimgentil.springmvcanddata.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = {"me.efraimgentil.springmvcanddata"  })
public class SpringConfig {
  
}

Nessa classe estamos apenas sinalizando ao Spring, por meio da anotação ComponentScan, para escanear a partir do pacote raiz em busca dos components do Spring

Em seguida temos a configuração do Spring MVC, para gerenciar nossas rotas

package me.efraimgentil.springmvcanddata.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "me.efraimgentil.springmvcanddata.controller" })
public class SpringMvcConfig extends WebMvcConfigurerAdapter {

}

Aqui, também não temos nada novo, estamos habilitando o MVC com a anotação EnableWebMvc, ela é basicamente o equivalente a configuração xml '', ou seja irá reconhecer nossas anotações Controller e RequestMapping. Estamos também dizendo onde procurar os nossos controllers com a anotação ComponentScan

Vamos configurar agora nossa base de dados, controle transacional e os repositórios com Spring Data

package me.efraimgentil.springmvcanddata.config;

import java.util.Properties;

import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;

import org.hibernate.jpa.HibernatePersistenceProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "me.efraimgentil.springmvcanddata.repository"
, entityManagerFactoryRef="entityManagerFactory" 
, transactionManagerRef="transactionManager")
public class PersistenceConfig {

	@Bean
	public DataSource datasource() {
		DriverManagerDataSource ds = new DriverManagerDataSource();
		ds.setDriverClassName( "org.h2.Driver");
		ds.setUrl("jdbc:h2:mem:data;DB_CLOSE_DELAY=-1");
		ds.setUsername("sa");
		ds.setPassword("");
		return ds;
	}
	
	@Bean(name= "entityManagerFactory")
	public LocalContainerEntityManagerFactoryBean  entityManagerFactoryBean(DataSource ds ) {
		LocalContainerEntityManagerFactoryBean  emfb = new LocalContainerEntityManagerFactoryBean ();
		emfb.setDataSource(ds);
		emfb.setPackagesToScan("me.efraimgentil.springmvcanddata.domain");
		emfb.setPersistenceProviderClass(HibernatePersistenceProvider.class);
		Properties jpaProterties = new Properties();
		jpaProterties.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
		jpaProterties.put("hibernate.format_sql", true );
		jpaProterties.put("hibernate.show_sql", true);
		jpaProterties.put("hibernate.hbm2ddl.auto", "update");
		emfb.setJpaProperties(jpaProterties);
		return emfb;
	}
	
	@Bean(name="transactionManager")
	public JpaTransactionManager transactionmanager(EntityManagerFactory emf){
		JpaTransactionManager transactionManager = new JpaTransactionManager();
		transactionManager.setEntityManagerFactory( emf );
		return transactionManager;
	}
	
}

Essa configuração é mais extensa, primeiro temos a anotação @EnableTransactionManagement ela irá permitir que utilizemos a anotação @Transactional em nossos métodos para sinalizar que serão rodados dentro de uma transação, em seguida temos a anotação @EnableJpaRepositories, ela habilita os repositórios do Spring Data, especificamos na anotação em que pacotes esses repositórios residirão.

Temos o nosso datasource, estamos fazendo uma configuração simples utilizando o DriverManagerDataSource, não utilize essa configuração em produção, pois a cada chamada do getConnection será criada uma nova conexão, para simplificar o exemplo estamos utilizando essa implementação.

Temos também a configuração do nosso EntityManagerFactory, aqui apenas configuramos o datasource que será utilizado, o pacote onde nossas entidades estão residindo, o provider que será utilizado no caso HibernatePersistenceProvider, e configuramos o hibernate.

Por fim temos a configuração do nosso transactionManager, aqui utilizamos o JpaTransactionManager, e apontamos para o nosso EntityManagerFactory configurado anteriormente

Veja que na assinatura dos métodos apontam para o DataSource e o EntityManagerFactory, não se preocupe, o Spring vai tratar da injeção desses objetos para você

Com isso vamos finalizar a configuração criando nosso WebInitializer como a seguir

package me.efraimgentil.springmvcanddata.config;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
	@Override
	protected Class<?>[] getRootConfigClasses() {
		return new Class[] { SpringConfig.class , PersistenceConfig.class  };
	}

	@Override
	protected Class<?>[] getServletConfigClasses() {
		return new Class[] { SpringMvcConfig.class  };
	}

	@Override
	protected String[] getServletMappings() {
		return new String[] { "/" };
	}
}

Com isso temos nosso projeto configurado e pronto para rodar, agora vamos criar nossa entidade User e nosso repositório UserRepository como a seguir:

package me.efraimgentil.springmvcanddata.domain;

// imports


@Entity
@Table(name="user")
public class User implements Serializable {
	
	private static final long serialVersionUID = -5613292949395788401L;

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private Integer id;
	
	private String name;
	
	@JsonIgnore
	private char[] password;
	
	private String role;
	
	private Date createdAt;

	public User() {	}
	
	public User(String name, char[] password) {
		super();
		this.name = name;
		this.password = password;
		this.createdAt = new Date();
	}
	
	public User(String name, char[] password , String role ) {
		this( name , password );
		this.role = role;
	}

	public User(String name, char[] password , String role , boolean active ) {
		this( name , password , role);
		this.active = active;
	}

	/* gets sets */
}
package me.efraimgentil.springmvcanddata.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

import me.efraimgentil.springmvcanddata.domain.User;

public interface UserRepository extends JpaRepository<User, Integer>, JpaSpecificationExecutor<User>  {

}

Não precisamos anotar nosso repositório com nenhuma anotação, já marcamos em nossas configurações qual package escanear, o Spring vai verificar a interface e reconhecer que se trata de um repositório, já que a interfece está extendẽndo de JpaRepository

Ok agora vamos criar nosso controller, que irá servir nossa entidade como JSON, veja a seguir

package me.efraimgentil.springmvcanddata.controller;

import java.util.List;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import me.efraimgentil.springmvcanddata.domain.User;
import me.efraimgentil.springmvcanddata.repository.UserRepository;

import static org.springframework.data.jpa.domain.Specifications.*;

@Controller
@RestController
public class UserController {
	
	@Autowired UserRepository repository;
	
	@RequestMapping(value = { "/" })
	public List<User> allUsers(){
		return repository.findAll();
	}
	
	@RequestMapping(value = { "/{id}" })
	public User user( @PathVariable("id") Integer id ){
		return repository.findOne(id);
	}
	
	@RequestMapping(value = { "/active" })
	public List<User> allActiveUsers( ){
		return repository.findAll( 
				where( (root, query, cb) -> { return cb.equal( root.get("active"), Boolean.TRUE ); } ) );
	}
	
	@RequestMapping(value = { "/with-role/{role}" })
	public List<User> allUsersWithRole( final @PathVariable("role") String role ){
		return repository.findAll( 
				where( (root, query, cb) -> { return cb.equal( root.get("role"), role ); } ) );
	}
	
	@RequestMapping(value = { "/active/with-role/{role}" })
	public List<User> allActiveUsersWithRole( final @PathVariable("role") String role ){
		return repository.findAll( 
				where( (Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) 
						-> { return cb.equal( root.get("active"), Boolean.TRUE ); } )
				 .and( (root, query, cb) -> { return cb.equal( root.get("role"), role ); }  )
				);
	}
	
}

Nos injetamos a interface repository, que irá tratar nossas requisições a base de dados, não se preocupe com a implementação da interface, o Spring Data vai cuidar disso para você, e fornecer alguns métodos padrões necessários para toda aplicação, como save, delete, find etc...

Note o método allActiveUsers no controller, nele estamos fazendo uma chamada ao método findAll do repositório, e passando como parâmetro a chamada where(...), que recebe como parâmetro um Specification, no nosso caso estamos passando um lambda expression e estamos deixando o Java cuidar do resto, o retorno da nossa expressão lambda DEVE retornar uma Prediction ou null ( null simplesmente será ignorado )

O método where esta vindo de um import static org.springframework.data.jpa.domain.Specifications.*;

Perceba que é bem prático utilizar as Specifications, porém como pode ser visto no método allActiveUsersWithRole, com mais critérios para consulta, fica mais complexo ler e entender o que está acontecendo, por isso vamos criar uma classe chamada UserSpecs como a seguir

package me.efraimgentil.springmvcanddata.domain.specs;

import org.springframework.data.jpa.domain.Specification;

import me.efraimgentil.springmvcanddata.domain.User;

public class UserSpecs {
	
	public static Specification<User> active(){
		return (root , query , cb) -> {
			return cb.equal( root.get("active"), Boolean.TRUE );
		};
	}
	
	public static Specification<User> withRole(final String role){
		return (root , query , cb) -> {
			return cb.equal( cb.upper( root.get("active") ), role.toUpperCase() );
		};
	}
	
}

Com isso podemos usar esses métodos com nomes mais intuitivos, e até encadear suas chamadas, criando assim uma DSL própria do nosso sistema, claro que aqui estamos fazendo um exemplo simples, mas é possível trabalhar com coisas bem mais complexas como sub-queries. Veja a seguir como fica o nosso controller

/* imports */

import static org.springframework.data.jpa.domain.Specifications.*;
import static me.efraimgentil.springmvcanddata.domain.specs.UserSpecs.*;

@Controller
@RestController
public class UserController {
	
	@Autowired UserRepository repository;
	
	@RequestMapping(value = { "/" })
	public List<User> allUsers(){
		return repository.findAll();
	}
	
	@RequestMapping(value = { "/{id}" })
	public User user( @PathVariable("id") Integer id ){
		return repository.findOne(id);
	}
	
	@RequestMapping(value = { "/active" })
	public List<User> allActiveUsers( ){
		return repository.findAll( active() );
	}
	
	@RequestMapping(value = { "/with-role/{role}" })
	public List<User> allUsersWithRole( @PathVariable("role") String role ){
		return repository.findAll( withRole( role ) );
	}
	
	@RequestMapping(value = { "/active/with-role/{role}" })
	public List<User> allActiveUsersWithRole( final @PathVariable("role") String role ){
		return repository.findAll( where( active() ).and( withRole(role) ) );
	}
	
}

Veja que temos uma API bem mais fluida, e facilita bastante a leitura do que estamos buscando, na tradução literal seria como "busque tudo onde ativo e com perfil especificado"

Agora basta fazer o deploy no tomcat e começar a experimentar, o UserController está mapeado para a raiz do projeto então provavelmente você vai acessar como na url a seguir "http://localhost:8080/springmvcanddata/". Faça seus próprios testes, crie uma entidade ou tente pegar uma implementação do trabalho e mudar para Spring Data, vai ser divertido!

Você pode encontrar os fontes do projeto aqui