domingo, 8 de octubre de 2017

Spring Data JPA using Hibernate and Java Configuration with Annotations

In this tutorial I’ll show you how to use Spring Data JPA to integrate a relational database (PostgreSQL in my example) into a Spring Boot application.
I’ll use these technologies and tools:
  • Spring Tool Suite (STS) 3.8.4.RELEASE
  • Java 8
  • Spring Boot 1.5.3.RELEASE
  • Maven 3.3.9
  • PostgreSQL 9.6.2

1. The Project Structure

The final folder structure of our project.
The folder structure of the Spring Data JPA project as seen in STS

2. Create a new Spring Boot project

If you’re using STS, you can easily create a starter project by either selecting File > New > Spring Starter Project from the main menu or right-clicking on the Package Explorer and select New > Spring Starter Project.
STS command to create a new Spring Starter Project
Dialog window to set parameters for the Spring Data JPA project
The list of dependencies of the Spring Data JPA project
In case you’re using another IDE like Eclipse, Netbeans or IntelliJ IDEA, you can create a new Maven project and add to the pom.xml file the dependencies listed in the next paragraph.

3. Maven Dependencies

spring-boot-starter-data-jpa is the fundamental dependency for our project, then we need the driver for whatever SQL database we have chosen, in this case postgresql.
  • pom.xml
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <groupId>com.thomasvitale</groupId>
  6. <artifactId>Application</artifactId>
  7. <version>0.0.1-SNAPSHOT</version>
  8. <packaging>jar</packaging>
  9. <name>Application</name>
  10. <description>Demo application for Spring Data JPA</description>
  11. <parent>
  12. <groupId>org.springframework.boot</groupId>
  13. <artifactId>spring-boot-starter-parent</artifactId>
  14. <version>1.5.3.RELEASE</version>
  15. <relativePath/> <!-- lookup parent from repository -->
  16. </parent>
  17. <properties>
  18. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  19. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  20. <java.version>1.8</java.version>
  21. </properties>
  22. <dependencies>
  23. <dependency>
  24. <groupId>org.springframework.boot</groupId>
  25. <artifactId>spring-boot-starter-data-jpa</artifactId>
  26. </dependency>
  27. <dependency>
  28. <groupId>org.postgresql</groupId>
  29. <artifactId>postgresql</artifactId>
  30. <scope>runtime</scope>
  31. </dependency>
  32. <dependency>
  33. <groupId>org.springframework.boot</groupId>
  34. <artifactId>spring-boot-starter-test</artifactId>
  35. <scope>test</scope>
  36. </dependency>
  37. </dependencies>
  38. <build>
  39. <plugins>
  40. <plugin>
  41. <groupId>org.springframework.boot</groupId>
  42. <artifactId>spring-boot-maven-plugin</artifactId>
  43. </plugin>
  44. </plugins>
  45. </build>
  46. </project>
<?xml version="1.0" encoding="UTF-8"?>
<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>com.thomasvitale</groupId>
  <artifactId>Application</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>Application</name>
  <description>Demo application for Spring Data JPA</description>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.3.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
  </properties>

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

    <dependency>
      <groupId>org.postgresql</groupId>
      <artifactId>postgresql</artifactId>
      <scope>runtime</scope>
    </dependency>
  
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

</project>
I’m using PostgreSQL, but this example works fine also with other relational databases. For example, if you want to use MySQL, all you need to do is replace the postgresql dependency with the mysql one.
  1. <dependency>
  2. <groupId>mysql</groupId>
  3. <artifactId>mysql-connector-java</artifactId>
  4. <scope>runtime</scope>
  5. </dependency>
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <scope>runtime</scope>
</dependency>

4. Spring Configuration using Java Annotations and Hibernate

