栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Springboot整合Spring Data JPA

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Springboot整合Spring Data JPA

1 Spring Data JPA 1.Spring Data JPA的概念


在介绍Spring Data JPA的时候,我们首先认识下Hibernate。Hibernate是数据访问解决技术的绝对霸主,使用O/R映射(Object-Relational Mapping)技术实现数据访问,O/R映射即将领域模型类和数据库的表进行映射,通过程序操作对象而实现表数据操作的能力,让数据访问操作无须关注数据库相关的技术。
随着Hibernate的盛行,Hibernate主导了EJB 3.0的JPA规范,JPA即Java Persistence API。JPA是一个基于O/R映射的标准规范(目前最新版本是JPA 2.1)。所谓规范即只定义标准规则(如注解、接口),不提供实现,软件提供商可以按照标准规范来实现,而使用者只需按照规范中定义的方式来使用,而不用和软件提供商的实现打交道。JPA的主要实现由Hibernate、EclipseLink和OpenJPA等,这也意味着我们只要使用JPA来开发,无论是哪一个开发方式都是一样的。
Spring Data JPA是Spring Data的一个子项目,它通过提供基于JPA的Repository极大地减少了JPA作为数据访问方案的代码量。


2. 定义数据访问层

使用Spring Data JPA建立数据访问层十分简单,只需在dao层定义一个继承JpaRepository的接口即可,如:

package com.example.demo.dao;

import com.example.demo.entity.SearchRecordEntity;
import org.springframework.data.jpa.repository.JpaRepository;


public interface SearchRecordRepository extends JpaRepository {

}
3.配置使用Spring Data JPA


在Spring环境中,使用Spring Data JPA可通过@EnableJpaRepositories注解来开启Spring Data JPA的支持,@EnableJpaRepositories接收的value参数用来扫描数据访问层所在包下的数据访问的接口定义。

4.定义查询方法


在讲解查询方法前,假设我们有一张数据表叫 t_search_record,有ID(Number)、NAME(Varchar)、remark (Varchar)几个字段;对应的实体类叫SearchRecordEntity,分别有id(Long)、name(String)、remark(String)。下面我们就以这个简单的实体查询作为演示。
(1)根据属性名查询
Spring Data JPA支持通过定义在Repository接口中的方法名来定义查询,而方法名是根据实体类的属性名来确定的。
1)常规查询。根据属性名来定义查询方法,示例如下:

package com.example.demo.dao;

import com.example.demo.entity.SearchRecordEntity;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;


public interface SearchRecordRepository extends JpaRepository {
    
    List findByName(String name);

    
    List findByNameLike(String name);

    
    List findByNameAndRemark(String name,String remark);


}

从代码可以看出,这里使用了findBy、Like、And这样的关键字。其中findBy可以用find、read、readBy、query、queryBy、get、getBy来代替。
2)限制结果数量。结果数量是用top和first关键字来实现的,例如:

package com.example.demo.dao;

import com.example.demo.entity.SearchRecordEntity;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;


public interface SearchRecordRepository extends JpaRepository {
  
    
    List findFirst10ByName(String name);

    
    List findTop30ByName(String name);


}

(2)使用JPA的NamedQuery查询
Spring Data JPA支持用JPA的NameQuery来定义查询方法,即一个名称映射一个查询语句。例如:

package com.example.demo.entity;

import lombok.Data;
import org.hibernate.annotations.NamedQuery;

import javax.persistence.Entity;


@Data
@Entity
@NamedQuery(name = "SearchRecordEntity.findByName",query = "select sr from SearchRecordEntity sr where sr.name=?1")
public class SearchRecordEntity {
    
    private Integer id;

    
    private String name;
 
    
    private String remark;

}

使用如下语句:

package com.example.demo.dao;

import com.example.demo.entity.SearchRecordEntity;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;


public interface SearchRecordRepository extends JpaRepository {
    
    List findByName(String name);

}

(3)使用@Query查询
1)使用参数索引。Spring Data JPA还支持用@Query注解在接口的方法上实现查询,例如:

package com.example.demo.dao;

import com.example.demo.entity.SearchRecordEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import java.util.List;


public interface SearchRecordRepository extends JpaRepository {
    
    @Query("select sr from SearchRecordEntity sr where sr.name = ?1")
    List findByName(String name);

}

2)使用命名参数。上面的例子是使用参数的索引号来查询的,在Spring Data JPA里还支持在语句里用名称来匹配查询参数,例如:

package com.example.demo.dao;

import com.example.demo.entity.SearchRecordEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import java.util.List;


public interface SearchRecordRepository extends JpaRepository {
    
    @Query("select sr from SearchRecordEntity sr where sr.name = :name")
    List findByName(String name);

}

3)更新查询。Spring Data JPA支持@Modifying和@Query注解组合来实现更新查询,例如:

package com.example.demo.dao;

import com.example.demo.entity.SearchRecordEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;


public interface SearchRecordRepository extends JpaRepository {
  
    
    @Modifying
    @Transactional
    @Query("update SearchRecordEntity sr  set  sr.name = ?1")
    int setName(String name);

}

(4)Specification
JPA提供了基于准则查询的方式,即Criteria查询。而Spring Data JPA提供了一个Specification(规范)接口让我们可以更方便地构造准则查询,Specification接口定义了一个toPredicate方法用来构造查询条件。
1)定义。我们的接口类必需实现JpaSpecificationExecutor接口,代码如下:

package com.example.demo.dao;

import com.example.demo.entity.SearchRecordEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

import java.util.List;


public interface SearchRecordRepository extends JpaRepository,JpaSpecificationExecutor {

}

然后需要定义Criterial查询,代码如下:

package com.example.demo.config;

import com.example.demo.entity.SearchRecordEntity;
import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;


public class CustomerSpecs {

    public static Specification  getSearchRecord(String name){
        return new Specification() {
            @Override
            public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder criteriaBuilder) {
                return criteriaBuilder.equal(root.get("name"),name);
            }
        };
    }


}

我们使用Root来获得需要查询的属性,通过CriteriaBuilder构造查询条件。
注意:CriteriaBuilder、CriteriaQuery、Predicate、Root都是来自JPA的接口。
CriteriaBuilder包含的条件构造有:exists、and、or、not、conjunction、disjunction、isTrue、isFalse、isNull、isNotNull、equal、notEqual、greaterThan、greaterThanOrEqualTo、lessThan、lessThanOrEqualTo、between等,详细请查看CriteriaBuilder的API。
2)使用静态导入:

package com.example.demo.service;

import com.example.demo.dao.SearchRecordRepository;
import com.example.demo.entity.SearchRecordEntity;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;
import static com.example.demo.config.CustomerSpecs.*;


@Service
public class SearchRecordService {
    @Resource
    private SearchRecordRepository searchRecordRepository;

    public List findByName(String name){
        List all = searchRecordRepository.findAll(getSearchRecord(name));
        return all;
    }


}

(5)排序与分页
Spring Data JPA充分考虑了在实际开发中所必需的排序和分页的场景,为我们提供了Sort类以及Page接口和Pageable接口。
1)定义:

package com.example.demo.dao;

import com.example.demo.entity.SearchRecordEntity;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;


import java.util.List;


@Repository
public interface SearchRecordRepository extends JpaRepository,JpaSpecificationExecutor {
 

    
    List findByName(String name, Sort sort);

    
    Page findByName(String name, Pageable pageable);

}

2)使用排序:

package com.example.demo.service;

import com.example.demo.dao.SearchRecordRepository;
import com.example.demo.entity.SearchRecordEntity;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import static com.example.demo.config.CustomerSpecs.*;


@Service
public class SearchRecordService {
    @Resource
    private SearchRecordRepository searchRecordRepository;

    public List findByNameAndSort(String name){
        List searchRecordEntities = searchRecordRepository.findByName(name, Sort.by(Sort.Direction.DESC,"id"));
        return searchRecordEntities;
    }
    

}

3)使用分页:

package com.example.demo.service;

import com.example.demo.dao.SearchRecordRepository;
import com.example.demo.entity.SearchRecordEntity;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import static com.example.demo.config.CustomerSpecs.*;


@Service
public class SearchRecordService {
    @Resource
    private SearchRecordRepository searchRecordRepository;

    public Page findByNamePage(String name){
        Page searchRecordEntities = searchRecordRepository.findByName(name, PageRequest.of(0, 10));
        return searchRecordEntities;
    }

}

其中Page接口可以获得当前页面的记录、总页数、总记录数、是否有上一页或下一页等。


5.自定义Repository的实现


Spring Data提供了和CrudRepository、PagingAndSortingRepository;Spring Data JPA也提供了JpaRepository。如果我们想把自己常用的数据库操作封装起来,像JpaRepository一样提供给我们领域类的Repository接口使用,应该怎么操做呢?
(1)定义自定义Repository接口

package com.example.demo.dao;

import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.PagingAndSortingRepository;

import java.io.Serializable;


@NoRepositoryBean
public interface CustomRepository extends PagingAndSortingRepository {
    
    public T getById(ID id);


}

(2)定义接口实现类

package com.example.demo.dao;

import org.springframework.data.jpa.repository.support.SimpleJpaRepository;

import javax.persistence.EntityManager;
import java.io.Serializable;


public class CustomRepositoryImpl extends SimpleJpaRepository implements CustomRepository {
    
    private final EntityManager entityManager;

    
    public CustomRepositoryImpl(Class clazz, EntityManager entityManager) {
        super(clazz, entityManager);
        this.entityManager = entityManager;
    }

    @Override
    public T getById(ID id) {
        //在此处定义数据访问操作,如调用findAll方法并构造一些查询条件。
        T t = null;
        return t;
    }

}

(3)自定义RepositoryFactoryBean。

自定义JpaRepositoryFactoryBean替代默认RepositoryFactoryBean,我们会获得一个RepositoryFactory,RepositoryFactory将会注册我们自定义的Repository的实现:

package com.example.demo.dao;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.support.*;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.util.Assert;

import javax.persistence.EntityManager;
import java.io.Serializable;


public class CustomRepositoryFactoryBean,S,ID extends Serializable> extends JpaRepositoryFactoryBean {

    public CustomRepositoryFactoryBean(Class repositoryInterface) {
        super(repositoryInterface);
    }

    
    @Override
    protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager){
        return new CustomRepositoryFactory(entityManager);
    }

    
    private static class CustomRepositoryFactory extends JpaRepositoryFactory {
        public CustomRepositoryFactory(EntityManager entityManager) {
            super(entityManager);
        }

        
        @Override
        @SuppressWarnings({"unchecked"})
        protected SimpleJpaRepository getTargetRepository(RepositoryInformation information, EntityManager entityManager) {
            return new CustomRepositoryImpl(information.getDomainType(), entityManager);
        }

        
        @Override
        protected Class getRepositoryBaseClass(RepositoryMetadata metadata){
            return CustomRepositoryImpl.class;
        }

    }


}

(4)开启自定义支持使用@EnableJpaRepositories的repositoryFactoryBeanClass来指定FactoryBean即可,代码如下:
@EnableJpaRepositories(repositoryFactoryBeanClass= CustomRepositoryFactoryBean.class) 

2 Spring Boot的支持
1.JDBC的自动配置
spring-boot-starter-data-jpa依赖于spring-boot-starter-jdbc,而Spring Boot对JDBC做了一些自动配置。源码在org.springframework.boot.autoconfigure.jdbc下

从源码分析可以看出,通过“spring.datasoure”为前缀的属性自动配置dataSource;Spring Boot自动开启了注解事务的支持(@EnableTransactionManagement);还配置了一个jdbcTemplate。
Spring Boot还提供了一个初始化数据的功能:放置在类路径下的schema.sql文件会自动用来初始化表结构;放置在类路径下的data.sql文件会自动用来填充表数据。


2.对JPA的自动配置


Spring Boot对JPA的自动配置放置在org.springframework.boot.autoconfigure.orm.jpa下

从HibernateJpaAutoConfiguration可以看出,Spring Boot默认JPA的实现者是Hibernate;HibernateJpaAutoConfiguration依赖于DataSourceAutoConfiguration。从JpaProperties的源码可以看出,配置JPA可以使用spring.jpa为前缀的属性在application.properties中配置。
从JpaBaseConfiguration的源码中可以看出,Spring Boot为我们配置了transactionManager、jpaVendorAdapter、entityManagerFactory等Bean。JpaBaseConfiguration还有一个getPackagesToScan方法,可以自动扫描注解有@Entity的实体类。
在Web项目中我们经常会遇到在控制器或者页面访问数据的时候出现会话连接已关闭的错误,这时候我们会配置一个Open EntityManager(Session)In View这个过滤器。令人惊喜的是,Spring Boot为我们自动配置了OpenEntityManagerInViewInterceptor这个Bean,并注册到Spring MVC的拦截器中。


3.对Spring Data JPA的自动配置


而Spring Boot对Spring Data JPA的自动配置放置在org.springframework.boot.autoconfigure.data.jpa下

从JpaRepositoriesAutoConfiguration和JpaRepositoriesAutoConfigureRegistrar源码可以看出,JpaRepositoriesAutoConfiguration是依赖于HibernateJpaAutoConfiguration配置的,且Spring Boot自动开启了对Spring Data JPA的支持,即我们无须在配置类显示声明@EnableJpaRepositories。


4.Spring Boot下的Spring Data JPA


由以上分析可知,我们在Spring Boot下使用Spring Data JPA,在项目的Maven依赖里添加spring-boot-stater-data-jpa,然后只需定义DataSource、实体类和数据访问层,并在需要使用数据访问的地方注入数据访问层的Bean即可,无须任何额外配置。

3  代码示例 1.entity层
package com.example.demo.entity;

import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.Data;
import org.hibernate.annotations.NamedQuery;

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


@Data
@Entity
@AllArgsConstructor
@NoArgsConstructor
@NamedQuery(name = "SearchRecordEntity.withNameAndRemarkQueryList",query = "select sr from SearchRecordEntity sr where sr.name=?1 and remark=?2")
public class SearchRecordEntity {
    
    @Id
    @GeneratedValue
    private Integer id;

    
    private String name;

    
    private String remark;
}

在此例中使用的注解也许和你平时经常使用的注解实体类不大一样,比如没有使用@Table(实体类映射表名)、@Column(属性映射字段名)注解。这是因为我们是采用正向工程通过实体类生成表结构,而不是通过逆向工程从表结构生成数据库。
在这里,我们没有通过@Column注解来注解普通属性,@Column是用来映射属性名和字段名,不注解的时候hibernate会自动根据属性名生成数据表的字段名。如属性名name映射成字段NAME;多字母属性如testName会自动映射为TEST_NAME。表名的映射规则也如此。


2.定义数据访问接口
package com.example.demo.dao;

import com.example.demo.entity.SearchRecordEntity;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;


import java.util.List;


@Repository
public interface SearchRecordRepository extends JpaRepository,JpaSpecificationExecutor {
    
    List findByName(String name);


    
    List findByNameAndRemark(String name,String remark);

    
    @Query("select sr from SearchRecordEntity sr where sr.name = :name and sr.remark = :remark")
    List withNameAndRemarkQuery(@Param("name") String name,@Param("remark") String remark);

    
    List withNameAndRemarkQueryList(String name, String remark);

 
}
3.controller层
package com.example.demo.controller;

import com.example.demo.dao.SearchRecordRepository;
import com.example.demo.entity.SearchRecordEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;


@RestController
@RequestMapping("/api/search")
public class SearchRecordController {
    @Autowired
    private SearchRecordRepository searchRecordRepository;

    
    @RequestMapping("/save")
    public SearchRecordEntity save(String name, String remark) {
        SearchRecordEntity sr = searchRecordRepository.save(new SearchRecordEntity(null, name, remark));
        return sr;
    }

    
    @RequestMapping("/q1")
    public List q1(String name) {
        List searchRecordEntityList = searchRecordRepository.findByName(name);
        return searchRecordEntityList;

    }

    
    @RequestMapping("/q2")
    public List q2(String name, String remark) {
        List searchRecordEntities = searchRecordRepository.findByNameAndRemark(name, remark);
        return searchRecordEntities;

    }

    
    @RequestMapping("/q3")
    public List q3(String name, String remark) {
        List searchRecordEntities = searchRecordRepository.withNameAndRemarkQuery(name, remark);
        return searchRecordEntities;

    }

    
    @RequestMapping("/q4")
    public List q4(String name, String address) {
        List searchRecordEntities = searchRecordRepository.withNameAndRemarkQueryList(name, address);
        return searchRecordEntities;
    }

    
    @RequestMapping("/sort")
    public List sort() {
        List searchRecordEntities = searchRecordRepository.findAll(Sort.by(Sort.Direction.DESC, "id"));
        return searchRecordEntities;
    }

    
    @RequestMapping("/page")
    public Page page() {
        Page pagePeople = searchRecordRepository.findAll(PageRequest.of(1, 2));
        return pagePeople;
    }


}
4.启动类
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@SpringBootApplication
@EnableJpaRepositories(value = "com.example.demo.dao")
@EnableTransactionManagement
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}
5.配置文件
server.port=8080
# mysql
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql://localhost:3306/data-portrait?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC&allowPublicKeyRetrieval=true
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
logging.file.name=logs/springboot-jpa.log
spring.jpa.database=mysql
# hibernate提供了根据实体类自动维护数据库表结构的功能,可通过spring.jpa.hibernate.ddl-auto来配置:
# create:启动时删除上一次生成的表,并根据实体类生成表,表中数据会被清空。
# create-drop:启动时根据实体类生成表,sessionFactory关闭时表会被删除。
# update:启动时会根据实体类生成表,当实体类属性变动的时候,表结构也会更新,在初期开发阶段使用此选项。
# validate:启动时验证实体类和数据表是否一致,在我们数据结构稳定时采用此选项。
# none:不采取任何措施。
spring.jpa.hibernate.ddl-auto=none
# spring.jpa.show-sql用来设置hibernate操作的时候在控制台显示其真实的sql语句
spring.jpa.show-sql=true
# 让控制器输出的json字符串格式更美观
spring.jackson.serialization.indent-output=true




6.测试

1》服务启动后,发现新建了两张表,hibernate会为我们自动生成一个名为HIBERNATE_SEQUENCE的序列表,用来记录search_record_entity表的主键信息

2》 插入数据(sql或接口均可),为保险起见,建表语句还是尽量不自动生成,自己规范定义即可。

数据准备: 

 

 hibernate_sequence表用于记录search_record_entity表下一条数据的主键值。

 3》查询接口

调用:http://localhost:8080/api/search/q1?name=全省影视

注意:这里有个小问题,SearchRecordEntity需要提供默认的无参构造方法,添加注解@NoArgsConstructor即可。

调用:​​​​​​http://localhost:8080/api/search/q2?name=全省影视&remark=备注1

调用:​​​​​​http://localhost:8080/api/search/q3?name=全省影视&remark=备注1

调用:​​​​​​http://localhost:8080/api/search/q4?name=全省影视&remark=备注1

调用:http://localhost:8080/api/search/sort

调用:http://localhost:8080/api/search/page

7.总结

springdatajpa支持查询方式有3种:

1》使用springdatajpa中规范定义的方法名查询,不需要写查询语句,即可完成查询。

规范定义的方法名,如findBy、getBy、deleteBy等开头的,如下:

 2》在entity层使用@NamedQuery注解查询

3》在接口层使用@Query注解自定义查询语句

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/837469.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号