一 、Spring Data JPA
JPA (Java Persistence API) 是由 Hibernate 主导的 EJB 3.0 规范,不提供标准规范实现,JPA 标准规范实现由提供商实现,诸如提供商 Hibernate、EclipseLink、OpenJPA 等。
1. 定义数据访问层
public interface UserRepository extends JpaRepository{
//定义数据访问操作的方法
}
UserRepository 接口继承 JpaRepository 接口,已经具有以下数据访问操作方法:
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#findAll()
*/
List findAll();
/*
* (non-Javadoc)
* @see org.springframework.data.repository.PagingAndSortingRepository#findAll(org.springframework.data.domain.Sort)
*/
List findAll(Sort sort);
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#findAll(java.lang.Iterable)
*/
List findAll(Iterable ids);
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#save(java.lang.Iterable)
*/
List save(Iterable entities);
/**
* Flushes all pending changes to the database.
*/
void flush();
/**
* Saves an entity and flushes changes instantly.
*
* @param entity
* @return the saved entity
*/
S saveAndFlush(S entity);
/**
* Deletes the given entities in a batch which means it will create a single {@link Query}. Assume that we will clear
* the {@link javax.persistence.EntityManager} after the call.
*
* @param entities
*/
void deleteInBatch(Iterable entities);
/**
* Deletes all entities in a batch call.
*/
void deleteAllInBatch();
/**
* Returns a reference to the entity with the given identifier.
*
* @param id must not be {@literal null}.
* @return a reference to the entity with the given identifier.
* @see EntityManager#getReference(Class, Object)
*/
T getOne(ID id);
/* (non-Javadoc)
* @see org.springframework.data.repository.query.QueryByExampleExecutor#findAll(org.springframework.data.domain.Example)
*/
@Override
List findAll(Example example);
/* (non-Javadoc)
* @see org.springframework.data.repository.query.QueryByExampleExecutor#findAll(org.springframework.data.domain.Example, org.springframework.data.domain.Sort)
*/
@Override
List findAll(Example example, Sort sort);
2.配置使用 Spring Data JPA
在 Spring 环境中,使用 Spring Data JPA 可通过@EnableJpaRepositories 注解开启 Spring Data JPA 的支持,@EnableJpaRepositories 接受的 value 参数用来扫描数据访问层所在包下的数据访问的定义接口。
@SpringBootApplication
@EnableJpaRepositories(value="org.zj.repository")
public class SpringdatajpaApplication {
@Bean
public EntityEntryFactory entityEntryFactory(){
//...
}
//还需要配置 DataSource、PlatformTransactionManager 等相关信息bean
3.定义查询方法
Spring Data JPA 支持自定义在 Respository 接口中的方法来定义查询,而方法名是根据实体类的属性名来确定。
自定义实体如下:
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private Double price;
private boolean sex;
private Date birth;
- 根据属性名定义查询方法:
/**
*
* 通过名字相等查询,参数为 name
* 相当于 JPQL: SELECT u FROM USER u WHERE u.name= ?1
*
*/
List findByName(String name);
/**
* 通过名字 like 查询,参数为name
* 相等于 JPQL: SELECT u FROM USER u WHERE u.name like =?1
*/
List findByNameLike(String name);
/**
* 通过名称和密码查询,参数为name 和 password
* 相当于 JPQL: SELECT u FROM USER u WHERE u.name=?1 AND u.password= ?2
*/
User getByNanmeAndPassword(String name, String password);
以上代码片段中出现的 findBy、getBy、Like、And 关键字,是帮助安装规则去查询,这样的关键子如表所示:
关键字 | 示例 | 同功能 JPQL |
---|---|---|
And | findByLastnameAndFirstname | where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | where x.lastname = ?1 or x.firstname = ?2 |
Is,Equals | findByFirstname,findByFirstnameIs,findByFirstnameEqual | where x.firstname = 1? |
Between | findByStartDateBetween | where x.startDate between 1? and ?2 |
LessThan | findByAgeLessThan | where x.age < ?1 |
LessThanEqual | findByAgeLessThanEqual | where x.age = ?1 |
GreaterThan | findByAgeGreaterThan | where x.age > ?1 |
GreaterThanEqual | findByAgeGreaterThanEqual | where x.age >= ?1 |
After | findByStartDateAfter | where x.startDate > ?1 |
Before | findByStartDateBefore | where x.startDate < ?1 |
IsNull | findByAgeIsNull | where x.age is null |
IsNotNull,NotNull | findByAge(Is)NotNull | where x.age not null |
Like | findByFirstnameLike | where x.firstname like ?1 |
NotLike | findByFirstnameNotLike | where x.firstname not like ?1 |
StartingWith | findByFirstnameStartingWith | where x.firstname like ?1 (parameter bound with appended %) |
EndingWith | findByFirstnameEndingWith | where x.firstname like ?1 (parameter bound with prepended %) |
Containing | findByFirstnameContaining | where x.firstname like ?1 (parameter bound wrapped in %) |
OrderBy | findByAgeOrderByLastnameDesc | where x.age = ?1 order by x.lastname desc |
Not | findByLastnameNot | where x.lastname <> ?1 |
In | findByAgeIn(Collection ages) | where x.age in ?1 |
NotIn | findByAgeNotIn(Collection age) | where x.age not in ?1 |
True | findByActiveTrue() | where x.active = true |
False | findByActiveFalse() | where x.active = false |
IgnoreCase | findByFirstnameIgnoreCase | where UPPER(x.firstame) = UPPER(?1) |
- 限制结果数量,使用 Top 和 first 关键字来实现,诸如:
/**
*
* 通过名字相等查询前10条数据,参数为 name
*
*/
List findFirst10ByName(String name);
/**
*
* 通过名字相等查询前30条数据,参数为 name
*
*/
List findTop30ByName(String name);
- 使用 JPA 的 NamedQuery 查询,例子:
@Entity
@NamedQuery(name="User.findByAge",query="SELECT u FROM USER u WHERE u.age = ?1")
public class User {
使用如下语句:
/**
* 使用的是 NameQuery 里定义的查询语句,而不是根据方法名称查询
*/
List findByAge(int age);
- 使用 @Query 查询
1.)使用参数索引
@Query(value="SELECT u FROM USER u WHERE u.name = ?1")
List findByName2(String name);
2.)使用 命名参数
@Query(value="SELECT u FROM USER u WHERE u.name = :name")
List findByName3(@Param("name") String name);
3.)更新查询
@Transactional
@Modifying
@Query(value="UPDATE USER u SET u.name = ?1")
int setName(String name);
4.)Specification(Criteria) 准则查询
Spring Data JPA 提供了一个Specification 规范接口完成构造准则查询, Specification 接口定义了 toPredicate 方法用来构造查询条件。
定义: 接口类必须实现 JpaSpecificationExecutor 接口
public interface UserRepository extends JpaRepository,JpaSpecificationExecutor{
然后定义 Criterial 查询,代码如下:
public class UserSpecs {
public static Specification userByName(final String name){
return new Specification() {
/**
* root : 通过 Root 获取查询属性
* cb : 通过 CriteriaBuilder 构建查询条件
*/
public Predicate toPredicate(Root root, CriteriaQuery> query,
CriteriaBuilder cb) {
return cb.equal(root.get("name"), name);
}
};
}
}
使用静态导入:
import static org.zj.springdatajpa.repository.spec.UserSpecs.*;
注入 userRepository 的 bean 后:
userRepository.findAll(userByName("张剑"));
- 排序与分页
Spring Data JPA 考虑到实际开发过程中有可能要排序和分页的场景,为我们提供了 Sort 类、Page 接口和 Pageable 接口。
1)定义
List findByName(String name,Sort sort);
Page findByName(String name,Pageable pageable);
2)使用
userRepository.findByName("xx",new Sort(Direction.ASC,"age"));
userRepository.findByName("xx",new PageRequest(0,10));
4.自定义 Repository 的实现 过后再说
二、Spring Boot 支持
-
JDBC 的自动配置
spring-boot-starter-data-jpa 依赖 spring-boot-starter-jdbc,而 Spring Boot 对 JDBC 做了一些自动配置。源码如下图:
查看源码可以看出,通过 “spring.datasoure” 为前缀的属性自动配置dataSoure;Spring Boot 自动开启了注解事务的支持(@EnableTransactionManagement);还配置一个 jdbcTemplate。
Spring Boot 还提供了一个初始化数据的功能,放置在类路径下的schema.sql 文件会自动用来初始化表结构;放置在类路径下的data.sql 文件会自动用来填充表数据。
-
JPA 的自动配置
通过查看Spring Boot 对 JPA自动配置源码如图:
Spring Boot 默认 JPA 的实现是 Hibernate;配置 JPA 使用 “spring.jpa” 为前缀的属性在 application.properties中配置。
-
Spring Data JPA 的自动配置
通过查看Spring Boot 对 Spring Data JPA自动配置源码如图:
Spring Boot 自动开启了对 Spring Data JPA 的支持,所有不需在配置类中显示声明 @EnableJpaRepositories。
- 总结:
Spring Boot 使用 Spring Data JPA,在项目中条件 spring-boot-stater-data-jpa,然后只需要定义好 DataSource、实体类、数据访问层,并在需要使用数据访问的地方注入数据访问层的Bean就可以了。不需要额外的配置。
三、实战开发步骤
1. 安装docker 镜像 Oracle XE
- 启动 Oracle XE 11g 容器:
docker run -d -p 9090:8080 -p 1521:1521 --name xe11g wnameless/oracle-xe-11g
- 运行 xe11g 容器:
docker start xe11g
外部访问 xe11g 管理界面:
2.创建 Spring Boot 项目
- 在类路径下创建 data.sql 测试数据
INSERT INTO PERSON (ID,NAME,AGE,ADDRESS) VALUES (HIBERNATE_SEQUENCE.NEXTVAL,'张剑',30,'重庆');
INSERT INTO PERSON (ID,NAME,AGE,ADDRESS) VALUES (HIBERNATE_SEQUENCE.NEXTVAL,'老何',31,'重庆');
INSERT INTO PERSON (ID,NAME,AGE,ADDRESS) VALUES (HIBERNATE_SEQUENCE.NEXTVAL,'辉辉',28,'重庆');
INSERT INTO PERSON (ID,NAME,AGE,ADDRESS) VALUES (HIBERNATE_SEQUENCE.NEXTVAL,'春春',29,'重庆');
INSERT INTO PERSON (ID,NAME,AGE,ADDRESS) VALUES (HIBERNATE_SEQUENCE.NEXTVAL,'老严',35,'重庆');
INSERT INTO PERSON (ID,NAME,AGE,ADDRESS) VALUES (HIBERNATE_SEQUENCE.NEXTVAL,'老邓',18,'重庆');
- 创建实体
@Entity
@NamedQuery(name="Person.byNameAndAddressNameQuery",query="SELECT p FROM Person p WHERE p.name =?1 AND p.address=?2")
public class Person {
@Id
@GeneratedValue //hibernate 自动生成 HIBERNATE_SEQUENCE序列
private Long id;
private String name;
private Integer age;
private String address;
- 配置基本属性
spring.datasource.driverClassName=oracle.jdbc.OracleDriver
spring.datasource.url=jdbc:oracle:thin:@192.168.0.112:1521:xe
spring.datasource.username=ZHANGJIAN
spring.datasource.password=zhangjian
#1
spring.jpa.hibernate.ddl-auto=update
#2
spring.jpa.show-sql=true
#3
spring.jackson.serialization.indent_output=true
- 定义数据访问接口
public interface PersonRepository extends JpaRepository {
List findByAddress(String address);
Person findByNameAndAddress(String name,String address);
@Query("SELECT p FROM Person p WHERE p.name= :name AND p.address= :address")
Person byNameAndAddressQuery(@Param("name")String name,@Param("address")String address);
Person byNameAndAddressNameQuery(String name,String address);
}
- 定义控制器 业务层省略
@RestController
public class HelloController {
@Autowired
PersonRepository personRepository;
@RequestMapping(value="/save")
public Person save(String name,String address,Integer age){
Person p = new Person(null,name,age,address);
personRepository.save(p);
return p;
}
@RequestMapping(value="/p1")
public List p1(String address){
return personRepository.findByAddress(address);
}
@RequestMapping(value="/p2")
public Person p2(String name,String address){
return personRepository.findByNameAndAddress(name, address);
}
@RequestMapping(value="/p3")
public Person p3(String name,String address){
return personRepository.byNameAndAddressQuery(name, address);
}
@RequestMapping(value="/p4")
public Person p4(String name,String address){
return personRepository.byNameAndAddressNameQuery(name, address);
}
/**
* 测试排序
*/
@RequestMapping(value="/sort")
public List sort() {
return personRepository.findAll(new Sort(Direction.ASC, "age"));
}
/**
* 测试分页
*/
@RequestMapping(value="/page")
public Page page() {
return personRepository.findAll(new PageRequest(1, 2));
}
}
- 测试
访问 http://127.0.0.1:8080/save?name=test&address=北京&age=21
访问 http://127.0.0.1:8080/p1?address=重庆
访问 http://127.0.0.1:8080/p2?address=重庆&name=张剑
访问 http://127.0.0.1:8080/p3?address=重庆&name=老何
访问 http://127.0.0.1:8080/p3?address=重庆&name=张剑
访问 http://127.0.0.1:8080/p4?address=重庆&name=张剑
访问 http://127.0.0.1:8080/sort
访问 http://127.0.0.1:8080/page