Spring lets you use either Java configuration or XML configuration or a mix of the two. I’ll use a pure Java configuration. There are a few implementations of JPA, in this example Hibernate is used. Its properties are stored in a dedicated file.
  • JpaConfig.java
  1. package com.thomasvitale;
  2. import java.util.Properties;
  3. import javax.persistence.EntityManagerFactory;
  4. import javax.sql.DataSource;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.context.annotation.Bean;
  7. import org.springframework.context.annotation.Configuration;
  8. import org.springframework.context.annotation.PropertySource;
  9. import org.springframework.core.env.Environment;
  10. import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
  11. import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
  12. import org.springframework.jdbc.datasource.DriverManagerDataSource;
  13. import org.springframework.orm.jpa.JpaTransactionManager;
  14. import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
  15. import org.springframework.orm.jpa.vendor.Database;
  16. import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
  17. import org.springframework.transaction.PlatformTransactionManager;
  18. import org.springframework.transaction.annotation.EnableTransactionManagement;
  19. @Configuration
  20. @EnableTransactionManagement
  21. @EnableJpaRepositories(basePackages = "com.thomasvitale.repository")
  22. @PropertySource("classpath:jpa.properties")
  23. public class JpaConfig {
  24. @Autowired
  25. private Environment env;
  26. @Bean
  27. public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
  28. HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
  29. vendorAdapter.setDatabase(Database.POSTGRESQL);
  30. vendorAdapter.setGenerateDdl(true);
  31. LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
  32. em.setDataSource(dataSource());
  33. em.setPackagesToScan("com.thomasvitale.model");
  34. em.setJpaVendorAdapter(vendorAdapter);
  35. em.setJpaProperties(additionalProperties());
  36. return em;
  37. }
  38. @Bean
  39. public DataSource dataSource(){
  40. DriverManagerDataSource dataSource = new DriverManagerDataSource();
  41. dataSource.setDriverClassName(env.getProperty("hibernate.connection.driver_class"));
  42. dataSource.setUrl(env.getProperty("hibernate.connection.url"));
  43. dataSource.setUsername(env.getProperty("hibernate.connection.username"));
  44. dataSource.setPassword(env.getProperty("hibernate.connection.password"));
  45. return dataSource;
  46. }
  47. @Bean
  48. public PlatformTransactionManager transactionManager(EntityManagerFactory emf){
  49. JpaTransactionManager transactionManager = new JpaTransactionManager();
  50. transactionManager.setEntityManagerFactory(emf);
  51. return transactionManager;
  52. }
  53. @Bean
  54. public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
  55. return new PersistenceExceptionTranslationPostProcessor();
  56. }
  57. Properties additionalProperties() {
  58. Properties properties = new Properties();
  59. properties.setProperty("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));
  60. properties.setProperty("hibernate.dialect", env.getProperty("hibernate.dialect"));
  61. properties.setProperty("hibernate.current_session_context_class", env.getProperty("hibernate.current_session_context_class"));
  62. return properties;
  63. }
  64. }
package com.thomasvitale;

import java.util.Properties;

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
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.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement 
@EnableJpaRepositories(basePackages = "com.thomasvitale.repository")
@PropertySource("classpath:jpa.properties")
public class JpaConfig {
  
  @Autowired
  private Environment env;

  @Bean
  public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
    
    HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
    vendorAdapter.setDatabase(Database.POSTGRESQL);
    vendorAdapter.setGenerateDdl(true);
    
    LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
    em.setDataSource(dataSource());
    em.setPackagesToScan("com.thomasvitale.model");
    em.setJpaVendorAdapter(vendorAdapter);
    em.setJpaProperties(additionalProperties());
 
