jpa 多数据源,springboot多数据源jpa

  jpa 多数据源,springboot多数据源jpa

  00-1010一、Spring数据JPA 1的基本用法。概念JPA的由来是什么?春季数据JPA2。快速入门1。添加依赖关系2。添加配置文件3。实体类别4。知识库建设5。测试6。基本查询7。自定义查询3。总结2。Spring数据JPA 1的高级用法。自定义SQL查询2、分页查询Spring数据JPA内置了分页功能3、复杂查询4、多表查询5、汇总3、Spring数据JPA多数据源用法1、前言2、多数据源支持3、汇总

  

目录

Spring Data JPA是Spring Boot系统中配置协议的最佳实现,大大简化了项目中数据库的操作。

 

  

一、Spring Data JPA基本用法

 

  

1、概念

ORM框架可以将Java对象映射到关系数据库,直接持久化复杂的Java对象。ORM框架的出现,开发者可以摆脱数据库编程,更加专注于业务模型和业务逻辑。目前流行的ORM框架有Hibernate、MyBatis、TopLink、Spring JDBC等。

 

  在JPA规范之前,由于没有官方标准,各个ORM框架的API差别很大,使用某个ORM框架的系统会受到这个ORM的标准的严格限制。基于此,Sun推出新的JPA ORM主要有以下几个原因:第一,简化现有的Java EE和Java SE应用开发;第二,Sun希望整合ORM技术,实现统一的API调用接口。

  

JPA由来

JPA(Java Persistence API)是由Sun正式提出的Java持久性规范。它为Java开发人员管理Java应用程序中的关系数据提供了一个对象/关联映射工具。它的出现主要是为了简化现有的持久化开发工作,整合ORM技术,结束Hibernate、TopLink、JDO等ORM框架各自为政的现状。

 

  注意:JPA是一套规范,不是一套产品,所以和Hibernate、TopLink、JDO一样,都是一套产品。如果这些产品实现了这个JPA规范,那么我们可以称之为JPA实现产品。

  

JPA是什么

Spring Data JPA是Spring基于ORM框架和JPA规范封装的JPA应用框架,允许开发者用最少的代码访问和操作数据。它提供了常见的功能,包括添加、删除、修改、搜索等。且易于扩展。学习和使用Spring Data JPA可以大大提高开发效率。实际上,Spring Data JPA是Spring基于Hibernate构建的JPA应用解决方案,方便在Spring Boot项目中使用JPA技术。

 

  Spring Data JPA把我们从DAO层的操作中解放出来,基本上所有的CRUD都可以由它来实现。

  

Spring Data JPA

 

  

2、快速上手

依赖关系groupIdorg.springframework.boot/groupId artifactId spring-boot-starter-data-JPA/artifactId/dependency dependency groupId MySQL/groupId artifactId MySQL-connector-Java/artifactId/dependency

 

  

1.添加依赖

spring . data source . URL=JDBC : my SQL ://localhost :3306/test?server time zone=utcuse unicode=true character encoding=utf-8 use SSL=true spring . data source . username=rootspring . data source . password=rootspring . data source . driv er-class-name=com . MySQL . CJ . JDBC . driver spring . JPA . properties . hibernate . hbm 2 DDL . auto=create spring . JPA . propertie

 

  s.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect#SQL 输出spring.jpa.show-sql=true#format 一下 SQL 进行输出spring.jpa.properties.hibernate.format_sql=truehibernate.hbm2ddl.auto 参数的作用主要用于:自动创建、更新、验证数据库表结构,有四个值。

  create:每次加载 Hibernate 时都会删除上一次生成的表,然后根据 model 类再重新来生成新表,哪怕两次没有任何改变也要这样执行,这就是导致数据库表数据丢失的一个重要原因。create-drop:每次加载 Hibernate 时根据 model 类生成表,但是 sessionFactory 一关闭,表就自动删除。update:最常用的属性,第一次加载 Hibernate 时根据 model 类会自动建立起表的结构(前提是先建立好数据库),以后加载 Hibernate 时根据 model 类自动更新表结构,即使表结构改变了,但表中的行仍然存在,不会删除以前的行。要注意的是当部署到服务器后,表结构是不会被马上建立起来的,是要等应用第一次运行起来后才会。validate:每次加载 Hibernate 时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。其他

  dialect 主要是指定生成表名的存储引擎为 InnoDBshow-sql 是否在日志中打印出自动生成的 SQL,方便调试的时候查看

 

  

