Việc thao tác với data là không thể tránh khỏi với bất cứ lập trình viên nào, nhưng mỗi lần cần phải làm việc với database thì lại phải vào tạo data cho từng table, chưa kể có những table có rất nhiều field, có khi lên đến hàng trăm. và mỗi lần như thế thì bạn sẽ làm như thế nào, tạo data cho tất cả, hay chỉ chọn một số cột chính rồi nhập data vào, rồi sẽ có lỗi phát sinh khi có những trường nó không cho phép để null. Hoặc trong trường hợp có rất nhiều người thao tác với data cùng một lúc thì sự chồng chéo làm mất data của nhau là rất dễ xảy ra. Và các trương hợp như vừa mới tạo dữ liệu để test xong thì khi chạy lên lại thấy không có dữ liệu. rồi nghi vấn là do code sai, quay đi quay lại xuống database thì lại thấy dữ liệu bị mất. Quá nhiều thời gian lãng phí cho việc tạo database phải không ạ.

Dựa vào cái gì để có thể generate data

Bình thường khi muốn insert một dữ liệu nào đó vào database thì chúng ta vẫn phải dựa vào các thuộc tính của data như data_type data_length ...
Như vậy tại sao mình không dựa vào những cái đó để tạo ra data. Và câu hỏi đặt ra là làm thế nào để có thể lấy được tất cả các thuộc tính cần thiết để có thể tạo ra được data?
Để giải quyết vấn đề này thì lại tìm hiểu cách quản lý data (ở đây mình chỉ mới tìm hiểu trên Oracle thôi nha).
Trong Oracle thì việc quản lý thông tin của database thì nó sẽ được lưu dưới dạng một Dictionary. và như vậy tất tần tật thông tin có thể lấy ra được, quan trọng là dữ liệu mình cần lấy là gì, và thông qua key như thế nào thôi.

  • Để lấy thông tin các column thì thông qua user_tab_columns
    SELECT *
    FROM user_tab_columns tabs
    WHERE tabs.table_name = /* tableName */
  • Để lấy thông tin Primary Key thì có thể dùng
    SELECT COLUMN_NAME
    FROM all_constraints cons, all_cons_columns cols
    WHERE cols.table_name = /* tableName */'
    AND cons.constraint_type = 'P'
    AND cons.constraint_name = cols.constraint_name
    AND cons.owner = cols.owner
    ORDER BY cols.table_name, cols.position;

Như vậy với thông tin là Table name và schema thì chúng ta có thể lấy hết thông tin cần thiết rồi.

Cách thực hiện

Cách 1. Ban đầu thì mình cũng bắt đầu nghĩ nếu như mình có thể dùng một câu SQl trực tiếp với tham số truyền vào là Table name là có thể tạo ra được dữ liệu rồi. Có thể bằng cách select ra các thông tin của tất cả các trường trong table đó và sau đó dựa vào các thông tin đó để tạo ra các dữ liệu cho câu lệnh insert. Nhưng mà nếu làm như vậy thì cực kỳ khó khăn, chưa kể việc check các dữ liệu như thế nào, nên cách này không khả quan.
Cách 2. Sử dụng java
Với cách này mình có thể tạo ra một trang web để thao tác cho việc này, với ý tưởng đơn giản là mình tạo ra một trường là table name và một trường là số lượng record cần tạo và rồi enter để tạo data thôi.
Thế là mới bắt đầu thực hiện
Bước đầu mình dựa vào table name để lấy tất cả các thông tin của tất cả các field.
SELECT
tabs.TABLE_NAME,
tabs.COLUMN_NAME,
tabs.DATA_TYPE,
tabs.DATA_TYPE_MOD,
tabs.DATA_TYPE_OWNER,
tabs.DATA_LENGTH,
tabs.DATA_PRECISION,
tabs.DATA_SCALE,
tabs.NULLABLE,
tabs.COLUMN_ID,
tabs.DEFAULT_LENGTH,
tabs.DATA_DEFAULT,
tabs.NUM_DISTINCT,
tabs.LOW_VALUE,
tabs.HIGH_VALUE,
tabs.DENSITY,
tabs.NUM_NULLS,
tabs.NUM_BUCKETS,
tabs.LAST_ANALYZED,
tabs.SAMPLE_SIZE,
tabs.CHARACTER_SET_NAME,
tabs.CHAR_COL_DECL_LENGTH,
tabs.GLOBAL_STATS,
tabs.AVG_COL_LEN,
tabs.CHAR_LENGTH,
tabs.CHAR_USED,
tabs.V80_FMT_IMAGE,
tabs.DATA_UPGRADED,
tabs.HISTOGRAM
FROM user_tab_columns tabs
WHERE tabs.table_name = /* tableName */'HTT_CHOSEI_GAKU'
ở đây mình để các thuộc tính cần lấy để dễ dàng sau này lấy những cái cần thiết thôi chứ không cần lấy hết

Sau khi xong bước này mình đã có các thôn tin của data mong muốn như column name, column type ... và đây cũng là thông tin cần thiết để tạo ra dữ liệu.
Nhưng đó chỉ là với những bảng đơn, thế thì những bảng có relationship thì sao, các thông tin về Id, Primarykey, ... thì như thế nào,
Với cách sau thì chúng ta có thể lấy ra được List các column làm primary key của table chỉ với table name .
SELECT COLUMN_NAME
FROM all_constraints cons, all_cons_columns cols
WHERE cols.table_name = /* tableName */'HTT_CHOSEI_GAKU'
AND cons.constraint_type = 'P'
AND cons.constraint_name = cols.constraint_name
AND cons.owner = cols.owner
ORDER BY cols.table_name, cols.position;

Như vậy là có đủ thông tin để có thể insert dữ liệu vào một Table rồi

Tạo nhiều record một lúc như thế nào

Như vậy việc tạo giá trị defalt cho một trường sẽ do mỗi người quy định, Nhưng với việc tạo ra mỗi table name và số record cần tạo thì làm thế nào để quản lý?
vậy là mình thay đổi view một chút
bây giờ sau khi nhập Table name thì chúng ta có thể lấy thông tin của các column trong table đó ra và hiển thị lên view lun
Và tại mỗi column là thành phần của khóa chính thì mình sẽ cho phép edit dữ liệu thay cho cái default mà mình quy định trước đó
và sau khi đã nhập các giá trị default đó thì mình sẽ dùng các giá trị đó để tạo ra các giá trị tiếp theo cho các record tiếp theo

Demo

Ở đây mình chỉ mới tạo ra một câu lệnh insert để bỏ vào database chạy thử xem có ngon không, và nó đã hoạt động được ở mức simple
mình chỉ dùng tới view controller vào dao vì là demo nên không có tạo service nhé

  • Controller
    @Controller
    @Scope("prototype")
    @Transactional
    public class GenerateInitiallyDataAutomaticallyController
    extends AbstractController<GenerateInitiallyDataAutomaticallyForm> {

    /**

    • GenerateInitiallyDataAutomaticallyDao
      @Autowired*/
      @Autowired
      GenerateInitiallyDataAutomaticallyDao generateInitiallyDataAutomaticallyDao;
      // ----- DIフィールドの宣言 -----

    //----- 初期化処理 -----

    //----- 主処理-----

    /**

    • 初期表示。
    • @param model Modelオブジェクト
    • @return 遷移先
      @RequestMapping*/
      @RequestMapping(method = RequestMethod.GET, path = "/common/generateInitiallyDataAutomatically/init")
      public String init(Model model) {
      GenerateInitiallyDataAutomaticallyForm form = new GenerateInitiallyDataAutomaticallyForm();
      form.setTableName("HTM_JOCHO");
      model.addAttribute(form);
      return getViewName();
      }
@RequestMapping(method = RequestMethod.POST, path = "/common/generateInitiallyDataAutomatically/getInfor")
public String commonStaffsentakuGetInfor(Model model, @Valid GenerateInitiallyDataAutomaticallyForm form,
        BindingResult bindingResult, RedirectAttributes redirectAttributes) {
    if (!validate(form, bindingResult)) {
        return getViewName();
    }
    List<GenerateInitiallyDataAutomaticallyEntity> infoTables = generateInitiallyDataAutomaticallyDao
            .getColumnAtribites(form.getTableName().toUpperCase());
    List<String> primaryKeyNames = generateInitiallyDataAutomaticallyDao.getPrimaryKeys(form.getTableName().toUpperCase());
    
    if (!CollectionUtils.isEmpty(primaryKeyNames)) {
        List<GenerateInitiallyDataAutomaticallySubForm> listSubForm = new ArrayList<>();
        for (String primaryKeyName : primaryKeyNames) {
            GenerateInitiallyDataAutomaticallySubForm subForm = new GenerateInitiallyDataAutomaticallySubForm();
            subForm.setPrimaryKeyName(primaryKeyName);
            subForm.setPrimaryKeyDefaultValue(createDefaultData(getInforTableByColumnName(infoTables, primaryKeyName)));
            listSubForm.add(subForm);
        }
        form.setListSubForm(listSubForm);
    }
    model.addAttribute(form);
    return getViewName();
}

@RequestMapping(method = RequestMethod.POST, path = "/common/generateInitiallyDataAutomatically/generateValue")
public String commonStaffsentakuGenerateValue(Model model, @Valid GenerateInitiallyDataAutomaticallyForm form,
        BindingResult bindingResult, RedirectAttributes redirectAttributes) {
    
    StringBuilder builder = new StringBuilder();
    StringBuilder insertColumnBuider = new StringBuilder();
    builder.append("INSERT INTO ");
    builder.append(form.getTableName());
    builder.append("(");
    
    insertColumnBuider.append(" VALUES (");
    List<GenerateInitiallyDataAutomaticallyEntity> dataInfors = generateInitiallyDataAutomaticallyDao
            .getColumnAtribites(form.getTableName().toUpperCase());
    for (GenerateInitiallyDataAutomaticallyEntity dataInfor : dataInfors) {
        builder.append(dataInfor.getColumnName());
        builder.append(",");
        insertColumnBuider.append(createDefaultData(dataInfor));
    }

    builder.setLength(builder.length() - 1);
    builder.append(")");
    insertColumnBuider.setLength(insertColumnBuider.length() - 1);
    insertColumnBuider.append(")");
    builder.append(insertColumnBuider);
    System.out.println(builder.toString());
    return getViewName();
}

@Override
protected String getViewName() {
    return "common/GenerateInitiallyDataAutomatically";
}

private String createDefaultData(GenerateInitiallyDataAutomaticallyEntity dataInfor ) {
    if (dataInfor != null) {
        if (GenerateInitiallyDataAutomaticallyEnum.DATE.toString().equals(dataInfor.getDataType()) || 
            "TIMESTAMP(6)".equals(dataInfor.getDataType())) {
            return defaultDateTimeValue();
        } else {
            return defaultStringValue(Integer.parseInt(dataInfor.getDataLength()));
        }
    } else {
        System.out.println("GenerateInitiallyDataAutomaticallyEntity is null");
    }
    return "";
}

private String defaultStringValue(int lengthData) {
    StringBuilder builder = new StringBuilder();
    builder.append("'");
    if (lengthData > 1) {
        for (int i = 1; i < lengthData; i++) {
            builder.append(i % 10);
            if (lengthData > 10) {
                break;
            }
        }
        builder.append(lengthData % 9 == 0 ? 0 : (lengthData % 10));
    } else {
        builder.append(0);
    }
    builder.append("',");
    return builder.toString();
}

private String defaultDateTimeValue() {
    return "TO_DATE('01/01/2017', 'DD/MM/YYYY'),";
}



/*TODO check type of data before create default value
 * for example date or timestamp type */
private GenerateInitiallyDataAutomaticallyEntity getInforTableByColumnName(
        List<GenerateInitiallyDataAutomaticallyEntity> inforTables, String columnName) {
    for (GenerateInitiallyDataAutomaticallyEntity inforTable : inforTables) {
        if (columnName.equals(inforTable.getColumnName())) {
            return inforTable;
        }
    }
    return null;
}

/** @author TamCT */
private static enum GenerateInitiallyDataAutomaticallyEnum {
    NUMBER,
    TIMESTAMP,
    CHAR,
    DATE,
    DECIMAL,
    NCHAR,
    NUMERIC,
    NVARCHAR2,
    TIMETAMP,
    VARCHAR,
    VARCHAR2;
    
    public static GenerateInitiallyDataAutomaticallyEnum find(String dataType) {
        for (GenerateInitiallyDataAutomaticallyEnum v : values()) {
            if (v.name().equals(dataType)) {
                return v;
            }
        }
        return null;
    }
}

}

  • View
    <!DOCTYPE html>
    <html lang="ja" xmlns:th="http://www.thymeleaf.org">
    <head>
    </head>
    <body class="generateInitiallyDataAutomaticallyForm">
    <form id="generateInitiallyDataAutomaticallyForm" method="post" th:action="@{/}" th:object="${generateInitiallyDataAutomaticallyForm}">
    <table>
    <tr>
    <td>Table Name : </td>
    <td><input type="text" th:field="{tableName}" class="" /> </td>
    </tr>
    <tr>
    <td>Number Record Init : </td>
    <td><input type="text" th:field="
    {recordNumber}" class="" /></td>
    </tr>
    </table>
    <button th:formaction="@{/common/generateInitiallyDataAutomatically/getInfor}" class="">Get Table Information</button>

      <table class="table table-group">
              <thead>
                  <tr>
                      <th>Primary Key Name</th>
                      <th>Primary Key Default value</th>
                  </tr>
              </thead>
              <tbody>
                  <tr>
                      <td class="no-padding" colspan="4">
                          <div>
                              <div class="nano-content">
                                  <table class="table table-nano">
                                      <tbody>
                                          <th:block th:each="subA, statusA : *{listSubForm}">
                                              <tr>
                                                  <td><span th:text="${subA.primaryKeyName}"></span></td>
                                                  <td><input th:field="*{listSubForm[__${statusA.index}__].primaryKeyDefaultValue}"/></td>
                                              </tr>
                                          </th:block>
                                      </tbody>
                                  </table>
                              </div>
                          </div>
                      </td>
                  </tr>
              </tbody>
          </table>
          <button th:formaction="@{/common/generateInitiallyDataAutomatically/generateValue}" class="">Generate Default Value</button>
    

    </form>
    </body>
    </html>

  • Dao
    Thì chính là 2 câu ở trên mình đã bỏ vào

Kết luận

Ở đây vì thời gian nghiên cứu chỉ vài ngày nên mới làm ở mức simple
Nó còn chưa thể hiện được relationship cho các bảng liên quan, và cách set dữ liệu phù hợp cho từng data_type
Hi vọng nhận được sự gop ý từ mọi người để có thêm nhiều kinh nghiệm hoàn thành phần tiếp theo. Thanks!