    return em;
  }
  
  @Bean
  public DataSource dataSource(){
    DriverManagerDataSource dataSource = new DriverManagerDataSource();
    dataSource.setDriverClassName(env.getProperty("hibernate.connection.driver_class"));
    dataSource.setUrl(env.getProperty("hibernate.connection.url"));
    dataSource.setUsername(env.getProperty("hibernate.connection.username"));
    dataSource.setPassword(env.getProperty("hibernate.connection.password"));
    return dataSource;
  }
  
  @Bean
  public PlatformTransactionManager transactionManager(EntityManagerFactory emf){
    JpaTransactionManager transactionManager = new JpaTransactionManager();
    transactionManager.setEntityManagerFactory(emf);
  
    return transactionManager;
  }
  
  @Bean
  public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
    return new PersistenceExceptionTranslationPostProcessor();
  }

  Properties additionalProperties() {
    Properties properties = new Properties();
    properties.setProperty("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));
    properties.setProperty("hibernate.dialect", env.getProperty("hibernate.dialect"));
    properties.setProperty("hibernate.current_session_context_class", env.getProperty("hibernate.current_session_context_class"));
    return properties;
  }

}
  • jpa.properties
  1. hibernate.connection.username=db-user
  2. hibernate.connection.password=db-password
  3. hibernate.connection.url=jdbc:postgresql://localhost:5432/app
  4. hibernate.connection.driver_class=org.postgresql.Driver
  5. hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
  6. hibernate.current_session_context_class=thread
  7. hibernate.hbm2ddl.auto=validate
  8. hibernate.show_sql=false
  9. hibernate.format_sql=false
hibernate.connection.username=db-user
hibernate.connection.password=db-password
hibernate.connection.url=jdbc:postgresql://localhost:5432/app
hibernate.connection.driver_class=org.postgresql.Driver
hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
hibernate.current_session_context_class=thread
hibernate.hbm2ddl.auto=validate
hibernate.show_sql=false
hibernate.format_sql=false
If you’re using MySQL, pay attention to replace line 36 of JpaConfig.java with this one: vendorAdapter.setDatabase(Database.MYSQL); and change the relative values in the jpa.properties file:
  1. hibernate.connection.url=jdbc:mysql://localhost:3306/app
  2. hibernate.connection.driver_class=com.mysql.jdbc.Driver
  3. hibernate.dialect=org.hibernate.dialect.MySQLDialect
hibernate.connection.url=jdbc:mysql://localhost:3306/app
hibernate.connection.driver_class=com.mysql.jdbc.Driver
hibernate.dialect=org.hibernate.dialect.MySQLDialect

5. Person Model

For the Person entity, first of all I create a new table (persons) in my PostgreSQL database (called app).
  1. CREATE TABLE persons(
  2. id SERIAL,
  3. firstName VARCHAR(32),
  4. lastName VARCHAR(32),
  5. PRIMARY KEY(id)
  6. );
CREATE TABLE persons(
  id SERIAL,
  firstName VARCHAR(32),
  lastName VARCHAR(32),
  PRIMARY KEY(id)
);
Then I code the mapping object model. The @Table(name = "persons") annotation specifies in which table to save a Person object. The @GeneratedValue(strategy = GenerationType.IDENTITY) is quite general. In PostgreSQL I have defined the id attribute as SERIAL. In MySQL you probably want to use AUTO_INCREMENT. That’s just fine, it works with both of them.
  • Person.java
  1. package com.thomasvitale.model;
  2. import javax.persistence.Entity;
  3. import javax.persistence.GeneratedValue;
  4. import javax.persistence.GenerationType;
  5. import javax.persistence.Id;
  6. import javax.persistence.Table;
  7. @Entity
  8. @Table(name = "persons")
  9. public class Person {
  10. @Id
  11. @GeneratedValue(strategy = GenerationType.IDENTITY)
  12. private Integer id;
  13. private String firstName;
  14. private String lastName;
  15. public Person() {
  16. }
  17. public Person(String firstName, String lastName) {
  18. this.firstName = firstName;
  19. this.lastName = lastName;
  20. }
  21. public Integer getId() {
  22. return id;
  23. }
  24. public void setId(Integer id) {
  25. this.id = id;
  26. }
  27. public String getFirstName() {
  28. return firstName;
  29. }
  30. public void setFirstName(String firstName) {
  31. this.firstName = firstName;
  32. }
  33. public String getLastName() {
  34. return lastName;
  35. }
  36. public void setLastName(String lastName) {
  37. this.lastName = lastName;
  38. }
  39. @Override
  40. public String toString() {
  41. return firstName + " " + lastName;
  42. }
  43. }
package com.thomasvitale.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "persons")
public class Person {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Integer id;
  
  private String firstName;
  private String lastName;

  public Person() {
  }

  public Person(String firstName, String lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  public Integer getId() {
    return id;
  }

  public void setId(Integer id) {
    this.id = id;
  }

  public String getFirstName() {
    return firstName;
  }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }

  @Override
  public String toString() {
    return firstName + " " + lastName;
  }
}

6. Person Repository

Our PersonRepository extends the JpaRepository interface, that provides us with a lot of operations out-of-the-box, including the standard CRUD operations. You can also add custom operations like findByFirstName and findByLastName without writing any implementation: Spring Data JPA creates them for you!
  • PersonRepository.java
  1. package com.thomasvitale.repository;
  2. import java.util.List;
  3. import org.springframework.data.jpa.repository.JpaRepository;
  4. import org.springframework.stereotype.Repository;
  5. import com.thomasvitale.model.Person;
  6. @Repository
  7. public interface PersonRepository extends JpaRepository<Person, Integer> {
  8. public Person findByFirstName(String firstName);
  9. public List<Person> findByLastName(String lastName);
  10. }
package com.thomasvitale.repository;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.thomasvitale.model.Person;

@Repository
public interface PersonRepository extends JpaRepository<Person, Integer> {
  
  public Person findByFirstName(String firstName);
  public List<Person> findByLastName(String lastName);
  
}

7. Demo

Let’s try some CRUD operations to test our application.
  1. package com.thomasvitale;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.boot.CommandLineRunner;
  4. import org.springframework.boot.SpringApplication;
  5. import org.springframework.boot.autoconfigure.SpringBootApplication;
  6. import com.thomasvitale.model.Person;
  7. import com.thomasvitale.repository.PersonRepository;
  8. @SpringBootApplication
  9. public class Application implements CommandLineRunner {
  10. @Autowired
  11. PersonRepository personRepository;
  12. public static void main(String[] args) {
  13. SpringApplication.run(Application.class, args);
  14. }
  15. @Override
  16. public void run(String... arg0) throws Exception {
  17. // Save three Person documents on PostgreSQL
  18. personRepository.save(new Person("Sheldon", "Cooper"));
  19. personRepository.save(new Person("Missy", "Cooper"));
  20. personRepository.save(new Person("Leonard", "Hofstadter"));
  21. // Get all the people
  22. System.out.println(">>> All the people in the database:");
  23. personRepository.findAll().forEach(System.out::println);
  24. // Get all the people with a specific last name
  25. System.out.println(">>> All people with last name = 'Cooper'");
  26. personRepository.findByLastName("Cooper").forEach(System.out::println);
  27. // Update an individual person
  28. Person person = personRepository.findByFirstName("Sheldon");
  29. person.setFirstName("Shelly");
  30. personRepository.save(person);
  31. // Delete all
  32. personRepository.deleteAll();
  33. }
  34. }
package com.thomasvitale;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import com.thomasvitale.model.Person;
import com.thomasvitale.repository.PersonRepository;

@SpringBootApplication
public class Application implements CommandLineRunner {
  
  @Autowired
  PersonRepository personRepository;

  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }

  @Override
  public void run(String... arg0) throws Exception {
    
      // Save three Person documents on PostgreSQL
      personRepository.save(new Person("Sheldon", "Cooper"));
      personRepository.save(new Person("Missy", "Cooper"));
      personRepository.save(new Person("Leonard", "Hofstadter"));
      
      // Get all the people
      System.out.println(">>> All the people in the database:");
      personRepository.findAll().forEach(System.out::println);
      
      // Get all the people with a specific last name
      System.out.println(">>> All people with last name = 'Cooper'");
      personRepository.findByLastName("Cooper").forEach(System.out::println);
      
      // Update an individual person
      Person person = personRepository.findByFirstName("Sheldon");
      person.setFirstName("Shelly");
      personRepository.save(person);
      
      // Delete all 
      personRepository.deleteAll();
    
  }
}

Resources

0 comentarios:

Publicar un comentario