提供新增、修改、刪除、讀取等功能,存入資料庫, EP 5-3 使用 JPA 增加範例 ,並透過 JUnit 5 來驗證產出結果。
Table of Contents
Toggle前言
Spring Data JPA 是基於 Hibernate 開發的 JPA 框架,簡化了 JPA 的寫法,可以在幾乎不用寫實作程式碼的情況下,實現對資料庫的存取操作,也能設定多個資料庫來源,使用多個資料表查詢,採用原生 SQL 自訂查詢等功能。
Web JPA
檔案目錄
./
+- build.gradle
+- src
+- main
+- resources
| +- application.properties
+- java
+- org
+- ruoxue
+- spring_boot_168
+- config
| +- DataSourceConfig
+- sso
| +- account
| +- model
| | +- Account.java
| +- repository
| | +- AccountRepository.java
設定
application.properties
顯示 SQL 語法:
spring.jpa.show-sql=true
DataSourceConfig.java
basePackages 增加:
org.ruoxue.spring_boot_168.sso.account.repository
packages 增加:
org.ruoxue.spring_boot_168.sso.account.model
package org.ruoxue.spring_boot_168.config;
import java.util.Map;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateSettings;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import lombok.extern.slf4j.Slf4j;
@EnableTransactionManagement
@EnableJpaRepositories(entityManagerFactoryRef = "entityManagerFactory", //
transactionManagerRef = "transactionManager", //
basePackages = { //
"org.ruoxue.spring_boot_168.sso.account.repository" //
})
@Configuration
@EnableConfigurationProperties
@Slf4j
public class DataSourceConfig extends DataSourceAutoConfiguration {
@Autowired
private HibernateProperties hibernateProperties;
@Autowired
private JpaProperties jpaProperties;
@Primary
@Bean(name = "dataSourceProperties")
@ConfigurationProperties(prefix = "spring.datasource.hikari")
public DataSourceProperties dataSourceProperties() {
DataSourceProperties dataSourceProperties = new DataSourceProperties();
return dataSourceProperties;
}
@Primary
@Bean(name = "hikariConfig")
@ConfigurationProperties(prefix = "spring.datasource.hikari")
public HikariConfig hikariConfig() {
HikariConfig hikariConfig = new HikariConfig();
return hikariConfig;
}
@Primary
@Bean(name = "dataSource")
public DataSource dataSource(@Qualifier("hikariConfig") HikariConfig hikariConfig) {
log.info("poolName: " + hikariConfig.getPoolName());
log.info("jdbcUrl: " + hikariConfig.getJdbcUrl());
log.info("username: " + hikariConfig.getUsername());
log.info("driverClassName: " + hikariConfig.getDriverClassName());
log.info("maximumPoolSize: " + hikariConfig.getMaximumPoolSize());
log.info("connectionTimeout: " + hikariConfig.getConnectionTimeout());
log.info("maxLifetime: " + hikariConfig.getMaxLifetime());
log.info("autoCommit: " + hikariConfig.isAutoCommit());
log.info("jpaProperties: " + jpaProperties.getProperties());
DataSource dataSource = new HikariDataSource(hikariConfig);
return dataSource;
}
@Primary
@Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder,
@Qualifier("dataSource") DataSource dataSource) {
Map<String, Object> properties = hibernateProperties.determineHibernateProperties(jpaProperties.getProperties(),
new HibernateSettings());
return builder.dataSource(dataSource)//
.packages( //
"org.ruoxue.spring_boot_168.sso.account.model" //
).persistenceUnit("primaryDatabase")//
.properties(properties)//
.build();
}
@Primary
@Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(
@Qualifier("entityManagerFactory") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
@Primary
@Bean(name = "jdbcTemplate")
public JdbcTemplate jdbcTemplate(@Qualifier("dataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
實作
Account.java
建立 Model,存放帳號、密碼等資訊,表格名稱 sso_account,可自定義,並且建立兩個唯一值欄位,使用 unique 索引。
package org.ruoxue.spring_boot_168.sso.account.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.Table;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.hibernate.annotations.GenericGenerator;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Entity
@Table(name = "sso_account", indexes = { @Index(name = "UK_sso_account_cid", columnList = "cid", unique = true),
@Index(name = "IDX_sso_account_name", columnList = "name", unique = true) })
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Builder
public class Account {
@GeneratedValue(generator = "uuid")
@GenericGenerator(name = "uuid", strategy = "uuid")
@Column(length = 32)
@Id
private String id;
/** 帳號 */
@Column(length = 30)
private String cid;
/** 名稱 */
@Column(length = 50)
private String name;
/** 密碼 */
@Column(length = 32)
private String password;
/** 密碼鹽 */
@Column(length = 32)
private String salt;
@Override
public String toString() {
ToStringBuilder builder = new ToStringBuilder(this, ToStringStyle.JSON_STYLE);
builder.appendSuper(super.toString());
builder.append("cid", cid);
builder.append("name", name);
return builder.toString();
}
}
Spring Boot Web JPA Example
AccountRepository.java
建立 Repository ,存取資料庫,繼承 PagingAndSortingRepository 介面,定義 findByCid,依 cid 取得帳號,其他不需再任何實作,因為 JPA 已經實現功能。
package org.ruoxue.spring_boot_168.sso.account.repository;
import org.ruoxue.spring_boot_168.sso.account.model.Account;
import org.springframework.data.repository.PagingAndSortingRepository;
/**
* 帳號儲存庫
*/
public interface AccountRepository extends PagingAndSortingRepository<Account, String> {
/**
* 依cid取得帳號
*
* @param cid
* @return
*/
Account findByCid(String cid);
}
測試 JUnit 5
AccountRepositoryTest.java
新增單元測試,驗證是否符合預期 。
package org.ruoxue.spring_boot_168.sso.account.repository;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.ruoxue.spring_boot_168.Application;
import org.ruoxue.spring_boot_168.sso.account.model.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = Application.class)
public class AccountRepositoryTest {
@Autowired
private AccountRepository repository;
@Test
public void repository() {
System.out.println(repository);
assertNotNull(repository);
}
@Test
public void insert() {
String value = "ruoxue";
Account found = repository.findByCid(value);
if (found != null) {
return;
}
Account account = new Account();
account.setCid(value);
account.setName("player");
account.setPassword("");
account.setSalt("");
Account result = repository.save(account);
System.out.println(result);
assertNotNull(result);
assertEquals(value, result.getCid());
}
@Test
public void findByCid() {
String value = "ruoxue";
Account account = repository.findByCid(value);
System.out.println(account);
if (account != null) {
assertEquals(value, account.getCid());
}
}
@Test
public void update() {
String value = "ruoxue";
String name = "test_player";
Account account = repository.findByCid(value);
if (account != null) {
account.setName(name);
Account updated = repository.save(account);
System.out.println(account);
assertEquals(name, updated.getName());
}
}
@Test
public void delete() {
String value = "ruoxue";
Account account = repository.findByCid(value);
if (account != null) {
repository.delete(account);
System.out.println(account);
}
}
}
web jpa insert
測試方法上點右鍵執行 Run As -> JUnit Test ,查看 console。
Hibernate: select account0_.id as id1_1_, account0_.cid as cid2_1_, account0_.name as name3_1_, account0_.password as password4_1_, account0_.salt as salt5_1_ from sso_account account0_ where account0_.cid=?
Hibernate: insert into sso_account (cid, name, password, salt, id) values (?, ?, ?, ?, ?)
{"cid":"ruoxue","name":"player"}
web jpa findByCid
測試方法上點右鍵執行 Run As -> JUnit Test ,查看 console。
Hibernate: select account0_.id as id1_1_, account0_.cid as cid2_1_, account0_.name as name3_1_, account0_.password as password4_1_, account0_.salt as salt5_1_ from sso_account account0_ where account0_.cid=?
Hibernate: select account0_.id as id1_1_0_, account0_.cid as cid2_1_0_, account0_.name as name3_1_0_, account0_.password as password4_1_0_, account0_.salt as salt5_1_0_ from sso_account account0_ where account0_.id=?
Hibernate: update sso_account set cid=?, name=?, password=?, salt=? where id=?
{"cid":"ruoxue","name":"test_player"}
web jpa update
測試方法上點右鍵執行 Run As -> JUnit Test ,查看 console。
Hibernate: select account0_.id as id1_1_, account0_.cid as cid2_1_, account0_.name as name3_1_, account0_.password as password4_1_, account0_.salt as salt5_1_ from sso_account account0_ where account0_.cid=?
Hibernate: select account0_.id as id1_1_0_, account0_.cid as cid2_1_0_, account0_.name as name3_1_0_, account0_.password as password4_1_0_, account0_.salt as salt5_1_0_ from sso_account account0_ where account0_.id=?
Hibernate: update sso_account set cid=?, name=?, password=?, salt=? where id=?
{"cid":"ruoxue","name":"test_player"}
web jpa delete
測試方法上點右鍵執行 Run As -> JUnit Test ,查看 console。
Hibernate: select account0_.id as id1_1_, account0_.cid as cid2_1_, account0_.name as name3_1_, account0_.password as password4_1_, account0_.salt as salt5_1_ from sso_account account0_ where account0_.cid=?
Hibernate: select account0_.id as id1_1_0_, account0_.cid as cid2_1_0_, account0_.name as name3_1_0_, account0_.password as password4_1_0_, account0_.salt as salt5_1_0_ from sso_account account0_ where account0_.id=?
Hibernate: delete from sso_account where id=?
{"cid":"ruoxue","name":"test_player"}
心得分享
Spring Web JPA
實現了 CRUD 功能,及提供分頁、排序等常見功能,快速方便建立 DAO,現稱為 Repository,可以大幅減少開發時間,日後若要新增其他欄位,只需對 Account 增加屬性,要是移除屬性,則需要到資料庫刪除欄位,複雜的查詢也可以使用原生的 SQL 語法來操作,當然方便開發不代表執行效率高,兩者要互相搭配,視情況而進行調整。