JPA是Java Persistence API的简称,中文名Java持久层API,是JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。
Sun引入新的JPA ORM规范出于两个原因:其一,简化现有Java EE和Java SE应用开发工作;其二,Sun希望整合ORM技术,实现天下归一。
–以上内容摘抄自百度百科
JPA Hibernate SpringDatAJpa 三者的关系
JPA 是一种规范而 Hibernate 和 SpringDataJpa 是 JPA 的具体实现, 另外Spring Data JPA 是 Spring 在 Hibernate 的基础上构建的 JPA 使用解决方案。值得一提的是在早期的时候 Hibernate是不支持JPA,因为Sun在提出 JPA 的时候就已经有 Hibernate 了。
第一步:引入 SpirngBoot JPA 的 starter 依赖,同时需要引入mysql 驱动依赖。具体代码如下:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
第二步:添加Mysql 数据源的配置
在 SpringBoot 的配置文件 application.yml 中添加数据库配置信息。
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/learn?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
# Ddl-auto : Create: 自动创建表 Update:自动修改表 Create-drop:应用停下来会把表删除掉 None:什么都不做 Validate:类和表明是否一致
jpa:
show-sql: true
hibernate:
ddl-auto: update
properties:
hibernate:
format_sql: true
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
在启动的时候报如下错误
java.sql.SQLException: The server time zone value ‘Öйú±ê׼ʱ¼ä’ is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the serverTimezone configuration property) to use a more specifc time zone value if you want to utilize time zone support.
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:129) ~[mysql-connector-java-8.0.13.jar:8.0.13]
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97) ~[mysql-connector-java-8.0.13.jar:8.0.13]
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:89) ~[mysql-connector-java-8.0.13.jar:8.0.13]
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:63) ~[mysql-connector-java-8.0.13.jar:8.0.13]
原来的url内容是: url: jdbc:mysql://127.0.0.1:3306/learn
将url 内容修改为
url: jdbc:mysql://127.0.0.1:3306/learn?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true 问题解决。
第三步:根据 JPA 规范的注解配置映射实体
添加映射实体通过JPA规范的注解,具体代码如下:
package cn.lijunkui.springbootlearn.test.model;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class User {
@Id
@GeneratedValue
private Long id;
private String name;
private Integer age;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
private String sex;
private String address;
public User(){
}
public User(Long id,String name,Integer age,String sex,String address){
this.id = id;
this.name = name;
this.age = age;
this.address = address;
this.sex = sex;
}
// 此处省略get 和set 方法
}
第四步:使用Spring Data Jpa 内置查询接口 CrudRepository 充当DAO,具体操作方式是定义接口然后继承CrudRepository即可。具体代码如下:
package cn.lijunkui.springbootlearn.test.dao;
import cn.lijunkui.springbootlearn.test.model.User;
import org.springframework.data.repository.CrudRepository;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
public interface UserCrudRepository extends CrudRepository<User,Long>{
}
你肯定会惊讶 这就完成啦,我可以确定的告诉我们的Dao开发完毕 基本的增删改查搞定。
查看CrudRepository源码我们发现他有的方法如下:
<S extends T> S save(S entity);//新增
<S extends T> Iterable<S> saveAll(Iterable<S> entities);//批量新增
Optional<T> findById(ID id);//查询通过id
boolean existsById(ID id);//id是否存在
Iterable<T> findAll();//查询所有
Iterable<T> findAllById(Iterable<ID> ids);//查询多个id的数据
long count();//数据的总数
void deleteById(ID id);//根据id进行删除
void delete(T entity);//根据实例进行删除
void deleteAll(Iterable<? extends T> entities);//批量删除
void deleteAll();//删除所有
第五步:编写测试用例。
SpringBoot 的单元测试 需要我们声明 @SpringBootTest 和 @RunWith 注解
这里只是简单写啦几个测试。
package cn.lijunkui.springbootlearn.test.dao;
@SpringBootTest
@RunWith(SpringRunner.class)
public class UserCrudRepositoryTest {
@Autowired
private UserCrudRepository userCrudRepository;
/**
* 添加用户 测试
*/
@Test
public void add(){
User user = new User();
user.setName("ljk2");
user.setSex("1");
user.setAge(18);
user.setAddress("beijing");
User result = userCrudRepository.save(user);
Assert.assertNotNull(result);
}
/**
* 修改用户
*/
@Test
public void edit(){
User user = new User();
user.setId(1l);
user.setName("ljk2edit");
user.setSex("1");
user.setAge(18);
user.setAddress("beijing");
User result = userCrudRepository.save(user);
Assert.assertNotNull(result);
}
/**
* 通过id 进行查找
*/
@Test
public void findById(){
Optional<User> userOptional = userCrudRepository.findById(1l);
User result = userOptional.get();
Assert.assertNotNull(result);
}
/**
* 查询所有
*/
@Test
public void findAll(){
List<User> userList = (List<User>)userCrudRepository.findAll();
Assert.assertTrue(userList.size()>0);
}
@Test
public void count(){
long count = userCrudRepository.count();
System.out.println(count);
}
}
到这里最简单SpringDataJAP 介绍完毕。接下来让我们继续深入SpringDataJAP其他内置接口
PagingAndSortRepository 使用介绍
CrudRepository 只是具有增删改查的一些基本功能,接下来 PagingAndSortingRepository是具有分页和排序的功能 同时他继承啦 CrudRepository。
编写测试用例:
package cn.lijunkui.springbootlearn.test.dao;
import cn.lijunkui.springbootlearn.test.model.User;
import org.hibernate.criterion.Order;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
import static org.junit.Assert.*;
@SpringBootTest
@RunWith(SpringRunner.class)
public class UserPageRepositoryTest {
@Autowired
private UserPageRepository userPageRepository;
@Test
public void findAllBySort(){
List<User> userList = (List<User>)userPageRepository.findAll(new Sort(Sort.Direction.ASC,"age"));
System.out.println(userList.size());
}
@Test
public void findAllByPageable(){
Page<User> userPage = userPageRepository.findAll(new PageRequest(0, 20));
userPage.getNumber();//页数
userPage.getContent();//分页的数据
userPage.getTotalPages();//总共的页数
System.out.println("number:"+userPage.getNumber()
+"Countet"+userPage.getContent().size()
+"TotalPages"+userPage.getTotalPages());
}
}
JpaRepository 使用介绍
JpaRepository 不仅继承啦 PagingAndSortingRepository 同时继承啦 QueryByExampleExecutor(示例匹配器)
通过我们的测试用例查询期详细的用法
package cn.lijunkui.springbootlearn.test.dao;
import cn.lijunkui.springbootlearn.test.model.ResultDTO;
import cn.lijunkui.springbootlearn.test.model.User;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.*;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.*;
@SpringBootTest
@RunWith(SpringRunner.class)
public class UserJpaRepositoryTest {
@Autowired
private UserJpaRepository userJpaRepository;
/**
* 执行秒数:49422 49145
* 批量保存数据
*/
@Test
public void BatchSave(){
long startTime = System.currentTimeMillis();
List<User> list = new ArrayList<User>();
for (int i = 0; i < 60000; i++) {
User user = new User();
user.setName("ljk"+i);
user.setAge(i);
user.setAddress("address"+i);
list.add(user);
if(i%100 == 0){
userJpaRepository.saveAll(list);
list.clear();
}
}
long endTime = System.currentTimeMillis();
System.out.println("执行秒数:"+ (endTime - startTime));
}
/**
* 执行秒数:48053 48394 执行速度比BatchSave 要快
* 批量保存数据 (高效处理方式)减少大事物的提交
*/
@Test
public void BatchSaveBest(){
long startTime = System.currentTimeMillis();
List<User> list = new ArrayList<User>();
for (int i = 0; i < 60000; i++) {
User user = new User();
user.setName("ljk"+i);
list.add(user);
if(i%100 == 0){
userJpaRepository.saveAll(list);
userJpaRepository.flush();
list.clear();
}
}
long endTime = System.currentTimeMillis();
System.out.println("执行秒数:"+ (endTime - startTime));
}
/**
* 查询所有数据
*/
@Test
public void findALL(){
List<User> userlists = userJpaRepository.findAll();
Assert.assertTrue(userlists.size() > 0);
}
/**
* 根据 age 排序查询
*/
@Test
public void findALLSortAge(){
List<User> lists = userJpaRepository.findAll(Sort.by(Sort.Direction.ASC ,"age"));
for (User list : lists) {
System.out.println(list);
}
}
/**
* 分页查询
*/
@Test
public void findAllByPage(){
PageRequest pageRequest = new PageRequest(0,1);
Page<User> userPage = userJpaRepository.findAll(pageRequest);
Assert.assertTrue(userPage.getContent().size() == 1);
}
/**
* 分页排序查询
*/
@Test
public void findAllByPageAndSort(){
PageRequest pageRequest = new PageRequest(0,3,Sort.by(Sort.Direction.ASC ,"age"));
Page<User> userPage = userJpaRepository.findAll(pageRequest);
List<User> userList= userPage.getContent();
for (User user : userList) {
System.out.println(user);
}
}
/**
* 根据id 的集合获取所有数据
*/
@Test
public void findAllByIds(){
List<Long> ids = new ArrayList<Long>();
ids.add(1l);
ids.add(2l);
ids.add(3l);
ids.add(4l);
List<User> userList = userJpaRepository.findAllById(ids);
Assert.assertTrue(userList.size()>0);
}
/**
* 批量删除所有数据
*/
@Test
public void deleteAllInBatch(){
userJpaRepository.deleteAllInBatch();
}
/**
* 保存数据并刷新缓存
*/
@Test
public void saveAndFlush(){
User user = new User();
user.setName("ljk");
user.setAge(18);
user.setAddress("beijing");
user.setSex("1");
User result = userJpaRepository.saveAndFlush(user);
Assert.assertNotNull(result);
}
/**
* 批量删除
*/
@Test
public void deleteInBatch(){
List<User> userList = new ArrayList<User>();
User user = new User();
user.setId(1l);
userList.add(user);
User user2 = new User();
user2.setId(2l);
userList.add(user2);
User user3 = new User();
user3.setId(3l);
userList.add(user3);
User user4 = new User();
user4.setId(4l);
userList.add(user4);
userJpaRepository.deleteInBatch(userList);
}
/**
* 根据id 获取数据 延迟加载
*/
@Test
public void getOne(){
User result = userJpaRepository.getOne(1l);
Long id = result.getId();
String name = result.getName();
System.out.println(id);
System.out.println(name);
Assert.assertNotNull(result);
}
/**
* 示例匹配器 ExampleMatcher
*/
@Test
public void findUserByExam(){
User user = new User();
user.setName("ljk");
List<User> list = userJpaRepository.findAll(Example.of(user));
System.out.println(list.size());
}
@Test
public void findUserByExamQuery(){
User user = new User();
user.setName("ljk");
user.setAddress("address8");
user.setAge(8);
ExampleMatcher matcher = ExampleMatcher.matching()
.withMatcher("name", ExampleMatcher.GenericPropertyMatchers.startsWith())//模糊查询匹配开头,即{username}%
.withMatcher("address" ,ExampleMatcher.GenericPropertyMatchers.contains())//全部模糊查询,即%{address}%
.withIgnorePaths("id");//忽略字段,即不管id是什么值都不加入查询条件
Example<User> example = Example.of(user ,matcher);
List<User> userList = userJpaRepository.findAll(example);
Assert.assertTrue(userList.size() > 0);
}
}
虽然 SpringDataJPA 提供内置查询接口,自定义查询方式它提供了方法名称的方式进行查询,只需要定义个接口方法你就可以进行查询你想要的数据。
@SpringBootTest
@RunWith(SpringRunner.class)
public class UserJpaRepositoryTest {
@Autowired
private UserJpaRepository userJpaRepository;
@Test
public void findByNameAndAge(){
List<User> userList = userJpaRepository.findByNameAndAge("ljk",18);
Assert.assertTrue( userList.size()>0 );
}
public void findByNameOrAge(){
List<User> userList = userJpaRepository.findByNameOrAge("ljk",18);
Assert.assertTrue( userList.size()>0 );
}
}
快速自定以查询方法:示例如下
Keyword | Sample | JPQL snippet |
---|---|---|
And | findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 |
Is,Equals | findByFirstname,findByFirstnameIs,findByFirstnameEquals | … 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 ages) | … 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) |
计数查询
SpringDataJap 提供针对某个字段数量统计使用 countBy+数据库映射实体字段名称即可完成,代码如下所示:
long countByName(String name);
计数删除
也可以通过 removeBy+数据库映射实体字段名称进行计数删除。
@Transactional
List<User> reomveByName(String name);
List 用于接收删除数据的信息
同时也可以通过 deleteBy+数据库映射实体字段名称进行数据的删除。
@Transactional
List<User> deleteByName(String name);
Spring Data JPA 不仅提供内置接口和方法名称查询方式同时还提供了通过 @Query 注解的方式拼写查询语句,对于经常使用Hibernate HQL 查询的福音啊。
具体使用方式代码如下:
public interface UserJpaRepository extends JpaRepository<User,Long>{
/**
* 根据姓名查询用户
* @param name
* @return
*/
@Query("select u from User u where u.name = ?1")
public List<User> findUserByNameByQuery(String name);
/**
* 根据姓名(like)和年龄查询用户
* @param name
* @param age
* @return
*/
@Query("select u from User u where u.name like CONCAT('%',?1,'%') and u.age = ?2" )
public List<User> findUserByLikeNameByQuery(String name,Integer age);
/**
* 根据姓名(like)和年龄查询用户
* 命名参数 进行查询
*/
@Query("select u from User u where u.name like CONCAT('%',:name,'%') and u.age = :age")
public User findUserByNameAndAgeWithQery(@Param("name") String name,@Param("age") Integer age);
/**
* 根据姓名(like)和年龄查询用户
* 命名参数 原生方式进行查询
*/
@Query(value = "select * from user u where u.name like CONCAT('%',:name,'%') and u.age = :age",nativeQuery = true)
public List<User> findUserByNameAndAgeWithQeryNative(@Param("name") String name,@Param("age") Integer age);
/**
* 查询每个地区的人的个数
* @return
*/
@Query("select new cn.lijunkui.springbootlearn.test.model.ResultDTO(u.address,count(u.id)) from User u group by u.address")
public List<ResultDTO> findCountGroupByAddress();
}
测试用例:
@SpringBootTest
@RunWith(SpringRunner.class)
public class UserJpaRepositoryTest {
@Autowired
private UserJpaRepository userJpaRepository;
@Test
public void findUserByNameByQuery(){
List<User> userList = userJpaRepository.findUserByNameByQuery("ljk");
Assert.assertNotNull(userList.size()>0);
}
@Test
public void findUserByLikeNameByQuery(){
List<User> userList = userJpaRepository.findUserByLikeNameByQuery("jk",18);
Assert.assertNotNull(userList.size()>0);
}
@Test
public void findUserByNameAndAgeWithQery(){
User user = userJpaRepository.findUserByNameAndAgeWithQery("jk",18);
Assert.assertNotNull(user);
}
@Test
public void findUserByNameAndAgeWithQeryNative(){
List<User> userList = userJpaRepository.findUserByNameAndAgeWithQeryNative("jk",18);
Assert.assertNotNull(userList.size()>0);
}
/**
* 零散参数的接收
*/
@Test
public void findCountGroupByAddress(){
List<ResultDTO> results = userJpaRepository.findCountGroupByAddress();
System.out.println(results);
}
}
SpringDataJpa 相对于Hibernate 的使用更为简洁,更容易快速上手SQL 逻辑不是很复杂的业务。如果你想更灵活的编写SQL 可以考虑使用Mybaties。 如果你还没有上手过 SpringDataJpa,还等什么抓紧跟着本文操作一遍吧。
我本地环境如下:
整合过程如出现问题可以在我的GitHub 仓库 springbootexamples 中模块名为 spring-boot-2.x-spring-data-jpa 项目中进行对比查看
GitHub:https://github.com/zhuoqianmingyue/springbootexamples
https://docs.spring.io/spring-data/jpa/docs/2.0.10.RELEASE/reference/html/