3.实体类

@Entitypublic class User {    @Id    @GeneratedValue    private Long id;    @Column(nullable = false, unique = true)    private String userName;    @Column(nullable = false)    private String passWord;    @Column(nullable = false, unique = true)    private String email;    @Column(nullable = true, unique = true)    private String nickName;    @Column(nullable = false)    private String regTime;    //省略 getter settet 方法、构造方法}

@Entity(name="EntityName") 必须,用来标注一个数据库对应的实体,数据库中创建的表名默认和类名一致。其中,name 为可选,对应数据库中一个表,使用此注解标记 Pojo 是一个 JPA 实体。@Table(name="",catalog="",schema="") 可选,用来标注一个数据库对应的实体,数据库中创建的表名默认和类名一致。通常和@Entity配合使用,只能标注在实体的 class 定义处,表示实体对应的数据库表的信息。@Id必须,@Id定义了映射到数据库表的主键的属性,一个实体只能有一个属性被映射为主键。@GeneratedValue(strategy=GenerationType,generator="") 可选,strategy: 表示主键生成策略,有 AUTO、INDENTITY、SEQUENCE 和 TABLE 4 种,分别表示让 ORM 框架自动选择,generator: 表示主键生成器的名称。@Column(name = "user_code", nullable = false, length=32) 可选,@Column 描述了数据库表中该字段的详细定义,这对于根据 JPA 注解生成数据库表结构的工具。name: 表示数据库表中该字段的名称,默认情形属性名称一致;nullable: 表示该字段是否允许为 null,默认为 true;unique: 表示该字段是否是唯一标识,默认为 false;length: 表示该字段的大小,仅对 String 类型的字段有效。@Transient可选,@Transient 表示该属性并非一个到数据库表的字段的映射,ORM 框架将忽略该属性。@Enumerated 可选,使用枚举的时候,我们希望数据库中存储的是枚举对应的 String 类型,而不是枚举的索引值,需要在属性上面添加 @Enumerated(EnumType.STRING) 注解。

 

  

4.Repository构建

创建的 Repository 只要继承 JpaRepository 即可,就会帮我们自动生成很多内置方法。另外还有一个功能非常实用,可以根据方法名自动生产 SQL,比如 findByUserName 会自动生产一个以 userName 为参数的查询方法,比如 findAll 会自动查询表里面的所有数据等。

 

  

public interface UserRepository extends JpaRepository<User,Long> {    User findByUserName(String userName);    User findByUserNameOrEmail(String username,String email);}

我们只需要在对应的 Repository 中创建好方法,使用的时候直接将接口注入到类中调用即可。在 IDEA 中打开类 UserRepository,在这个类的大括号内的区域右键单击,选择 Diagrams Show Diagram 选项,即可打开类图,如下:

 

  

 

  通过上图我们发现 JpaRepository 继承 PagingAndSortingRepository 和 QueryByExampleExecutor,PagingAndSortingRepository 类主要负责排序和分页内容,QueryByExampleExecutor 提供了很多示例的查询方法,如下:

  

public interface QueryByExampleExecutor<T> {     <S extends T> S findOne(Example<S> example);   //根据“实例”查找一个对象    <S extends T> Iterable<S> findAll(Example<S> example);     //根据“实例”查找一批对象    <S extends T> Iterable<S> findAll(Example<S> example,Sort sort);  //根据“实例”查找一批对象,且排序    <S extends T> Page<S> findAll(Example<S> example,Pageable pageable);  //根据“实例”查找一批对象,且排序和分页    <S extends T> long count(Example<S> example);  //根据“实例”查找,返回符合条件的对象个数    <S extends T> boolean exists(Example<S> example);  //根据“实例”判断是否有符合条件的对象}

因此,继承 JpaRepository 的会自动拥有上述这些方法和排序、分页功能。查看源码我们发现 PagingAndSortingRepository 又继承了 CrudRepository。CrudRepository 的源码如下:

 

  

@NoRepositoryBeanpublic interface CrudRepository<T,ID> extends Repository<T,ID> {    <S extends T> S save(S entity);    <S extends T> Iterable<S> saveAll(Iterable<S> entities);    Optional<T> findById(ID id);    boolean existsById(ID id);    Iterable<T> findAll();    Iterable<T> findAllById(Iterable<ID> ids);    long count();    void deleteById(ID id);    void delete(T entity);    void deleteAll(Iterable<? extends T> entities);    void deleteAll();}

从 CrudRepository 的源码可以看出 CrudRepository 内置了我们最常用的增、删、改、查的方法,方便我们去使用,因为 JpaRepository 继承了 PagingAndSortingRepository,PagingAndSortingRepository 继承了 CrudRepository,所以继承 JpaRepository 的类也默认拥有了上述方法。

 

  因此使用 JPA 操作数据库时,只需要构建的 Repository 继承了 JpaRepository,就会拥有了很多常用的数据库操作方法。

  

 

  

5.测试

创建好 UserRepository 之后,当业务代码中需要使用时直接将此接口注入到对应的类中,在 Spring Boot 启动时,会自动根据注解内容创建实现类并注入到目标类中。

 

  

@RunWith(SpringRunner.class)@SpringBootTestpublic class UserRepositoryTests {    @Resource    private UserRepository userRepository;    @Test    public void test()  {        Date date = new Date();        DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG,DateFormat.LONG);                String formattedDate = dateFormat.format(date);        userRepository.save(new User("aa","aa@126.com","aa","aa123456",formattedDate));        userRepository.save(new User("bb","bb@126.com","bb","bb123456",formattedDate));        userRepository.save(new User("cc","cc@126.com","cc","cc123456",formattedDate));        Assert.assertEquals(9,userRepository.findAll().size());        Assert.assertEquals("bb",userRepository.findByUserNameOrEmail("bb","cc@126.com").getNickName());        userRepository.delete(userRepository.findByUserName("aa1"));    }}

 

  

6.基本查询

我们可以将 Spring Data JPA 查询分为两种,一种是 Spring Data JPA 默认实现的,另一种是需要根据查询的情况来自行构建。

 

  预生成方法

  预生成方法就是我们上面看到的那些方法,因为继承了 JpaRepository 而拥有了父类的这些内容。

  (1)继承 JpaRepository

  

public interface UserRepository extends JpaRepository<User,Long> {}

(2)使用默认方法

 

  

//所有父类拥有的方法都可以直接调用,根据方法名也可以看出它的含义。@Testpublic void testBaseQuery() {userRepository.findAll();userRepository.findById(1l);userRepository.save(user);userRepository.delete(user);userRepository.count();userRepository.existsById(1l);// ...    }

 

  

7.自定义查询

Spring Data JPA 可以根据接口方法名来实现数据库操作,主要的语法是 findXXBy、readAXXBy、queryXXBy、countXXBy、getXXBy 后面跟属性名称,利用这个功能仅需要在定义的 Repository 中添加对应的方法名即可,使用时 Spring Boot 会自动帮我们实现,示例如下。

 

  根据用户名查询用户:

  

User findByUserName(String userName);

也可以加一些关键字 And、or:

 

  

User findByUserNameOrEmail(String username,String email);

修改、删除、统计也是类似语法:

 

  

Long deleteById(Long id);Long countByUserName(String userName)

基本上 SQL 体系中的关键词都可以使用,如 Like 、IgnoreCase、OrderBy:

 

  

List<User> findByEmailLike(String email);User findByUserNameIgnoreCase(String userName);List<User> findByUserNameOrderByEmailDesc(String email);

可以根据查询的条件不断地添加和拼接,Spring Boot 都可以正确解析和执行,其他使用示例可以参考下表。

 

