Spring Boot data访问
1. Spring Boot data访问overview
Spring Boot providing了 many 种data访问方式, including:
- Spring Data JPA - 基于 JPA ORM framework
- Spring Data JDBC - 轻量级 JDBC operation
- MyBatis - 流行 SQL mapframework
- NoSQL support - Redis, MongoDB, Elasticsearch etc.
in 本章节in, 我们将重点介绍 Spring Data JPA and MyBatis using.
2. Spring Data JPA configuration
Spring Data JPA is Spring ecosystemin用于简化 JPA Development framework.
2.1 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
2.2 configurationdatalibrary连接
in application.properties fileinconfigurationdatalibrary连接:
# datasourcesconfiguration
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
# JPA configuration
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
2.3 creation实体class
package com.example.demo.entity;
import jakarta.persistence.*;
import lombok.Data;
@Entity
@Table(name = "users")
@Data
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false)
private String password;
private String fullName;
private boolean active = true;
}
2.4 creation Repository interface
package com.example.demo.repository;
import com.example.demo.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
Optional<User> findByEmail(String email);
boolean existsByUsername(String username);
boolean existsByEmail(String email);
}
3. Spring Data JPA basicoperation
Spring Data JPA providing了丰富 CRUD operationmethod, 无需writing SQL 语句.
3.1 注入 Repository
package com.example.demo.service;
import com.example.demo.entity.User;
import com.example.demo.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
// 业务method...
}
3.2 CRUD operationexample
// creationuser
User user = new User();
user.setUsername("admin");
user.setEmail("admin@example.com");
user.setPassword("password");
user.setFullName("Administrator");
userRepository.save(user);
// queryuser
Optional<User> optionalUser = userRepository.findById(1L);
optionalUser.ifPresent(System.out::println);
// updateuser
optionalUser.ifPresent(u -> {
u.setFullName("Updated Admin");
userRepository.save(u);
});
// deleteuser
userRepository.deleteById(1L);
3.3 自定义querymethod
// 按user名query
Optional<User> userByUsername = userRepository.findByUsername("admin");
// 按邮箱query
Optional<User> userByEmail = userRepository.findByEmail("admin@example.com");
// checkuser名 is 否存 in
boolean exists = userRepository.existsByUsername("admin");
4. Spring Data JPA advancedquery
Spring Data JPA support many 种advancedquery方式.
4.1 JPQL query
package com.example.demo.repository;
import com.example.demo.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT u FROM User u WHERE u.active = ?1")
List<User> findByActiveStatus(boolean active);
@Query("SELECT u FROM User u WHERE u.fullName LIKE %?1%")
List<User> findByFullNameContaining(String keyword);
}
4.2 原生 SQL query
@Query(value = "SELECT * FROM users WHERE created_at > ?1", nativeQuery = true)
List<User> findByCreatedAtAfter(Date date);
4.3 分页query
package com.example.demo.controller;
import com.example.demo.entity.User;
import com.example.demo.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@Autowired
private UserRepository userRepository;
@GetMapping("/users")
public Page<User> getUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
Pageable pageable = PageRequest.of(page, size);
return userRepository.findAll(pageable);
}
}
5. MyBatis 集成
MyBatis is 另一个流行 data访问framework, 它providing了强 big SQL mapfunctions.
5.1 添加依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
5.2 configuration MyBatis
# datasourcesconfiguration
spring.datasource.url=jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# MyBatis configuration
mybatis.mapper-locations=classpath:mappers/*.xml
mybatis.type-aliases-package=com.example.demo.entity
mybatis.configuration.map-underscore-to-camel-case=true
5.3 creation实体class
package com.example.demo.entity;
import lombok.Data;
@Data
public class Product {
private Long id;
private String name;
private Double price;
private Integer stock;
private String description;
}
5.4 creation Mapper interface
package com.example.demo.mapper;
import com.example.demo.entity.Product;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface ProductMapper {
List<Product> findAll();
Product findById(@Param("id") Long id);
int insert(Product product);
int update(Product product);
int delete(@Param("id") Long id);
List<Product> findByPriceRange(@Param("min") Double min, @Param("max") Double max);
}
5.5 creation XML Mapper file
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.ProductMapper">
<resultMap id="ProductResultMap" type="Product">
<id column="id" property="id" />
<result column="name" property="name" />
<result column="price" property="price" />
<result column="stock" property="stock" />
<result column="description" property="description" />
</resultMap>
<select id="findAll" resultMap="ProductResultMap">
SELECT * FROM products
</select>
<select id="findById" parameterType="Long" resultMap="ProductResultMap">
SELECT * FROM products WHERE id = #{id}
</select>
<insert id="insert" parameterType="Product" useGeneratedKeys="true" keyProperty="id">
INSERT INTO products (name, price, stock, description)
VALUES (#{name}, #{price}, #{stock}, #{description})
</insert>
<update id="update" parameterType="Product">
UPDATE products
SET name = #{name}, price = #{price}, stock = #{stock}, description = #{description}
WHERE id = #{id}
</update>
<delete id="delete" parameterType="Long">
DELETE FROM products WHERE id = #{id}
</delete>
<select id="findByPriceRange" resultMap="ProductResultMap">
SELECT * FROM products WHERE price BETWEEN #{min} AND #{max}
</select>
</mapper>
5.6 using MyBatis Mapper
package com.example.demo.service;
import com.example.demo.entity.Product;
import com.example.demo.mapper.ProductMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ProductService {
@Autowired
private ProductMapper productMapper;
public List<Product> getAllProducts() {
return productMapper.findAll();
}
public Product getProductById(Long id) {
return productMapper.findById(id);
}
public void addProduct(Product product) {
productMapper.insert(product);
}
public void updateProduct(Product product) {
productMapper.update(product);
}
public void deleteProduct(Long id) {
productMapper.delete(id);
}
public List<Product> getProductsByPriceRange(Double min, Double max) {
return productMapper.findByPriceRange(min, max);
}
}
6. transactionmanagement
Spring Boot providing了声明式transactionmanagement, using @Transactional 注解即可.
6.1 basictransactionconfiguration
package com.example.demo.service;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class OrderService {
@Transactional
public void createOrder(Order order) {
// 保存订单
// updatelibrary存
// 发送notification
// such as果任何一步失败, 所 has operation都会rollback
}
}
6.2 transactionpropertyconfiguration
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.READ_COMMITTED,
timeout = 30,
readOnly = false,
rollbackFor = Exception.class
)
public void complexBusinessLogic() {
// complex 业务逻辑
}
6.3 transaction传播behavior
REQUIRED- such as果当 before 存 in transaction, 则加入该transaction; 否则, creation一个 new transactionSUPPORTS- such as果当 before 存 in transaction, 则加入该transaction; 否则, 以非transaction方式执行MANDATORY- such as果当 before 存 in transaction, 则加入该transaction; 否则, 抛出exceptionREQUIRES_NEW- creation一个 new transaction, such as果当 before 存 in transaction, 则挂起该transactionNOT_SUPPORTED- 以非transaction方式执行, such as果当 before 存 in transaction, 则挂起该transactionNEVER- 以非transaction方式执行, such as果当 before 存 in transaction, 则抛出exceptionNESTED- such as果当 before 存 in transaction, 则 in 嵌套transaction in 执行; 否则, creation一个 new transaction
7. datavalidation
Spring Boot 集成了 Hibernate Validator, 可以方便地fordatavalidation.
7.1 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
7.2 in 实体classin添加validation注解
package com.example.demo.entity;
import jakarta.validation.constraints.*;
import lombok.Data;
@Data
public class User {
private Long id;
@NotBlank(message = "user名不能 for 空")
@Size(min = 3, max = 20, message = "user名 long 度必须 in 3-20 个字符之间")
private String username;
@NotBlank(message = "邮箱不能 for 空")
@Email(message = "邮箱格式不正确")
private String email;
@NotBlank(message = "password不能 for 空")
@Size(min = 6, message = "password long 度不能 few 于 6 个字符")
private String password;
@NotBlank(message = "姓名不能 for 空")
private String fullName;
private boolean active = true;
}
7.3 in Controller inusingvalidation
package com.example.demo.controller;
import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
@PostMapping
public ResponseEntity<User> createUser(@Validated @RequestBody User user) {
User createdUser = userService.createUser(user);
return ResponseEntity.ok(createdUser);
}
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(
@PathVariable Long id,
@Validated @RequestBody User user) {
User updatedUser = userService.updateUser(id, user);
return ResponseEntity.ok(updatedUser);
}
}
7.4 全局exceptionprocessing
package com.example.demo.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.Exceptionprocessingr;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
@RestControllerAdvice
public class GlobalExceptionprocessingr {
@Exceptionprocessingr(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidationExceptions(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
}
}
8. datalibrarymigration
using Flyway or Liquibase fordatalibraryversionmanagement and migration.
8.1 Flyway configuration
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
# Flyway configuration
spring.flyway.url=jdbc:mysql://localhost:3306/mydb
spring.flyway.user=root
spring.flyway.password=password
spring.flyway.baseline-on-migrate=true
8.2 creationmigration脚本
in src/main/resources/db/migration Table of Contents under creation SQL migration脚本:
-- V1__Create_users_table.sql
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(20) NOT NULL UNIQUE,
email VARCHAR(100) NOT NULL UNIQUE,
password VARCHAR(100) NOT NULL,
full_name VARCHAR(100) NOT NULL,
active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
8.3 runmigration
启动 Spring Boot application时, Flyway 会自动执行未执行 migration脚本.