写作时间:2019-01-04
Spring Boot: 2.1 ,JDK: 1.8, IDE: IntelliJ IDEA, MySQL 8.0.13
对数据的操作无非CRUD(增加查询修改删除),每次写的SQL都类似,是否可以交由框架处理。JPA就是为释放程序员不用谢CRUD而出来的规范。2006年5月11号,JPA 1.0 规范作为 JCP JSR 220 的一部分最终被发布。
JPA(Java Persistence API)是Sun官方提出的Java持久化规范。它为Java开发人员提供了一种对象/关联映射工具来管理Java应用中的关系数据。他的出现主要是为了简化现有的持久化开发工作和整合ORM技术,结束现在Hibernate,TopLink,JDO等ORM框架各自为营的局面。
注意:JPA是一套规范,不是一套产品,那么像Hibernate,TopLink,JDO他们是一套产品,如果说这些产品实现了这个JPA规范,那么我们就可以叫它们为JPA的实现产品。
属性 | Object | RDBMS |
---|---|---|
粒度 | 类 | 表 |
继承 | 有 | 没有 |
唯一性 | a == b, a.equals(b) | 主键 |
关联 | 引用 | 外键 |
数据访问 | 逐级访问 | SQL 表间关联Join,数量要少 |
笔者的MySQL版本为8.0.13,搭建环境可参考文章JdbcTemplate访问MySQL。
工程建立的时候,需要勾选SQL的JPA和MySQL两项:
参照教程【SpringBoot 2.1 | 第一篇:构建第一个SpringBoot工程】新建一个Spring Boot项目,名字叫demojpa, 在目录src/main/java/resources
下找到配置文件application.properties
,重命名为application.yml
。
mysql-connector-java
改为如下 :<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.13version>
dependency>
src/main/resources/application.yml
:spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db_example
username: springuser
password: ThePassword
jpa:
hibernate:
ddl-auto: create # 第一次简表create 后面用update
show-sql: true
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
配置信息解析:
其它配置项解析请参考文章JdbcTemplate访问MySQL。
新建实体类com.zgpeace.demojpa.bean.Customer
package com.zgpeace.demojpa.bean;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
public Customer() {}
public Customer(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
@Override
public String toString() {
return String.format(
"Customer[id=%d, firstName='%s', lastName='%s']",
id, firstName, lastName
);
}
//getter... setter...
}
实体类解析:
Customer()
是为JPA要求的,如果代码中未用到,可以修饰为protected
。新建数据库操作接口com.zgpeace.demojpa.dao.CustomerRepository
package com.zgpeace.demojpa.dao;
import com.zgpeace.demojpa.bean.Customer;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface CustomerRepository extends JpaRepository<Customer, Long> {
List<Customer> findByLastName(String lastName);
}
接口解析:
JpaRepository实现了PagingAndSortingRepository接口,PagingAndSortingRepository接口实现了CrudRepository接口,CrudRepository接口实现了Repository接口;
简单说明下:
由于JpaRepository接口继承了以上所有接口,所以拥有它们声明的所有方法;
另外注意下,以findAll方法为例,JpaRepository接口返回的是List, PagingAndSortingRepository和CrudRepository返回的是迭代器;
初始化数据,完善项目启动类:com.zgpeace.demojpa.DemojpaApplication
package com.zgpeace.demojpa;
import com.zgpeace.demojpa.bean.Customer;
import com.zgpeace.demojpa.dao.CustomerRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class DemojpaApplication {
private static final Logger log = LoggerFactory.getLogger(DemojpaApplication.class);
public static void main(String[] args) {
SpringApplication.run(DemojpaApplication.class, args);
}
@Bean
public CommandLineRunner demo(CustomerRepository repository) {
return (args) -> {
// save a couple of customers
repository.save(new Customer("Jack", "Bauer"));
repository.save(new Customer("Chloe", "O'Brian"));
repository.save(new Customer("Kim", "Bauer"));
repository.save(new Customer("David", "Palmer"));
repository.save(new Customer("Michelle", "Dessler"));
// fetch all customers
log.info("Customers found with findAll():");
log.info("-------------------------------");
for (Customer customer: repository.findAll()) {
log.info(customer.toString());
}
log.info("");
// fetch an individual customer by ID
repository.findById(1L)
.ifPresent(customer -> {
log.info("Customer found with findById(1L):");
log.info("--------------------------------");
log.info(customer.toString());
log.info("");
});
// fetch customers by last name
log.info("Customer found with findByLastName('Bauer'):");
log.info("--------------------------------------------");
repository.findByLastName("Bauer").forEach(customer -> {
log.info(customer.toString());
});
log.info("");
};
}
}
代码解析:
运行项目,控制台打印如下:
== Customers found with findAll():
Customer[id=1, firstName='Jack', lastName='Bauer']
Customer[id=2, firstName='Chloe', lastName='O'Brian']
Customer[id=3, firstName='Kim', lastName='Bauer']
Customer[id=4, firstName='David', lastName='Palmer']
Customer[id=5, firstName='Michelle', lastName='Dessler']
== Customer found with findOne(1L):
Customer[id=1, firstName='Jack', lastName='Bauer']
== Customer found with findByLastName('Bauer'):
Customer[id=1, firstName='Jack', lastName='Bauer']
Customer[id=3, firstName='Kim', lastName='Bauer']
上面代码没有分层,逻辑不清晰,下面用MVC实现Restful的增删改查。
新建Service类com.zgpeace.demojpa.service.CustomerService
package com.zgpeace.demojpa.service;
import com.zgpeace.demojpa.bean.Customer;
import com.zgpeace.demojpa.dao.CustomerRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class CustomerService {
@Autowired
private CustomerRepository customerRepository;
public List<Customer> getCustomers() {
return (List<Customer>) customerRepository.findAll();
}
public Customer getCustomerById(long id) {
return customerRepository.findById(id).orElse(null);
}
public Customer addCustomer(Customer customer) {
return customerRepository.save(customer);
}
public Customer updateCustomer(Customer customer) {
return customerRepository.saveAndFlush(customer);
}
public void deleteCustomerById(long id) {
customerRepository.deleteById(id);
}
}
代码解析:
customerRepository.findById(id)
返回的是一个Optional
对象。Optional 类的常用方式
I) 设置默认值,如果不是泛型对象则用默认值
public T orElse(T other) {
return value != null ? value : other;
}
II) 如果值存在,则在闭包里调用泛型对象
public void ifPresent(Consumer<? super T> consumer) {
if (value != null)
consumer.accept(value);
}
新建类com.zgpeace.demojpa.web.CustomerController
package com.zgpeace.demojpa.web;
import com.zgpeace.demojpa.bean.Customer;
import com.zgpeace.demojpa.service.CustomerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/customer")
public class CustomerController {
@Autowired
private CustomerService customerService;
@RequestMapping(value = "/list", method = RequestMethod.GET)
public List<Customer> getCustomers() {
return customerService.getCustomers();
}
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public Customer getCustomerById(@PathVariable("id") long id) {
return customerService.getCustomerById(id);
}
@RequestMapping(value = "", method = RequestMethod.POST)
public String addCustomer(@RequestParam(value = "firstName", required = true) String firstName,
@RequestParam(value = "lastName", required = true) String lastName) {
Customer customer = new Customer();
customer.setFirstName(firstName);
customer.setLastName(lastName);
Customer result = customerService.addCustomer(customer);
return result.toString();
}
@RequestMapping(value = "/{id}", method = RequestMethod.PUT)
public String updateCustomer(@PathVariable("id") long id, @RequestParam(value = "firstName", required = true) String firstName,
@RequestParam(value = "lastName", required = true) String lastName) {
Customer customer = new Customer();
customer.setId(id);
customer.setFirstName(firstName);
customer.setLastName(lastName);
Customer result = customerService.updateCustomer(customer);
return result.toString();
}
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
public String deleteCustomerById(@PathVariable("id") long id) {
customerService.deleteCustomerById(id);
return "finish delete, Please check whether is success";
}
}
上面代码用Postman全部测试通过,Postman用法可点击链接。
获取全部数据截图:
实际项目中经常用到分页、排序查询,spring data jpa已经帮我们实现了分页的功能,在查询的方法中,需要传入参数Pageable
,当查询中有多个参数的时候Pageable建议做为最后一个参数传入。
Service类com.zgpeace.demojpa.service.CustomerService
public List<Customer> getCustomersByPage(Pageable pageable) {
return (List<Customer>) customerRepository.findAll(pageable).getContent();
}
代码解析:
findAll(Pageable)
返回的是Page
对象,需要.getContent()
获取List
Controller类com.zgpeace.demojpa.web.CustomerController
@RequestMapping(value = "/listPageable", method = RequestMethod.GET)
public List<Customer> getCustomersByPage() {
int page = 0, size =3;
Sort sort = new Sort(Sort.Direction.DESC, "lastName");
Pageable pageable = PageRequest.of(page, size, sort);
return customerService.getCustomersByPage(pageable);
}
头文件为
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
代码解析:
命令行测试:
$ curl http://localhost:8080/customer/listPageable
[{"id":4,"firstName":"David","lastName":"Palmer"},
{"id":2,"firstName":"Chloe","lastName":"O'Brian"},
{"id":5,"firstName":"Michelle","lastName":"Dessler"}]
有的需求比较复杂,需要写SQL,spring data也是完美支持的;在SQL的查询方法上面使用@Query注解,如涉及到删除和修改在需要加上@Modifying.也可以根据需要添加 @Transactional 对事物的支持,查询超时的设置等
Dao添加com.zgpeace.demojpa.dao.CustomerRepository
@Transactional(timeout = 10)
@Modifying
@Query("delete from Customer where id = ?1")
void deleteCustomerWithSqlByUserId(Long id);
头文件
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.transaction.annotation.Transactional;
Service添加com.zgpeace.demojpa.service.CustomerService
public void deleteCustomerWithSqlByUserId(long id) {
customerRepository.deleteCustomerWithSqlByUserId(id);
}
Controller添加com.zgpeace.demojpa.web.CustomerController
@RequestMapping(value = "/sql/{id}", method = RequestMethod.DELETE)
public String deleteCustomerWithSqlById(@PathVariable("id") long id) {
customerService.deleteCustomerWithSqlByUserId(id);
return "finish sql delete, Please check whether is success";
}
命令行测试
删除id为2的记录
$ curl --request DELETE \
--url 'http://localhost:8080/customer/sql/2'
finish sql delete, Please check whether is success%
查询id为2的记录为空,验证通过
$ curl http://localhost:8080/customer/2
实体
主键
映射
关系
恭喜你!学会了SPA的增删改查,分页排序查询,自定义SQL查询。
代码下载:
https://github.com/zgpeace/Spring-Boot2.1/tree/master/db/demojpa
参考:
https://spring.io/guides/gs/accessing-data-jpa/
https://www.cnblogs.com/ityouknow/p/5891443.html
http://blog.didispace.com/springbootdata2/
https://blog.csdn.net/forezp/article/details/70545038
https://juejin.im/post/5aa733af518825558a0646fb
https://www.cnblogs.com/chenpi/p/6357527.html