通常專案中的資料庫設計,會有多個欄位組合Key值存取取資料的情境,Spring boot 提供了JPA為以@Embeddable宣告Entity Key的功能,先前資料庫設計與存取單一Key架構時,已包容了針對多欄位Key的情境設計,原先應該在同一文件說明,但更新存檔時有問題,所以新增多欄位 Key的設計與存取的說明 。
與單一Key 的Entity結構相同,但 DataKey 有多個欄位 以及 Mybatis 的DataKeyHandler 也要處裡多個欄位。
package tw.lewishome.webapp.database.primary.entity;
import java.io.Serializable;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.EmbeddedId;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import tw.lewishome.webapp.database.audit.EntityAudit;
/**
* 系統參數資料 Entity
*
* @author Lewis
*/
@Entity
@Table(name = "sysparmdata")
@Data
@EqualsAndHashCode(callSuper = true)
// @SQLDelete(sql = "UPDATE sysparmdata SET deleted = true WHERE dataKey=?")
// @SQLRestriction("deleted = false")
@AllArgsConstructor
public class SysParmDataEntity extends EntityAudit<String> {
/**
* Fix for javadoc warning :
* use of default constructor, which does not provide a comment
* Constructs a new SysParmDataEntity instance.
* This is the default constructor, implicitly provided by the compiler
* if no other constructors are defined.
*/
public SysParmDataEntity() {
// Constructor body (can be empty)
}
private static final long serialVersionUID = 1L;
/** Primary Key */
@EmbeddedId
public DataKey dataKey;
/** 參數值(Value) */
@Column(name = "parmValue", length = 1024)
public String parmValue = "";
/** 參數說明 */
@Column(name = "parmDesc", length = 256)
public String parmDesc = "";
/** 是否加密 */
@Column(name = "isEncrypt")
public Boolean isEncrypt = false;
/**
* SysParm Entity Key
*
*/
@Embeddable
@Data
public static class DataKey implements Serializable {
/**
* Fix for javadoc warning :
* use of default constructor, which does not provide a comment
* Constructs a new DataKey instance.
* This is the default constructor, implicitly provided by the compiler
* if no other constructors are defined.
*/
public DataKey() {
// Constructor body (can be empty)
}
private static final long serialVersionUID = 1L;
/** 參數環境 */
@Column(name = "env", length = 16)
public String env;
/** 參數名稱 */
@Column(name = "parmName", length = 128)
public String parmName;
/** 參數鍵(Key) */
@Column(name = "parmKey", length = 128)
public String parmKey;
}
/** MyBatis TypeHandler for DataKey */
public static class DataKeyHandler extends BaseTypeHandler<DataKey> {
/**
* Fix for javadoc warning :
* use of default constructor, which does not provide a comment
* Constructs a new AsyncServiceWorkerSample instance.
* This is the default constructor, implicitly provided by the compiler
* if no other constructors are defined.
*/
public DataKeyHandler() {
// Constructor body (can be empty)
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, DataKey parameter, JdbcType jdbcType)
throws SQLException {
try {
ps.setString(1, parameter.getEnv());
ps.setString(2, parameter.getParmKey());
ps.setString(3, parameter.getParmName());
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public DataKey getNullableResult(ResultSet rs, String columnName) throws SQLException {
DataKey dataKey = new DataKey();
if (rs.wasNull() == false) {
dataKey.setEnv(rs.getString("env"));
dataKey.setParmKey(rs.getString("parm_key"));
dataKey.setParmName(rs.getString("parm_name"));
}
return dataKey;
}
@Override
public DataKey getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
DataKey dataKey = new DataKey();
if (rs.wasNull() == false) {
dataKey.setEnv(rs.getString(1));
dataKey.setParmKey(rs.getString(2));
dataKey.setParmName(rs.getString(3));
}
return dataKey;
}
@Override
public DataKey getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
DataKey dataKey = new DataKey();
if (cs.wasNull() == false) {
dataKey.setEnv(cs.getString(1));
dataKey.setParmKey(cs.getString(2));
dataKey.setParmName(cs.getString(3));
}
return dataKey;
}
}
}
原則上與單一Key的Repository相同結構。
package tw.lewishome.webapp.database.primary.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import tw.lewishome.webapp.database.primary.entity.SysParmDataEntity;
/**
* SysParm JPA Repository
*
* @author lewis
* @version $Id: $Id
*/
@Transactional
@Repository
public interface SysParmDataRepository extends JpaRepository<SysParmDataEntity, SysParmDataEntity.DataKey>,
JpaSpecificationExecutor<SysParmDataEntity> {
}
原則上與單一Key的Repository相同結構。
package tw.lewishome.webapp.database.primary.specification;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.jpa.domain.Specification;
import jakarta.persistence.criteria.Predicate;
import tw.lewishome.webapp.GlobalConstants;
import tw.lewishome.webapp.database.primary.entity.SysParmDataEntity;
/**
*
* SysParmDataSpecification class.
*
*
* @author lewis
*/
public class SysParmDataSpecification {
// Private constructor to prevent instantiation
private SysParmDataSpecification() {
throw new IllegalStateException("This is a Specification class and cannot be instantiated");
}
/**
*
* getDataKeys.
*
*
* @param env a String object
* @param parmName a String object
* @param parmKey a String object
* @return a {@link org.springframework.data.jpa.domain.Specification} object
*/
public static Specification<SysParmDataEntity> getDataKeys(String env, String parmName,
String parmKey) {
return (root, query, criteriaBuilder) -> {
Predicate findDataPredicate = criteriaBuilder.conjunction(); // Start with no condition
if (StringUtils.isNotBlank(env)) {
findDataPredicate = criteriaBuilder.and(findDataPredicate,
criteriaBuilder.like(root.get("dataKey").get("env"), env));
}
if (StringUtils.isNotBlank(parmName)) {
findDataPredicate = criteriaBuilder.and(findDataPredicate,
criteriaBuilder.like(root.get("dataKey").get("parmName"),
parmName));
}
if (StringUtils.isNotBlank(parmKey)) {
findDataPredicate = criteriaBuilder.and(findDataPredicate,
criteriaBuilder.like(root.get("dataKey").get("parmKey"),
parmKey));
}
return findDataPredicate;
};
}
/**
*
* getSysParmDataEntityByKeys.
*
*
* @param dataKey a
* {@link tw.lewishome.webapp.database.primary.entity.SysParmDataEntity.DataKey}
* object
* @return a {@link org.springframework.data.jpa.domain.Specification} object
*/
public static Specification<SysParmDataEntity> getSysParmDataEntityByKeys(SysParmDataEntity.DataKey dataKey) {
String env = dataKey.getEnv();
String parmName = dataKey.getParmName();
String parmKey = dataKey.getParmName();
return getDataKeys(env, parmName, parmKey);
}
/**
*
* getPageSearchCondition.
*
*
* @param headerDataString a String object
* @param oSearch a String object
* @return a {@link org.springframework.data.jpa.domain.Specification} object
*/
public static Specification<SysParmDataEntity> getPageSearchCondition(String headerDataString, String oSearch) {
return (root, query, criteriaBuilder) -> {
Predicate searchPredicate = criteriaBuilder.disjunction(); // Start with no condition (false)
if (StringUtils.isNotBlank(oSearch)) { // search string condition
searchPredicate = criteriaBuilder.or(searchPredicate,
criteriaBuilder.like(
criteriaBuilder.upper(
root.get("dataKey").get("env")),
"%" + oSearch.toUpperCase() + "%"));
searchPredicate = criteriaBuilder.or(searchPredicate,
criteriaBuilder.like(
criteriaBuilder.upper(root.get("dataKey")
.get("parmName")),
"%" + oSearch.toUpperCase() + "%"));
searchPredicate = criteriaBuilder.or(searchPredicate,
criteriaBuilder.like(
criteriaBuilder.upper(root.get("dataKey")
.get("parmKey")),
"%" + oSearch.toUpperCase() + "%"));
searchPredicate = criteriaBuilder.or(searchPredicate,
criteriaBuilder.like(
criteriaBuilder.upper(root.get("parmDesc")),
"%" + oSearch.toUpperCase() + "%"));
searchPredicate = criteriaBuilder.or(searchPredicate,
criteriaBuilder.like(
criteriaBuilder.upper(root.get("parmValue")),
"%" + oSearch.toUpperCase() + "%"));
} else {
searchPredicate = criteriaBuilder.conjunction();
}
if (StringUtils.isNotBlank(headerDataString)) { // and condition when exist headerDataString
Predicate headerPredicate = criteriaBuilder.conjunction(); // Start with no condition
// (true)
// SysParmDataPageModel.TableHeaderData headerData = new SysParmDataPageModel.TableHeaderData();
// try {
// headerData = (SysParmDataPageModel.TableHeaderData) TypeConvert
// .base64StringToObject(headerDataString);
// } catch (ClassNotFoundException | IOException e) {
// e.printStackTrace();
// }
// String dataKeyEnv = headerData.getSelectedEnv();
// headerPredicate = criteriaBuilder.and(headerPredicate,
// criteriaBuilder.equal(
// criteriaBuilder.upper(
// root.get("dataKey").get("env")),
// dataKeyEnv.toUpperCase()));
searchPredicate = criteriaBuilder.and(searchPredicate, headerPredicate);
}
return searchPredicate;
};
}
/**
*
* getListParmKeyByName.
*
*
* @param dataKeyParmName a String object
* @return a {@link org.springframework.data.jpa.domain.Specification} object
*/
public static Specification<SysParmDataEntity> getListParmKeyByName(String dataKeyParmName) {
return (root, query, criteriaBuilder) -> {
Predicate findDataPredicate = criteriaBuilder.disjunction();
if (StringUtils.isNotBlank(dataKeyParmName)) {
findDataPredicate = criteriaBuilder.and(findDataPredicate, criteriaBuilder.equal(
criteriaBuilder.upper(root.get("dataKey").get("env")),
GlobalConstants.SYS_PROFILE_ACTIVE.toUpperCase()));
findDataPredicate = criteriaBuilder.and(findDataPredicate, criteriaBuilder.equal(
criteriaBuilder.upper(root.get("dataKey").get("parmName")),
dataKeyParmName.toUpperCase()));
}
return findDataPredicate;
};
}
}
原則上與單一Key的Mapper相同結構。
package tw.lewishome.webapp.database.primary.mybatis;
import java.util.List;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
// import org.apache.ibatis.annotations.Update;
import tw.lewishome.webapp.database.audit.AuditMybatisSqlString;
import tw.lewishome.webapp.database.primary.entity.SysParmDataEntity;
/**
*
* SysAccessLogMapper interface.
*
*
* @author lewis
*/
@Mapper
public interface SysParmDataMapper {
/**
*
* findAll.
* wiill refereance SysParmDataMapper.xml
* @return a List object SysParmDataEntity
*/
List<SysParmDataEntity> findByAll();
/**
*
* findByDataKey.
*
* wiill refereance SysParmDataMapper.xml
* @param env the environment to search for
* @param parmName the parameter name to search for
* @param parmKey the parameter key to search for
* @return a List object
*/
SysParmDataEntity findByDataKey(@Param("parmEnv") String env, @Param("parmName") String parmName,
@Param("parmKey") String parmKey);
/**
*
* findByEnv.
*
* will refereance SysParmDataMapper.xml
* @param env the environment to search for
* @return a List object
*/
List<SysParmDataEntity> findByEnv(@Param("parmEnv") String env);
/**
* Insert a SysParmDataEntity.
*
* Mybatis will auto-handle EntityAudit fields,
* so we do not need to set them manually.
* but need declare them in the SQL.
*
* @param sysParmDataEntity the entity to insert
* @return the number of rows affected
*/
@Insert("INSERT INTO sysparmdata (env, parm_name, parm_key, parm_value, parm_desc, is_encrypt, "
+ AuditMybatisSqlString.AUDIT_COLUMN_STRING + " ) VALUES ("
+ " #{dataKey.env}, #{dataKey.parmName}, #{dataKey.parmKey}, "
+ " #{parmValue}, #{parmDesc}, #{isEncrypt}," + AuditMybatisSqlString.AUDIT_FIELD_STRING + " )")
int insert(SysParmDataEntity sysParmDataEntity);
/**
* Delete a sysparmdata by Env.
*
* @param parmEnv the ID of the parmdata to delete
* @param parmName the name of the parmdata to delete
* @param parmType the key of the parmdata to delete
* @return the number of rows affected
*/
@Delete("DELETE FROM sysparmdata WHERE env = #{sysEnv} and parm_name = #{sysParmName} and parm_key = #{sysParmKey}")
int deleteByDataKey(@Param("sysEnv") String parmEnv,
@Param("sysParmName") String parmName,
@Param("sysParmKey") String parmType);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- https://mybatis.org/thymeleaf-scripting/user-guide.html -->
<mapper namespace="tw.lewishome.webapp.database.primary.mybatis.SysParmDataMapper">
<resultMap id="sysParmDataMap"
type="tw.lewishome.webapp.database.primary.entity.SysParmDataEntity"
extends="AuditFieldsResultMap.auditFields">
<result property="parmDesc" column="parm_desc" />
<result property="parmValue" column="parm_value" />
<result property="isEncrypt" column="is_encrypt" />
<association property="dataKey" javaType="tw.lewishome.webapp.database.primary.entity.SysParmDataEntity$DataKey"
typeHandler="tw.lewishome.webapp.database.primary.entity.SysParmDataEntity$DataKeyHandler">
</association>
</resultMap>
<select id="findByAll" resultMap="sysParmDataMap">
SELECT * FROM sysparmdata
</select>
<select id="findByEnv" resultMap="sysParmDataMap">
SELECT * FROM sysparmdata WHERE env =
#{parmEnv};
</select>
<select id="findByDataKey" resultMap="sysParmDataMap">
SELECT * FROM sysparmdata WHERE env =
#{parmEnv} and parm_name = #{parmName} and parm_key = #{parmKey};
</select>
</mapper>
package tw.lewishome.webapp.database.primary.repository;
import static org.junit.jupiter.api.Assertions.*;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.test.context.TestPropertySource;
import tw.lewishome.webapp.database.primary.entity.SysParmDataEntity;
import tw.lewishome.webapp.database.primary.specification.SysParmDataSpecification;
/**
* SysParmDataRepository Integration Tests
*
* Tests for SysParmDataRepository using real repository with
* @BeforeEach to add test data and @AfterEach to clean up.
*
* @author lewis
*/
@SpringBootTest
@TestPropertySource(locations = "classpath:application.properties")
public class SysParmDataRepositoryTest {
@Autowired
private SysParmDataRepository sysParmDataRepository;
private SysParmDataEntity testEntity1;
private SysParmDataEntity testEntity2;
private SysParmDataEntity testEntity3;
/**
* Setup test data before each test
* Creates test parameter entities and saves them to database
*/
@BeforeEach
void setUp() {
// Create first test entity
testEntity1 = new SysParmDataEntity();
SysParmDataEntity.DataKey dataKey1 = new SysParmDataEntity.DataKey();
dataKey1.setEnv("DEV");
dataKey1.setParmName("DATABASE_URL");
dataKey1.setParmKey("PRIMARY");
testEntity1.setDataKey(dataKey1);
testEntity1.setParmValue("jdbc:mysql://localhost:3306/testdb");
testEntity1.setParmDesc("Primary Database Connection URL");
testEntity1.setIsEncrypt(false);
SysParmDataEntity existingEntity1 = sysParmDataRepository.findById(dataKey1).orElse(null);
if (existingEntity1 != null && existingEntity1.getDataKey() != null) {
sysParmDataRepository.delete(existingEntity1);
}
// Create second test entity
testEntity2 = new SysParmDataEntity();
SysParmDataEntity.DataKey dataKey2 = new SysParmDataEntity.DataKey();
dataKey2.setEnv("DEV");
dataKey2.setParmName("DATABASE_PASSWORD");
dataKey2.setParmKey("ADMIN");
testEntity2.setDataKey(dataKey2);
testEntity2.setParmValue("encrypted_password_123");
testEntity2.setParmDesc("Database Admin Password");
testEntity2.setIsEncrypt(true);
SysParmDataEntity existingEntity2 = sysParmDataRepository.findById(dataKey2).orElse(null);
if (existingEntity2 != null && existingEntity2.getDataKey() != null) {
sysParmDataRepository.delete(existingEntity2);
}
// Create third test entity
testEntity3 = new SysParmDataEntity();
SysParmDataEntity.DataKey dataKey3 = new SysParmDataEntity.DataKey();
dataKey3.setEnv("PROD");
dataKey3.setParmName("DATABASE_URL");
dataKey3.setParmKey("SECONDARY");
testEntity3.setDataKey(dataKey3);
testEntity3.setParmValue("jdbc:mysql://prod-server:3306/proddb");
testEntity3.setParmDesc("Secondary Database Connection URL for Production");
testEntity3.setIsEncrypt(false);
SysParmDataEntity existingEntity3 = sysParmDataRepository.findById(dataKey3).orElse(null);
if (existingEntity3 != null && existingEntity3.getDataKey() != null) {
sysParmDataRepository.delete(existingEntity3);
}
// Save all test entities to database
testEntity1 = sysParmDataRepository.saveAndFlush(testEntity1);
testEntity2 = sysParmDataRepository.saveAndFlush(testEntity2);
testEntity3 = sysParmDataRepository.saveAndFlush(testEntity3);
}
/**
* Cleanup test data after each test
* Removes all test entities from database
*/
@AfterEach
void tearDown() {
// Delete test entities
SysParmDataEntity.DataKey dataKey1 = new SysParmDataEntity.DataKey();
dataKey1.setEnv("DEV");
dataKey1.setParmName("DATABASE_URL");
dataKey1.setParmKey("PRIMARY");
SysParmDataEntity existingEntity1 = sysParmDataRepository.findById(dataKey1).orElse(null);
if (existingEntity1 != null && existingEntity1.getDataKey() != null) {
sysParmDataRepository.delete(existingEntity1);
}
SysParmDataEntity.DataKey dataKey2 = new SysParmDataEntity.DataKey();
dataKey2.setEnv("DEV");
dataKey2.setParmName("DATABASE_PASSWORD");
dataKey2.setParmKey("ADMIN");
SysParmDataEntity existingEntity2 = sysParmDataRepository.findById(dataKey2).orElse(null);
if (existingEntity2 != null && existingEntity2.getDataKey() != null) {
sysParmDataRepository.delete(existingEntity2);
}
SysParmDataEntity.DataKey dataKey3 = new SysParmDataEntity.DataKey();
dataKey3.setEnv("PROD");
dataKey3.setParmName("DATABASE_URL");
dataKey3.setParmKey("SECONDARY");
SysParmDataEntity existingEntity3 = sysParmDataRepository.findById(dataKey3).orElse(null);
if (existingEntity3 != null && existingEntity3.getDataKey() != null) {
sysParmDataRepository.delete(existingEntity3);
}
}
/**
* Test: Find parameter by ID (primary key)
* Verifies that findById returns correct entity
*/
@Test
void testFindById() {
// Query by DataKey
Optional<SysParmDataEntity> found = sysParmDataRepository.findById(testEntity1.getDataKey());
// Verify entity was found
assertTrue(found.isPresent());
assertEquals("DEV", found.get().getDataKey().getEnv());
assertEquals("DATABASE_URL", found.get().getDataKey().getParmName());
assertEquals("PRIMARY", found.get().getDataKey().getParmKey());
assertEquals("jdbc:mysql://localhost:3306/testdb", found.get().getParmValue());
}
/**
* Test: Find parameter by environment
* Verifies that specification-based query works correctly
*/
@Test
void testFindByEnvironment() {
// Create specification for DEV environment
Specification<SysParmDataEntity> spec = SysParmDataSpecification.getDataKeys("DEV", null, null);
List<SysParmDataEntity> found = sysParmDataRepository.findAll(spec);
// Verify entities were found
assertNotNull(found);
assertTrue(found.size() >= 2, "Should find at least 2 DEV parameters");
// Verify both DEV entities are in results
long devCount = found.stream()
.filter(e -> "DEV".equals(e.getDataKey().getEnv()))
.count();
assertTrue(devCount >0);
}
/**
* Test: Find parameter by name
* Verifies that specification-based query by parameter name works
*/
@Test
void testFindByParameterName() {
// Create specification for DATABASE_URL
Specification<SysParmDataEntity> spec = SysParmDataSpecification.getDataKeys(null, "DATABASE_URL", null);
List<SysParmDataEntity> found = sysParmDataRepository.findAll(spec);
// Verify entities were found
assertNotNull(found);
assertTrue(found.size() >= 2, "Should find at least 2 DATABASE_URL parameters");
// Verify both DATABASE_URL entities are in results
long urlCount = found.stream()
.filter(e -> "DATABASE_URL".equals(e.getDataKey().getParmName()))
.count();
assertEquals(2, urlCount, "Should find exactly 2 DATABASE_URL parameters");
}
/**
* Test: Find parameter by key
* Verifies that specification-based query by parameter key works
*/
@Test
void testFindByParameterKey() {
// Create specification for ADMIN key
Specification<SysParmDataEntity> spec = SysParmDataSpecification.getDataKeys(null, null, "ADMIN");
List<SysParmDataEntity> found = sysParmDataRepository.findAll(spec);
// Verify entity was found
assertNotNull(found);
assertTrue(found.size() >= 1, "Should find at least 1 ADMIN parameter");
// Verify ADMIN entity is in results
boolean hasAdmin = found.stream()
.anyMatch(e -> "ADMIN".equals(e.getDataKey().getParmKey()));
assertTrue(hasAdmin, "Should find ADMIN parameter");
}
/**
* Test: Find with combined criteria
* Verifies that specification with multiple criteria works
*/
@Test
void testFindByEnvironmentAndName() {
// Create specification for DEV environment and DATABASE_URL parameter
Specification<SysParmDataEntity> spec = SysParmDataSpecification.getDataKeys("DEV", "DATABASE_URL", null);
List<SysParmDataEntity> found = sysParmDataRepository.findAll(spec);
// Verify entity was found
assertNotNull(found);
assertTrue(found.size() >= 1, "Should find at least 1 matching parameter");
// Verify correct entity is in results
boolean hasMatch = found.stream()
.anyMatch(e -> "DEV".equals(e.getDataKey().getEnv()) &&
"DATABASE_URL".equals(e.getDataKey().getParmName()));
assertTrue(hasMatch, "Should find DEV DATABASE_URL parameter");
}
/**
* Test: Verify all fields of saved entity
* Ensures all entity fields are correctly stored and retrieved
*/
@Test
void testEntityFieldsArePersisted() {
// Query entity
Optional<SysParmDataEntity> found = sysParmDataRepository.findById(testEntity2.getDataKey());
// Verify all fields
assertTrue(found.isPresent());
SysParmDataEntity entity = found.get();
assertEquals("DEV", entity.getDataKey().getEnv());
assertEquals("DATABASE_PASSWORD", entity.getDataKey().getParmName());
assertEquals("ADMIN", entity.getDataKey().getParmKey());
assertEquals("encrypted_password_123", entity.getParmValue());
assertEquals("Database Admin Password", entity.getParmDesc());
assertEquals(true, entity.getIsEncrypt());
}
/**
* Test: Update parameter value
* Verifies that entity updates are persisted correctly
*/
@Test
void testUpdateParameterValue() {
// Get entity from database
Optional<SysParmDataEntity> found = sysParmDataRepository.findById(testEntity1.getDataKey());
assertTrue(found.isPresent());
SysParmDataEntity entity = found.get();
// Update fields
entity.setParmValue("jdbc:mysql://new-server:3306/newdb");
entity.setParmDesc("Updated Database Connection");
entity.setIsEncrypt(true);
// Save changes
sysParmDataRepository.saveAndFlush(entity);
// Query again to verify
Optional<SysParmDataEntity> updated = sysParmDataRepository.findById(testEntity1.getDataKey());
assertTrue(updated.isPresent());
assertEquals("jdbc:mysql://new-server:3306/newdb", updated.get().getParmValue());
assertEquals("Updated Database Connection", updated.get().getParmDesc());
assertEquals(true, updated.get().getIsEncrypt());
}
/**
* Test: Delete parameter
* Verifies that entity can be deleted and no longer found
*/
@Test
void testDeleteParameter() {
// Verify entity exists
Optional<SysParmDataEntity> found = sysParmDataRepository.findById(testEntity1.getDataKey());
assertTrue(found.isPresent());
// Delete entity
sysParmDataRepository.delete(testEntity1);
sysParmDataRepository.flush();
// Verify entity is deleted
Optional<SysParmDataEntity> notFound = sysParmDataRepository.findById(testEntity1.getDataKey());
assertFalse(notFound.isPresent());
// Reset testEntity1 to null to prevent duplicate deletion in tearDown
testEntity1 = null;
}
/**
* Test: Query all parameters
* Verifies that multiple entities can be found
*/
@Test
void testFindAllParameters() {
// Find all entities
List<SysParmDataEntity> all = sysParmDataRepository.findAll();
// Verify at least our three test entities exist
assertNotNull(all);
assertTrue(all.size() >= 3, "Should have at least 3 parameters in database");
// Verify all three test entities are in results
long testCount = all.stream()
.filter(e -> (testEntity1 != null && e.getDataKey().equals(testEntity1.getDataKey())) ||
(testEntity2 != null && e.getDataKey().equals(testEntity2.getDataKey())) ||
(testEntity3 != null && e.getDataKey().equals(testEntity3.getDataKey())))
.count();
assertEquals(3, testCount, "Should find exactly 3 test parameters");
}
/**
* Test: Verify encrypted parameter
* Verifies that encrypted flag is correctly set and retrieved
*/
@Test
void testEncryptedParameterFlag() {
// Query encrypted parameter
Optional<SysParmDataEntity> encrypted = sysParmDataRepository.findById(testEntity2.getDataKey());
assertTrue(encrypted.isPresent());
assertTrue(encrypted.get().getIsEncrypt(), "Password parameter should be encrypted");
// Query non-encrypted parameter
Optional<SysParmDataEntity> notEncrypted = sysParmDataRepository.findById(testEntity1.getDataKey());
assertTrue(notEncrypted.isPresent());
assertFalse(notEncrypted.get().getIsEncrypt(), "URL parameter should not be encrypted");
}
/**
* Test: Find by specification with all criteria
* Verifies complex specification query with environment, name, and key
*/
@Test
void testFindBySpecificationWithAllCriteria() {
// Create specification with all criteria
Specification<SysParmDataEntity> spec = SysParmDataSpecification
.getDataKeys("DEV", "DATABASE_PASSWORD", "ADMIN");
List<SysParmDataEntity> found = sysParmDataRepository.findAll(spec);
// Verify entity was found
assertNotNull(found);
assertTrue(found.size() >= 1, "Should find matching parameter");
// Verify exact match
boolean hasExactMatch = found.stream()
.anyMatch(e -> "DEV".equals(e.getDataKey().getEnv()) &&
"DATABASE_PASSWORD".equals(e.getDataKey().getParmName()) &&
"ADMIN".equals(e.getDataKey().getParmKey()));
assertTrue(hasExactMatch, "Should find exact matching parameter");
}
}
package tw.lewishome.webapp.database.primary.mybatis;
import static org.junit.jupiter.api.Assertions.*;
import java.util.List;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import tw.lewishome.webapp.database.primary.entity.SysParmDataEntity;
/**
* SysParmDataMapper Unit Tests
*
* Tests for SysParmDataMapper using Mockito for mocking.
*
* @author lewis
*/
@SpringBootTest
public class SysParmDataMapperTest {
@Autowired
private SysParmDataMapper sysParmDataMapper;
private SysParmDataEntity testEntity1;
private SysParmDataEntity testEntity2;
@BeforeEach
void setUp() {
// Create first test entity
testEntity1 = new SysParmDataEntity();
SysParmDataEntity.DataKey dataKey1 = new SysParmDataEntity.DataKey();
dataKey1.setEnv("TEST_ENV_001");
dataKey1.setParmName("TEST_PARM_NAME_001");
dataKey1.setParmKey("TEST_PARM_KEY_001");
testEntity1.setDataKey(dataKey1);
testEntity1.setParmDesc("TEST_DESC_001");
testEntity1.setParmValue("TEST_VALUE_001");
testEntity1.setIsEncrypt(false);
// Create second test entity
testEntity2 = new SysParmDataEntity();
SysParmDataEntity.DataKey dataKey2 = new SysParmDataEntity.DataKey();
dataKey2.setEnv("TEST_ENV_002");
dataKey2.setParmName("TEST_PARM_NAME_002");
dataKey2.setParmKey("TEST_PARM_KEY_002");
testEntity2.setDataKey(dataKey2);
testEntity2.setParmDesc("TEST_DESC_002");
testEntity2.setParmValue("TEST_VALUE_002");
testEntity2.setIsEncrypt(false);
// Save both test entities to database
sysParmDataMapper.deleteByDataKey("TEST_ENV_001", "TEST_PARM_NAME_001", "TEST_PARM_KEY_001");
sysParmDataMapper.insert(testEntity1);
sysParmDataMapper.deleteByDataKey("TEST_ENV_002", "TEST_PARM_NAME_002", "TEST_PARM_KEY_002");
sysParmDataMapper.insert(testEntity2);
}
/**
* Cleanup test data after each test
* Removes all test entities from database
*/
@AfterEach
void tearDown() {
sysParmDataMapper.deleteByDataKey("TEST_ENV_001", "TEST_PARM_NAME_001", "TEST_PARM_KEY_001");
sysParmDataMapper.deleteByDataKey("TEST_ENV_002", "TEST_PARM_NAME_002", "TEST_PARM_KEY_002");
}
@Test
void testFindByIdReturnsListOfEntities() {
List<SysParmDataEntity> listSysParmDataEntity = sysParmDataMapper.findByAll();
assertNotNull(listSysParmDataEntity);
assertTrue(listSysParmDataEntity.size()>=2);
}
@Test
void testFindByIdReturnsCorrectData() {
List<SysParmDataEntity> result = sysParmDataMapper.findByEnv("TEST_ENV_001");
assertNotNull(result);
assertEquals(1, result.size());
}
@Test
void testFindByIdReturnsEmptyList() {
List<SysParmDataEntity> result = sysParmDataMapper.findByEnv("Empty_ENV_001");
assertNotNull(result);
assertEquals(0, result.size());
}
@Test
void testFindByIdMultipleInvocations() {
SysParmDataEntity result1 = sysParmDataMapper.findByDataKey("TEST_ENV_001", "TEST_PARM_NAME_001", "TEST_PARM_KEY_001");
assertNotNull(result1);
SysParmDataEntity result2 = sysParmDataMapper.findByDataKey("TEST_ENV_002", "TEST_PARM_NAME_002", "TEST_PARM_KEY_002");
assertNotNull(result2);
}
}