Spring Boot 整合 Spring Data JPA

JPA 是一个基于 O/R 映射的 Java 持久化规范,其定义了一系列对象持久化的标准,目前实现这一规范的产品有 Hibernate、EclipseLink、OpenJPA、TopLink 等,这也意外着我们只要使用 JPA 来开发,无论是哪一个开发方式都是一样的。Spring Data JPA 是 Spring Data 的一个子项目,它通过提供基于 JPA 的 Repository 极大地减少了 JPA 作为数据访问方案的代码量。

Spring Boot 对 JPA 的自动配置放置在 org.springframework.boot.autoconfigure.orm.jpa 下,从 HibernateJpaAutoConfiguration 可以看出,Spring Boot 默认 JPA 的实现者是 Hibernate。

1.JPA的使用

首先在 MySQL 数据库创建表:

CREATE TABLE `product_info` (
  `product_id` bigint(18) unsigned NOT NULL COMMENT '商品id, 主键',
  `product_name` varchar(32) NOT NULL COMMENT '商品名称',
  `product_price` decimal(8,2) unsigned NOT NULL DEFAULT '99999.99' COMMENT '商品单价',
  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
  PRIMARY KEY (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

使用 JPA 需要添加 Maven 依赖:

<!-- jpa -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

然后通过 application.properties 中的 spring.datasource.* 和 spring.jpa.* 前缀配置属性:

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/spring_test?characterEncoding=utf8&useSSL=false
spring.datasource.username=mysql
spring.datasource.password=123456
spring.jpa.show-sql=true

并新建 domain 对象:

import javax.persistence.Entity;
@Entity
@DynamicUpdate
public class ProductInfo implements Serializable {
    @Id
    private Long productId;
    private String productName;
    private Double productPrice;
    private Date gmtCreate;
    private Date gmtModified;
    // 省略无参构造方法、getter、setter方法
}

1.JPA默认实现的方法

JPA 默认实现了一些基本的 CURD 的方法,只需要继承 JpaRepository 接口即可:

import org.springframework.data.jpa.repository.JpaRepository;
public interface ProductInfoRepository extends JpaRepository<ProductInfo, Long> { // Long表示主键id的类型
}

JpaRepository 提供了大量的通用接口:

findOne
findAll
save
saveAndFlush
delete
count
exists
方法太多,省略其他...

使用 ProductInfoRepository 的时候当作普通的类注入就可以了:

@Resource
private ProductInfoRepository productInfoRepository;

2.自定义查询方法

JPA 支持通过定义在 Repository 接口中的方法名来定义查询,主要的语法是 findXXBy、readAXXBy、queryXXBy、countXXBy、getXXBy 后面跟属性名称,然后根据查询的方法来自动解析成 SQL:

public interface ProductInfoRepository extends JpaRepository<ProductInfo, Long> {
    /**
     * 查询商品名称为productName的所有记录
     * 相当于 JPQL: select pi from ProductInfo pi where pi.productName = ?1
     */
    List<ProductInfo> findByProductNameIs(String productName);
    /**
     * 查询商品名称为productName或单价为productPrice的所有记录
     * 相当于 JPQL: select pi from ProductInfo pi where pi.productName = ?1 or pi.productPrice = ?2
     */
	List<ProductInfo> findByProductNameOrProductPrice(String productName, Double productPrice);
}

修改、删除、统计也是类似语法。基本上 SQL 体系中的关键词都可以使用:

关键字示例同功能JPQL
AndfindByLastnameAndFirstnamewhere x.lastname = ?1 and x.firstname = ?2
OrfindByLastnameOrFirstnamewhere x.lastname = ?1 or x.firstname = ?2
Is,EqualsfindByFirstname/FirstnameIs/FirstnameEqualswhere x.firstname = ?1
BetweenfindByStartDateBetweenwhere x.startDate between ?1 and ?2
LessThanfindByAgeLessThanwhere x.age < ?1
LessThanEqualfindByAgeLessThanEqualwhere x.age <= ?1
GreaterThanfindByAgeGreaterThanwhere x.age > ?1
GreaterThanEqualfindByAgeGreaterThanEqualwhere x.age >= ?1
AfterfindByStartDateAfterwhere x.startDate > ?1
BeforefindByStartDateBeforewhere x.startDate < ?1
IsNullfindByAgeIsNullwhere x.age is null
IsNotNull,NotNullfindByAge(Is)NotNullwhere x.age not null
LikefindByFirstnameLikewhere x.firstname like ?1
NotLikefindByFirstnameNotLikewhere x.firstname not like ?1
StartingWithfindByFirstnameStartingWithwhere x.firstname like ?1 (参数前面加%)
EndingWithfindByFirstnameEndingWithwhere x.firstname like ?1 (参数后面加%)
ContainingfindByFirstnameContainingwhere x.firstname like ?1 (参数两边加%)
OrderByfindByAgeOrderByLastnameDescwhere x.age = ?1 order by x.lastname desc
NotfindByLastnameNotwhere x.lastname <> ?1
InfindByAgeIn(Collection< Age> ages)where x.age in ?1
NotInfindByAgeNotIn(Collection< Age> age)where x.age not in ?1
TRUEfindByActiveTrue()where x.active = true
FALSEfindByActiveFalse()where x.active = false
IgnoreCasefindByFirstnameIgnoreCasewhere UPPER(x.firstame) = UPPER(?1)

3.JPA复杂查询

1、限制结果数量

限制结果数量是用 top 和 first 关键字实现的,写在 By 之前:

//获得符合查询条件的前10条数据,findTop10ByProductName功能也一样
List<ProductInfo> findFirst10ByProductName(String productName);

2、自定义SQL查询

JPA 还支持使用 @Query 注解在接口的方法上实现查询:

//使用参数索引的方式
@Query("select pi from ProductInfo pi where pi.productName = ?1")
List<ProductInfo> findByProductName(String productName);

//使用命名参数的方式
@Query("select pi from ProductInfo pi where pi.productName = :productName")
List<ProductInfo> findByProductName(@Param("productName") String productName);

//更新查询,返回值int表示更新语句影响的行数
@Modifying //涉及到更新和删除的需要加上@Modifying注解
@Transactional //事务的支持
@Query("update ProductInfo pi set pi.productName = ?1")
int setProductName(String productName);

//删除操作,返回值int表示删除语句影响的行数
@Modifying
@Transactional
@Query("delete from ProductInfo where productId = ?1")
int deleteByProductId(Long productId);

@Query 还支持原生 SQL,只需要将 nativeQuery 属性设置为 true 即可。

3、Specification构造准则查询

我们的接口类必须实现 JpaSpecificationExecutor 接口:

public interface ProductInfoRepository extends JpaRepository<ProductInfo, Long>, JpaSpecificationExecutor<ProductInfo> {
}

然后需要定义 Criterial 查询:

public class CustomerSpecs {
	//查出商品名是book的所有商品信息
    public static Specification<ProductInfo> productInfoFromBook() {
        return new Specification<ProductInfo>() {
            @Override
            public Predicate toPredicate(Root<ProductInfo> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                return cb.equal(root.get("productName"), "book");
            }
        };
    }
}

这里通过 Root 来获得需要查询的属性,通过 CriteriaBuilder 构造查询条件,CriteriaBuilder 包含的条件构造有 exists、and、or、not、conjunction、disjunction、isTree、isFalse、isNull、isNotNull、equal、notEqual、greaterThan、greaterThanOrEqualTo、lessThan、lessThanOrEqualTo、between 等。然后就可以使用了:

List<ProductInfo> productInfos = productInfoRepository.findAll(productInfoFromBook());

4、排序与分页查询

JPA 充分考虑了在实际开发过程中所需要的排序和分页的场景,为我们提供了 Sort 类以及 Page 接口和 Pageable 接口。在分页查询的方法中,需要传入参数 Pageable,当分页查询中有多个参数的时候 Pageable 建议做为最后一个参数传入。

public interface ProductInfoRepository extends JpaRepository<ProductInfo, Long> {
	List<ProductInfo> findByProductName(String productName, Sort sort);
	
	//Pageable 是 Spring 封装的分页实现类,需要传入页数、每页条数和排序规则
	Page<ProductInfo> findByProductName(String productName, Pageable pageable);
}

使用排序(DESC: 倒序排序,ASC: 正序排序):

Sort sort = new Sort(Sort.Direction.DESC, "productId");
List<ProductInfo> productInfos = productInfoRepository.findByProductName("xx", sort);

使用分页:

int page = 0, size = 10; //page是从0开始的
Sort sort = new Sort(Sort.Direction.DESC, "productId");
Pageable pageable = new PageRequest(page, size, sort);
Page<ProductInfo> productInfos = productInfoRepository.findByProductName("xx", pageable);

5、多表查询

多表查询在 JPA 中有两种实现方式,第一种是利用 hibernate 的级联查询来实现,第二种是创建一个结果集的接口来接收连表查询后的结果,这里主要第二种方式。首先需要创建另一张数据库表和 Entity:

CREATE TABLE `product_detail` (
  `product_id` bigint(32) NOT NULL,
  `product_description` varchar(1024) NOT NULL,
  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
  PRIMARY KEY (`product_id`)
) 
import javax.persistence.Entity;
@Entity
@DynamicUpdate
public class ProductDetail implements Serializable {
    @Id
    private Long productId;
    private String productDescription;
    private Date gmtCreate;
    private Date gmtModified;
    //省略无参构造方法、getter、setter方法
}

定义一个结果集的接口类:

public interface ProductSummary {
    Long getProductId();
    String getProductName();
    String getProductDescription();
}

查询的方法返回类型设置为新创建的接口:

public interface ProductInfoRepository extends JpaRepository<ProductInfo, Long> {
	@Query("select pi.productId as productId, pi.productName as productName, pd.productDescription as productDescription from ProductInfo pi, ProductDetail pd where pi.productId = pd.productId group by pi")
	List<ProductSummary> findByProductId();
	
	@Query("select pi.productId as productId, pi.productName as productName, pd.productDescription as productDescription from ProductInfo pi, ProductDetail pd where pi.productId = pd.productId group by pi")
	Page<ProductSummary> findByProductId(Pageable pageable);
	
	@Query("select pi.productId as productId, pi.productName as productName, pd.productDescription as productDescription from ProductInfo pi, ProductDetail pd where pi.productId = pd.productId and pi.productId = ?1 group by pi")
	Page<ProductSummary> findByProductId(Long productId, Pageable pageable);
}

在运行中 Spring 会给接口(ProductSummary)自动生产一个代理类来接收返回的结果,使用 getXX 的形式来获取值。

4.多数据源的支持

1、同源数据库的多源支持

日常项目中因为使用的分布式开发模式,不同的服务有不同的数据源,常常需要在一个项目中使用多个数据源,因此需要配置sping data jpa对多数据源的使用,一般分一下为三步:

1、配置多数据源
2、不同源的实体类放入不同包路径
3、声明不同的包路径下使用不同的数据源、事务支持

这里有一篇文章写的很清楚:Spring Boot多数据源配置与使用

2、异构数据库多源支持

比如我们的项目中,即需要对 mysql 的支持,也需要对 mongodb 的查询等。实体类声明@Entity 关系型数据库支持类型、声明@Document 为 mongodb支持类型,不同的数据源使用不同的实体就可以了。

public interface ProductInfoRepository extends JpaRepository<ProductInfo, Long> {
}

@Entity
@DynamicUpdate
public class ProductInfo implements Serializable {
}

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

@Document
public class User implements Serializable {
}

如果 User 既使用 mysql 也使用 mongodb,也可以做混合使用:

public interface JpaUserRepository extends Repository<User, Long> {
}

public interface MongoDBUserRepository extends Repository<User, Long> {
}

@Entity
@DynamicUpdate
@Document
public class Person implements Serializable {
}

也可以通过对不同的包路径进行声明,比如 A 包路径下使用 mysql,B 包路径下使用 mongoDB:

@EnableJpaRepositories(basePackages = "com.example.server.repository.jpa")
@EnableMongoRepositories(basePackages = "com.example.server.repository.mongo")
@Configuration
public class RepositoryConfig {
}

5.其它补充

1、使用枚举

使用枚举的时候,我们希望数据库中存储的是枚举对应的 String 类型,而不是枚举的索引值,需要在属性上面添加 @Enumerated(EnumType.STRING) 注解:

@Enumerated(EnumType.STRING) 
@Column(nullable = true)
private ProductType type;

2、不需要和数据库映射的属性

正常情况下我们在实体类上加入注解 @Entity,就会让实体类和表相关联,如果其中某个属性我们不需要和数据库来关联只是在展示的时候做计算,只需要加上 @Transient 属性既可:

@Transient
private String productXx;

参考:
http://docs.spring.io/spring-data/jpa/docs/current/reference/html/
http://www.gitbook.com/book/ityouknow/spring-data-jpa-reference-documentation/details

  • 14
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值