메인 콘텐츠로 건너뛰기
UpsertBuilder는 대량의 관계 데이터를 효율적으로 처리할 수 있습니다.

배치 작업 개요

대량 등록

한 번에 여러 레코드반복문으로 등록

일괄 저장

단일 쿼리로 저장성능 최적화

관계 유지

UBRef로 관계 보존외래 키 자동 처리

트랜잭션

원자성 보장All or Nothing

기본 배치 등록

여러 User 생성

await db.transaction(async (trx) => {
  // 1. 여러 User 등록
  const users = [
    { email: "[email protected]", username: "user1" },
    { email: "[email protected]", username: "user2" },
    { email: "[email protected]", username: "user3" },
  ];
  
  for (const user of users) {
    trx.ubRegister("users", {
      email: user.email,
      username: user.username,
      password: "password",
      role: "normal",
    });
  }
  
  // 2. 일괄 저장
  const userIds = await trx.ubUpsert("users");
  
  console.log(userIds);
  // [1, 2, 3]
});
ubUpsert는 등록된 모든 레코드를 단일 INSERT 쿼리로 처리하여 성능을 최적화합니다.

관계가 있는 배치 등록

여러 Employee 생성 (같은 Department)

await db.transaction(async (trx) => {
  // 1. Department 등록
  const deptRef = trx.ubRegister("departments", {
    name: "Engineering",
  });
  
  // 2. 여러 Employee 등록
  const employees = [
    { name: "John", number: "E001", salary: "70000" },
    { name: "Jane", number: "E002", salary: "75000" },
    { name: "Bob", number: "E003", salary: "80000" },
  ];
  
  for (const emp of employees) {
    const userRef = trx.ubRegister("users", {
      email: `${emp.name.toLowerCase()}@test.com`,
      username: emp.name,
      password: "pass",
      role: "normal",
    });
    
    trx.ubRegister("employees", {
      user_id: userRef,
      department_id: deptRef, // ← 같은 Department
      employee_number: emp.number,
      salary: emp.salary,
    });
  }
  
  // 3. 순서대로 일괄 저장
  const [deptId] = await trx.ubUpsert("departments");
  const userIds = await trx.ubUpsert("users"); // [1, 2, 3]
  const empIds = await trx.ubUpsert("employees"); // [1, 2, 3]
  
  console.log({
    deptId,
    userCount: userIds.length,
    empCount: empIds.length,
  });
});

Array.map을 활용한 배치

Array.map으로 UBRef 배열 생성

await db.transaction(async (trx) => {
  // 1. 여러 User 등록 (UBRef 배열 생성)
  const userRefs = ["alice", "bob", "charlie"].map((name) =>
    trx.ubRegister("users", {
      email: `${name}@test.com`,
      username: name,
      password: "password",
      role: "normal",
    })
  );
  
  // 2. 각 User에 대해 Profile 등록
  userRefs.forEach((userRef, index) => {
    trx.ubRegister("profiles", {
      user_id: userRef,
      bio: `Hello, I'm user ${index + 1}`,
    });
  });
  
  // 3. 저장
  const userIds = await trx.ubUpsert("users");
  await trx.ubUpsert("profiles");
  
  console.log({ userCount: userIds.length });
});

조직 구조 대량 생성

Company + Departments + Employees

await db.transaction(async (trx) => {
  // 1. Company
  const companyRef = trx.ubRegister("companies", {
    name: "Tech Startup",
  });
  
  // 2. 여러 Department 등록
  const departments = ["Engineering", "Sales", "Marketing"];
  const deptRefs = departments.map((name) =>
    trx.ubRegister("departments", {
      name,
      company_id: companyRef,
    })
  );
  
  // 3. 각 Department에 Employee 배치
  const employeeData = [
    { dept: 0, name: "John", number: "E001" },
    { dept: 0, name: "Jane", number: "E002" },
    { dept: 1, name: "Alice", number: "E003" },
    { dept: 1, name: "Bob", number: "E004" },
    { dept: 2, name: "Charlie", number: "E005" },
  ];
  
  for (const emp of employeeData) {
    const userRef = trx.ubRegister("users", {
      email: `${emp.name.toLowerCase()}@startup.com`,
      username: emp.name,
      password: "pass",
      role: "normal",
    });
    
    trx.ubRegister("employees", {
      user_id: userRef,
      department_id: deptRefs[emp.dept], // Department 선택
      employee_number: emp.number,
    });
  }
  
  // 4. 순서대로 저장
  const [companyId] = await trx.ubUpsert("companies");
  const deptIds = await trx.ubUpsert("departments");
  const userIds = await trx.ubUpsert("users");
  const empIds = await trx.ubUpsert("employees");
  
  console.log({
    companyId,
    deptCount: deptIds.length,
    userCount: userIds.length,
    empCount: empIds.length,
  });
});

