【Spring Boot】SpringBoot and database interaction: using Spring Data JPA

1. Databases and Java Applications

In the development of modern applications, data is a core part. In order to be able to persist, retrieve, update, and delete data, applications need to interact with the database.

1.1 Why database interaction is needed

  1. Data persistence : When you shut down the application or server, you still want the data to be preserved. The database provides a durable storage solution so that the data persists even after the application is closed.

  2. Data retrieval and analysis : The database provides powerful query capabilities, allowing applications to easily retrieve and analyze data.

  3. Multi-user concurrency : Database systems usually have built-in concurrency control mechanisms to support concurrent access to data by multiple users and ensure data integrity and consistency.

  4. Security : Through the database, you can control the permissions of data access and ensure data security.

  5. Data backup and recovery : Most databases have backup and recovery functions to ensure data security.

1.2 Traditional database interaction methods

The earliest way to interact with a database in a Java application is to use JDBC (Java Database Connectivity) . The following briefly describes the process of using JDBC to interact with the database:

  1. Establishing a connection : First, you need to use DriverManagera class to establish a connection to the database.

    Connection conn = DriverManager.getConnection("jdbc:url", "username", "password");
    
  2. Create statement : Use Connectionobject create Statementor PreparedStatementobject.

    PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE id = ?");
    
  3. Execute query : Use Statementor PreparedStatementobject to execute query and obtain ResultSet.

    stmt.setInt(1, 123);  // 设置参数
    ResultSet rs = stmt.executeQuery();
    
  4. Processing results : Traverse ResultSetand obtain query results.

    while(rs.next()) {
          
          
        String name = rs.getString("name");
        // ...
    }
    
  5. Close connection : After all operations are completed, close ResultSet, Statementand Connection.

    rs.close();
    stmt.close();
    conn.close();
    

Although JDBC provides the basic ability to interact with the database, it also has some problems, such as code duplication, manual exception handling, manual management of connection pools, etc. In order to solve these problems, developers began to look for more advanced solutions, such as ORM (Object-Relational Mapping) tools, the most famous of which is Hibernate. Spring Data JPA further simplifies database interaction operations. It provides a layer of abstraction on JPA, allowing developers to use less code to complete database operations.

2. What is JPA

Before discussing Spring Data JPA, it is very important to understand the concept of JPA and its place in the Java world. JPA, the Java Persistence API, is part of the Java EE standard. It provides Java developers with an object-relational mapping solution.

2.1 Definition of JPA

Java Persistence API (JPA) is a specification on the Java platform that describes how an object-relational mapping (ORM) system manages data in a relational database. In short, JPA allows you to map database tables to Java classes, and database records to Java objects.

For example, if you have a database table named "users", you can create a Java class named "User" and use JPA annotations to define the mapping between the two.

@Entity
@Table(name="users")
public class User {
    
    
    
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

    private String name;

    // getters, setters, and other methods...
}

2.2 Advantages of JPA

Using JPA for database interaction has the following advantages:

  1. Reduced boilerplate code : Compared with traditional JDBC, JPA reduces a lot of duplication and boilerplate code. Developers no longer need to write SQL statements for creating, updating, deleting, and querying database records.

  2. Object-oriented : JPA allows you to handle database operations in an object-oriented manner instead of handling SQL queries and result sets.

  3. Database independence : Because JPA provides an abstraction layer, applications can more easily switch to another database with little or no code changes.

  4. Flexible query capabilities : JPA’s query language (JPQL) allows the creation of complex database queries without relying on the specific SQL dialect of the underlying database.

  5. Caching : Many JPA implementations (such as Hibernate) provide first- and second-level caching, which helps improve the performance of the application because it reduces the number of actual queries to the database.

  6. Annotation-driven : Through annotations, developers can specify ORM configuration on Java classes and methods, making the code more concise and easier to read.

In short, JPA provides Java developers with a simpler and more intuitive way to interact with relational databases.

3. Introduction to Spring Data JPA

Spring Data JPA is a sub-project of Spring Data, designed to simplify the implementation of JPA-based data access layer (DAO). What does Spring Data JPA do? It makes writing a fully implemented JPA data access layer very simple.

