ToB企服应用市场:ToB评测及商务社交产业平台

标题: SpringBoot(15)ORM ( Object Relation Mapping )和JPA—Java持久层API [打印本页]

作者: 一给    时间: 2022-8-30 04:22
标题: SpringBoot(15)ORM ( Object Relation Mapping )和JPA—Java持久层API
1.认识ORM

  ORM ( Object Relation Mapping )是对象/关系映射。它提供了概念性的、易于理解的数据模型,将数据库中的表和内存中的对象建立映射关系。它是随着面向对象的软件开发方法的发展而产生的,面向对象的开发方法依然是当前主流的开发方法。
  对象和关系型数据是业务实体的两种表现形式。业务实体在内存中表现为对象,在数据库中表现为关系型数据。内存中的对象不会被永久保存,只有关系型数据库(或NoSQL数据库,或文件) 中的对象会被永久保存。
  对象/关系映射(ORM)系统一般以中间件的形式存在,因为内存中的对象之间存在关联和继承关系,而在数据库中,关系型数据无法直接表达多对多的关联和继承关系。对象、数据库通过ORM 映射的关系如图所示。
  
2.JPA

  2.1认识 Spring Data

  Spring Data是Spring的一个子项目,旨在统一和简化各类型数据的持久化存储方式,而不拘泥于是关系型数据库还是NoSQL数据库。无论是哪种持久化存储方式,数据访问对象(Data Access Objects, DAO)都会提供对对象的增加、删除、修改和查询的方法,以及排序和分页方法等。Spring Data 提供了基于这些层面的统一接口(如:CrudRepository、PagingAndSorting- Repository),以实现持久化的存储。Spring Data包含多个子模块,主要分为主模块和社区模块。
  (1)主要模块

  (2)社区模块

  2.2认识JPA

  JPA (Java Persistence API )是Java的持久化API,用于对象的持久化。它是一个非常强大的ORM持久化的解决方案,免去了使用JDBCTemplate开发的编写脚本工作。JPA通过简单约定好接口方法的规则自动生成相应的JPQL语句,然后映射成POJO对象。
  JPA是一个规范化接口,封装了Hibernate的操作作为默认实现,让用户不通过任何配置即可完成数据库的操作。JPA、Spring Date和Hibernate的关系如图所示。
  
  Hibernate 主要通过 hibernate-annotation、hibernate-entitymanager、hibernate-core 三个组件来操作数据。
  可使用以下代码来创建实体类
  1. @Data
  2. @Entity
  3. public class User{
  4.     private int id;
  5.     @Id
  6.     @GeneratedValue(strategy = GenerationType.IDENTITY)
  7.     private String name;
  8.     private int age;
  9. }
复制代码
  对比JPA与JDBCTemplate创建实体的方式可以看出:JPA的实现方式简单明了,不需要重写映射(支持自定义映射),只需要设置好属性即可。id的自増由数据库自动管理,也可以由程序管理,其他的工作JPA自动处理好了。
  2.3使用JPA

  (1)添加JPA和MySQL数据库的依赖
  1. <dependency>
  2.     <groupId>org.springframework.boot</groupId>
  3.     <artifactId>spring-boot-starter-data-jpa</artifactId>
  4. </dependency>
  5. <dependency>
  6.     <groupId>mysql</groupId>
  7.     <artifactId>mysql-connector-java</artifactId>
  8.     <scope>runtime</scope>
  9. </dependency>
复制代码
  (2)配置数据库连接信息

   Spring Boot项目使用MySQL等关系型数据库,需要配置连接信息,可以在 application.yml文件中进行配置。以下代码配置了与MySQL数据库的连接信息:
  1. spring:
  2.   jpa:
  3.     hibernate:
  4.       ddl-auto: update
  5.     show-sql: true
  6.   jooq:
  7.     sql-dialect: org.hibernate.dialect.Mysql5InnoDBDialect
  8.   datasource:
  9.     driver-class-name: com.mysql.cj.jdbc.Driver
  10.     url: jdbc:mysql://localhost:3306/springboot?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC&useSSL=true
  11.     username: root
  12.     password: 123456
