SpringBoot使用JPA实现增删查改


运行环境

  • SpringBoot2.3.0
  • JDK1.8
  • IDEA2020.1.2
  • MySQL5.7

依赖及应用程序配置


    org.springframework.boot
    spring-boot-starter-data-jpa


    org.springframework.boot
    spring-boot-starter-thymeleaf


    org.springframework.boot
    spring-boot-starter-validation


    org.springframework.boot
    spring-boot-starter-web



    mysql
    mysql-connector-java
    runtime


    org.projectlombok
    lombok
    true


    org.springframework.boot
    spring-boot-starter-test
    test
    
        
            org.junit.vintage
            junit-vintage-engine
        
    

  1. 升级到SpringBoot2.2,spring-boot-starter-test默认使用JUnit 5作为单元测试框架 ,写单元测试时注解@RunWith(Spring.class)升级为@ExtendWith(SpringExtension.class)
  2. 升级到SpringBoot2.3,hibernate-validator从spring-boot-starter-web移除,需要单独引入
  3. 升级到SpringBoot2.3,MySQL驱动由com.mysql.jdbc.Driver变更为com.mysql.cj.jdbc.Driver;同时,数据源url需要添加serverTimezone=UTC&useSSL=false参数
  4. 升级到SpringBoot2.x,默认不自动注入HiddenHttpMethodFilter ,需要设置spring.mvc.hiddenmethod.filter.enabled=true开启PUT、DELETE方法支持

应用程序配置如下:

spring.application.name=springbootjpa
management.endpoints.jmx.exposure.include=*
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
# 应用服务 WEB 访问端口
server.port=8080
# Actuator Web 访问端口
management.server.port=8081

# mysql setting
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springbootjpa?serverTimezone=UTC&useSSL=false
spring.datasource.username=username
spring.datasource.password=password

# JPA setting
spring.jpa.properties.hibernate.hbm2ddl.auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.show-sql=true

# thymeleaf setting
spring.thymeleaf.cache=false

# delete、put方法支持
spring.mvc.hiddenmethod.filter.enabled=true

定义实体

使用@Entity标记实体类

import lombok.Data;

import javax.persistence.*;
import javax.validation.constraints.NotEmpty;
import java.io.Serializable;

@Entity
@Data
public class Article extends BaseEntity implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    @NotEmpty(message = "标题不能为空")
    private String title;

    @Column(nullable = false)
    private String body;
}

为了自动添加创建日期、修改日期、创建人及修改人,我们把创建、修改信息放到父类中由实体类继承,并开启SpringBoot的自动审计功能,将创建/修改信息自动注入

1、定义实体类的父类,@CreatedDate、@LastModifiedDate、@CreatedBy、@LastModifiedBy标注相应字段

import org.hibernate.annotations.Columns;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseEntity {
    @CreatedDate
    private Long createTime;

    @LastModifiedDate
    private Long updateTime;
    
    @Column(name = "create_by")
    @CreatedBy
    private String createBy;

    @Column(name = "lastmodified_by")
    @LastModifiedBy
    private String lastmodifiedBy;
    
    public Long getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Long createTime) {
        this.createTime = createTime;
    }

    public Long getUpdateTime() {
        return updateTime;
    }

    public void setUpdateTime(Long updateTime) {
        this.updateTime = updateTime;
    }
    
    public String getCreateBy() {
        return createBy;
    }

    public void setCreateBy(String createBy) {
        this.createBy = createBy;
    }

    public String getLastmodifiedBy() {
        return lastmodifiedBy;
    }

    public void setLastmodifiedBy(String lastmodifiedBy) {
        this.lastmodifiedBy = lastmodifiedBy;
    }
}

@MappedSuperclass注解:

  • 作用于实体类的父类上,父类不生成对应的数据库表
  • 标注@MappedSuperclass的类不能再标注@Entity或@Table注解,也无需实现序列化接口
  • 每个子类(实体类)对应一张数据库表,数据库表包含子类属性和父类属性
  • 标注@MappedSuperclass的类可以直接标注@EntityListeners实体监听器

@EntityListeners(AuditingEntityListener.class)注解:

  • 作用范围仅在标注@MappedSuperclass类的所有继承类中,并且实体监听器可以被其子类继承或重载
  • 开启JPA的审计功能,需要在SpringBoot的入口类标注@EnableJpaAuditing

创建日期、修改日期有默认方法注入值,但创建人和修改人注入则需要手动实现AuditorAware接口:

@Configuration
public class BaseEntityAuditor implements AuditorAware {
    @Override
    public Optional getCurrentAuditor() {
        return "";
    }
}

DAO层实现

JPA支持通过约定方法名进行数据库查询、修改:

import org.springframework.data.jpa.repository.JpaRepository;
import springbootjpa.entity.Article;

public interface ArticleRepository extends JpaRepository {
    Article findById(long id);
}

通过约定方法名查询,只需实现JpaRepository接口声明查询方法而不需要具体实现

此外,可以在方法上标注@Query实现JPQL或原生SQL查询

JpaRepository,T表示要操作的实体对象,ID表示主键。该接口继承了分页排序接口PadingAndSortRepository,通过构建Pageable实现分页查询:

@Autowired
private ArticleRepository articleRepository;

@RequestMapping("")
public ModelAndView articlelist(@RequestParam(value = "start", defaultValue = "0") Integer start, @RequestParam(value = "limit", defaultValue = "5") Integer limit) {
        start = start < 0 ? 0 : start;
        Sort sort = Sort.by(Sort.Direction.DESC, "id");
        Pageable pageable = PageRequest.of(start, limit, sort);
        Page
page = articleRepository.findAll(pageable); ModelAndView modelAndView = new ModelAndView("article/list"); modelAndView.addObject("page", page); return modelAndView; }

如果根据某一字段排序,可以用Sort.by方法构建Sort对象;如果根据多个字段排序,首先构建Sort.Order数组List,然后再传入Sort.by方法构建Sort对象。

PageRequest.of方法生成Pageable对象

Contrller控制器

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.data.domain.Sort;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import springbootjpa.entity.Article;
import springbootjpa.repository.ArticleRepository;

@Controller
@RequestMapping("/article")
public class ArticleController {
    @Autowired
    private ArticleRepository articleRepository;

    @RequestMapping("")
    public ModelAndView articlelist(@RequestParam(value = "start", defaultValue = "0") Integer start,
                                    @RequestParam(value = "limit", defaultValue = "5") Integer limit) {
        start = start < 0 ? 0 : start;
        Sort sort = Sort.by(Sort.Direction.DESC, "id");
        Pageable pageable = PageRequest.of(start, limit, sort);
        Page
page = articleRepository.findAll(pageable); ModelAndView modelAndView = new ModelAndView("article/list"); modelAndView.addObject("page", page); return modelAndView; } @RequestMapping("/add") public String addArticle() { return "article/add"; } @PostMapping("") public String saveArticle(Article article) { articleRepository.save(article); return "redirect:/article"; } @GetMapping("/{id}") public ModelAndView getArticle(@PathVariable("id") Integer id) { Article article = articleRepository.findById(id); ModelAndView modelAndView = new ModelAndView("article/show"); modelAndView.addObject("article", article); return modelAndView; } @DeleteMapping("/{id}") public String deleteArticle(@PathVariable("id") long id) { System.out.println("put 方法"); articleRepository.deleteById(id); return "redirect:/article"; } @GetMapping("edit/{id}") public ModelAndView editArticle(@PathVariable("id") Integer id) { Article article = articleRepository.findById(id); ModelAndView modelAndView = new ModelAndView("article/edit"); modelAndView.addObject("article", article); return modelAndView; } @PutMapping("/{id}") public String editArticleSave(Article article, long id) { System.out.println("put 方法"); article.setId(id); articleRepository.save(article); return "redirect:/article"; } }

因为

表单只能发送GETPOST请求,spring3引入一个监听器HiddenHttpMethodFilter 来将POST请求转换为PUTPOST请求。

SpringBoot2.x开始默认不自动注入HiddenHttpMethodFilter ,需要设置spring.mvc.hiddenmethod.filter.enabled=true开启PUT、DELETE方法支持

配置完后,前端页面需要在表单中加入隐藏域,表明实际请求方法:



    




其他

th:valueth:field区别:

  • th:value解析成html,表现为:value="${th:value}"
  • th:field解析成html,表现为:name="${th:name}" value="${th:value}"