Spring Boot + Data JPA + Oracle一对多示例
在本文中,我们将看到一个Spring Boot + Data JPA + Oracle DB + Spring REST的示例。在示例中,两个表用于演示一对多和多对一关系。
在示例中使用的Spring Data JPA存储库中,自定义方法还用于显示如何编写方法以通过在数据JPA存储库中的方法中使用这些关键字来自动生成"介于"和"大于"之间的查询。我们还将看到如何通过在Spring Data JPA中使用@Query注释自己编写查询。
数据库表
客户和交易有两个表,因为客户可以进行许多交易,这意味着客户和交易之间存在一对多的关系。
创建表的查询
CREATE TABLE "TEST"."CUSTOMER"
( "CUST_NAME" VARCHAR2(20 BYTE),
"CUST_EMAIL" VARCHAR2(20 BYTE),
"CUST_ADDR" VARCHAR2(30 BYTE),
"CUST_ID" NUMBER GENERATED ALWAYS AS IDENTITY MINVALUE 1 MAXVALUE 9999999999999999999999999999 INCREMENT BY 1 START WITH 1000 CACHE 20 NOORDER NOCYCLE NOKEEP NOSCALE NOT NULL ENABLE,
CONSTRAINT "CUSTOMER_PK" PRIMARY KEY ("CUST_ID")
);
CREATE TABLE "TEST"."TRANSACTION"
( "TXN_DATE" DATE,
"TXN_AMOUNT" NUMBER(10,2),
"CUST_ID" NUMBER,
"TXN_ID" NUMBER(4,0) GENERATED ALWAYS AS IDENTITY MINVALUE 1 MAXVALUE 9999999999999999999999999999 INCREMENT BY 1 START WITH 1000 CACHE 20 NOORDER NOCYCLE NOKEEP NOSCALE NOT NULL ENABLE,
CONSTRAINT "TRANSACTION_PK" PRIMARY KEY ("TXN_ID"),
CONSTRAINT "TRANSACTION_FK" FOREIGN KEY ("CUST_ID")
REFERENCES "TEST"."CUSTOMER" ("CUST_ID")
);
如我们所见,Id是使用Identity在两个表中自动创建的。
在事务表中,有一个外键约束引用了客户表的CUST_ID。
Maven依赖关系– pom.xml
这个Spring Boot Rest服务示例使用Data JPA和Oracle DB,因此必须添加它们的依赖项。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.theitroad</groupId>
<artifactId>jpademo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SBJPADemo</name>
<description>Project for JPA</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!--Oracle driver -->
<dependency>
<groupId>com.oracle.ojdbc</groupId>
<artifactId>ojdbc8</artifactId>
<version>19.3.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
JPA实体类
有两个实体类,分别映射到客户表和交易表。
@Entity
@Table(name="CUSTOMER")
public class Customer implements Serializable{
private static final long serialVersionUID = -7496362624106858939L;
// Primary key
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="CUST_ID")
private int custId;
@Column(name="CUST_NAME")
private String custName;
@Column(name="CUST_ADDR")
private String custAddr;
@Column(name="CUST_EMAIL")
private String custEmail;
// One to many mapping with transactions
@OneToMany(cascade = CascadeType.ALL)
@Fetch(FetchMode.JOIN)
@JoinColumn(name="CUST_ID")
private Set<Transaction> transactions;
public Set<Transaction> getTransactions() {
return transactions;
}
public void setTransactions(Set<Transaction> transactions) {
this.transactions = transactions;
}
public int getCustId() {
return custId;
}
public void setCustId(int custId) {
this.custId = custId;
}
public String getCustName() {
return custName;
}
public void setCustName(String custName) {
this.custName = custName;
}
public String getCustAddr() {
return custAddr;
}
public void setCustAddr(String custAddr) {
this.custAddr = custAddr;
}
public String getCustEmail() {
return custEmail;
}
public void setCustEmail(String custEmail) {
this.custEmail = custEmail;
}
}
@Entity
@Table(name="TRANSACTION")
public class Transaction implements Serializable{
private static final long serialVersionUID = 6392890629580631252L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="TXN_ID")
private int txnId;
@Column(name="TXN_DATE")
private LocalDateTime txnDate;
@Column(name="TXN_AMOUNT")
private double txnAmount;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="CUST_ID", nullable=false)
private Customer customer;
public int getTxnId() {
return txnId;
}
public void setTxnId(int txnId) {
this.txnId = txnId;
}
public LocalDateTime getTxnDate() {
return txnDate;
}
public void setTxnDate(LocalDateTime txnDate) {
this.txnDate = txnDate;
}
public double getTxnAmount() {
return txnAmount;
}
public void setTxnAmount(double txnAmount) {
this.txnAmount = txnAmount;
}
@JsonIgnore
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
}
在这里,@ JsonIgnore注释用于避免事务获取客户数据的无限循环,而后者又会获取事务数据,依此类推。
DTO班
有一个DTO类同时具有Transaction和Customer字段,如果同时需要Customer和Transaction的数据,则可以填充并返回此DTO。
public class CustomerTransactionDTO {
private int txnId;
private LocalDateTime txnDate;
private double txnAmount;
private int customerId;
private String customerName;
public int getTxnId() {
return txnId;
}
public void setTxnId(int txnId) {
this.txnId = txnId;
}
public LocalDateTime getTxnDate() {
return txnDate;
}
public void setTxnDate(LocalDateTime txnDate) {
this.txnDate = txnDate;
}
public double getTxnAmount() {
return txnAmount;
}
public void setTxnAmount(double txnAmount) {
this.txnAmount = txnAmount;
}
public int getCustomerId() {
return customerId;
}
public void setCustomerId(int customerId) {
this.customerId = customerId;
}
public String getCustomerName() {
return customerName;
}
public void setCustomerName(String customerName) {
this.customerName = customerName;
}
}
控制器类
带有REST API方法的控制器类映射到URL路径。
客户控制器
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import com.theitroad.dto.CustomerTransactionDTO;
import com.theitroad.entities.Customer;
import com.theitroad.service.CustomerService;
@RestController
public class CustomerController {
@Autowired
CustomerService customerService;
// insert customer
@PostMapping("/customer")
@ResponseStatus(HttpStatus.CREATED)
public Customer addCustomer(@RequestBody Customer customer){
return customerService.insertCustomer(customer);
}
// Get all customers
@GetMapping("/customers")
public List<Customer> getAllCustomers(){
return customerService.getAllCustomers();
}
// Updating customer record
@PutMapping("/updatecustomer")
public Customer updateCustomer(@RequestBody Customer customer) {
return customerService.updateCustomer(customer);
}
// delete customer
@DeleteMapping("/customer/{id}")
@ResponseStatus(value=HttpStatus.OK, reason="Customer Deleted")
public void deleteCustomer(@PathVariable int id){
customerService.deleteCustomer(id);
}
// Get customers with transaction amount greater than the passed amount
@GetMapping("/customers/amount/{amount}")
public List<CustomerTransactionDTO> getAllCustomersByAmount(@PathVariable double amount){
return customerService.getAllCustomersByAmount(amount);
}
}
TransactionController
import java.time.LocalDateTime;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import com.theitroad.entities.Transaction;
import com.theitroad.service.TransactionService;
@RestController
public class TransactionController {
@Autowired
TransactionService transactionService;
// Get transaction by ID
@GetMapping("transaction/{id}")
public Transaction getTransactionById(@PathVariable int id) {
return transactionService.getTransactionById(id);
}
// Get transactions by Date
@GetMapping("transactions/{date}")
public List<Transaction> getTransactionsByDate(@PathVariable("date") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime date) {
return transactionService.getTransactionsByDate(date);
}
// Get transactions between the passed amount range
@GetMapping("/transactions/range/{amount}")
public List<Transaction> getAllTransactionsBetweenAmount(@PathVariable("amount") String amountRange){
return transactionService.getAllTransactionsBetweenAmount(amountRange);
}
// Get transactions greater than the passed amount
@GetMapping("/transactions/amount/{amount}")
public List<Transaction> getAllTransactionsByAmount(@PathVariable double amount){
return transactionService.getAllTransactionsByAmount(amount);
}
}
JPA储存库
由于使用了Spring数据,因此我们只需要创建Repository接口。
客户资料库
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import com.theitroad.entities.Customer;
import com.theitroad.entities.Transaction;
public interface CustomerRepository extends JpaRepository<Customer, Integer>{
@Query("select t from Transaction t where t.txnAmount > ?1")
List<Transaction> getAllCustomersByTxnAmountGreaterThan(double amount);
//List<Transaction> findByTransactionsTxnAmountGreaterThan(double amount);
}
除了CustomerRepository通过扩展JpaRepository继承的常规CRUD方法之外,还有一个自定义方法getAllCustomersByTxnAmountGreaterThan(),该方法以@Query方法进行注释,并且查询带有该注释。
交易库
import java.time.LocalDateTime;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import com.theitroad.entities.Transaction;
public interface TransactionRepository extends JpaRepository<Transaction, Integer> {
List<Transaction> findTransactionByTxnAmountBetween(double fromAmount, double toAmount);
List<Transaction> findTransactionByTxnAmountGreaterThan(double amount);
List<Transaction> findTransactionsByTxnDateBetween(LocalDateTime fromDate, LocalDateTime toDate);
}
在TransactionRepository中,有三种自定义方法
- findTransactionByTxnAmountBetween –查找具有给定范围内的交易金额的所有交易。
- findTransactionByTxnAmountGreaterThan –查找所有交易金额大于通过金额的交易。
- findTransactionsByTxnDateBetween –查找过去日期范围之间的所有交易。
这些方法未提供查询。Spring数据本身通过解析方法名称来生成查询。或者,我们可以使用@Query注释自行提供查询。
服务等级
客户服务接口
import java.util.List;
import com.theitroad.dto.CustomerTransactionDTO;
import com.theitroad.entities.Customer;
public interface CustomerService {
Customer insertCustomer(Customer customer);
List<Customer> getAllCustomers();
Customer updateCustomer(Customer customer);
void deleteCustomer(int id);
List<CustomerTransactionDTO> getAllCustomersByAmount(double amount);
}
CustomerServiceImpl类
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.theitroad.dao.CustomerRepository;
import com.theitroad.dto.CustomerTransactionDTO;
import com.theitroad.entities.Customer;
import com.theitroad.entities.Transaction;
@Service
public class CustomerServiceImpl implements CustomerService{
@Autowired
private CustomerRepository repository;
@Override
public Customer insertCustomer(Customer customer) {
return repository.save(customer);
}
@Override
public List<Customer> getAllCustomers() {
return repository.findAll();
}
@Override
public Customer updateCustomer(Customer customer) {
Customer custDB = repository.findById(customer.getCustId()).get();
custDB.setCustEmail(customer.getCustEmail());
return repository.save(custDB);
}
@Override
public void deleteCustomer(int id) {
repository.deleteById(id);
}
@Override
public List<CustomerTransactionDTO> getAllCustomersByAmount(double amount) {
List<Transaction> transactions = repository.getAllCustomersByTxnAmountGreaterThan(amount);
CustomerTransactionDTO ct;
List<CustomerTransactionDTO> ctList = new ArrayList<>();
for(Transaction t : transactions) {
ct = new CustomerTransactionDTO();
ct.setCustomerId(t.getCustomer().getCustId());
ct.setCustomerName(t.getCustomer().getCustName());
ct.setTxnId(t.getTxnId());
ct.setTxnDate(t.getTxnDate());
ct.setTxnAmount(t.getTxnAmount());
ctList.add(ct);
}
return ctList;
}
}
在CustomerServiceImpl类的getAllCustomersByAmount()方法中,我们可以看到CustomerTransactionDTO的用法。
TransactionService接口
import java.time.LocalDateTime;
import java.util.List;
import com.theitroad.entities.Transaction;
public interface TransactionService {
Transaction getTransactionById(int id);
List<Transaction> getTransactionsByDate(LocalDateTime date);
List<Transaction> getAllTransactionsBetweenAmount(String amountRange);
List<Transaction> getAllTransactionsByAmount(double amount);
}
TransactionServiceImpl类
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.theitroad.dao.TransactionRepository;
import com.theitroad.entities.Transaction;
@Service
public class TransactionServiceImpl implements TransactionService{
@Autowired
private TransactionRepository repository;
@Override
public Transaction getTransactionById(int id) {
return repository.findById(id).get();
}
@Override
public List<Transaction> getTransactionsByDate(LocalDateTime fromDate) {
// Passing the range for date- 00:00:00 to 23:59:00
LocalDateTime toDate = LocalDateTime.of(fromDate.toLocalDate(), LocalTime.of(23, 59, 59));
return repository.findTransactionsByTxnDateBetween(fromDate, toDate);
}
@Override
public List<Transaction> getAllTransactionsBetweenAmount(String amountRange) {
// Splitting the amount range passed in the form amt1-amt2
String[] temp = amountRange.split("-");
double fromAmount = Double.parseDouble(temp[0]);
double toAmount = Double.parseDouble(temp[1]);
System.out.println("fromAmount " + fromAmount);
System.out.println("toAmount " + toAmount);
return repository.findTransactionByTxnAmountBetween(fromAmount, toAmount);
}
@Override
public List<Transaction> getAllTransactionsByAmount(double amount) {
return repository.findTransactionByTxnAmountGreaterThan(amount);
}
}
数据库配置
可以将数据库配置(例如连接URL,用户,密码)放在src / main / resources中的application.properties文件中。请根据配置更改值。
spring.datasource.url=jdbc:oracle:thin:@localhost:1521/XEPDB1 spring.datasource.username=test spring.datasource.password=test spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver spring.jpa.show-sql=true #spring.jpa.properties.hibernate.format_sql=true
应用类别
应用程序类具有main方法。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SbjpaDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SbjpaDemoApplication.class, args);
}
}
运行应用程序并访问REST服务
我们可以通过将应用程序类作为Java应用程序(或者Spring引导应用程序)运行来启动示例。
Spring Boot会将应用程序自动配置为Spring Boot Rest服务+ Data JPA应用程序。一旦看到嵌入式Tomcat服务器已启动的消息,就可以使用Postman测试方法。
建立客户
我们可以在DB表中验证是否插入了相应的客户和交易记录。
更新现有客户
获取交易金额大于通过金额的客户和交易数据
通过ID获取交易
获取给定金额范围内的交易