  KeywordSampleJPQL snippetAndfindByLastnameAndFirstname… where x.lastname = ?1 and x.firstname = ?2OrfindByLastnameOrFirstname… where x.lastname = ?1 or x.firstname = ?2Is,EqualsfindByFirstname,findByFirstnameIs,findByFirstnameEquals… where x.firstname = ?1BetweenfindByStartDateBetween… where x.startDate between ?1 and ?2LessThanfindByAgeLessThan… where x.age < ?1LessThanEqualfindByAgeLessThanEqual… where x.age <= ?1GreaterThanfindByAgeGreaterThan… where x.age > ?1GreaterThanEqualfindByAgeGreaterThanEqual… where x.age >= ?1AfterfindByStartDateAfter… where x.startDate > ?1BeforefindByStartDateBefore… where x.startDate < ?1IsNullfindByAgeIsNull… where x.age is nullIsNotNull,NotNullfindByAge(Is)NotNull… where x.age not nullLikefindByFirstnameLike… where x.firstname like ?1NotLikefindByFirstnameNotLike… where x.firstname not like ?1StartingWithfindByFirstnameStartingWith… where x.firstname like ?1(parameter bound with appended%)EndingWithfindByFirstnameEndingWith… where x.firstname like ?1(parameter bound with prepended%)ContainingfindByFirstnameContaining… where x.firstname like ?1(parameter bound wrapped in%)OrderByfindByAgeOrderByLastnameDesc… where x.age = ?1 order by x.lastname descNotfindByLastnameNot… where x.lastname <> ?1InfindByAgeIn(Collection<Age> ages)… where x.age in ?1NotInfindByAgeNotIn(Collection<Age> age)… where x.age not in ?1TruefindByActiveTrue()… where x.active = trueFalsefindByActiveFalse()… where x.active = falseIgnoreCasefindByFirstnameIgnoreCase… where UPPER(x.firstame) = UPPER(?1)CountcountByFirstNameselect count(*) from ... where x.firstName = ?1ExistsexistsByFirstNamelike the dao.exists(Example),judge by attribution of firstName:select keyindex0_.id as col_0_0_ from key_index keyindex0_ where keyindex0_.name=? limit ?

 

  

 

  

3、小结

第一部分讲解了使用 JPA 大大解放了我们对数据库的操作,经常使用的 SQL 大部分都已经被预生成,直接使用即可。另外 JPA 还有一个特点,那就是再也不用关心数据库的表结构了,需要更改的时候只需要修改对应 Model 的属性即可。在微服务架构中,因为服务拆分得越来越小,微服务内部只关心自己的业务,需要复杂查询的场景会越来越少,在微服务架构中更推荐使用 JPA 技术。

 

  

 

  

二、Spring Data JPA高级用法

第一部分介绍了 Spring Data JPA 的使用方式和基本查询,常用的增、删、改、查需求 Spring Data JPA 已经实现了。但对于复杂的数据库场景,动态生成方法不能满足,对此 Spring Data JPA 提供了其他的解决方案。

 

  

 

  

1、自定义 SQL 查询

使用 Spring Data 大部分的 SQL 都可以根据方法名定义的方式来实现,但是由于某些原因必须使用自定义的 SQL 来查询,Spring Data 也可以完美支持。

 

  在 SQL 的查询方法上面使用 @Query 注解,在注解内写 Hql 来查询内容。

  

@Query("select u from User u")Page<User> findALL(Pageable pageable);

当然如果感觉使用原生 SQL 更习惯,它也是支持的,需要再添加一个参数 nativeQuery = true。

 

  

@Query("select * from user u where u.nick_name = ?1", nativeQuery = true)Page<User> findByNickName(String nickName, Pageable pageable);

@Query 上面的 1 代表的是方法参数里面的顺序,如果有多个参数也可以按照这个方式添加 1、2、3....。除了按照这种方式传参外,还可以使用 @Param 来支持。

 

  

@Query("select u from User u where u.nickName = :nickName")Page<User> findByNickName(@Param("nickName") String nickName, Pageable pageable);

如涉及到删除和修改需要加上 @Modifying,也可以根据需要添加 @Transactional 对事务的支持、操作超时设置等。

 

  

@Transactional(timeout = 10)@Modifying@Query("update User set userName = ?1 where id = ?2")int modifyById(String  userName, Long id);@Transactional@Modifying@Query("delete from User where id = ?1")void deleteById(Long id);

使用已命名的查询

 