复制代码
  
代码解释如下。
  
  2.4了解JPA注解和属性

  (1)JPA的常用注解

  
  
  (2)映射关系的注解

  
  (3)映射关系的属性

  
  
  在 Spring Data JPA 中,要控制 Session 的生命周期:否则会出现"could not initialize proxy
  [xxxx#18]-no Session”错误。可以在配置文件中配置以下代码来控制Session的生命周期:
  1. open-in-view: true
  2. properties:
  3.   hibernate:
  4.     enable_lazy_load_no_trans: true
复制代码
  2.5 实例:用JPA构建实体数据表
  1. package com.itheima.domain;
  2. import lombok.Data;
  3. import javax.persistence.*;
  4. import javax.validation.constraints.NotEmpty;
  5. import javax.validation.constraints.Size;
  6. import java.awt.*;
  7. import java.io.Serializable;
  8. import java.util.Arrays;
  9. @Entity
  10. @Data
  11. public class Article implements Serializable {
  12.     @Id
  13.     @GeneratedValue(strategy = GenerationType.IDENTITY)
  14.     /**
  15.      * Description: IDENTITY代表由数据库控制,auto代表由Spring Boot应用程序统一控制(有多个表时,id 的自增值不一定从1开始)
  16.      */
  17.     private long id;
  18.     @Column(nullable = false,unique = true)
  19.     @NotEmpty(message = "标题不能为空")
  20.     private String title;
  21.     /**
  22.      *        Description:枚举类型
  23.      */
  24.     @Column(columnDefinition = "enum('图','图文','文')")
  25.     private String type;//类型
  26.     /**
  27.      *        Description: Boolean 类型默认false
  28.      */
  29.     private Boolean available= Boolean.FALSE;
  30.     @Size(min = 0,max = 20)
  31.     private String keyword;
  32.     @Size(max = 255)
  33.     private String description;
  34.     @Column(nullable = false)
  35.     private String body;
  36.     /**
  37.      * Description:创建虚拟字段
  38.      */
  39.     @Transient
  40.     private List keywordlists;
  41.     public List getKeywordlists(){
  42.         return (List) Arrays.asList(this.keyword.trim().split("|"));
  43.     }
  44.     public void setKeywordlists(List keywordlists) {
  45.         this.keywordlists = keywordlists;
  46.     }
  47. }
复制代码

   2.6认识JPA的接口

  JPA提供了操作数据库的接口。在开发过程中继承和使用这些接口,可简化现有的持久化开发 工作。可以使Spring找到自定义接口,并生成代理类,后续可以把自定义接口注入Spring容器中进行管理。在自定义接口过程中,可以不写相关的SQL操作,由代理类自动生成。
  (1)JPA 接口 JpaRepository

  JpaRepository 继承自 PagingAndSortingRepository, 该接口提供了 JPA 的相关实用功能, 以及通过Example进行查询的功能。Example对象是JPA提供用来构造查询条件的对象。该接口的关键代码如下:
  public interface JpaRepository extends PagingAndSortingRepository, Query By ExampleExecutor {}
  在上述代码中,T表示实体对象,ID表示主键。ID必须实现序列化。
  JpaRepository提供的方法见下表
  
  (2)分页排序接口 PagingAndSortingRepository

  PagingAndSortingRepository继承自CrudRepository提供的分页和排序方法。其关键代码如下:
  @NoRepositoryBean
  public interface PagingAndSortingRepository extends CrudRepository {
    lterable findAll(Sort var1);
    Page findAII(Pageable var1);
  }
其方法有如下两种。
  (3)数据操作接口 CrudRepository

  CrudRepository接口继承自Repository接口,并新增了增加、删除、修改和查询方法。
   CrudRepository提供的方法见下表
  
  (4)分页接口 Pageable 和 Page
  1. @RequestMapping("/article")
  2. public ModelAndView articleList(@RequestParam(value = "start",defaultValue = "0") Integer start,
  3.                                 @RequestParam(value = "limit",defaultValue = "10") Integer limit){
  4.     start = start < 0?0:start;
  5.     Sort sort = new Sort(Sort.Direction.DESC,"id");
  6.     Pageable pageable = PageRequest.of(start, limit, sort);
  7.     Page<Article> page = articleRepository.findAll(pageable);
  8.     ModelAndView mav = new ModelAndView("admin/article/list");
  9.     mav.addObject("page",page);
  10.     return mav;
  11. }
复制代码
  (5)排序类Sort

  Sort类专门用来处理排序。最简单的排序就是先传入一个属性列,然后根据属性列的值进行排序。默认情况下是升序排列。它还可以根据提供的多个字段属性值进行排序。例如以下代码是通过 Sort.Order对象的List集合来创建Sort对象的:
  1. List<Sort.Order> orders = new ArrayList<Sort.Order>();
  2. orders.add(new Sort.Order(Sort.Direction.DESC,"id"));
  3. orders.add(new Sort.Order(Sort.Direction.ASC,"view"));
  4. Pageable pageable = PageRequest.of(start,limit,sort);
  5. Pageable pageable = PageRequest.of(start,limit,Sort.by(orders));
复制代码
Sort排序的方法还有下面几种:
3.JPA的查询方式

  3.1使用约定方法名

  约定方法名一定要根据命名规范来写,Spring Data会根据前缀、中间连接词(Or、And、Like、NotNull等类似SQL中的关键词)、内部拼接SQL代理生成方法的实现。约定方法名的方法见下表
  
  接口方法的命名规则也很简单,只要明白And、Or、Is、Equal、Greater、StartingWith等英文单词的含义,就可以写接口方法。具体用法如下:
  1. package com.itheima.domain;
  2. import org.springframework.data.repository.Repository;
  3. import java.util.List;
  4. public interface UserRepository extends Repository<User,Long> {
  5.     List<User> findByEmailOrName(String email,String name);
  6. }
复制代码
上述代码表示,通过email或name来查找User对象。
约定方法名还可以支持以下几种语法:
  3.2用JPQL进行查询

  JPQL语言(Java Persistence Query Language)是一种和SQL非常类似的中间性和对象化查询语言,它最终会被编译成针对不同底层数据库的SQL语言,从而屏蔽不同数据库的差异。
  JPQL语言通过Query接口封装执行,Query接口封装了执行数据库查询的相关方法。调用 EntityManager的Query、NamedQuery及NativeQuery方法可以获得查询对象,进而可调用 Query接口的相关方法来执行查询操作。
  JPQL是面向对象进行查询的语言,可以通过自定义的JPQL完成UPDATE和DELETE操作。JPQL不支持使用INSERT,对于UPDATE或DELETE操作,必须使用注解@Modifying 进行修饰。
  (1)下面代码表示根据name值进行查找
  1. public interface UserRepository extends JpaRepository<User,Long> {
  2.     @Query("select u from User u where u.name = ?1")
  3.     User findByName(String name);
  4. }
复制代码
  (2)下面代码表示根据name值逬行模糊查找
  1. public interface UserRepository extends JpaRepository<User,Long> {
  2.     @Query("select u from User u where u.name like %?1")
  3.     List<User> findByName(String name);
  4. }
复制代码
  3.3用原生SQL进行查询

  (1)根据ID查询用户
  1. @Override
  2. @Query(value = "select * from user u where u.id = :id", nativeQuery = true)
  3. Optional<User> findById(@Param("id") Long id);
复制代码
  (2)查询所有用户
  1. @Query(value = "select * from user", nativeQuery = true)
  2. List<User> findAllNative();
复制代码
  (3)根据email查询用户
  1. @Query(value = "select * from user where email = ?1", nativeQuery = true)
  2. User findByEmail(String email);
复制代码
  (4)根据name查询用户,并返回分页对象Page
  1. @Query(value = "select * from user where name = ?1",
  2.     countQuery = "select count(*) from user where name = ?1",
  3.     nativeQuery = true)
  4. Page<User> findByName(String name, Pageable pageable);
复制代码
  (5)根据名字来修改email的值
  1. @Modifying
  2. @Query("update user set email = :email where name = :name")
  3. Void updateUserEmaliByName(@Param("name") String name,@Param("email") String email);
复制代码
  3.4 用 Specifications 进行查询

  如果要使 Repository 支持 Specification 查询,则需要在 Repository 中继承 JpaSpecification- Executor接口,具体使用见如下代码:
  1. package com.itheima.executor;
  2. import com.itheima.dao.ArticleRepository;
  3. import com.itheima.domain.Article;
  4. import org.junit.Test;
  5. import org.junit.runner.RunWith;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.boot.test.context.SpringBootTest;
  8. import org.springframework.data.domain.Page;
  9. import org.springframework.data.domain.PageRequest;
  10. import org.springframework.data.jpa.domain.Specification;
  11. import org.springframework.test.context.junit4.SpringRunner;
  12. import javax.persistence.criteria.*;
  13. @SpringBootTest
  14. @RunWith(SpringRunner.class)
  15. public class testJpaSpecificationExecutor {
  16.     @Autowired
  17.     private ArticleRepository articleRepository;
  18.     @Test
  19.     public void testJpaSpecificationExecutor(){
  20.         int pageNo = 0;
  21.         int pageSize = 5;
  22.         PageRequest pageable = PageRequest.of(pageNo,pageSize);
  23.         Specification<Article> specification = new Specification<Article>(){
  24.             @Override
  25.             public Predicate toPredicate(Root<Article> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
  26.                 Path path = root.get("id");
  27.                 Predicate predicate1 = criteriaBuilder.gt(path,2);
  28.                 Predicate predicate2 = criteriaBuilder.equal(root.get("num"),42283);
  29.                 Predicate predicate = criteriaBuilder.and(predicate1,predicate2);
  30.                 return predicate;
  31.             }
  32.         };
  33.         Page<Article> page = articleRepository.findAll(specification,pageable);
  34.         System.out.println("总记录数:"+page.getTotalElements());
  35.         System.out.println("当前第:"+(page.getNumber()+1)+"页");
  36.         System.out.println("总页数:"+page.getTotalPages());
  37.         System.out.println("当前页面的List:"+page.getContent());
  38.         System.out.println("当前页面的记录数:"+page.getNumberOfElements());
  39.     }
  40. }
复制代码
  
代码解释如下。
运行上面的测试代码,在控制台会输出如下结果(确保数据库己经存在数据):
  1. Hibernate: select card0_.id as id1_0_, card0_.num as num2_0_ from card card0_ where card0_.id>2 and card0_.num=422803 limit ?
  2. Hibernate: select count(cardO_.id) as col_0_0_ from card card0_ where cardO_Jd>2 and card0_.num=422803
  3. 总记录数:6
  4. 当前第:1页 总页数:2 当前页面的 List:
  5. [Card(id=4, num二422803), Card(id=8, num=422803), Card(id=10, num=422803), Card(id=20t num=422803), Card(id=23, num=422803)]
  6. 当前页面的记录数:5
复制代码
  3.5 用 ExampleMatcher 进行查询

  Spring Data可以通过Example对象来构造JPQL查询,具体用法见以下代码:
  
  1. User user = new User();
  2. user.setName("test");
  3. ExampleMatcher matcher = ExampleMatcher.matching()
  4.         .withIgnorePaths("name")
  5.         .withIncludeNullValues()
  6.         .withStringMatcher(ExampleMatcher.StringMatcher.ENDING);
  7. Example<User> example = Example.of(user,matcher);
  8. List<User> list = userRepository.findAll(example);
复制代码
  3.6 用谓语QueryDSL进行查询

  QueryDSL也是基于各种ORM之上的一个通用查询框架,它与Spring Data JPA是同级别的。 使用QueryDSL的API可以写岀SQL语句(Java代码,非真正标准SQL),不需要懂SQL语句。 它能够构建类型安全的查询。这与JPA使用原生查询时有很大的不同,可以不必再对“Object[]' 进行操作。它还可以和JPA联合使用。
4.用JPA开发文章管理模块

  4.1实现文章实体
  1. @Entity
  2. @Data
  3. public class Article extends BaseEntity implements Serializable {
  4.     @Id
  5.     @GeneratedValue(strategy = GenerationType.IDENTITY)
  6.     private long id;
  7.     @Column(nullable = false,unique = true)
  8.     @NotEmpty(message = "标题不能为空")
  9.     private String title;
  10.     @Column(nullable = false)
  11.     private String body;
  12. }
复制代码
代码解释如下:

  4.2 实现数据持久层
  1. @Service
  2. public interface ArticleRepository extends JpaRepository<Article,Long>,
  3.         JpaSpecificationExecutor<Article>{
  4.     Article findById(long id);
  5. }
复制代码
  4.3 通过创建服务接口和服务接口的实现类来完成业务逻辑功能

  (1)创建服务接口,见以下代码
  1. public interface ArticleService {
  2.     public List<Article> getArticleList();
  3.     public Article findArticleById(long id);
  4. }
复制代码
  (2)编写服务接口的实现

  在impl包下,新建article的impl实现service,并标注这个类为service服务类。
  通过implements声明使用ArticleService接口,并重写其方法,见以下代码:
  1. @Service
  2. public class ArticleServiceImpl implements ArticleService{
  3.     @Autowired
  4.     private ArticleRepository articleRepository;
  5.     @Override
  6.     public List<Article> getArticleList() {
  7.         return articleRepository.findAll();
  8.     }
  9.     @Override
  10.     public Article findArticleById(long id) {
  11.         return articleRepository.findById(id);
  12.     }
  13. }
复制代码
  (3)实现增加、删除、修改和查询的控制层API功能
  1. package com.itheima.controller;
  2. import com.itheima.dao.ArticleRepository;
  3. import com.itheima.domain.Article;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.data.domain.Page;
  6. import org.springframework.data.domain.PageRequest;
  7. import org.springframework.data.domain.Pageable;
  8. import org.springframework.data.domain.Sort;
  9. import org.springframework.stereotype.Controller;
  10. import org.springframework.web.bind.annotation.*;
  11. import org.springframework.web.servlet.ModelAndView;
  12. @Controller
  13. @RequestMapping("article")
  14. public class ArticleController {
  15.     @Autowired
  16.     private ArticleRepository articleRepository;
  17.     @RequestMapping("")
  18.     public ModelAndView articlelist(@RequestParam(value = "start",defaultValue = "0")Integer start,
  19.                                     @RequestParam(value = "limit",defaultValue = "5")Integer limit) {
  20.         start = start < 0 ? 0 : start;
  21.         Sort sort = Sort.by(Sort.Direction.DESC, "id");
  22.         Pageable pageable = PageRequest.of(start, limit, sort);
  23.         Page<Article> page = articleRepository.findAll(pageable);
  24.         ModelAndView mav = new ModelAndView("article/list");
  25.         mav.addObject("page", page);
  26.         return mav;
  27.     }
  28.     @GetMapping("/{id}")
  29.     public ModelAndView getArticle(@PathVariable("id") long id) {
  30.         Article article = articleRepository.findById(id);
  31.         ModelAndView mav = new ModelAndView("article/show");
  32.         mav.addObject("article", article);
  33.         return mav;
  34.     }
  35.     @GetMapping("/add")
  36.     public String addArticle(){
  37.         return "article/add";
  38.     }
  39.     @PostMapping("")
  40.     public String saveArticle(Article model){
  41.         articleRepository.save(model);
  42.         return "redirect:/article/";
  43.     }
  44.     @DeleteMapping("/{id}")
  45.     public String deleteArticle(@PathVariable("id") long id){
  46.         articleRepository.deleteById(id);
  47.         return "redirect:";
  48.     }
  49.     @GetMapping("/edit/{id}")
  50.     public ModelAndView editArticle(@PathVariable("id") long id) {
  51.         Article model = articleRepository.findById(id);
  52.         ModelAndView mav = new ModelAndView("article/edit");
  53.         mav.addObject("article", model);
  54.         return mav;
  55.     }
  56.     @PutMapping("/{id}")
  57.     public String editArticleSave(Article model,long id){
  58.         model.setId(id);
  59.         articleRepository.save(model);
  60.         return "redirect:";
  61.     }
  62. }
复制代码
5.实现自动填充字段

  在操作实体类时,通常需要记录创建时间和更新时间。如果每个对象的新增或修改都用手工来操作,则会显得比较烦琐。这时可以使用Spring Data JPA的注解@EnableJpaAuditing来实现自动填充字段功能。具体步骤如下。
  (1)开启JPA的审计功能

  通过在入口类中加上注解@EnableJpaAuditing,来开启JPA的Auditing功能
  (2)创建基类
  1. @Data
  2. @MappedSuperclass
  3. @EntityListeners(AuditingEntityListener.class)
  4. public abstract class BaseEntity {
  5.     @CreatedDate
  6.     private Long createTime;     //createTime
  7.     @LastModifiedDate
  8.     private Long updateTime;     //updateTime
  9.     @Column(name = "create_by")
  10.     @CreatedBy
  11.     private Long createBy; //createBy
  12.     @Column(name = "lastmodified_by")
  13.     @LastModifiedBy
  14.     private Long lastModifiedBy; //lastModifiedBy
  15. }
复制代码
  (3)赋值给 CreatedBy 和 LastModifiedBy

  上述代码已经自动实现了创建和更新时间赋值,但是创建人和最后修改人并没有赋值,所以需要实现"AuditorAware"接口来返回需要插入的值
  1. public class InjectAuditor implements AuditorAware<String> {
  2.     //给 Bean 中的 @CreatedBy @LastModifiedBy 注入操作人
  3.     @Override
  4.     public Optional<String> getCurrentAuditor() {
  5.         SecurityContext securityContext = SecurityContextHolder.getContext();
  6.         if (securityContext==null) {
  7.             return null;
  8.         }
  9.         if (securityContext.getAuthentication()==null) {
  10.             return null;
  11.         }else {
  12.             String loginUserName = securityContext.getAuthentication().getName();
  13.             Optional<String> name = Optional.ofNullable(loginUserName);
  14.             return name;
  15.         }
  16.     }
  17. }
复制代码
  代码解释如下。
  @Configuration:表示此类是配置类。让Spring来加我该类配置。
   SecurityContextHolder:用于获取 SecurityContext,其存放了 Authentication 和特定于请求的安全信息。这里是判断用户是否登录。如果用户登录成功,则荻取用户名,然后把用户名返回给操作人。
  (4)使用基类

  要在其他类中使用基类,通过在其他类中继承即可
6.关系映射开发


  6.1认识实体间关系映射

    对象关系映射(object relational mapping )是指通过将对象状态映射到数据库列'来开发和 维护对象和关系数据库之间的关系。它能够轻松处理(执行)各种数据库操作,如插入、更新、 删除等
  (1)映射方向

   ORM的映射方向是表与表的关联(join ),可分为两种。
  (2)ORM映射类型

  

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4