SprintBoot系列(九):JPA初介绍

首先·讲解下JAVA的MVC思想

  • Model:用于定义数据模型
  • Controller:控制层,用来调用逻辑方法
  • Service:用于编写逻辑方法
  • View:前端展示

结合之前讲到OCP思想,分层可以更好地协同开发。

当下的分层模式主要分为:水平分割和垂直分割

  • 水平分割:利用MVC,不同的项目不同的开发者/团队进行开发
  • 垂直分割:微服务架构,低层分割。

JAVA作为经典的后端语言,与数据库的交互是必备的掌握技能。常用的模式是:

  • JPA:关系型数据库查询。优点:简单易用;缺点:学习成本大;国外常用
  • MyBatis: 优点:简单易用;缺点:需要维护和调优

现在我们开始讲解SpringBoot的JPA模式

1、SpringBoot引入第三方数据库链接依赖

1.1、修改项目下面的pom.xml文件

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.8.1</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

1.2、reload porject并查看maven依赖加载情况

JAVA、基础技术、技术与框架SprintBoot系列(九):JPA初介绍插图

2、使用JPA和Model模型创建数据表

2.1、编写Service层 ,编写业务层

前面讲到,@Service也可以加入到容器中,因为这个是service层,规范化的话,使用@Service而不使用@Compent,根据依赖注入,在controller处private BannerService bannerservice;前记得添加@Autowired

方法一:简单模式

创建一个BannerService类

在项目创建一个package并命名为service,并创建一个类BannerService

package com.fcors.fcors.service;

import org.springframework.stereotype.Service;

@Service
public class BannerService {
    void getByName(String name){
        /* 此处开始编写 业务逻辑代码 */
    }
}

方法二:OCP模式

根据之前说讲,为了更好的实现OCP,我们的类都要实现接口,然后控制层调用接口,而不是直接调用实例类

在项目创建一个package并命名为service,并创建一个 interface:BannerService

package com.fcors.fcors.service;

import org.springframework.boot.Banner;

public interface BannerService {
    Banner getByName(String name);
}

在service中创建一个 BannerServiceImpl 类去实现接口

package com.fcors.fcors.service;

import com.fcors.fcors.model.Banner;
import org.springframework.stereotype.Service;

@Service
public class BannerServiceImpl implements BannerService{
    @Override
    public Banner getByName(String name) {
        return null;
    }
}

2.2、编写Model层

Model主要是存储数据库的数据模型。 一个Model类就是一个数据库的一个表

创建一个package,命名为Model, 并在创建一个类BannerModel类,加上 @Entity(实体类)注解

注意,为了接口在controller能输出,需要在此处加入@Getter和@Setter

package com.fcors.fcors.model;

import javax.persistence.*;

@Getter
@Setter
@Entity
/* 实体类名与数据库表名不一致的时候 */
@Table(name="banner")
public class Banner {
    /* 定义主键*/
    @Id
    private long id;

    /*定义长度*/
    @Column(length=16)
    private String name;

    /* 定义该成员不在数据库,不与数据库关联 */
    @Transient
    private String description;
    
    private String img;
    
    private String title;


}

2.3、编写数据库配置

因为数据库配置信息,一般不同环境信息都有所差异,因此我们把信息写在application-dev.yml中

2.3.1Mysql的配置

server:
  port:8082
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/missyou?characterEncoding=utf-8&serverTimezone=GMT%2B8
    username: root
    password: 123
  jpa:
    show-sql: true
    properties:
      hibernate:
        format_sql: true
        enable_lazy_load_no_trans: true

2.3.2.1SqlServer的配置

server:
  port: 8082
spring:
  datasource:
    driver-class-name: net.sourceforge.jtds.jdbc.Driver
    url: jdbc:jtds:sqlserver://ip:1433;databaseName=Databasename
    username: sa
    password: password
  jpa:
    show-sql: true
    properties:
      hibernate:
        format_sql: true
        enable_lazy_load_no_trans: true

2.3.2.2引入sqlserver的依赖

        <!-- JPA依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!-- sql server-->
        <dependency>
            <groupId>net.sourceforge.jtds</groupId>
            <artifactId>jtds</artifactId>
            <version>1.3.1</version>
        </dependency>

记得启用该配置文件

JAVA、基础技术、技术与框架SprintBoot系列(九):JPA初介绍插图1

2.3.3设置JPA参数,建立与Mysql的链接,修改application.yml

mysql:
  port: 3306
  ip: 192.168.8.8
hero:
  condition: fox222
porject-PathName: com.fcors.fcors
spring:
  profiles:
    active: dev
  jpa:
    open-in-view: false
    hibernate:
      ddl-auto: create-drop
JAVA、基础技术、技术与框架SprintBoot系列(九):JPA初介绍插图2

create-drop:每次运行都会删除和重建表

2.4、编写Controller层,控制器层

package com.fcors.fcors.api.sample.v1;


import com.fcors.fcors.service.BannerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotBlank;
import org.springframework.validation.annotation.Validated;
import java.io.IOException;

/** 声明这个类是一个controller */

@RestController
@RequestMapping("/banner/")
@Validated
public class BannerController {
    @Autowired
    private BannerService bannerService;

    @GetMapping("/name/{name}")
    public void getByName(@PathVariable @NotBlank String name){

        
    }
}
JAVA、基础技术、技术与框架SprintBoot系列(九):JPA初介绍插图3

3、使用JPA和Model模型查询数据库

3.1、修改application.yml的JAP属性

JAVA、基础技术、技术与框架SprintBoot系列(九):JPA初介绍插图4

3.2、数据表插入两条数据

JAVA、基础技术、技术与框架SprintBoot系列(九):JPA初介绍插图5

3.3、JPA定义接口Repository

3.3.1 创建一个Repository接口

在项目中创建一个package并命名为repository,在下面创建一个接口BannerRepository

实现JpaRepository<@value1,@value2>,两个参数分别是

  • @value1:model类名
  • @value:model的主键即表的主键
package com.fcors.fcors.repository;

import com.fcors.fcors.model.Banner;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

/*JpaRepository<@value1,@value2>
* @value1:model类名
* @value:model的主键即表的主键
* */
@Repository
public interface BannerRepository extends JpaRepository<Banner,Long> {
    Banner finOneById(String name);
}

3.4、修改service文件BannerServiceImpl

package com.fcors.fcors.service;

import com.fcors.fcors.model.Banner;
import com.fcors.fcors.repository.BannerRepository;
import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;

@Service
public class BannerServiceImpl implements BannerService {
    @Autowired
    private BannerRepository bannerRepository;

    public Banner getOneByName(String name){

        return bannerRepository.findOneByName(name);
    }
}

3.5、修改controller

package com.fcors.fcors.api.sample.v1;


import com.fcors.fcors.model.Banner;
import com.fcors.fcors.service.BannerService;
import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotBlank;
import java.io.IOException;

/** 声明这个类是一个controller */

//@Controller
//@ResponseBody
//@RestController 等同于同时声明@Controller和@ResponseBody
@RestController
@RequestMapping("/banner/")
@Validated
public class BannerController {

    @Autowired
    private BannerService bannerService;

    @GetMapping("/name/{name}")
    public Banner getOneByName(@PathVariable @NotBlank String name){
        Banner banner = bannerService.getByName(name);
        return banner;
    }
}
JAVA、基础技术、技术与框架SprintBoot系列(九):JPA初介绍插图6
结果输出成功
https://www.fcors.com/%e6%8a%80%e6%9c%af%e4%b8%8e%e6%a1%86%e6%9e%b6/jap%e7%9a%84%e5%91%bd%e5%90%8d%e8%a7%84%e5%88%99/

4、JPA单向一对多模型

单向一对多模型:是指两个表,其中表1的条数据对应表2的数据是多条。我们通过查询表1的时候,把表2的信息也能显示出来。

例如:Banner表,和BannerItem表;一个Banner会对应多个BannerItem;下面就针对这个来实现。

4.1、数据库创建BannerItem表

JAVA、基础技术、技术与框架SprintBoot系列(九):JPA初介绍插图7
banneritem表结构

如果通过sql表达

select * from banner a
left join banneritem b on a.id=b.bannerid
where 1=1

4.2、在model下创建BannerItem的类

package com.fcors.fcors.model;

import lombok.Getter;
import lombok.Setter;

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

@Entity
@Getter
@Setter
/* 实体类名与数据库表名不一致的时候 */
@Table(name="banneritem")
public class BannerItem {
    /*定义主键*/
    @Id
    private long id;

    private String name;

    private String url;

    private long bannerid;

    private long status;

}

4.3、在model下Banner的类

package com.fcors.fcors.model;

import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;
import java.util.List;


@Entity
@Getter
@Setter
/* 实体类名与数据库表名不一致的时候 */
@Table(name="banner")
public class Banner {
    /* 定义主键*/
    @Id
    private long id;

    /*定义长度*/
    @Column(length=16)
    private String name;

    /* 定义该成员不在数据库,不与数据库关联 */
    @Transient
    private String description;

    private String img;

    private String title;

    /* 设置关联
    * banner and banneritem 两个实体类和数据表的关联
    * */

    @OneToMany(fetch = FetchType.EAGER)
    @JoinColumn(name = "bannerid")
    private List<BannerItem> Banneritem;
}

4.4、Repository下的BannerRepository接口

package com.fcors.fcors.repository;

import com.fcors.fcors.model.Banner;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

/*JpaRepository<@value1,@value2>
* @value1:model类名
* @value:model的主键即表的主键
* */
@Repository
public interface BannerRepository extends JpaRepository<Banner,Long> {
    Banner findOneByName(String name);
}

4.5、Service下的BannerService接口和BannerServiceImpl类

4.5.1、BannerService接口

package com.fcors.fcors.service;


import com.fcors.fcors.model.Banner;

public interface BannerService {
    Banner getOneByName(String name);
}

4.5.2、BannerServiceImpl类

package com.fcors.fcors.service;

import com.fcors.fcors.model.Banner;
import com.fcors.fcors.repository.BannerRepository;
import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;

@Service
public class BannerServiceImpl implements BannerService {
    @Autowired
    private BannerRepository bannerRepository;

    public Banner getOneByName(String name){

        return bannerRepository.findOneByName(name);
    }
}

4.6 Controller文件

package com.fcors.fcors.api.sample.v1;


import com.fcors.fcors.model.Banner;
import com.fcors.fcors.service.BannerService;
import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotBlank;
import java.io.IOException;

/** 声明这个类是一个controller */

//@Controller
//@ResponseBody
//@RestController 等同于同时声明@Controller和@ResponseBody
@RestController
@RequestMapping("/banner/")
@Validated
public class BannerController {

    @Autowired
    private BannerService bannerService;

    @GetMapping("/name/{name}")
    public Banner getOneByName(@PathVariable @NotBlank String name){
        Banner banner = bannerService.getOneByName(name);
        return banner;
    }
}
JAVA、基础技术、技术与框架SprintBoot系列(九):JPA初介绍插图8

5、JPA双向一对多模型

为什么会有双向一对多的出现?

例如我们需要查bannerItem是属于哪个banner,需要banner表的某些信息,这就是双向1对多。

注意当双向的时候,@JoinColumn打在多方【多端】处。

5.1、model下的BannerItem类

package com.fcors.fcors.model;

import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import javax.persistence.*;

@Entity
@Getter
@Setter
/* 实体类名与数据库表名不一致的时候 */
@Table(name="banneritem")
public class BannerItem {
    /*定义主键*/
    @Id
    private long id;

    private String name;

    private String url;

    private long bannerid;

    private long status;


    @ManyToOne(cascade={CascadeType.MERGE,CascadeType.REFRESH},optional=false,fetch=FetchType.LAZY)
    @JoinColumn(insertable = false,updatable = false,name="bannerid",referencedColumnName="id", nullable = false)
    @JsonBackReference
    private Banner banner;
}

5.2、model下的Banner类

package com.fcors.fcors.model;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;
import java.util.List;


@Entity
@Getter
@Setter
/* 实体类名与数据库表名不一致的时候 */
@Table(name="banner")
public class Banner {
    /* 定义主键*/
    @Id
    private long id;

    /*定义长度*/
    @Column(length=16)
    private String name;

    /* 定义该成员不在数据库,不与数据库关联 */
    @Transient
    private String description;

    private String img;

    private String title;

    /* 设置关联
    * banner and banneritem 两个实体类和数据表的关联
    * */

    @OneToMany(mappedBy = "banner",cascade = CascadeType.REMOVE,fetch = FetchType.EAGER)
//    @JoinColumn(name = "bannerid")
    private List<BannerItem> Banneritem;
}

5.3、BannerItemRepository的类

package com.fcors.fcors.repository;

import com.fcors.fcors.model.Banner;
import com.fcors.fcors.model.BannerItem;
import org.springframework.data.jpa.repository.JpaRepository;

public interface BannerItemRepository extends JpaRepository<BannerItem,Long> {
    BannerItem findOneByName(String name);
}

5.4、service下的BannerItem接口

package com.fcors.fcors.service;

import com.fcors.fcors.model.BannerItem;

public interface BannerItemService {
    BannerItem getOneByName(String name);
}

5.5、service下BannerItem类

切记如果model一对多是懒加载的话,在service层一定要加上@Transactional

不然会报错:

Unable to evaluate the expression Method threw 'org.hibernate.LazyInitializationException' exception.
package com.fcors.fcors.service;

import com.fcors.fcors.model.BannerItem;
import com.fcors.fcors.repository.BannerItemRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Transactional
@Service
public class BannerItemServiceImpl implements BannerItemService{

    @Autowired
    private BannerItemRepository bannerItemRepository;

    public BannerItem getOneByName(String name){
        return bannerItemRepository.findOneByName(name);
    }
}

5.6、控制器层Bannercontroller

package com.fcors.fcors.api.sample.v1;


import com.fcors.fcors.model.Banner;
import com.fcors.fcors.model.BannerItem;
import com.fcors.fcors.service.BannerItemService;
import com.fcors.fcors.service.BannerService;
import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotBlank;
import java.io.IOException;

/** 声明这个类是一个controller */

//@Controller
//@ResponseBody
//@RestController 等同于同时声明@Controller和@ResponseBody
@RestController
@RequestMapping("/banner/")
@Validated
public class BannerController {

    @Autowired
    private BannerItemService bannerItemService;
    @GetMapping("/bannerItem/name/{name}")
    public BannerItem getBannerItemOneByName(@PathVariable @NotBlank String name){
        BannerItem bannerItem = bannerItemService.getOneByName(name);
        return bannerItem;
    }
}

上面的例子还是有个bug,就是访问BannerItem的时候,并获取不了banner的内容。(需后续更改)

6、JPA单向多对多模型

单向的多对多模型,我们只有一方保留导航模式,即@ManyToMany

6.1、创建数据表Spu、Theme、Spu_Theme三个表

JAVA、基础技术、技术与框架SprintBoot系列(九):JPA初介绍插图9
JAVA、基础技术、技术与框架SprintBoot系列(九):JPA初介绍插图10
JAVA、基础技术、技术与框架SprintBoot系列(九):JPA初介绍插图11

6.2、在model下创建Spu的类

package com.fcors.fcors.model;

import lombok.Getter;
import lombok.Setter;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import java.util.List;

@Entity
@Getter
@Setter
@Table(name="spu")
public class Spu {
    @Id
    private Long id;
    private String title;
    private String subtitle;

//    @ManyToMany
//    private List<Theme> themeList;
}

6.3、在model下创建Theme的类

package com.fcors.fcors.model;

import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;
import java.util.List;

@Entity
@Getter
@Setter
@Table(name="theme")
public class Theme {
    @Id
    private Long id;
    private String title;
    private String name;

    @ManyToMany
    /* joinColumns 第三张表的*/
    @JoinTable(name="spu_theme",joinColumns = @JoinColumn(name="themeid"),
            inverseJoinColumns = @JoinColumn(name="spuid")
    )
    private List<Spu> spuList;
}

更多的级联操作可以参考

SprintBoot系列(十五):JPA级联操作 – 星伴同行 (fcors.com)

下面我们将讲解如何通过IDEA逆向生成Model

http://www.fcors.com/%E6%8A%80%E6%9C%AF%E4%B8%8E%E6%A1%86%E6%9E%B6/sprintboot%E7%B3%BB%E5%88%97%EF%BC%88%E5%8D%81%EF%BC%89%EF%BC%9Aorm%E5%AE%9E%E6%88%98%E9%93%BE%E6%8E%A5%E6%95%B0%E6%8D%AE%E5%BA%93%E5%8F%8A%E8%BF%94%E5%9B%9E%E6%95%B0%E6%8D%AE