本文是读《Spring Boot2精髓-从构件小系统到架构分布式大系统》的读书笔记。
访 问数据库的方式有两个流派 , 一派 以 SQL 为中 心,在 JDBC 上做了 一定程度的封装 , 比直接操作 JDBC 更加方便和便捷,流行 DAO 工具 MyBatis 也属于这个流派。
另外一个流派则是 以 Java Entity 为 中心, 将实体和实体关系对应到数据库 的表和表关系,这类工具通常就是 ORM ( Object Relational Mappi°;g)工具 。 对实体和实体关系的操作会映射到数据库操作。
本章将介绍 Spring Data JPA , 它在 JPA 提供的简单语义上做了 一定的封装,满足 CRUD 查询。同时,也会介绍 Spring Data,它为 Spring 框架对访问 SQL 和 NoSQL 数据库提供了一致的方式 。
6.1 集成Spring Data JPA
集成数据源:
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
//Hikari作为数据源提供者
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.5</version>
</dependency>
为了在 Spring Boot 应用中使用 Spring Data JPA,需要通过 Java 来配置数据源。我们使用
Hikari 作为数据源:
package com.bee.sample.ch6.conf;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import com.zaxxer.hikari.HikariDataSource;
@Configuration
public class DataSourceConfig {
@Bean(name = "dataSource")
public DataSource datasource(Environment env) {
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl(env.getProperty("spring.datasource.url"));
ds.setUsername(env.getProperty("spring.datasource.username"));
ds.setPassword(env.getProperty("spring.datasource.password"));
ds.setDriverClassName(env.getProperty("spring.datasource.driver-class-name"));
return ds;
}
}
配置JPA支持:
大多数项目都是在需求分析后建立物理模型 。 也就是先有数据库表设计,随后才是 Sp「ing Boot 应用,因此数据库的 DDL 语句早已经具备 。 从另一方面讲,项目数据库管理人员、需求分析人员,甚至是项目经理并不喜欢 Hibernate 创建的表结构,比如,它的外键命名就很随机,不符合命名规范 。 自动建表对于小型项目或者工具类项目(如工作流引擎)还是很方便的 。
创建Entity:
User 表对应的实体表如下:
package com.bee.sample.ch6.entity;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
@Entity
public class User {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id ;
//部门
@Column
private String name ;
//创建时间
@Column(name="create_time")
private Date createTime ;
@ManyToOne
@JoinColumn(name="department_id")
Department department;
public User() {
//JPA要求实体必须有一个空的构造函数
}
public Integer getId(){
return id;
}
public void setId(Integer id ){
this.id = id;
}
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
public String getName(){
return name;
}
public void setName(String name ){
this.name = name;
}
public Date getCreateTime(){
return createTime;
}
public void setCreateTime(Date createTime ){
this.createTime = createTime;
}
}
Department 对象的定义如下:
package com.bee.sample.ch6.entity;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
@Entity
public class Department {
@Id
//
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id ;
@Column
private String name ;
@OneToMany(mappedBy="department")
private Set<User> users = new HashSet<User>();
public Set<User> getUsers() {
return users;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setUsers(Set<User> users) {
this.users = users;
}
}
不同于 BeetlSQL 对 Entity 的“松散”要求, JPA 既然以 Entity 为中心, 实体类就必须使用
@Entity 来注解, JPA 也提供了大量的注解来表明实体属性和关系。
@Id , 声明了 一 个属性将映射到数据库主键宇段。主键生成策略由注解@GeneratedValue 来指定。本例中, id 为自增主键,是一种简单的数据库主键生成策略,因此使用 GenerationType .IDENTITY 作为主键生成的策略 。
@Column,此注解表明属性对应到数据库的一个字段,且列名为 name 指定的名称 。
@ManyToOne, Many 指的是定义此属性的实体,即 User 实体,而 One 指的就是此注
解所注解的属性, ManyToOne 说明对象 User 和 Department 的关系是多对一关系,多
个用户属于一个部门 。
@JoinColumn ,与 ManyToOne 搭配使用,说明外键字段是 department一id 。
Department 对象的定义如下:
简化Entity:
去掉 了关系映射的相关配置,去掉了数据库外键设置,一个表对应了一个简单的对象
6.2 Repository
Repository 是 Spring Data 的核心概念,抽象了对数据库和 NoSQL 的操作,提供了如下接口供开发者使用:
- CrudRepository ,提供了基本的增删改查,批量操作接口。
- PagingAndSortingRepository , 集成 CrudRepository ,提供了附加的分页 查询功能。
- JpaRepository , 专门用于 JPA ,提供了 更多丰富 的数据库访问接口,比如根据 Example来查询,类似 BeetlSQL 的根据模板查询。
NoSQL :Redis、MongoDB、Elasticsearch
CrudRepository:
Spring Data 提供 了 CrudRepository 接口来实现 Entity 的简单增删改查功能。
PagingAndSortingRepository:
PagingAndSortingRepository 增加了翻页查找和排序相关的操作:
**JpaRepository **:
JpaRepository 提供了更多的实用功能,以及通过 Example 对象进行查询 。
持久化Entity:
Sort:
Pageable和Page:
基于方法名字查询:
Spring Data 通过查询的方法名和参数名来自动构造一个 JPA OQL 查询,我们可以在
UserRepository 中添加一个查询方法:
public interface UserRepository extends JpaRepository<User,Integer>{
public User findByName(String name);
}
方法名和参数名需要遵守一定的规则, Spring Data JPA 才能自动转化为 JPQL:
JPOL片段可以自己看看
@Query查询:
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import com.bee.sample.ch6.entity.User;
public interface UserRepository extends JpaRepository<User, Integer> {
public User findByName(String name);
//注解 Query 允许在方法上使用 JPQL 。
@Query("select u from User u where u.name=?1 and u.department.id=?2")
public User findUserByDepartment(String name,Integer departmentId);
//如果你更喜欢 SQL 而不是 JPQL,可以使用@Query 的 nativeQuery 属性 , 设置为 true:
@Query(value="select * from user where name=?1 and department_id=?2",nativeQuery=true)
public User nativeQuery(String name,Integer departmentId);
//无论是 JPQL , 还是 SQL 语句,都支持 “命名参数” :
@Query(value="select * from user where name=:name and department_id=:departmentId",nativeQuery=true)
public User nativeQuery2(@Param("name") String name,@Param("departmentId") Integer departmentId);
//如果 SQL 或者 JPQL 查询结果集并非 Entity , 可以用 Object[]数组代替 ,比如分组统计每个部 门的用户数 :
@Query(value="select department_id,count(1) total from user group by department_id",nativeQuery=true)
public List<Object[]> queryUserCount();
@Query(value="select id from user where department_id=?1",nativeQuery=true)
public List<Integer> queryUserIds(Integer departmentId);
//查询时可以使用 Pageable 和 Sort 来对协助“ JPQL ” 完成翻页和排序。
@Query(value="select u from User u where u.department.id=?1")
public Page<User> queryUsers(Integer departmentId,Pageable page);
//@Query 还允许 SQL 更新、删除语句,此时必须搭配@Modifying 使用,比如:
@Modifying
@Query("update User u set u.name=?1 where u.id=?2")
int updateName(String name,Integer id);
}
使用JPA Query:
Example查询: