iT邦幫忙

0

VScode 開發應用系統專案(4) - Spring Boot資料庫設計與存取 - 多欄位 Key

  • 分享至 

  • xImage
  •  

Spring boot 資料庫設計與存取 (多欄位Key)

概述

通常專案中的資料庫設計,會有多個欄位組合Key值存取取資料的情境,Spring boot 提供了JPA為以@Embeddable宣告Entity Key的功能,先前資料庫設計與存取單一Key架構時,已包容了針對多欄位Key的情境設計,原先應該在同一文件說明,但更新存檔時有問題,所以新增多欄位 Key的設計與存取的說明 。

準備與檢核

  1. 建置Spring Boot專案後系統自動產生了 application.properties。
  1. 工具類程式已經準備好可以使用。
  1. Spring boot 多資料庫支援的配置。
  1. Spring Boot資料庫設計與存取 - 單一欄位 Key (必須要先理解)
  • 參考 URL:https://ithelp.ithome.com.tw/articles/10398629

增加JPA Entity物件及存取程式

  1. 統一繼承 Audit欄位
  2. Key欄位統一使用 DataKey (多個欄位)
  3. 對DataKey,提供Mybatis處理資料的 DataKeyHendler。
  • 新增 SysParmDataEntity.java於資料庫entity子目錄中:
  • 新增 SysParmDataRepository.java於資料庫repository子目錄中
  • 新增 SysParmDataSpecification.java 於資料庫specification子目錄中。
  • 新增 SysParmDataMapper,於資料庫mybatis子目錄
  • 新增 SysParmDataMapper.xml (SQL 定義)於Primary mapperLocationa目錄。
  • 新增 SysParmDataRepositoryTest.java 於單元測試目錄(src/test/java)
  • 新增 SysParmDataMapperTest.java 於單元測試目錄(src/test/java)

新增SysParmData相關資料庫存取程式 (多欄位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;
        }
    }
}

新增SysParmDataRepository.java於資料庫repository子目錄中

原則上與單一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> {

}

新增SysParmDataSpecification.java於資料庫specification子目錄中

原則上與單一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;
                };
        }

}

新增 SysParmDataMapper,於資料庫mybatis子目錄

原則上與單一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);

}

新增 SysParmDataMapper.xml (SQL 定義)於Primary mapperLocationa目錄。

<?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> 

SysUserProfile相關單元測試

新增 SysParmDataRepositoryTest.java 於單元測試目錄(src/test/java) tw.lewishome.webapp.database.primary.repository 的 package

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");
    }
}

新增 SysParmDataMapperTest.java 於單元測試目錄(src/test/java) tw.lewishome.webapp.database.primary.mapper 的 package

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);

        
    }
}

圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言