  除了使用 @Query 注解外,还可以预先定义好一些查询,并为其命名,然后再 Repository 中添加相同命名的方法,定义命名的 Query:

  

@Entity@NamedQueries({        @NamedQuery(name = "User.findByPassWord", query = "select u from User u where u.passWord = ?1"),        @NamedQuery(name = "User.findByNickName", query = "select u from User u where u.nickName = ?1"),})public class User {    ……}

通过 @NamedQueries 注解可以定义多个命名 Query,@NamedQuery 的 name 属性定义了 Query 的名称,注意加上 Entity 名称 . 作为前缀,query 属性定义查询语句,定义对应的方法:

 

  

List<User> findByPassWord(String passWord);List<User> findByNickName(String nickName);

Query 查找策略

 

  到此,我们有了三种方法来定义 Query:(1)通过方法名自动创建 Query,(2)通过 @Query 注解实现自定义 Query,(3)通过 @NamedQuery 注解来定义 Query。那么,Spring Data JPA 如何来查找这些 Query 呢?

  通过配置 @EnableJpaRepositories 的 queryLookupStrategy 属性来配置 Query 查找策略,有如下定义。

  CREATE:尝试从查询方法名构造特定于存储的查询。一般的方法是从方法名中删除一组已知的前缀,并解析方法的其余部分。USE_DECLARED_QUERY:尝试查找已声明的查询,如果找不到,则抛出异常。查询可以通过某个地方的注释定义,也可以通过其他方式声明。CREATE_IF_NOT_FOUND(默认):CREATE 和 USE_DECLARED_QUERY 的组合,它首先查找一个已声明的查询,如果没有找到已声明的查询,它将创建一个自定义方法基于名称的查询。它允许通过方法名进行快速查询定义,还可以根据需要引入声明的查询来定制这些查询调优。

 

  

2、分页查询Spring Data JPA 已经帮我们内置了分页功能

在查询的方法中,需要传入参数 Pageable,当查询中有多个参数的时候 Pageable 建议作为最后一个参数传入。

 

  

@Query("select u from User u")Page<User> findALL(Pageable pageable);Page<User> findByNickName(String nickName, Pageable pageable);

Pageable 是 Spring 封装的分页实现类,使用的时候需要传入页数、每页条数和排序规则,Page 是 Spring 封装的分页对象,封装了总页数、分页数据等。返回对象除使用 Page 外,还可以使用 Slice 作为返回值。

 

  

Slice<User> findByNickNameAndEmail(String nickName, String email,Pageable pageable);

Page 和 Slice 的区别如下:

 

  Page 接口继承自 Slice 接口,而 Slice 继承自 Iterable 接口。

  Page 接口扩展了 Slice 接口,添加了获取总页数和元素总数量的方法,因此,返回 Page 接口时,必须执行两条 SQL,一条复杂查询分页数据,另一条负责统计数据数量。

  返回 Slice 结果时,查询的 SQL 只会有查询分页数据这一条,不统计数据数量。

  用途不一样:Slice 不需要知道总页数、总数据量,只需要知道是否有下一页、上一页,是否是首页、尾页等,比如前端滑动加载一页可用;而 Page 知道总页数、总数据量,可以用于展示具体的页数信息,比如后台分页查询。

  

@Testpublic void testPageQuery()  {int page=1,size=2;Sort sort = new Sort(Sort.Direction.DESC, "id");Pageable pageable = PageRequest.of(page, size, sort);userRepository.findALL(pageable);userRepository.findByNickName("aa", pageable);    }

Sort,控制分页数据的排序,可以选择升序和降序。PageRequest,控制分页的辅助类,可以设置页码、每页的数据条数、排序等。限制查询:有时候我们只需要查询前 N 个元素,或者只取前一个实体。

 

  

User findFirstByOrderByLastnameAsc();User findTopByOrderByAgeDesc();Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);List<User> findFirst10ByLastname(String lastname, Sort sort);List<User> findTop10ByLastname(String lastname, Pageable pageable);

 

  