3.1 Features of Spring Data JPA

  1. Repository automatic implementation : Developers only need to define an interface that extends the Repository interface provided by Spring Data JPA, and Spring will automatically provide the implementation of the interface.

    public interface UserRepository extends JpaRepository<User, Long> {
          
          
        // 自动实现了常见的CRUD操作
    }
    
  2. Query method auto-generation : Spring Data JPA allows you to define queries simply by defining method signatures in the Repository interface without providing an implementation. For example:

    public interface UserRepository extends JpaRepository<User, Long> {
          
          
        List<User> findByName(String name);
    }
    

    The above interface will automatically generate a query to find users by name.

  3. Annotation queries : For more complex queries, developers can use @Queryannotations to specify JPQL queries.

  4. Audit function : Able to automatically fill in common fields such as creation time and modification time.

  5. Paging and sorting : Spring Data JPA supports paging and sorting without writing a lot of additional code.

  6. Transparent transaction management : With Spring's transaction management function, data access becomes simpler and more consistent.

3.2 How to simplify database operations

The main purpose of Spring Data JPA is to simplify data access code. Here's how it does it:

  1. Reduce boilerplate code : Traditional data access layers contain a lot of duplicate code. For example, opening and closing database connections, exception handling, etc. With Spring Data JPA, this boilerplate code is almost completely eliminated.

  2. Simplified query creation : Just define interface methods, and findByLastnameAndFirstnameSpring Data JPA will automatically handle query creation and execution for you.

  3. Integrated into the Spring ecosystem : As part of the Spring ecosystem, Spring Data JPA is perfectly integrated with other Spring technologies such as transaction management, DI, etc.

  4. Powerful Repository and DAO support : Developers can directly use the provided JpaRepository and CrudRepository interfaces, or define their own interfaces as needed.

  5. Custom queries : For non-standard queries, developers can use @Queryannotations to define their own JPQL or native SQL queries.

Through the above functions, Spring Data JPA effectively simplifies the interaction between developers and databases, making data access easier and faster.

4. Integrate Spring Data JPA in SpringBoot

Using Spring Boot, integrating Spring Data JPA becomes extremely simple. Spring Boot provides automatic configuration and dependency management to help you easily start using JPA and interact with databases.

4.1 Add dependencies

To use Spring Data JPA in a Spring Boot project, you first need to add the relevant starting dependencies. Here is an example from Maven:

<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>

The above code indicates that we want to use Spring Data JPA, and we have selected PostgreSQL as the database. You can replace the dependencies with other databases according to actual needs.

4.2 Configure data source

In Spring Boot, most configurations can be done through application.propertiesor application.ymlfiles. The same is true for the configuration of Spring Data JPA and data sources.

The following is a simple configuration example using a PostgreSQL database application.properties:

spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
spring.datasource.username=dbuser
spring.datasource.password=dbpass
spring.datasource.driver-class-name=org.postgresql.Driver

spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect

A brief explanation:

  • spring.datasource.url : Specifies the URL of the database.
  • spring.datasource.username and spring.datasource.password : username and password for database connection.
  • spring.datasource.driver-class-name : JDBC driver class name.
  • spring.jpa.hibernate.ddl-auto : Hibernate's DDL mode, updatewhich means that if the database table does not exist, it will be created and if it exists, it will be updated. In a production environment, this value will typically be set to noneor validate.
  • spring.jpa.properties.hibernate.dialect : Specify the database dialect to ensure that Hibernate can generate optimized queries for a specific database.

This is just an example of basic configuration. Spring Boot provides a large number of configuration items for you to adjust according to your needs. For example, connection pool settings, JPA properties, etc.

5. Creation and configuration of entities

In database interaction, entities play a central role. They exist as classes in Java and are mapped to tables in the database through annotations. Spring Data JPA and JPA provide rich annotations to describe this mapping.

5.1 Create a Java entity class

Entity classes are usually ordinary Java classes, but they have specific annotations to describe the relationship to the database. Here, we will create a simple Bookentity as an example.

package com.example.demo.model;

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

@Entity
public class Book {
    
    

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String title;
    private String author;

    // 省略 getters 和 setters
}

A brief explanation:

  • @Entity: declares that this is a JPA entity.
  • @Id: Specify the attribute as the primary key of the table.
  • @GeneratedValue: Specify the primary key generation strategy. Here, we chose the database auto-increment strategy.

5.2 Use annotations to configure entity properties

In addition to the basic @Entityand @Idannotations, there are many other annotations that can be used to configure the properties of entities.

For example:

package com.example.demo.model;

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

@Entity
@Table(name = "books")
public class Book {
    
    

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "book_title", nullable = false, length = 200)
    private String title;

    @Column(name = "book_author", length = 100)
    private String author;

    // 省略 getters 和 setters
}

Here is a brief description of the new annotations:

  • @Table: Specifies which database table the entity is mapped to. If you do not use this annotation, the default is to use the class name as the table name.
  • @Column: Provides a detailed definition of the column. For example, you can define the name of the column, whether it can be null, the maximum length, etc.

This is only part of the annotations provided by JPA. There are many other annotations that can be used to define relationship mapping (such as one-to-many, many-to-many, etc.), cascade operations and other advanced functions.

6. Create Repository interface

Repository is the core part of Spring Data JPA, which represents the logic of data access. By simply declaring an interface, Spring Data JPA allows you to define operations on your data without writing implementation code. This is achieved by automatically generating the implementation at runtime.

6.1 What is Repository

In Spring Data JPA, Repository is an interface that represents a data repository. It is responsible for data access logic. You only need to declare the method signature and do not need to write a specific implementation.

Spring Data JPA provides some predefined interfaces, such as CrudRepositoryor JpaRepository, which contain many common data access operations.

For example, let's say you have an Bookentity named. You can create an BookRepositoryinterface:

package com.example.demo.repository;

import com.example.demo.model.Book;
import org.springframework.data.repository.CrudRepository;

public interface BookRepository extends CrudRepository<Book, Long> {
    
    
}

Through inheritance CrudRepository, BookRepositorycommon CRUD operations are automatically available.

6.2 Using the CRUD method provided by Spring Data JPA

When your Repository interface inherits CrudRepositoryor JpaRepository, you can inject this interface directly into your service or controller and start using the methods it provides.

Here is BookRepositorya simple example of how to perform basic CRUD operations using:

  1. Save a new Book entity:
Book book = new Book();
book.setTitle("Spring Boot Guide");
book.setAuthor("John Doe");
bookRepository.save(book);
  1. Find all Books:
Iterable<Book> allBooks = bookRepository.findAll();
  1. Find a Book with a specific ID:
Optional<Book> book = bookRepository.findById(1L);
  1. Delete a Book:
bookRepository.deleteById(1L);

These methods are CrudRepositorypredefined by the interface. Spring Data JPA also allows you to define your own query methods, just name the methods according to specific naming conventions.

For example, to find all books written by a certain author:

List<Book> booksByAuthor = bookRepository.findByAuthor("John Doe");

In this case, you don't need to provide an implementation of the method. Spring Data JPA will generate the correct query for you at runtime.

7. Custom query method

@QuerySpring Data JPA not only provides basic CRUD operations, but also allows developers to define their own query methods through simple method naming or annotations , which greatly simplifies the development of the data access layer.

7.1 Query based on method naming rules

Spring Data JPA allows you to create queries in a very intuitive way: just name your Repository methods according to a specific naming convention, and the framework will generate the corresponding queries for you.

Here are a few examples of queries based on naming rules:

  1. Find books by author :
List<Book> findByAuthor(String author);

This generates a SQL query based on the author field.

  1. Find books by title and author :
List<Book> findByTitleAndAuthor(String title, String author);

This generates a compound query based on the title and author fields.

  1. Find books whose titles contain a keyword :
List<Book> findByTitleContaining(String keyword);

This method searches for all books that contain the specified keyword in the title field.

This is just the tip of the iceberg of query capabilities based on method naming. You can define more complex queries based on actual needs.

7.2 Use @Query annotation to customize the query

Although querying based on method names is very useful, sometimes you may need more flexibility. At this point, you can use @Queryannotations to write custom queries.

  1. Create a custom query using JPQL :
@Query("SELECT b FROM Book b WHERE b.title LIKE %:keyword%")
List<Book> searchByTitleKeyword(@Param("keyword") String keyword);

Here, we use JPQL (Java Persistence Query Language) to define the query. :keywordis a named parameter, which is specified by an annotation in the method parameter @Param.

  1. Use native SQL query :

If you want to use native SQL instead of JPQL, you can do this:

@Query(value = "SELECT * FROM books WHERE title LIKE %:keyword%", nativeQuery = true)
List<Book> searchByTitleUsingNativeQuery(@Param("keyword") String keyword);

Use nativeQuery = truespecifies that this is a native SQL query.

Note: Although native queries provide more flexibility, they are not database agnostic and may cause database migration issues. Therefore, unless there is a specific reason, it is recommended to use JPQL whenever possible.

In short, whether it is query based on method naming or using @Queryannotations, Spring Data JPA provides powerful tools to make database interaction simpler and more efficient.

8. Transaction management

Transaction management is a key technology to ensure the integrity and consistency of database operations. In daily application development, transaction management can ensure that when performing a series of operations, either all operations are completed successfully or none are completed, and there will be no situation where some operations succeed and some operations fail.

8.1 Why transactions are needed

  1. Data consistency : Suppose you are developing a banking application and a customer transfers money from one account to another. This operation involves two steps: debiting money from one account and sending money to another account. If only the first step is completed and the second step fails for some reason, this will lead to data inconsistency. Transactions ensure that both operations succeed or fail.

  2. Isolation : In a multi-user environment, transactions can ensure that the operations of each user do not interfere with each other, that is, each transaction feels like running in an independent environment.

  3. Durability : Once a transaction is completed, the changes it makes to the database are permanent and will not be lost even if the system crashes.

  4. Atomicity : This is the fundamental property of transactions that ensures that all operations within a transaction complete completely, or not complete at all.

8.2 Using @Transactional annotation in SpringBoot

SpringBoot provides us with a very simple transaction management tool, the most commonly used one is @Transactionalannotation.

  1. Basic usage :

    Just add annotations to the method @Transactionaland the method will be executed in a transaction. If an exception is thrown during method execution, all database operations will be rolled back.

    @Service
    public class BankService {
          
          
        @Autowired
        private AccountRepository accountRepository;
    
        @Transactional
        public void transferMoney(Long fromAccountId, Long toAccountId, BigDecimal amount) {
          
          
            Account fromAccount = accountRepository.findById(fromAccountId).orElseThrow();
            Account toAccount = accountRepository.findById(toAccountId).orElseThrow();
    
            fromAccount.setBalance(fromAccount.getBalance().subtract(amount));
            toAccount.setBalance(toAccount.getBalance().add(amount));
    
            accountRepository.save(fromAccount);
            accountRepository.save(toAccount);
        }
    }
    

    In the above example, if any exception occurs during the transfer process, such as insufficient balance, the entire operation will be rolled back to ensure data integrity and consistency.

  2. Isolation levels and propagation behavior :

    @TransactionalAnnotations provide more advanced options such as isolation level ( isolation) and propagation behavior ( propagation). These options give you fine-grained control over transaction behavior.

    @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
    public void someServiceMethod() {
          
          
        // business logic here
    }
    

In general, SpringBoot @Transactionalprovides powerful and simple transaction management functions through annotations, allowing developers to easily ensure data integrity and consistency.

9. Example: From modeling to data access

Through practical examples, we can have a deeper understanding of how to use Spring Data JPA for data access in SpringBoot. In this section, we will create a simple user management system, including user addition, deletion, modification and query operations.

9.1 Create entities and Repository

Create entity :

First, we need to define the user entity. This entity will represent a table in the database.

@Entity
@Table(name = "users")
public class User {
    
    

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "name", nullable = false)
    private String name;

    @Column(name = "email", unique = true)
    private String email;

    // Getter, Setter, and other methods...
}

Here, we define an Userentity, which has an ID, a name and an email.

Create Repository :

After having the entity, we need to create a Repository interface for data access.

public interface UserRepository extends JpaRepository<User, Long> {
    
    
    Optional<User> findByEmail(String email);
}

This UserRepositoryinterface is inherited JpaRepository, which means it already has many built-in methods, such as save(), delete(), findAll()etc.

9.2 Implement basic CRUD operations

Using Spring Data JPA, we can implement basic CRUD operations very easily.

Create user :

@Autowired
private UserRepository userRepository;

public User createUser(User user) {
    
    
    return userRepository.save(user);
}

Find users :

public Optional<User> findById(Long id) {
    
    
    return userRepository.findById(id);
}

public List<User> findAll() {
    
    
    return userRepository.findAll();
}

public Optional<User> findByEmail(String email) {
    
    
    return userRepository.findByEmail(email);
}

Update user :

public User updateUser(User user) {
    
    
    if (userRepository.existsById(user.getId())) {
    
    
        return userRepository.save(user);
    } else {
    
    
        throw new EntityNotFoundException("User not found");
    }
}

Delete user :

public void deleteUser(Long id) {
    
    
    userRepository.deleteById(id);
}

9.3 Test data access logic

After completing the basic CRUD operations, we need to test to ensure the correctness of the logic.

You can use the JUnit framework and SpringBoot @DataJpaTestto perform integration testing of the data layer.

For example, to test the find function:

@RunWith(SpringRunner.class)
@DataJpaTest
public class UserRepositoryTest {
    
    

    @Autowired
    private TestEntityManager entityManager;

    @Autowired
    private UserRepository userRepository;

    @Test
    public void whenFindByEmail_thenReturnUser() {
    
    
        // given
        User john = new User("John", "[email protected]");
        entityManager.persist(john);
        entityManager.flush();

        // when
        Optional<User> found = userRepository.findByEmail(john.getEmail());

        // then
        assertTrue(found.isPresent());
        assertEquals(found.get().getEmail(), john.getEmail());
    }
}

Overall, Spring Data JPA provides a fast and efficient way to handle data access in Spring Boot, allowing developers to focus more on business logic rather than the details of data access.

10. Frequently Asked Questions and Solutions

When using Spring Data JPA, developers may encounter some common problems. In this section, we will discuss two of these common problems and how to solve them.

10.1 N+1 query problem

Problem description :

The N+1 query problem is a common performance problem in ORM (Object Relational Mapping). When we obtain one-to-many or many-to-many relational data, a large number of unnecessary SQL queries will be triggered, which will affect performance.

Take one Userand its Postsas an example. If we try to get all users and all their posts, 1 query may be triggered to get all users, and then for each user 1 query will be triggered to get their posts, which is the N+1 query problem.

Solution :

  1. UsageJOIN FETCH : Using JPQL, JOIN FETCHyou can obtain all relevant data at once.

    @Query("SELECT u FROM User u JOIN FETCH u.posts")
    List<User> findAllWithPosts();
    
  2. Usage@EntityGraph : Use annotations on the Repository method @EntityGraphto define how to obtain related data.

    @EntityGraph(attributePaths = "posts")
    List<User> findAll();
    

10.2 Lazy loading and eager loading

Problem description :

In ORM, there are two strategies for loading related data: lazy loading and eager loading. By default, most relationships are lazy loaded, which means that the relationship data is only loaded when it is actually accessed.

Solution :

  1. Usage@Fetch(FetchMode.JOIN) : This annotation enables eager loading on a specific relationship.

    @OneToMany(fetch = FetchType.EAGER)
    @Fetch(FetchMode.JOIN)
    private Set<Post> posts;
    
  2. Dynamically select loading strategy : In actual development, it may be necessary to dynamically select a loading strategy based on the situation. Can be used EntityGraphsto dynamically define loading relationships.

  3. Note : Although eager loading can load all data at once, it may cause a large amount of unnecessary data to be loaded, affecting performance. Therefore, whether to use eager loading needs to be weighed on a case-by-case basis.

In short, although Spring Data JPA provides many convenient features, it is still necessary to have a deep understanding of the working mechanism behind it, so as to avoid potential performance traps and ensure the efficient operation of the application.

11. Summary

In this blog, we take an in-depth look at how to use Spring Data JPA to simplify database interaction in Spring Boot projects. First, we review traditional database interaction methods to give readers a better understanding of why modern Java applications require frameworks such as Spring Data JPA to help us manage these operations.

We learned that JPA provides a standard way to map Java objects and database tables, and Spring Data JPA further simplifies this process, allowing us to implement most CRUD operations through simple interfaces and method name conventions , without writing cumbersome SQL code.

Next, we introduced in detail how to integrate Spring Data JPA in the Spring Boot project, create entities (Entities), configure the Repository interface, implement custom query methods, and how to effectively manage transactions. In particular, through practical examples, we demonstrate the entire process from modeling to data access, allowing readers to more intuitively understand how to apply this knowledge in real projects.

Of course, although Spring Data JPA provides a lot of convenience, you may also encounter some common problems during use. To this end, we discussed the N+1 query problem and the difference between lazy loading and eager loading, providing readers with effective solution strategies.

Overall, Spring Data JPA is a powerful and flexible tool that greatly simplifies database operations and allows developers to focus more on the implementation of business logic. Of course, to give full play to its effect, continuous learning and practice need to be combined with actual project needs. I hope this article can provide you with a clear direction to help you better master and apply Spring Data JPA.

Guess you like

Origin blog.csdn.net/weixin_52665939/article/details/132404668