ManyToMany 배치

여러 Project에 여러 Employee 배정

await db.transaction(async (trx) => {
  // 1. Projects 등록
  const projects = ["Project A", "Project B", "Project C"];
  const projectRefs = projects.map((name) =>
    trx.ubRegister("projects", {
      name,
      status: "planning",
    })
  );
  
  // 2. Employees 등록
  const employees = ["John", "Jane", "Bob"];
  const userRefs = employees.map((name) =>
    trx.ubRegister("users", {
      email: `${name.toLowerCase()}@test.com`,
      username: name,
      password: "pass",
      role: "normal",
    })
  );
  
  const empRefs = userRefs.map((userRef, i) =>
    trx.ubRegister("employees", {
      user_id: userRef,
      employee_number: `E${100 + i}`,
    })
  );
  
  // 3. ManyToMany 관계 (모든 조합)
  for (const projectRef of projectRefs) {
    for (const empRef of empRefs) {
      trx.ubRegister("projects__employees", {
        project_id: projectRef,
        employee_id: empRef,
      });
    }
  }
  
  // 4. 저장
  const projectIds = await trx.ubUpsert("projects");
  const userIds = await trx.ubUpsert("users");
  const empIds = await trx.ubUpsert("employees");
  await trx.ubUpsert("projects__employees");
  
  console.log({
    projectCount: projectIds.length,
    empCount: empIds.length,
    relationCount: projectIds.length * empIds.length,
  });
  // { projectCount: 3, empCount: 3, relationCount: 9 }
});

데이터 파일에서 배치 생성

CSV/JSON 데이터로 대량 등록

// 데이터 파일 (employees.json)
const employeeData = [
  {
    email: "[email protected]",
    username: "john",
    department: "Engineering",
    employeeNumber: "E001",
    salary: "70000",
  },
  {
    email: "[email protected]",
    username: "jane",
    department: "Engineering",
    employeeNumber: "E002",
    salary: "75000",
  },
  // ... 더 많은 데이터
];

async function importEmployees(data: typeof employeeData) {
  return await db.transaction(async (trx) => {
    // 1. 고유한 Department 추출
    const uniqueDepts = [...new Set(data.map((d) => d.department))];
    
    // 2. Department 등록
    const deptRefMap = new Map<string, any>();
    for (const deptName of uniqueDepts) {
      const deptRef = trx.ubRegister("departments", {
        name: deptName,
      });
      deptRefMap.set(deptName, deptRef);
    }
    
    // 3. Employee 등록
    for (const emp of data) {
      const userRef = trx.ubRegister("users", {
        email: emp.email,
        username: emp.username,
        password: "default_password",
        role: "normal",
      });
      
      const deptRef = deptRefMap.get(emp.department);
      
      trx.ubRegister("employees", {
        user_id: userRef,
        department_id: deptRef,
        employee_number: emp.employeeNumber,
        salary: emp.salary,
      });
    }
    
    // 4. 저장
    await trx.ubUpsert("departments");
    await trx.ubUpsert("users");
    await trx.ubUpsert("employees");
    
    return { importedCount: data.length };
  });
}

// 사용
await importEmployees(employeeData);

성능 최적화

단일 트랜잭션

// ✅ 좋음: 한 번의 트랜잭션
await db.transaction(async (trx) => {
  for (let i = 0; i < 1000; i++) {
    trx.ubRegister("users", { ... });
  }
  await trx.ubUpsert("users");
});

// ❌ 나쁨: 여러 트랜잭션
for (let i = 0; i < 1000; i++) {
  await db.transaction(async (trx) => {
    trx.ubRegister("users", { ... });
    await trx.ubUpsert("users");
  });
}

청크 단위 처리

대량 데이터는 청크로 나누어 처리:
async function importLargeDataset(data: any[], chunkSize = 100) {
  for (let i = 0; i < data.length; i += chunkSize) {
    const chunk = data.slice(i, i + chunkSize);
    
    await db.transaction(async (trx) => {
      for (const item of chunk) {
        trx.ubRegister("users", item);
      }
      await trx.ubUpsert("users");
    });
    
    console.log(`Processed ${i + chunk.length} / ${data.length}`);
  }
}

// 10,000개 데이터를 100개씩 나누어 처리
await importLargeDataset(largeDataset, 100);

실전 예제

시드 데이터 생성

async function seedDatabase() {
  await db.transaction(async (trx) => {
    // 1. Companies
    const companies = ["Tech Corp", "Startup Inc", "BigCo"];
    const companyRefs = companies.map((name) =>
      trx.ubRegister("companies", { name })
    );
    
    // 2. Departments (각 Company당 3개)
    const deptNames = ["Engineering", "Sales", "Marketing"];
    const deptRefs: any[] = [];
    
    for (const companyRef of companyRefs) {
      for (const deptName of deptNames) {
        const ref = trx.ubRegister("departments", {
          name: deptName,
          company_id: companyRef,
        });
        deptRefs.push(ref);
      }
    }
    
    // 3. Users + Employees (각 Department당 5명)
    for (let i = 0; i < deptRefs.length; i++) {
      for (let j = 0; j < 5; j++) {
        const index = i * 5 + j;
        const userRef = trx.ubRegister("users", {
          email: `user${index}@test.com`,
          username: `user${index}`,
          password: "password",
          role: "normal",
        });
        
        trx.ubRegister("employees", {
          user_id: userRef,
          department_id: deptRefs[i],
          employee_number: `E${1000 + index}`,
        });
      }
    }
    
    // 4. 저장
    await trx.ubUpsert("companies");
    await trx.ubUpsert("departments");
    await trx.ubUpsert("users");
    await trx.ubUpsert("employees");
    
    console.log("Seed data created!");
    console.log({
      companies: companies.length,
      departments: deptRefs.length,
      employees: deptRefs.length * 5,
    });
  });
}

팀 재편성

async function reorganizeTeams(
  projectId: number,
  newMemberIds: number[]
) {
  await db.transaction(async (trx) => {
    // 1. 기존 멤버 제거
    await trx
      .table("projects__employees")
      .where("project_id", projectId)
      .delete();
    
    // 2. 새 멤버 추가
    for (const empId of newMemberIds) {
      trx.ubRegister("projects__employees", {
        project_id: projectId,
        employee_id: empId,
      });
    }
    
    await trx.ubUpsert("projects__employees");
    
    return { newMemberCount: newMemberIds.length };
  });
}

// 사용
await reorganizeTeams(1, [5, 10, 15, 20]);

에러 처리

부분 실패 처리

async function importEmployeesWithValidation(data: any[]) {
  const results = {
    success: [] as number[],
    failed: [] as { index: number; error: string }[],
  };
  
  for (let i = 0; i < data.length; i++) {
    try {
      await db.transaction(async (trx) => {
        const userRef = trx.ubRegister("users", {
          email: data[i].email,
          username: data[i].username,
          password: "pass",
          role: "normal",
        });
        
        trx.ubRegister("employees", {
          user_id: userRef,
          employee_number: data[i].employeeNumber,
        });
        
        await trx.ubUpsert("users");
        const [empId] = await trx.ubUpsert("employees");
        
        results.success.push(empId);
      });
    } catch (error) {
      results.failed.push({
        index: i,
        error: error.message,
      });
    }
  }
  
  return results;
}

다음 단계