Web JPA - Spring Boot 168 EP 5-3

Web JPA – Spring Boot 168 EP 5-3

提供新增、修改、刪除、讀取等功能,存入資料庫, EP 5-3 使用 JPA 增加範例 ,並透過 JUnit 5 來驗證產出結果。

前言

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

Config 如何設定,參考此篇:

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 語法來操作,當然方便開發不代表執行效率高,兩者要互相搭配,視情況而進行調整。

發佈留言