3、复杂查询

我们可以通过 AND 或者 OR 等连接词来不断拼接属性来构建多条件查询,但如果参数大于 6 个时,方法名就会变得非常的长,并且还不能解决动态多条件查询的场景。到这里就需要给大家介绍另外一个利器 JpaSpecificationExecutor 了。

 

  JpaSpecificationExecutor 是 JPA 2.0 提供的 Criteria API 的使用封装,可以用于动态生成 Query 来满足我们业务中的各种复杂场景。Spring Data JPA 为我们提供了 JpaSpecificationExecutor 接口,只要简单实现 toPredicate 方法就可以实现复杂的查询。

  我们来看一下 JpaSpecificationExecutor 的源码:

  

public interface JpaSpecificationExecutor<T> {   //根据 Specification 条件查询单个对象,注意的是,如果条件能查出来多个会报错   T findOne(@Nullable Specification<T> spec);   //根据 Specification 条件查询 List 结果   List<T> findAll(@Nullable Specification<T> spec);   //根据 Specification 条件,分页查询   Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable);   //根据 Specification 条件,带排序的查询结果   List<T> findAll(@Nullable Specification<T> spec, Sort sort);   //根据 Specification 条件,查询数量   long count(@Nullable Specification<T> spec);}

JpaSpecificationExecutor 的源码很简单,根据 Specification 的查询条件返回 List、Page 或者 count 数据。在使用 JpaSpecificationExecutor 构建复杂查询场景之前,我们需要了解几个概念:

 

  Root root,代表了可以查询和操作的实体对象的根,开一个通过 get("属性名") 来获取对应的值。CriteriaQuery query,代表一个 specific 的顶层查询对象,它包含着查询的各个部分,比如 select 、from、where、group by、order by 等。CriteriaBuilder cb,来构建 CritiaQuery 的构建器对象,其实就相当于条件或者是条件组合,并以 Predicate 的形式返回。使用案例: 首先定义一个 UserDetail 对象,作为演示的数据模型。

  

@Entitypublic class UserDetail {    @Id    @GeneratedValue    private Long id;    @Column(nullable = false, unique = true)    private Long userId;    private Integer age;    private String realName;    private String status;    private String hobby;    private String introduction;    private String lastLoginIp;}

创建 UserDetail 对应的 Repository:

 

  

public interface UserDetailRepository extends JpaSpecificationExecutor<UserDetail>,JpaRepository<UserDetail, Long>  {}

定义一个查询 Page<UserDetail> 的接口:

 

  

public interface UserDetailService {    public Page<UserDetail> findByCondition(UserDetailParam detailParam, Pageable pageable);}

在 UserDetailServiceImpl 中,我们来演示 JpaSpecificationExecutor 的具体使用。

 

  

@Servicepublic class UserDetailServiceImpl implements  UserDetailService{    @Resource    private UserDetailRepository userDetailRepository;    @Override    public Page<UserDetail> findByCondition(UserDetailParam detailParam, Pageable pageable){        return userDetailRepository.findAll((root, query, cb) -> {            List<Predicate> predicates = new ArrayList<Predicate>();            //equal 示例            if (!StringUtils.isNullOrEmpty(detailParam.getIntroduction())){                predicates.add(cb.equal(root.get("introduction"),detailParam.getIntroduction()));            }            //like 示例            if (!StringUtils.isNullOrEmpty(detailParam.getRealName())){                predicates.add(cb.like(root.get("realName"),"%"+detailParam.getRealName()+"%"));            }            //between 示例            if (detailParam.getMinAge()!=null && detailParam.getMaxAge()!=null) {                Predicate agePredicate&a      

	  
	  
	  
	  
	  
	  
        

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: