ЛЕКЦ 08

Мэдээллийн Сангийн Дизайн

ЛЕКЦ 08: МЭДЭЭЛЛИЙН САНГИЙН ДИЗАЙН (Database Design & SQL)

Хичээлийн зорилго: Мэдээллийн сангийн суурь ойлголт, харилцааны мэдээллийн сан (RDBMS), хүснэгтийн дизайн, нормалчлал, SQL хэл, индекс, гүйлгээ (transaction), JPA/Hibernate-ийн суурь мэдлэгийг Java жишээнүүдтэйгээр эзэмшүүлэх.

Хамрах хүрээ: Мэдээллийн сангийн төрлүүд, ER диаграм, хүснэгтийн бүтэц, Primary Key, Foreign Key, нормалчлал (1NF-3NF), SQL (DDL, DML, DQL), JOIN, индекс, гүйлгээ (ACID), JPA Entity Mapping, харилцааны төрлүүд, N+1 асуудал, NoSQL ойлголт.



ХЭСЭГ 1: ОНОЛЫН СУУРЬ (Theory & Foundations)

1.1 Мэдээллийн сан гэж юу вэ?

Мэдээллийн сан (Database) — Зохион байгуулагдсан, хадгалагдсан, удирдагддаг өгөгдлийн цуглуулга. Програмын өгөгдлийг бүтэцтэй хэлбэрээр хадгалж, хайлт, шинэчлэлт, устгалт хийх боломжийг олгоно.

💡 Зүйрлэл: Номын сан шиг — номууд (өгөгдөл) ангилагдаж, тавиур дээр (хүснэгт) байрлаж, каталог (индекс)-аар хурдан олддог.

DBMS (Database Management System) — Мэдээллийн сангийн удирдлагын систем. Өгөгдөл хадгалах, хайх, өөрчлөх, аюулгүй байдлыг хангах програм хангамж.

#DBMSТөрөлТайлбар
1PostgreSQLRDBMSНээлттэй эх, дэвшилтэт, enterprise-д тохиромжтой
2MySQLRDBMSТүгээмэл, вэб хөгжүүлэлтэд
3OracleRDBMSТомоохон байгууллагын стандарт
4SQL ServerRDBMSMicrosoft-ийн RDBMS
5H2RDBMSIn-memory, Java хөгжүүлэлт/тестэд
6MongoDBNoSQL (Document)JSON-д суурилсан
7RedisNoSQL (Key-Value)Кэш, session хадгалалт
8CassandraNoSQL (Wide Column)Олон тооны бичлэг, өндөр хүрэлцээ

1.2 Харилцааны мэдээллийн сан (RDBMS)

RDBMS (Relational Database Management System) — Өгөгдлийг хүснэгт (table) хэлбэрээр хадгалж, хүснэгтүүд хоорондоо харилцаа (relationship)-аар холбогддог.

Суурь нэр томьёо:

НэрАнглиТайлбар
ХүснэгтTableӨгөгдлийн бүтэц (оюутнууд, хичээлүүд)
МөрRow / RecordНэг бичлэг (нэг оюутан)
БаганаColumn / FieldНэг шинж чанар (нэр, имэйл)
Анхдагч түлхүүрPrimary Key (PK)Мөр бүрийг давтагдашгүй тодорхойлох
Гадаад түлхүүрForeign Key (FK)Өөр хүснэгттэй холбох
СхемSchemaХүснэгтүүдийн бүтцийн тодорхойлолт
┌─────────────────────────────────────────────┐
│               students (хүснэгт)            │
├─────┬──────────┬─────────────────┬──────────┤
│ id  │ name     │ email           │ gpa      │  ← Баганууд (Columns)
├─────┼──────────┼─────────────────┼──────────┤
│ 1   │ Батболд  │ bat@example.com │ 3.75     │  ← Мөр (Row)
│ 2   │ Сараа    │ sar@example.com │ 3.90     │  ← Мөр (Row)
│ 3   │ Дорж     │ dor@example.com │ 3.20     │  ← Мөр (Row)
└─────┴──────────┴─────────────────┴──────────┘
  ↑ PK

1.3 Түлхүүрүүд (Keys)

1.3.1 Primary Key (PK) — Анхдагч түлхүүр

  • Хүснэгтийн мөр бүрийг давтагдашгүй тодорхойлох
  • NULL байж болохгүй
  • Хүснэгтэд ганц PK байна
  • Ихэвчлэн auto-increment тоо (1, 2, 3...) эсвэл UUID
CREATE TABLE students (
    id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(150) NOT NULL UNIQUE
);

1.3.2 Foreign Key (FK) — Гадаад түлхүүр

  • Өөр хүснэгтийн PK-г заах → Хүснэгт хоорондын ХОЛБООС
  • Мэдээллийн бүрэн бүтэн байдал (Referential Integrity) хангах
CREATE TABLE enrollments (
    id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
    student_id BIGINT NOT NULL,
    course_id BIGINT NOT NULL,
    enrolled_at DATE DEFAULT CURRENT_DATE,
    FOREIGN KEY (student_id) REFERENCES students(id),
    FOREIGN KEY (course_id) REFERENCES courses(id)
);
students               enrollments              courses
┌─────┬─────────┐     ┌─────┬─────┬─────┐     ┌─────┬──────────┐
│ id  │ name    │     │ s_id│ c_id│ date │     │ id  │ name     │
├─────┼─────────┤     ├─────┼─────┼─────┤     ├─────┼──────────┤
│ 1   │ Батболд │◄────│ 1   │ 101 │ 9/1 │────►│ 101 │ Java     │
│ 2   │ Сараа   │◄────│ 2   │ 101 │ 9/1 │     │ 102 │ Python   │
│     │         │     │ 1   │ 102 │ 9/5 │────►│     │          │
└─────┴─────────┘     └─────┴─────┴─────┘     └─────┴──────────┘
  PK                    FK     FK                 PK

1.3.3 Бусад түлхүүрүүд

ТүлхүүрТайлбар
Composite Key2+ баганаас бүрдсэн PK: (student_id, course_id)
Unique KeyДавтагдашгүй, гэхдээ NULL байж болно
Natural KeyБизнесийн утгатай PK (жишээ: email, ИД-ийн дугаар)
Surrogate KeyБизнесийн утгагүй PK (жишээ: auto-increment id)

Зөвлөмж: Surrogate Key (auto-increment ID) ашиглах нь илүү уян хатан, бизнес дүрэм өөрчлөгдөхөд эвдрэхгүй.


1.4 Харилцааны төрлүүд (Relationships)

1.4.1 Нэг-Олон (One-to-Many / 1:N)

Хамгийн түгээмэл. Нэг тал → Олон тал.

Нэг тэнхим → Олон оюутан
┌─────────────┐        ┌──────────────┐
│ departments │        │   students   │
├─────────────┤        ├──────────────┤
│ id (PK)     │───┐    │ id (PK)      │
│ name        │   │    │ name         │
└─────────────┘   └───►│ dept_id (FK) │
                        └──────────────┘
CREATE TABLE departments (
    id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
    name VARCHAR(100) NOT NULL
);

CREATE TABLE students (
    id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(150) NOT NULL UNIQUE,
    department_id BIGINT,
    FOREIGN KEY (department_id) REFERENCES departments(id)
);

1.4.2 Олон-Олон (Many-to-Many / M:N)

Завсрын хүснэгт (Junction Table) шаардлагатай.

Олон оюутан ↔ Олон хичээл
┌──────────┐     ┌──────────────┐     ┌──────────┐
│ students │     │ enrollments  │     │ courses  │
├──────────┤     ├──────────────┤     ├──────────┤
│ id (PK)  │◄───│ student_id   │     │ id (PK)  │
│ name     │     │ course_id    │───►│ name     │
└──────────┘     │ grade        │     │ credits  │
                 └──────────────┘     └──────────┘
CREATE TABLE enrollments (
    student_id BIGINT NOT NULL,
    course_id BIGINT NOT NULL,
    grade VARCHAR(2),
    enrolled_at DATE DEFAULT CURRENT_DATE,
    PRIMARY KEY (student_id, course_id),
    FOREIGN KEY (student_id) REFERENCES students(id),
    FOREIGN KEY (course_id) REFERENCES courses(id)
);

1.4.3 Нэг-Нэг (One-to-One / 1:1)

Нэг оюутан → Нэг профайл
┌──────────┐        ┌──────────────────┐
│ students │        │ student_profiles │
├──────────┤        ├──────────────────┤
│ id (PK)  │───┐    │ id (PK)          │
│ name     │   └───►│ student_id (FK)  │  ← UNIQUE
└──────────┘        │ bio              │
                    │ avatar_url       │
                    └──────────────────┘
CREATE TABLE student_profiles (
    id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
    student_id BIGINT NOT NULL UNIQUE,
    bio TEXT,
    avatar_url VARCHAR(500),
    FOREIGN KEY (student_id) REFERENCES students(id)
);

1.5 ER диаграм (Entity-Relationship Diagram)

ER диаграм = Мэдээллийн сангийн бүтцийг зурагаар илэрхийлэх. Хүснэгтүүд, баганууд, харилцаануудыг харуулна.

┌──────────────┐       ┌───────────────┐       ┌──────────────┐
│  departments │       │   students    │       │   courses    │
├──────────────┤       ├───────────────┤       ├──────────────┤
│ * id         │       │ * id          │       │ * id         │
│   name       │───1:N─│   name        │──M:N──│   name       │
│   code       │       │   email       │       │   credits    │
└──────────────┘       │   gpa         │       │   code       │
                       │   dept_id (FK)│       └──────────────┘
                       │   status      │              │
                       └───────────────┘              │
                              │                       │
                              │ 1:1                   │
                       ┌──────▼────────┐       ┌──────▼───────┐
                       │student_profiles│       │ enrollments  │
                       ├───────────────┤       ├──────────────┤
                       │ * id          │       │ student_id   │
                       │   student_id  │       │ course_id    │
                       │   bio         │       │ grade        │
                       │   avatar_url  │       │ enrolled_at  │
                       └───────────────┘       └──────────────┘

Зурагласны ашиг тус:

  • Хүснэгтүүдийн холбоосыг визуалаар харах
  • Баг хамтран ажиллахад ойлголт нэгтгэх
  • Дизайны алдааг эрт олох
  • Шинэ хөгжүүлэгчид системийг хурдан ойлгох

Хэрэгслүүд: dbdiagram.io, Lucidchart, draw.io, IntelliJ Database Diagrams, DBeaver.


1.6 Нормалчлал (Normalization)

Нормалчлал = Давхардлыг арилгаж, өгөгдлийн бүрэн бүтэн байдлыг хангахын тулд хүснэгтийг зөв хуваах.

1.6.1 Нормалчлалгүй (Unnormalized)

┌─────┬─────────┬─────────────────────┬──────────────┐
│ id  │ name    │ courses             │ dept         │
├─────┼─────────┼─────────────────────┼──────────────┤
│ 1   │ Батболд │ Java, Python, DB    │ CS           │
│ 2   │ Сараа   │ Java, Web           │ CS           │
│ 3   │ Дорж    │ Math, Statistics    │ Mathematics  │
└─────┴─────────┴─────────────────────┴──────────────┘
⚠️ Асуудал: courses баганад олон утга, dept давхардаж байна.

1.6.2 Эхний нормал хэлбэр (1NF)

Дүрэм: Багана бүрт ганц утга (atomic). Олон утга → Тусдаа мөр.

┌─────┬─────────┬───────────┬──────┐
│ id  │ name    │ course    │ dept │
├─────┼─────────┼───────────┼──────┤
│ 1   │ Батболд │ Java      │ CS   │
│ 1   │ Батболд │ Python    │ CS   │
│ 1   │ Батболд │ DB        │ CS   │
│ 2   │ Сараа   │ Java      │ CS   │
│ 2   │ Сараа   │ Web       │ CS   │
└─────┴─────────┴───────────┴──────┘
✅ Багана бүрт ганц утга. ⚠️ Гэхдээ name, dept давхардаж байна.

1.6.3 Хоёрдугаар нормал хэлбэр (2NF)

Дүрэм: 1NF + PK-ийн бүх хэсэгт хамааралтай (Partial Dependency арилгах).

students              enrollments          courses
┌─────┬─────────┬──┐  ┌─────┬─────┐      ┌─────┬─────────┐
│ id  │ name    │dp│  │ s_id│ c_id│      │ id  │ name    │
├─────┼─────────┼──┤  ├─────┼─────┤      ├─────┼─────────┤
│ 1   │ Батболд │CS│  │ 1   │ 1   │      │ 1   │ Java    │
│ 2   │ Сараа   │CS│  │ 1   │ 2   │      │ 2   │ Python  │
└─────┴─────────┴──┘  │ 2   │ 1   │      │ 3   │ DB      │
                      └─────┴─────┘      └─────┴─────────┘
✅ Давхардал арилсан. Оюутны мэдээлэл нэг л газар.

1.6.4 Гуравдугаар нормал хэлбэр (3NF)

Дүрэм: 2NF + PK-аас шууд бус хамааралтай (Transitive Dependency) арилгах.

// ❌ 2NF: dept_name нь dept_id-аас хамааралтай (шууд бус)
students: id, name, dept_id, dept_name
         → dept_name нь id-аас шууд бус → dept_id-аар дамжуулж

// ✅ 3NF: dept_name тусдаа хүснэгтэд
students:    id, name, dept_id (FK)
departments: id, name

Нормалчлалын дүрэм (Товч):

ХэлбэрДүрэм
1NFБагана бүрт ГАНЦ утга (atomic)
2NF1NF + Partial Dependency арилгах
3NF2NF + Transitive Dependency арилгах

Бодит байдал: Ихэвчлэн 3NF хүрэлцээтэй. Зарим тохиолдолд performance-ийн тулд denormalize (давхардуулах) хийж болно.


1.7 SQL хэл (Structured Query Language)

1.7.1 SQL-ийн төрлүүд

ТөрөлНэрКомандуудЗорилго
DDLData Definition LanguageCREATE, ALTER, DROP, TRUNCATEБүтэц тодорхойлох
DMLData Manipulation LanguageINSERT, UPDATE, DELETEӨгөгдөл өөрчлөх
DQLData Query LanguageSELECTӨгөгдөл хайх
DCLData Control LanguageGRANT, REVOKEЭрх удирдах
TCLTransaction Control LanguageCOMMIT, ROLLBACK, SAVEPOINTГүйлгээ удирдах

1.7.2 DDL — Хүснэгт үүсгэх, өөрчлөх

-- Хүснэгт үүсгэх
CREATE TABLE students (
    id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(150) NOT NULL UNIQUE,
    gpa DECIMAL(3,2) CHECK (gpa >= 0.00 AND gpa <= 4.00),
    status VARCHAR(20) DEFAULT 'ACTIVE',
    department_id BIGINT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (department_id) REFERENCES departments(id)
);

-- Багана нэмэх
ALTER TABLE students ADD COLUMN phone VARCHAR(20);

-- Багана устгах
ALTER TABLE students DROP COLUMN phone;

-- Хүснэгт устгах (бүх өгөгдөлтэй)
DROP TABLE IF EXISTS students;

-- Бүх мөр устгах (бүтэц хадгалах)
TRUNCATE TABLE students;

1.7.3 DML — Өгөгдөл нэмэх, шинэчлэх, устгах

-- INSERT — Шинэ мөр нэмэх
INSERT INTO students (name, email, gpa, department_id)
VALUES ('Батболд', 'bat@example.com', 3.75, 1);

-- Олон мөр нэмэх
INSERT INTO students (name, email, gpa, department_id) VALUES
    ('Сараа', 'sar@example.com', 3.90, 1),
    ('Дорж', 'dor@example.com', 3.20, 2),
    ('Оюуна', 'oyu@example.com', 3.60, 1);

-- UPDATE — Мөр шинэчлэх
UPDATE students SET gpa = 3.80 WHERE id = 1;
UPDATE students SET status = 'GRADUATED' WHERE gpa >= 3.5;

-- DELETE — Мөр устгах
DELETE FROM students WHERE id = 1;
DELETE FROM students WHERE status = 'INACTIVE';

-- ⚠️ WHERE-гүй бол БҮГДИЙГ устгана!
-- DELETE FROM students;  ← Бүх оюутан устана! АЮУЛТАЙ!

1.7.4 DQL — Өгөгдөл хайх (SELECT)

-- Бүх оюутан
SELECT * FROM students;

-- Тодорхой баганууд
SELECT name, email, gpa FROM students;

-- Нөхцөлтэй (WHERE)
SELECT * FROM students WHERE gpa >= 3.5;
SELECT * FROM students WHERE status = 'ACTIVE' AND department_id = 1;
SELECT * FROM students WHERE name LIKE 'Бат%';
SELECT * FROM students WHERE gpa BETWEEN 3.0 AND 3.5;
SELECT * FROM students WHERE department_id IN (1, 2, 3);
SELECT * FROM students WHERE email IS NOT NULL;

-- Эрэмбэлэх (ORDER BY)
SELECT * FROM students ORDER BY gpa DESC;
SELECT * FROM students ORDER BY name ASC, gpa DESC;

-- Хязгаарлах (LIMIT + OFFSET)
SELECT * FROM students ORDER BY gpa DESC LIMIT 10;           -- Шилдэг 10
SELECT * FROM students ORDER BY id LIMIT 10 OFFSET 20;       -- 3-р хуудас

-- Агрегат функцүүд
SELECT COUNT(*) FROM students;                                -- Нийт тоо
SELECT AVG(gpa) FROM students;                                -- Дундаж GPA
SELECT MAX(gpa) FROM students;                                -- Хамгийн өндөр GPA
SELECT MIN(gpa) FROM students;                                -- Хамгийн бага GPA
SELECT SUM(gpa) FROM students;                                -- GPA нийлбэр

-- Бүлэглэх (GROUP BY)
SELECT department_id, COUNT(*) as student_count, AVG(gpa) as avg_gpa
FROM students
GROUP BY department_id;

-- Бүлэглэсний дараа шүүх (HAVING)
SELECT department_id, AVG(gpa) as avg_gpa
FROM students
GROUP BY department_id
HAVING AVG(gpa) > 3.5;

-- DISTINCT — Давтагдашгүй утгууд
SELECT DISTINCT status FROM students;
SELECT DISTINCT department_id FROM students;

1.7.5 JOIN — Хүснэгт холбох

-- INNER JOIN — Хоёр хүснэгтэд ХОЁУЛАНГД нь байгаа мөрүүд
SELECT s.name, d.name as department
FROM students s
INNER JOIN departments d ON s.department_id = d.id;

-- LEFT JOIN — Зүүн хүснэгтийн БҮХ мөр + баруунаас тохирох
SELECT s.name, d.name as department
FROM students s
LEFT JOIN departments d ON s.department_id = d.id;
-- Тэнхимгүй оюутнууд ч гарна (department = NULL)

-- RIGHT JOIN — Баруун хүснэгтийн БҮХ мөр
SELECT s.name, d.name as department
FROM students s
RIGHT JOIN departments d ON s.department_id = d.id;
-- Оюутангүй тэнхимүүд ч гарна

-- Олон хүснэгт холбох
SELECT s.name as student, c.name as course, e.grade
FROM students s
JOIN enrollments e ON s.id = e.student_id
JOIN courses c ON e.course_id = c.id
WHERE s.id = 1;
INNER JOIN:          LEFT JOIN:           RIGHT JOIN:
   A ∩ B               A (бүгд)             B (бүгд)
  ┌───┬───┐           ┌───┬───┐           ┌───┬───┐
  │   │███│           │███│███│           │   │███│
  │   │███│           │███│███│           │   │███│
  └───┴───┘           └───┴───┘           └───┴───┘
   Зөвхөн тохирох     Зүүн бүгд +         Баруун бүгд +
                       тохирох              тохирох

1.8 Индекс (Index)

Индекс гэж юу вэ?

Индекс = Номын сангийн каталог шиг — хайлтыг хурдасгах тусгай бүтэц. Индексгүй бол хүснэгтийн БҮХ мөрийг шалгана (Full Table Scan).

// Индексгүй: Бүх 1,000,000 мөр шалгана
SELECT * FROM students WHERE email = 'bat@example.com';
⏱️ Удаан (Full Scan)

// Индекстэй: B-Tree бүтцээр хурдан олно
CREATE INDEX idx_students_email ON students(email);
SELECT * FROM students WHERE email = 'bat@example.com';
⏱️ Хурдан (Index Scan)

Индексийн төрлүүд

ТөрөлТайлбарЖишээ
B-TreeЕрөнхий зориулалт (default)=, <, >, BETWEEN, ORDER BY
HashЯг тэнцүү хайлт= шалгалт
GINМассив, Full-text searchARRAY, tsvector
PartialЗөвхөн нөхцөлтэй мөрүүдэдWHERE status = 'ACTIVE'
CompositeОлон баганат(department_id, gpa)
-- Нэг баганат индекс
CREATE INDEX idx_students_email ON students(email);

-- Олон баганат (Composite) индекс
CREATE INDEX idx_students_dept_gpa ON students(department_id, gpa);

-- Partial индекс (зөвхөн идэвхтэй оюутнууд)
CREATE INDEX idx_active_students ON students(email)
WHERE status = 'ACTIVE';

-- UNIQUE индекс
CREATE UNIQUE INDEX idx_students_email_unique ON students(email);

Хэзээ индекс ашиглах вэ?

✅ Ашиглах❌ Ашиглахгүй
WHERE-д байнга ашигладаг баганаБага тооны мөр (<1000)
JOIN-д ашигладаг FKINSERT/UPDATE маш олон хүснэгт
ORDER BY баганаБараг бүх утга ижил багана
UNIQUE шаардлагатай баганаХүснэгтийн ихэнх баганыг SELECT хийдэг

⚠️ Анхаарах: Индекс нь READ хурдасгана, гэхдээ WRITE (INSERT/UPDATE/DELETE) удаашруулна — индексийг ч шинэчлэх шаардлагатай учраас.


1.9 Гүйлгээ (Transaction) ба ACID

Transaction гэж юу вэ?

Transaction = Нэг логик нэгж болох олон үйлдлийн багц. Бүгд амжилттай бол COMMIT, нэг ч алдаа бол ROLLBACK.

-- Мөнгө шилжүүлэх (2 үйлдэл = 1 transaction)
BEGIN TRANSACTION;

UPDATE accounts SET balance = balance - 100000 WHERE id = 1;  -- Батаас -100,000
UPDATE accounts SET balance = balance + 100000 WHERE id = 2;  -- Дорж руу +100,000

-- Хоёулаа амжилттай бол:
COMMIT;

-- Аль нэг нь алдаатай бол:
ROLLBACK;  -- ХОЁУЛАНГ нь буцаах!

ACID зарчим

ЗарчимАнглиТайлбарЖишээ
AAtomicityБүгд эсвэл юу ч ҮГҮМөнгө хасагдсан + нэмэгдсэн, эсвэл хоёулаа буцна
CConsistencyБүрэн бүтэн байдал хангагдсанБаланс < 0 болохгүй
IIsolationЗэрэг ажиллаж буй transaction бие биедээ нөлөөлөхгүйА ба Б зэрэг шилжүүлсэн ч зөв
DDurabilityCOMMIT хийсний дараа өгөгдөл ХАДГАЛАГДСАНСервер унтарсан ч өгөгдөл алдагдахгүй

Isolation Level

ТүвшинDirty ReadNon-Repeatable ReadPhantom Read
READ UNCOMMITTED✅ Болно✅ Болно✅ Болно
READ COMMITTED❌ Болохгүй✅ Болно✅ Болно
REPEATABLE READ❌ Болохгүй❌ Болохгүй✅ Болно
SERIALIZABLE❌ Болохгүй❌ Болохгүй❌ Болохгүй

PostgreSQL default: READ COMMITTED. MySQL default: REPEATABLE READ.


1.10 JPA / Hibernate Entity Mapping

1.10.1 Entity ба Table холбох

@Entity
@Table(name = "students")
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "name", nullable = false, length = 100)
    private String name;

    @Column(name = "email", nullable = false, unique = true)
    private String email;

    @Column(precision = 3, scale = 2)
    private BigDecimal gpa;

    @Enumerated(EnumType.STRING)
    @Column(length = 20)
    private StudentStatus status = StudentStatus.ACTIVE;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "department_id")
    private Department department;

    @OneToOne(mappedBy = "student", cascade = CascadeType.ALL)
    private StudentProfile profile;

    @ManyToMany
    @JoinTable(
        name = "enrollments",
        joinColumns = @JoinColumn(name = "student_id"),
        inverseJoinColumns = @JoinColumn(name = "course_id")
    )
    private Set<Course> courses = new HashSet<>();

    @Column(name = "created_at")
    private LocalDateTime createdAt = LocalDateTime.now();
}

1.10.2 Харилцааны Annotation

AnnotationХарилцааFK байршил
@ManyToOneОлон → НэгОдоогийн хүснэгтэд FK
@OneToManyНэг → ОлонНөгөө хүснэгтэд FK
@OneToOneНэг → НэгАль нэг хүснэгтэд FK
@ManyToManyОлон ↔ ОлонЗавсрын хүснэгтэд

1.10.3 FetchType — LAZY vs EAGER

ТөрөлТайлбарХэзээ ачаалах
LAZYШаардлагатай үед ачаалнаstudent.getDepartment() дуудахад
EAGERШууд ачаалнаStudent ачаалахад Department ч ачаална

Зөвлөмж: Бараг бүх тохиолдолд LAZY ашиглах. EAGER = N+1 асуудал үүсэх эрсдэлтэй.

1.10.4 N+1 асуудал

// ❌ N+1 Problem
List<Student> students = studentRepository.findAll();  // 1 query
for (Student s : students) {
    System.out.println(s.getDepartment().getName());   // N query (оюутан бүрт 1)
}
// Нийт: 1 + N query (100 оюутан = 101 query!)

// ✅ JOIN FETCH-аар шийдэх
@Query("SELECT s FROM Student s JOIN FETCH s.department")
List<Student> findAllWithDepartment();  // 1 query (JOIN)

1.11 Өгөгдлийн төрлүүд (Data Types)

PostgreSQL-ийн үндсэн төрлүүд

ТөрөлSQLJavaТайлбар
Бүхэл тооINTEGER, BIGINTInteger, LongID, тоо
БутархайDECIMAL(p,s), NUMERICBigDecimalМөнгө, GPA
ТекстVARCHAR(n), TEXTStringНэр, имэйл
ОгнооDATELocalDateОгноо
ЦагTIMESTAMPLocalDateTimeОгноо + цаг
BooleanBOOLEANBooleantrue/false
JSONJSONBString / CustomБүтэцтэй өгөгдөл
UUIDUUIDUUIDДавтагдашгүй ID

⚠️ Мөнгөн дүнд DECIMAL ашиглах, FLOAT/DOUBLE биш! Float/Double = Бутархай тооны нарийвчлал алдагдна.


1.12 NoSQL ойлголт

SQL vs NoSQL

ШинжSQL (RDBMS)NoSQL
БүтэцХатуу schema (хүснэгт)Уян хатан schema
ХэлSQLAPI / Query Language
ХарилцааJOIN, FKEmbedded document
ScaleVertical (сервер сайжруулах)Horizontal (сервер нэмэх)
ACIDБүрэн дэмжинэЗарим нь л
ТохиромжтойБүтэцтэй өгөгдөл, гүйлгээУян хатан, том хэмжээ

NoSQL-ийн төрлүүд

ТөрөлЖишээХэрэглээ
DocumentMongoDBJSON бүтэцтэй өгөгдөл, CMS
Key-ValueRedisКэш, session
Wide ColumnCassandraОлон тооны бичлэг, IoT
GraphNeo4jНийгмийн сүлжээ, зөвлөмж
// SQL (RDBMS)                    // NoSQL (MongoDB)
┌──────────────┐                  {
│   students   │                    "_id": "abc123",
├──────────────┤                    "name": "Батболд",
│ id: 1        │                    "email": "bat@example.com",
│ name: Батболд│       ───►         "gpa": 3.75,
│ email: bat@  │                    "department": {
│ dept_id: 1   │                      "name": "CS",
└──────────────┘                      "code": "CSE"
┌──────────────┐                    },
│ departments  │                    "courses": [
├──────────────┤                      {"name": "Java", "grade": "A"},
│ id: 1        │                      {"name": "DB", "grade": "B+"}
│ name: CS     │                    ]
└──────────────┘                  }
2 хүснэгт + JOIN                 1 document (embedded)

Хэзээ SQL? Бүтэцтэй, харилцаатай өгөгдөл, ACID чухал (банк, ERP). Хэзээ NoSQL? Уян хатан бүтэц, том хэмжээ, хурдан бичих (лог, IoT, кэш).


1.13 Мэдээллийн сангийн дизайны шилдэг туршлагууд

#ЗарчимТайлбар
1Нормалчлал (3NF)Давхардал арилгах, бүрэн бүтэн байдал
2Surrogate PKAuto-increment ID ашиглах (бизнес утгагүй)
3Нэрлэлтsnake_case: student_id, created_at, олон тоо: students
4FK ConstraintReferential Integrity хангах
5ИндексWHERE, JOIN, ORDER BY баганад
6NOT NULLШаардлагатай баганууд
7DEFAULT утгаstatus DEFAULT 'ACTIVE', created_at DEFAULT NOW()
8CHECK Constraintgpa CHECK (gpa >= 0 AND gpa <= 4)
9Огноо баганаcreated_at, updated_at — Audit trail
10Soft Deletedeleted_at багана — Бүрмөсөн устгахгүй
11DECIMAL мөнгөндFLOAT/DOUBLE биш — нарийвчлал
12LAZY fetchN+1 асуудлаас зайлсхийх


ХЭСЭГ 2: ТҮЛХҮҮР ҮГ БА МЭРГЭЖЛИЙН НЭР ТОМЬЁО (Keywords & Glossary)

#Англи нэр томьёоМонгол утгаДэлгэрэнгүй тайлбар
1DatabaseМэдээллийн санЗохион байгуулагдсан өгөгдлийн цуглуулга.
2DBMSМС удирдлагын системDatabase Management System — Өгөгдлийг удирдах програм.
3RDBMSХарилцааны МСХүснэгт, харилцаанд суурилсан МС (PostgreSQL, MySQL).
4TableХүснэгтМөр, баганаас бүрдсэн өгөгдлийн бүтэц.
5Row / RecordМөр / БичлэгХүснэгтийн нэг бичлэг (нэг оюутан).
6Column / FieldБагана / ТалбарХүснэгтийн нэг шинж чанар (нэр, имэйл).
7Primary KeyАнхдагч түлхүүрМөр бүрийг давтагдашгүй тодорхойлох.
8Foreign KeyГадаад түлхүүрӨөр хүснэгтийн PK-г заах — холбоос.
9SchemaСхемХүснэгтүүдийн бүтцийн тодорхойлолт.
10NormalizationНормалчлалДавхардал арилгах, бүрэн бүтэн байдал хангах.
111NF / 2NF / 3NFНормал хэлбэрүүдНормалчлалын түвшнүүд.
12DenormalizationНормалчлалаас буцахPerformance-ийн тулд давхардуулах.
13ER DiagramER диаграмEntity-Relationship — Бүтцийг зурагаар илэрхийлэх.
14SQLSQL хэлStructured Query Language.
15DDLБүтэц тодорхойлохCREATE, ALTER, DROP.
16DMLӨгөгдөл өөрчлөхINSERT, UPDATE, DELETE.
17DQLӨгөгдөл хайхSELECT.
18JOINХолбохХүснэгтүүдийг холбож мэдээлэл авах.
19INNER JOINДотоод холбоосХоёуланд тохирох мөрүүд.
20LEFT JOINЗүүн холбоосЗүүн хүснэгтийн бүх мөр + тохирох.
21IndexИндексХайлтыг хурдасгах тусгай бүтэц.
22TransactionГүйлгээОлон үйлдлийн нэг логик нэгж.
23ACIDACID зарчимAtomicity, Consistency, Isolation, Durability.
24COMMITБаталгаажуулахTransaction амжилттай — өгөгдлийг хадгалах.
25ROLLBACKБуцаахTransaction цуцалж, бүгдийг буцаах.
26Isolation LevelТусгаарлалтын түвшинTransaction хоорондын нөлөөллийн зохицуулалт.
27ConstraintХязгаарлалтNOT NULL, UNIQUE, CHECK, FK зэрэг дүрмүүд.
28Aggregate FunctionАгрегат функцCOUNT, SUM, AVG, MAX, MIN.
29GROUP BYБүлэглэхМөрүүдийг бүлэглэж агрегат тооцоолох.
30HAVINGБүлэг шүүхGROUP BY-н дараа шүүлт хийх.
31SubqueryДэд асуулгаSELECT дотор SELECT.
32ViewХарагдацХадгалагдсан SELECT — виртуал хүснэгт.
33Stored ProcedureХадгалагдсан процедурDB-д хадгалагдсан SQL логик.
34TriggerТриггерINSERT/UPDATE/DELETE-д автоматаар ажиллах логик.
35JPAJava Persistence APIJava объектыг DB-тэй холбох стандарт.
36HibernateHibernateJPA-ийн хамгийн түгээмэл implementation.
37ORMОбъект-Харилцааны зураглалObject-Relational Mapping.
38Lazy LoadingХойшлуулсан ачаалалШаардлагатай үед л өгөгдөл ачаалах.
39N+1 ProblemN+1 асуудал1 query + N нэмэлт query = Удаан.
40NoSQLNoSQLХүснэгтэд суурилдаггүй мэдээллийн сан.


ХЭСЭГ 3: ЛАБОРАТОРИ БА ПРАКТИК ЗААВАР (Labs & Step-by-Step Guide)

3.1 Лабораторийн зорилго

Энэ лабораторид та:

  1. PostgreSQL (эсвэл H2)-д хүснэгтүүд үүсгэх
  2. SQL командуудаар өгөгдөл удирдах
  3. JOIN, агрегат функц, индекс ашиглах
  4. Spring Boot JPA Entity Mapping хийх

Хэл: SQL (PostgreSQL) + Java 17+ | IDE: Eclipse IDE + DBeaver (эсвэл H2 Console)


3.2 Лаб 1: Мэдээллийн сангийн хүснэгтүүд үүсгэх (DDL)

Алхам 1: Хүснэгтүүд үүсгэх

-- 1. Тэнхимүүд
CREATE TABLE departments (
    id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
    name VARCHAR(100) NOT NULL UNIQUE,
    code VARCHAR(10) NOT NULL UNIQUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 2. Оюутнууд
CREATE TABLE students (
    id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(150) NOT NULL UNIQUE,
    gpa DECIMAL(3,2) CHECK (gpa >= 0.00 AND gpa <= 4.00),
    status VARCHAR(20) DEFAULT 'ACTIVE',
    department_id BIGINT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (department_id) REFERENCES departments(id)
);

-- 3. Хичээлүүд
CREATE TABLE courses (
    id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
    name VARCHAR(100) NOT NULL,
    code VARCHAR(10) NOT NULL UNIQUE,
    credits INTEGER NOT NULL CHECK (credits > 0 AND credits <= 6),
    department_id BIGINT,
    FOREIGN KEY (department_id) REFERENCES departments(id)
);

-- 4. Бүртгэл (M:N завсрын хүснэгт)
CREATE TABLE enrollments (
    id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
    student_id BIGINT NOT NULL,
    course_id BIGINT NOT NULL,
    grade VARCHAR(2),
    enrolled_at DATE DEFAULT CURRENT_DATE,
    UNIQUE (student_id, course_id),
    FOREIGN KEY (student_id) REFERENCES students(id) ON DELETE CASCADE,
    FOREIGN KEY (course_id) REFERENCES courses(id) ON DELETE CASCADE
);

-- 5. Оюутны профайл (1:1)
CREATE TABLE student_profiles (
    id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
    student_id BIGINT NOT NULL UNIQUE,
    bio TEXT,
    avatar_url VARCHAR(500),
    phone VARCHAR(20),
    FOREIGN KEY (student_id) REFERENCES students(id) ON DELETE CASCADE
);

Алхам 2: Индексүүд үүсгэх

CREATE INDEX idx_students_email ON students(email);
CREATE INDEX idx_students_dept ON students(department_id);
CREATE INDEX idx_students_status ON students(status);
CREATE INDEX idx_enrollments_student ON enrollments(student_id);
CREATE INDEX idx_enrollments_course ON enrollments(course_id);

3.3 Лаб 2: Өгөгдөл нэмэх ба хайх (DML + DQL)

Алхам 1: Өгөгдөл нэмэх

-- Тэнхимүүд
INSERT INTO departments (name, code) VALUES
    ('Компьютерийн шинжлэх ухаан', 'CSE'),
    ('Математик', 'MATH'),
    ('Физик', 'PHYS');

-- Оюутнууд
INSERT INTO students (name, email, gpa, department_id) VALUES
    ('Батболд', 'batbold@example.com', 3.75, 1),
    ('Сарангэрэл', 'sarangerel@example.com', 3.90, 1),
    ('Дорж', 'dorj@example.com', 3.20, 2),
    ('Оюуна', 'oyuuna@example.com', 3.60, 1),
    ('Ганбаатар', 'ganbaatar@example.com', 2.80, 3),
    ('Нарангэрэл', 'narangerel@example.com', 3.45, 2),
    ('Болдбаатар', 'boldbaatar@example.com', 3.10, 3),
    ('Цэцэгмаа', 'tsetsegmaa@example.com', 3.85, 1),
    ('Энхбат', 'enkhbat@example.com', 2.50, 2),
    ('Алтанцэцэг', 'altantsetseg@example.com', 3.95, 1);

-- Хичээлүүд
INSERT INTO courses (name, code, credits, department_id) VALUES
    ('Java програмчлал', 'CS101', 3, 1),
    ('Мэдээллийн сан', 'CS201', 3, 1),
    ('Веб хөгжүүлэлт', 'CS301', 4, 1),
    ('Математик анализ', 'MATH101', 4, 2),
    ('Статистик', 'MATH201', 3, 2),
    ('Квантум физик', 'PHYS301', 3, 3);

-- Бүртгэл
INSERT INTO enrollments (student_id, course_id, grade) VALUES
    (1, 1, 'A'), (1, 2, 'A-'), (1, 3, 'B+'),
    (2, 1, 'A+'), (2, 2, 'A'),
    (3, 4, 'B'), (3, 5, 'B+'),
    (4, 1, 'A-'), (4, 3, 'A'),
    (5, 6, 'C+'),
    (6, 4, 'A'), (6, 5, 'A-'),
    (8, 1, 'A'), (8, 2, 'A+'), (8, 3, 'A'),
    (10, 1, 'A+'), (10, 2, 'A+');

-- Профайл
INSERT INTO student_profiles (student_id, bio, phone) VALUES
    (1, 'Java хөгжүүлэгч болох зорилготой', '99001122'),
    (2, 'Full-stack developer', '99003344');

Алхам 2: SELECT хайлтууд

-- 1. GPA >= 3.5 оюутнууд
SELECT name, email, gpa FROM students WHERE gpa >= 3.5 ORDER BY gpa DESC;

-- 2. Тэнхим бүрийн оюутны тоо, дундаж GPA
SELECT d.name as department, COUNT(s.id) as student_count, 
       ROUND(AVG(s.gpa), 2) as avg_gpa
FROM departments d
LEFT JOIN students s ON d.id = s.department_id
GROUP BY d.name
ORDER BY avg_gpa DESC;

-- 3. Хамгийн олон оюутантай тэнхим
SELECT d.name, COUNT(s.id) as count
FROM departments d
JOIN students s ON d.id = s.department_id
GROUP BY d.name
ORDER BY count DESC
LIMIT 1;

-- 4. Java хичээл авсан оюутнууд
SELECT s.name, e.grade
FROM students s
JOIN enrollments e ON s.id = e.student_id
JOIN courses c ON e.course_id = c.id
WHERE c.code = 'CS101'
ORDER BY s.name;

-- 5. Оюутан бүрийн авсан хичээлийн тоо
SELECT s.name, COUNT(e.course_id) as course_count
FROM students s
LEFT JOIN enrollments e ON s.id = e.student_id
GROUP BY s.name
ORDER BY course_count DESC;

-- 6. A+ дүнтэй оюутнууд
SELECT DISTINCT s.name, s.email
FROM students s
JOIN enrollments e ON s.id = e.student_id
WHERE e.grade = 'A+';

-- 7. Хичээл аваагүй оюутнууд
SELECT s.name, s.email
FROM students s
LEFT JOIN enrollments e ON s.id = e.student_id
WHERE e.id IS NULL;

-- 8. Дундаж GPA-аас дээш оюутнууд (Subquery)
SELECT name, gpa
FROM students
WHERE gpa > (SELECT AVG(gpa) FROM students);

-- 9. Хичээл бүрийн оюутны тоо
SELECT c.name as course, c.code, COUNT(e.student_id) as students
FROM courses c
LEFT JOIN enrollments e ON c.id = e.course_id
GROUP BY c.name, c.code
ORDER BY students DESC;

-- 10. UPDATE: GPA шинэчлэх
UPDATE students SET gpa = 3.80, updated_at = CURRENT_TIMESTAMP
WHERE id = 1;

3.4 Лаб 3: JPA Entity Mapping (Spring Boot)

Алхам 1: Department Entity

// src/main/java/com/example/studentapi/entity/Department.java
package com.example.studentapi.entity;

import jakarta.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "departments")
public class Department {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true, length = 100)
    private String name;

    @Column(nullable = false, unique = true, length = 10)
    private String code;

    @OneToMany(mappedBy = "department", fetch = FetchType.LAZY)
    private List<Student> students = new ArrayList<>();

    public Department() {}

    // Getter, Setter
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getCode() { return code; }
    public void setCode(String code) { this.code = code; }
    public List<Student> getStudents() { return students; }
    public void setStudents(List<Student> students) { this.students = students; }
}

Алхам 2: Student Entity (харилцаатай)

// src/main/java/com/example/studentapi/entity/Student.java
package com.example.studentapi.entity;

import jakarta.persistence.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.Set;

@Entity
@Table(name = "students")
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, length = 100)
    private String name;

    @Column(nullable = false, unique = true, length = 150)
    private String email;

    @Column(precision = 3, scale = 2)
    private BigDecimal gpa;

    @Enumerated(EnumType.STRING)
    @Column(length = 20)
    private StudentStatus status = StudentStatus.ACTIVE;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "department_id")
    private Department department;

    @OneToOne(mappedBy = "student", cascade = CascadeType.ALL, 
              fetch = FetchType.LAZY)
    private StudentProfile profile;

    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(
        name = "enrollments",
        joinColumns = @JoinColumn(name = "student_id"),
        inverseJoinColumns = @JoinColumn(name = "course_id")
    )
    private Set<Course> courses = new HashSet<>();

    @Column(name = "created_at")
    private LocalDateTime createdAt = LocalDateTime.now();

    @Column(name = "updated_at")
    private LocalDateTime updatedAt = LocalDateTime.now();

    public Student() {}

    // Getter, Setter
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
    public BigDecimal getGpa() { return gpa; }
    public void setGpa(BigDecimal gpa) { this.gpa = gpa; }
    public StudentStatus getStatus() { return status; }
    public void setStatus(StudentStatus status) { this.status = status; }
    public Department getDepartment() { return department; }
    public void setDepartment(Department department) { this.department = department; }
    public StudentProfile getProfile() { return profile; }
    public void setProfile(StudentProfile profile) { this.profile = profile; }
    public Set<Course> getCourses() { return courses; }
    public void setCourses(Set<Course> courses) { this.courses = courses; }
}

Алхам 3: N+1 шийдэл — JOIN FETCH

// StudentRepository.java
public interface StudentRepository extends JpaRepository<Student, Long> {

    // N+1 асуудалгүй — 1 query-ээр бүгдийг авна
    @Query("SELECT s FROM Student s JOIN FETCH s.department")
    List<Student> findAllWithDepartment();

    // Тэнхимээр шүүх
    @Query("SELECT s FROM Student s JOIN FETCH s.department d WHERE d.code = :code")
    List<Student> findByDepartmentCode(@Param("code") String code);

    // GPA-аар шүүх + тэнхимтэй
    @Query("SELECT s FROM Student s JOIN FETCH s.department WHERE s.gpa >= :minGpa")
    List<Student> findByMinGpaWithDepartment(@Param("minGpa") BigDecimal minGpa);
}

3.5 Лаб 4: Transaction ба Бизнес логик

// StudentService.java — Transaction жишээ
@Service
public class StudentService {

    private final StudentRepository studentRepository;
    private final EnrollmentRepository enrollmentRepository;

    public StudentService(StudentRepository studentRepository,
                          EnrollmentRepository enrollmentRepository) {
        this.studentRepository = studentRepository;
        this.enrollmentRepository = enrollmentRepository;
    }

    @Transactional
    public void enrollStudentInCourse(Long studentId, Long courseId) {
        // 1. Оюутан олох
        Student student = studentRepository.findById(studentId)
            .orElseThrow(() -> new StudentNotFoundException(
                "ID=" + studentId + " оюутан олдсонгүй"
            ));

        // 2. Бүртгэлтэй эсэхийг шалгах
        if (enrollmentRepository.existsByStudentIdAndCourseId(studentId, courseId)) {
            throw new DuplicateEnrollmentException(
                "Оюутан энэ хичээлд бүртгэлтэй байна"
            );
        }

        // 3. Бүртгэл үүсгэх
        Enrollment enrollment = new Enrollment();
        enrollment.setStudentId(studentId);
        enrollment.setCourseId(courseId);
        enrollmentRepository.save(enrollment);

        // 4. Оюутны хичээлийн тоо шинэчлэх
        // Алдаа гарвал бүгд ROLLBACK — Atomicity!
    }
}

---
---

# ХЭСЭГ 4: ШАЛГАЛТЫН АСУУЛТ (Knowledge Check — 100 тест)

## Тест 1
**Мэдээллийн сан (Database) гэж юу вэ?**
- A) Зөвхөн Excel файл
- B) Зохион байгуулагдсан, удирдагддаг өгөгдлийн цуглуулга
- C) Зөвхөн вэб хуудас
- D) Програмын код

**Зөв хариулт: B**
> **Тайлбар:** Database = Өгөгдлийг бүтэцтэй хадгалж, хайлт, шинэчлэлт, устгалт хийх боломжтой цуглуулга. DBMS (PostgreSQL, MySQL) ашиглан удирдана.

## Тест 2
**RDBMS гэж юу вэ?**
- A) NoSQL мэдээллийн сан
- B) Харилцааны мэдээллийн сангийн удирдлагын систем — хүснэгт, харилцаанд суурилсан
- C) Файл систем
- D) Кэш систем

**Зөв хариулт: B**
> **Тайлбар:** RDBMS = Relational DBMS. Өгөгдлийг хүснэгтэд хадгалж, хүснэгтүүд FK-аар холбогдоно. PostgreSQL, MySQL, Oracle = RDBMS.

## Тест 3
**Primary Key (PK) юу хийдэг вэ?**
- A) Хүснэгтийг устгах
- B) Хүснэгтийн мөр бүрийг ДАВТАГДАШГҮЙ тодорхойлох
- C) Зөвхөн текст хадгалах
- D) Индекс устгах

**Зөв хариулт: B**
> **Тайлбар:** PK = Мөр бүрийн давтагдашгүй ID. NOT NULL + UNIQUE. Хүснэгтэд ганц PK. Ихэвчлэн auto-increment (1, 2, 3...).

## Тест 4
**Foreign Key (FK) юу хийдэг вэ?**
- A) Хүснэгт үүсгэх
- B) Өөр хүснэгтийн PK-г заах — хүснэгт хоорондын ХОЛБООС
- C) Индекс үүсгэх
- D) Мөр устгах

**Зөв хариулт: B**
> **Тайлбар:** FK = students.department_id → departments.id. Referential Integrity = FK-ийн заасан PK заавал байх ёстой.

## Тест 5
**Нэг-Олон (One-to-Many) харилцааны жишээ аль нь вэ?**
- A) Оюутан ↔ Оюутан
- B) Нэг тэнхим → Олон оюутан
- C) Нэг оюутан → Нэг профайл
- D) Олон оюутан ↔ Олон хичээл

**Зөв хариулт: B**
> **Тайлбар:** 1:N = Нэг тэнхимд олон оюутан. FK нь "олон" талд байна: students.department_id → departments.id.

## Тест 6
**Many-to-Many харилцааг хэрхэн хэрэгжүүлэх вэ?**
- A) FK нэмэх
- B) ЗАВСРЫН хүснэгт (Junction Table) ашиглах — enrollments(student_id, course_id)
- C) PK устгах
- D) Нэг хүснэгтэд олон багана нэмэх

**Зөв хариулт: B**
> **Тайлбар:** M:N = Олон оюутан ↔ Олон хичээл. Завсрын хүснэгт enrollments(student_id FK, course_id FK) ашиглана. Хоёр FK = Хоёр хүснэгтийг холбоно.

## Тест 7
**Нормалчлалын (Normalization) гол зорилго юу вэ?**
- A) Хурд нэмэх
- B) Өгөгдлийн ДАВХАРДЛЫГ арилгаж, бүрэн бүтэн байдлыг хангах
- C) Хүснэгт устгах
- D) Индекс нэмэх

**Зөв хариулт: B**
> **Тайлбар:** Нормалчлал = Давхардал арилгах → Өгөгдөл нэг л газар → Нэг газар засахад л хангалттай. Inconsistency-ээс хамгаалах.

## Тест 8
**1NF (Эхний нормал хэлбэр)-ийн дүрэм юу вэ?**
- A) FK арилгах
- B) Багана бүрт ГАНЦ утга (atomic) — олон утга нэг баганад байхгүй
- C) PK арилгах
- D) Индекс нэмэх

**Зөв хариулт: B**
> **Тайлбар:** 1NF = Atomic. "Java, Python, DB" нэг баганад = 1NF ЗӨРЧИЛ. Тус тусдаа мөрөнд = 1NF.

## Тест 9
**3NF-ийн дүрэм юу вэ?**
- A) Бүх давхардлыг арилгах
- B) 2NF + Transitive Dependency арилгах — PK-аас ШУУД БУС хамааралтай баганыг тусдаа хүснэгтэд
- C) Зөвхөн PK байх
- D) Индекс заавал байх

**Зөв хариулт: B**
> **Тайлбар:** students(id, name, dept_id, dept_name) → dept_name нь id-аас шууд бус (dept_id-аар дамжуулж). → departments(id, name) тусдаа = 3NF.

## Тест 10
**SQL-ийн DDL (Data Definition Language) аль командуудыг агуулдаг вэ?**
- A) SELECT, INSERT
- B) CREATE, ALTER, DROP, TRUNCATE
- C) COMMIT, ROLLBACK
- D) GRANT, REVOKE

**Зөв хариулт: B**
> **Тайлбар:** DDL = Мэдээллийн сангийн БҮТЦИЙГ тодорхойлох. CREATE = Үүсгэх, ALTER = Өөрчлөх, DROP = Устгах, TRUNCATE = Бүх мөрийг арилгах.

## Тест 11
**`SELECT * FROM students WHERE gpa >= 3.5` юу хийдэг вэ?**
- A) Бүх оюутнуудыг устгах
- B) GPA 3.5 ба түүнээс дээш бүх оюутнуудыг СОНГОХ
- C) GPA-г шинэчлэх
- D) Хүснэгт үүсгэх

**Зөв хариулт: B**
> **Тайлбар:** SELECT = Өгөгдөл уншиx (DQL). WHERE = Нөхцөл. gpa >= 3.5 = GPA 3.5+ оюутнууд. * = Бүх багана.

## Тест 12
**INNER JOIN юу хийдэг вэ?**
- A) Бүх мөрийг авах
- B) ХОЁР хүснэгтэд ХОЁУЛАНД нь тохирох мөрүүдийг буцаах
- C) Зүүн хүснэгтийн бүх мөр
- D) Баруун хүснэгтийн бүх мөр

**Зөв хариулт: B**
> **Тайлбар:** INNER JOIN = A ∩ B. Хоёр хүснэгтэд хоёуланд тохирох мөрүүд. Тохирохгүй мөрүүд орохгүй.

## Тест 13
**LEFT JOIN юу хийдэг вэ?**
- A) Зөвхөн тохирох мөрүүд
- B) ЗҮҮН хүснэгтийн БҮХ мөр + баруунаас тохирох (тохирохгүй бол NULL)
- C) Баруун хүснэгтийн бүх мөр
- D) Хоёулангийн бүх мөр

**Зөв хариулт: B**
> **Тайлбар:** LEFT JOIN = Зүүн бүгд + тохирох. Тэнхимгүй оюутан ч гарна (department = NULL). Хичээл аваагүй оюутан олоход тохиромжтой.

## Тест 14
**GROUP BY юу хийдэг вэ?**
- A) Мөр устгах
- B) Мөрүүдийг бүлэглэж, агрегат функц (COUNT, AVG, SUM) тооцоолох
- C) Мөр нэмэх
- D) Индекс үүсгэх

**Зөв хариулт: B**
> **Тайлбар:** `GROUP BY department_id` = Тэнхимээр бүлэглэж, `COUNT(*)` = тэнхим бүрийн оюутны тоо, `AVG(gpa)` = дундаж GPA.

## Тест 15
**HAVING ба WHERE-ийн ялгаа юу вэ?**
- A) Ялгаагүй
- B) WHERE = GROUP BY-н ӨМНӨ мөр шүүх, HAVING = GROUP BY-н ДАРАА бүлэг шүүх
- C) WHERE нь агрегатад ашиглагдана
- D) HAVING нь мөр бүрт

**Зөв хариулт: B**
> **Тайлбар:** WHERE = Мөр бүрт нөхцөл (GROUP BY-н өмнө). HAVING = Бүлэг бүрт нөхцөл (GROUP BY-н дараа). `HAVING AVG(gpa) > 3.5` = Дундаж GPA > 3.5 бүлгүүд.

## Тест 16
**Индекс (Index) юу хийдэг вэ?**
- A) Өгөгдөл устгах
- B) Хайлтыг ХУРДАСГАХ тусгай бүтэц — Full Table Scan-аас зайлсхийх
- C) Хүснэгт үүсгэх
- D) FK нэмэх

**Зөв хариулт: B**
> **Тайлбар:** Индекс = Номын каталог шиг. B-Tree бүтцээр хурдан олно. Индексгүй = 1,000,000 мөрийг нэг нэгээр шалгана (Full Scan).

## Тест 17
**Индексийн сул тал юу вэ?**
- A) Сул талгүй
- B) INSERT/UPDATE/DELETE-ийг УДААШРУУЛНА — Индексийг ч шинэчлэх шаардлагатай
- C) SELECT удаашруулна
- D) Хүснэгт устна

**Зөв хариулт: B**
> **Тайлбар:** Индекс = READ хурдасгана, WRITE удаашруулна. Мөр нэмэх/устгахад индексийг ч шинэчлэх → Хэтэрхий олон индекс = WRITE удаан.

## Тест 18
**Transaction (Гүйлгээ) гэж юу вэ?**
- A) Нэг SQL команд
- B) Олон үйлдлийн НЭГ логик нэгж — бүгд амжилттай эсвэл бүгд буцаагдах
- C) Зөвхөн SELECT
- D) Индекс

**Зөв хариулт: B**
> **Тайлбар:** Transaction = Мөнгө шилжүүлэх (хасах + нэмэх) = 1 transaction. Нэг алдаа → Бүгд ROLLBACK. Хоёулаа амжилттай → COMMIT.

## Тест 19
**ACID зарчмын "A" (Atomicity) юу вэ?**
- A) Хурд
- B) Бүгд эсвэл юу ч ҮГҮЙ — Transaction дотор бүх үйлдэл амжилттай эсвэл бүгд буцна
- C) Нэвтрэлт
- D) Индекс

**Зөв хариулт: B**
> **Тайлбар:** Atomicity = "All or Nothing". Мөнгө хасагдсан, гэхдээ нөгөө рүү нэмэгдээгүй = БАЙЖ БОЛОХГҮЙ. Бүгд эсвэл юу ч үгүй.

## Тест 20
**ACID зарчмын "I" (Isolation) юу вэ?**
- A) Хурд
- B) Зэрэг ажиллаж буй transaction-ууд бие биедээ НӨЛӨӨЛӨХГҮЙ
- C) Өгөгдөл хадгалагдах
- D) Бүгд эсвэл юу ч үгүй

**Зөв хариулт: B**
> **Тайлбар:** Isolation = Transaction A ажиллаж байхад Transaction B-ийн хийж буй өөрчлөлтийг харахгүй. Isolation Level-ээр зохицуулна.

## Тест 21
**COMMIT ба ROLLBACK-ийн ялгаа юу вэ?**
- A) Ялгаагүй
- B) COMMIT = Өөрчлөлтийг БАТАЛГААЖУУЛЖ хадгалах, ROLLBACK = Бүгдийг БУЦААХ
- C) Хоёулаа устгах
- D) Хоёулаа нэмэх

**Зөв хариулт: B**
> **Тайлбар:** COMMIT = "Зөв! Хадгал." ROLLBACK = "Буруу! Буцаа." Transaction дотор алдаа гарвал ROLLBACK → Өгөгдөл хэвээрээ.

## Тест 22
**`DELETE FROM students WHERE id = 1` юу хийдэг вэ?**
- A) Бүх оюутнуудыг устгах
- B) ID=1 оюутны мөрийг УСТГАХ
- C) Хүснэгтийг устгах
- D) GPA шинэчлэх

**Зөв хариулт: B**
> **Тайлбар:** DELETE + WHERE = Нөхцөлд тохирох мөрийг устгана. ⚠️ WHERE-гүй бол БҮГДИЙГ устгана! Болгоомжтой!

## Тест 23
**`DROP TABLE students` ба `TRUNCATE TABLE students`-ийн ялгаа юу вэ?**
- A) Ялгаагүй
- B) DROP = Хүснэгтийг БҮТЦИЙН ХАМТ устгах, TRUNCATE = Бүх мөрийг устгах (бүтэц хадгалах)
- C) Хоёулаа бүтэц хадгалах
- D) Хоёулаа бүтэц устгах

**Зөв хариулт: B**
> **Тайлбар:** DROP = Хүснэгт алга болно. TRUNCATE = Хүснэгт хоосорно (бүтэц хэвээрээ). DELETE = WHERE-тэй мөр устгах.

## Тест 24
**Subquery (Дэд асуулга) гэж юу вэ?**
- A) Хоёр хүснэгт холбох
- B) SELECT дотор SELECT — Нэг query-ийн ÜР ДҮНГ нөгөөд ашиглах
- C) Индекс
- D) Transaction

**Зөв хариулт: B**
> **Тайлбар:** `WHERE gpa > (SELECT AVG(gpa) FROM students)` = Дундаж GPA-аас дээш. Дотоод SELECT эхлээд ажиллаж, үр дүнг гадна SELECT-д дамжуулна.

## Тест 25
**`NOT NULL` constraint юу хийдэг вэ?**
- A) NULL утга оноох
- B) Багана NULL утга авахыг ХОРИГЛОХ — Заавал утга оруулна
- C) Индекс нэмэх
- D) FK нэмэх

**Зөв хариулт: B**
> **Тайлбар:** `name VARCHAR(100) NOT NULL` = Нэр ЗААВАЛ оруулна. NULL утга = "Мэдэхгүй" → NOT NULL = Өгөгдлийн бүрэн бүтэн байдал.

## Тест 26
**`UNIQUE` constraint юу хийдэг вэ?**
- A) PK болгох
- B) Баганын утга ДАВТАГДАХГҮЙ — Ижил утга 2 мөрөнд байхгүй
- C) NOT NULL нэмэх
- D) FK нэмэх

**Зөв хариулт: B**
> **Тайлбар:** `email VARCHAR(150) UNIQUE` = 2 оюутан ижил имэйлтэй байж болохгүй. PK-аас ялгаа: UNIQUE багана NULL байж болно.

## Тест 27
**`CHECK` constraint юу хийдэг вэ?**
- A) Хүснэгт үүсгэх
- B) Баганын утгад НӨХЦӨЛ тавих — `CHECK (gpa >= 0 AND gpa <= 4)`
- C) FK нэмэх
- D) Индекс нэмэх

**Зөв хариулт: B**
> **Тайлбар:** CHECK = Бизнес дүрэм DB түвшинд. GPA 0-4, credits 1-6 зэрэг. Буруу утга оруулахаас хамгаална.

## Тест 28
**`ON DELETE CASCADE` юу хийдэг вэ?**
- A) Юу ч хийхгүй
- B) Эцэг хүснэгтийн мөр устахад хүүхэд хүснэгтийн холбоотой мөрүүдийг АВТОМАТААР устгах
- C) FK арилгах
- D) PK арилгах

**Зөв хариулт: B**
> **Тайлбар:** `FOREIGN KEY (student_id) REFERENCES students(id) ON DELETE CASCADE` = Оюутан устгахад түүний бүртгэл (enrollments) автоматаар устна.

## Тест 29
**`ORDER BY gpa DESC` юу хийдэг вэ?**
- A) GPA нэмэгдэх дарааллаар
- B) GPA БУУРАХ дарааллаар эрэмбэлэх
- C) GPA устгах
- D) GPA шинэчлэх

**Зөв хариулт: B**
> **Тайлбар:** DESC = Descending (буурах). ASC = Ascending (өсөх, default). `ORDER BY gpa DESC` = 4.0 → 3.5 → 3.0...

## Тест 30
**`LIMIT 10 OFFSET 20` юу хийдэг вэ?**
- A) 10 мөр устгах
- B) Эхний 20 мөрийг АЛГАСАЖ, дараагийн 10 мөрийг авах (3-р хуудас)
- C) 20 мөр нэмэх
- D) Бүх мөр авах

**Зөв хариулт: B**
> **Тайлбар:** LIMIT 10 = 10 мөр авах. OFFSET 20 = Эхний 20-ийг алгасах. Хуудаслалт: page 3, size 10 = OFFSET 20 LIMIT 10.

## Тест 31
**`COUNT(*)` функц юу хийдэг вэ?**
- A) Мөр устгах
- B) Мөрийн ТООГ тоолох
- C) Дундаж тооцоолох
- D) Мөр нэмэх

**Зөв хариулт: B**
> **Тайлбар:** `SELECT COUNT(*) FROM students` = Нийт оюутны тоо. `COUNT(column)` = NULL биш утгуудыг тоолно.

## Тест 32
**`AVG(gpa)` функц юу хийдэг вэ?**
- A) Хамгийн их GPA
- B) GPA-ийн ДУНДАЖИЙГ тооцоолох
- C) GPA-ийн нийлбэр
- D) Хамгийн бага GPA

**Зөв хариулт: B**
> **Тайлбар:** AVG = Average (дундаж). `SELECT AVG(gpa) FROM students` = Бүх оюутны GPA-ийн дундаж. NULL утгуудыг тооцохгүй.

## Тест 33
**JPA `@ManyToOne` annotation юу хийдэг вэ?**
- A) Хүснэгт үүсгэх
- B) Олон → Нэг харилцаа тодорхойлох — FK одоогийн Entity-д
- C) PK тодорхойлох
- D) Индекс нэмэх

**Зөв хариулт: B**
> **Тайлбар:** `@ManyToOne` = Олон оюутан → Нэг тэнхим. Student Entity-д `department_id` FK. `@JoinColumn(name = "department_id")`.

## Тест 34
**FetchType.LAZY ба FetchType.EAGER-ийн ялгаа юу вэ?**
- A) Ялгаагүй
- B) LAZY = Шаардлагатай үед ачаална, EAGER = Шууд бүгдийг ачаална
- C) LAZY = Хурдан, EAGER = Удаан
- D) LAZY = Бүгдийг, EAGER = Зарим

**Зөв хариулт: B**
> **Тайлбар:** LAZY = `student.getDepartment()` дуудахад л DB query. EAGER = Student ачаалахад Department ч шууд. LAZY = N+1 асуудалтай, EAGER = Хэт олон ачаалах.

## Тест 35
**N+1 асуудал гэж юу вэ?**
- A) Нэрлэлтийн асуудал
- B) 1 query-ээр жагсаалт авч, бичлэг БҮРД нэмэлт query ажиллуулах → N+1 query
- C) Зөвхөн индексийн асуудал
- D) Зөвхөн тестийн асуудал

**Зөв хариулт: B**
> **Тайлбар:** 100 оюутан (1 query) + тэнхим нь бүрт (100 query) = 101 query! `JOIN FETCH`, `@EntityGraph`-аар 1 query-ээр шийднэ.

## Тест 36
**Surrogate Key ба Natural Key-ийн ялгаа юу вэ?**
- A) Ялгаагүй
- B) Surrogate = Бизнес утгагүй (auto-increment ID), Natural = Бизнес утгатай (email, ИД дугаар)
- C) Хоёулаа auto-increment
- D) Хоёулаа бизнес утгатай

**Зөв хариулт: B**
> **Тайлбар:** Surrogate Key = 1, 2, 3 (бизнес утгагүй, уян хатан). Natural Key = email (бизнес дүрэм өөрчлөгдвөл эвдрэх эрсдэлтэй). Surrogate илүү тохиромжтой.

## Тест 37
**Composite Key гэж юу вэ?**
- A) Нэг баганат PK
- B) 2 ба түүнээс олон баганаас бүрдсэн PK — `PRIMARY KEY (student_id, course_id)`
- C) FK
- D) Индекс

**Зөв хариулт: B**
> **Тайлбар:** Composite Key = Завсрын хүснэгтэд (enrollments) ашигладаг. `(student_id, course_id)` хос = Давтагдашгүй. Нэг оюутан нэг хичээлд нэг удаа.

## Тест 38
**`ALTER TABLE students ADD COLUMN phone VARCHAR(20)` юу хийдэг вэ?**
- A) Хүснэгт устгах
- B) students хүснэгтэд phone БАГАНА НЭМЭХ
- C) Мөр нэмэх
- D) Индекс нэмэх

**Зөв хариулт: B**
> **Тайлбар:** ALTER TABLE = Хүснэгтийн БҮТЦИЙГ өөрчлөх (DDL). ADD COLUMN = Шинэ багана нэмэх. DROP COLUMN = Багана устгах.

## Тест 39
**`INSERT INTO students (name, email) VALUES ('Бат', 'bat@test.com')` юу хийдэг вэ?**
- A) Мөр устгах
- B) students хүснэгтэд шинэ мөр НЭМЭХ
- C) Мөр шинэчлэх
- D) Хүснэгт үүсгэх

**Зөв хариулт: B**
> **Тайлбар:** INSERT = Шинэ мөр нэмэх (DML). VALUES дахь утгууд баганын дарааллаар оруулагдана.

## Тест 40
**`UPDATE students SET gpa = 3.80 WHERE id = 1` юу хийдэг вэ?**
- A) Бүх GPA-г шинэчлэх
- B) ID=1 оюутны GPA-г 3.80 болгож ШИНЭЧЛЭХ
- C) Мөр устгах
- D) Хүснэгт үүсгэх

**Зөв хариулт: B**
> **Тайлбар:** UPDATE + WHERE = Нөхцөлд тохирох мөрийг шинэчлэх. ⚠️ WHERE-гүй бол БҮГДИЙГ шинэчилнэ!

## Тест 41
**`LIKE 'Бат%'` юу хийдэг вэ?**
- A) Яг "Бат" тэнцүү
- B) "Бат"-аар ЭХЭЛСЭН бүх утгуудыг хайх (Батболд, Батсайхан...)
- C) "Бат"-аар төгссөн
- D) "Бат" агуулсан

**Зөв хариулт: B**
> **Тайлбар:** `%` = 0 ба түүнээс олон тэмдэгт. `'Бат%'` = Бат-аар эхэлсэн. `'%бат'` = бат-аар төгссөн. `'%бат%'` = бат агуулсан.

## Тест 42
**`BETWEEN 3.0 AND 3.5` юу хийдэг вэ?**
- A) 3.0-аас бага
- B) 3.0 ба 3.5-ийн ХООРОНД (хоёулангийг оролцуулсан)
- C) 3.5-аас их
- D) Зөвхөн 3.0

**Зөв хариулт: B**
> **Тайлбар:** `BETWEEN 3.0 AND 3.5` = `gpa >= 3.0 AND gpa <= 3.5`. Хоёр хязгаарыг ОРОЛЦУУЛНА (inclusive).

## Тест 43
**`IN (1, 2, 3)` юу хийдэг вэ?**
- A) 1, 2, 3 нийлбэр
- B) Утга нь 1, 2, 3-ийн АЛЬ НЭГТЭЙ тэнцүү эсэхийг шалгах
- C) 1-ээс 3 хүртэл
- D) 3 мөр нэмэх

**Зөв хариулт: B**
> **Тайлбар:** `WHERE department_id IN (1, 2, 3)` = `department_id = 1 OR department_id = 2 OR department_id = 3`. Олон OR-ийн товчлол.

## Тест 44
**`IS NULL` ба `= NULL`-ийн ялгаа юу вэ?**
- A) Ялгаагүй
- B) `IS NULL` = NULL утга шалгах ЗӨВ арга, `= NULL` = Үргэлж FALSE (буруу)
- C) Хоёулаа зөв
- D) Хоёулаа буруу

**Зөв хариулт: B**
> **Тайлбар:** SQL-д NULL = NULL → UNKNOWN (TRUE биш!). NULL утгыг шалгахад `IS NULL`, `IS NOT NULL` ашиглана. `= NULL` хэзээ ч TRUE буцаахгүй.

## Тест 45
**`DISTINCT` юу хийдэг вэ?**
- A) Мөр устгах
- B) Давтагдсан утгуудыг АРИЛГАЖ, зөвхөн давтагдашгүй утгуудыг буцаах
- C) Мөр нэмэх
- D) Эрэмбэлэх

**Зөв хариулт: B**
> **Тайлбар:** `SELECT DISTINCT status FROM students` = ACTIVE, INACTIVE, GRADUATED (давтагдашгүй). 100 оюутнаас зөвхөн 3 статус.

## Тест 46
**View (Харагдац) гэж юу вэ?**
- A) Шинэ хүснэгт
- B) Хадгалагдсан SELECT query — Виртуал хүснэгт шиг ашиглагдах
- C) Индекс
- D) Transaction

**Зөв хариулт: B**
> **Тайлбар:** `CREATE VIEW active_students AS SELECT * FROM students WHERE status = 'ACTIVE'` → `SELECT * FROM active_students` = Идэвхтэй оюутнуудыг хялбар авах.

## Тест 47
**Stored Procedure юу вэ?**
- A) Java код
- B) DB-д ХАДГАЛАГДСАН SQL логик — Олон SQL командыг нэг нэрээр дуудах
- C) Индекс
- D) Тест

**Зөв хариулт: B**
> **Тайлбар:** Stored Procedure = DB серверт ажиллана. Сүлжээний зардал бага (олон SQL → нэг дуудалт). Гэхдээ debug, version control хэцүү.

## Тест 48
**Trigger юу хийдэг вэ?**
- A) Гараар ажиллуулах
- B) INSERT/UPDATE/DELETE-д АВТОМАТААР ажиллах DB логик
- C) Индекс нэмэх
- D) Хүснэгт устгах

**Зөв хариулт: B**
> **Тайлбар:** `AFTER INSERT ON students` = Оюутан нэмэгдэхэд автоматаар audit_log хүснэгтэд бичлэг нэмэх. ⚠️ Хэтэрхий олон trigger = Debug хэцүү.

## Тест 49
**ORM (Object-Relational Mapping) юу хийдэг вэ?**
- A) HTML үүсгэх
- B) Java объект ↔ DB хүснэгт автоматаар ЗУРАГЛАХ — SQL-гүйгээр CRUD хийх
- C) Тест ажиллуулах
- D) API үүсгэх

**Зөв хариулт: B**
> **Тайлбар:** ORM = Student объект → students хүснэгт. `repository.save(student)` → INSERT SQL автомат. Hibernate = JPA-ийн хамгийн түгээмэл ORM.

## Тест 50
**`@GeneratedValue(strategy = GenerationType.IDENTITY)` юу хийдэг вэ?**
- A) Гараар ID оноох
- B) DB автоматаар ID үүсгэх (AUTO INCREMENT)
- C) UUID үүсгэх
- D) Timestamp үүсгэх

**Зөв хариулт: B**
> **Тайлбар:** IDENTITY = DB-ийн auto-increment. 1, 2, 3... автоматаар. SEQUENCE = DB sequence ашиглах. UUID = Давтагдашгүй 128-bit ID.

## Тест 51
**`@Enumerated(EnumType.STRING)` юу хийдэг вэ?**
- A) Тоогоор хадгалах
- B) Enum утгыг DB-д STRING болгон хадгалах — "ACTIVE", "INACTIVE"
- C) JSON-д хөрвүүлэх
- D) Validation хийх

**Зөв хариулт: B**
> **Тайлбар:** STRING = "ACTIVE" хадгалагдана. ORDINAL = 0, 1, 2 (Enum дарааллыг өөрчлөвөл эвдэрнэ!). STRING = Аюулгүй, ойлгомжтой.

## Тест 52
**`@JoinColumn(name = "department_id")` юу хийдэг вэ?**
- A) PK тодорхойлох
- B) FK БАГАНЫН нэрийг тодорхойлох — DB-д department_id багана
- C) Индекс нэмэх
- D) Хүснэгт үүсгэх

**Зөв хариулт: B**
> **Тайлбар:** `@JoinColumn` = Харилцааны FK багана. `@ManyToOne` + `@JoinColumn(name = "department_id")` = students.department_id → departments.id.

## Тест 53
**`CascadeType.ALL` юу хийдэг вэ?**
- A) Юу ч хийхгүй
- B) Эцэг Entity-д хийсэн үйлдлийг хүүхэд Entity-д ч АВТОМАТААР хийх (persist, merge, remove...)
- C) Зөвхөн устгах
- D) Зөвхөн нэмэх

**Зөв хариулт: B**
> **Тайлбар:** `CascadeType.ALL` = Student хадгалахад Profile ч хадгалагдана. Student устгахад Profile ч устагдана. Бүх үйлдэл "cascade" хийгдэнэ.

## Тест 54
**`@JoinTable` annotation хэзээ ашигладаг вэ?**
- A) 1:1 харилцаанд
- B) Many-to-Many харилцааны ЗАВСРЫН ХҮСНЭГТИЙГ тодорхойлоход
- C) 1:N харилцаанд
- D) Индекс нэмэхэд

**Зөв хариулт: B**
> **Тайлбар:** `@JoinTable(name = "enrollments", joinColumns = ..., inverseJoinColumns = ...)` = M:N харилцааны enrollments завсрын хүснэгт.

## Тест 55
**Denormalization гэж юу вэ?**
- A) Нормалчлал хийх
- B) Performance-ийн тулд зориудаар ДАВХАРДУУЛАХ — JOIN-ийн тоог бууруулах
- C) Хүснэгт устгах
- D) Индекс нэмэх

**Зөв хариулт: B**
> **Тайлбар:** Нормалчлал = Давхардал арилгах (бүрэн бүтэн байдал). Denormalization = Давхардуулах (хурд). Read-heavy системд зарим талбарыг давхардуулж JOIN-ийг багасгана.

## Тест 56
**Дараах SQL-д юу буруу вэ?**
```sql
SELECT department_id, name, COUNT(*)
FROM students
GROUP BY department_id;
  • A) Бүх зүйл зөв
  • B) name нь GROUP BY-д ОРООГҮЙ ч SELECT-д байна — Алдаа
  • C) COUNT(*) буруу
  • D) FROM буруу

Зөв хариулт: B

Тайлбар: GROUP BY-д ороогүй багана SELECT-д агрегатгүйгээр байж БОЛОХГҮЙ. Аль оюутны нэрийг харуулах вэ? → Тодорхойгүй. name GROUP BY-д нэмэх эсвэл хасах.

Тест 57

COALESCE(phone, 'N/A') юу хийдэг вэ?

  • A) Хоёр утгыг нийлүүлэх
  • B) phone NULL бол 'N/A' буцаах — NULL утгыг ОРЛУУЛАХ
  • C) phone устгах
  • D) phone шинэчлэх

Зөв хариулт: B

Тайлбар: COALESCE = Эхний NULL биш утгыг буцаана. COALESCE(phone, mobile, 'N/A') = phone → mobile → 'N/A' (бүгд NULL бол).

Тест 58

"Dirty Read" гэж юу вэ?

  • A) Зөв уншилт
  • B) COMMIT хийгдээгүй өөрчлөлтийг унших — Transaction B ROLLBACK хийвэл буруу мэдээлэл
  • C) Удаан уншилт
  • D) Индексгүй уншилт

Зөв хариулт: B

Тайлбар: Dirty Read = Transaction A GPA-г 3.5 → 4.0 болгосон (COMMIT-гүй). Transaction B 4.0-г уншина. A ROLLBACK хийвэл B буруу мэдээлэл ашиглаж байна.

Тест 59

READ COMMITTED isolation level юу хийдэг вэ?

  • A) Бүх асуудлыг шийднэ
  • B) Зөвхөн COMMIT хийгдсэн өгөгдлийг уншина — Dirty Read-аас хамгаална
  • C) Бүх уншилтыг хориглоно
  • D) Transaction ашиглахгүй

Зөв хариулт: B

Тайлбар: READ COMMITTED = PostgreSQL-ийн default. COMMIT хийгдсэн л бол унших → Dirty Read БОЛОХГҮЙ. Гэхдээ Non-Repeatable Read болж болно.

Тест 60

EXPLAIN ANALYZE юу хийдэг вэ?

  • A) Мөр устгах
  • B) SQL query-ийн ГҮЙЦЭТГЭЛИЙН ТӨЛӨВЛӨГӨӨГ харуулах — хурд, индекс ашиглалтыг шинжлэх
  • C) Хүснэгт үүсгэх
  • D) Мөр нэмэх

Зөв хариулт: B

Тайлбар: EXPLAIN ANALYZE SELECT * FROM students WHERE email = 'bat@test.com' → Seq Scan (Full Scan) эсвэл Index Scan, query хугацаа, зардал (cost) харуулна.

Тест 61

B-Tree индекс юу вэ?

  • A) Binary Tree
  • B) Тэнцвэрт мод бүтэц — =, <, >, BETWEEN, ORDER BY хайлтад тохиромжтой DEFAULT индекс
  • C) Hash функц
  • D) Array бүтэц

Зөв хариулт: B

Тайлбар: B-Tree = PostgreSQL, MySQL-ийн default индекс. Бүх төрлийн хайлтад ашиглагдана. Тэнцвэрт → O(log n) хурдтай.

Тест 62

Partial Index юу вэ?

  • A) Бүх мөрөнд индекс
  • B) Зөвхөн НӨХЦӨЛД тохирох мөрүүдэд индекс — WHERE status = 'ACTIVE'
  • C) Нэг баганат индекс
  • D) Олон баганат индекс

Зөв хариулт: B

Тайлбар: CREATE INDEX idx_active ON students(email) WHERE status = 'ACTIVE' = Зөвхөн идэвхтэй оюутнуудын email-д индекс. Жижиг, хурдан.

Тест 63

Мөнгөн дүнд ямар төрөл ашиглах вэ?

  • A) FLOAT
  • B) DECIMAL / NUMERIC — Нарийвчлал алдагдахгүй
  • C) DOUBLE
  • D) INTEGER

Зөв хариулт: B

Тайлбар: FLOAT/DOUBLE = Бутархай тооны нарийвчлал алдагдна (0.1 + 0.2 ≠ 0.3). DECIMAL = Яг тодорхой нарийвчлал. Мөнгө, GPA = DECIMAL.

Тест 64

"Soft Delete" гэж юу вэ?

  • A) Бүрмөсөн устгах
  • B) Мөрийг УСТГАХГҮЙГЭЭР deleted_at баганад цаг тэмдэглэх — Сэргээх боломжтой
  • C) Хүснэгт устгах
  • D) Индекс устгах

Зөв хариулт: B

Тайлбар: Hard Delete = DELETE FROM students WHERE id = 1 (бүрмөсөн). Soft Delete = UPDATE students SET deleted_at = NOW() WHERE id = 1. WHERE deleted_at IS NULL шүүнэ.

Тест 65

@Transactional annotation (Spring) юу хийдэг вэ?

  • A) Тест зарлах
  • B) Методын бүх DB үйлдлийг НЭГ transaction-д багтааж, алдаа гарвал ROLLBACK
  • C) Лог бичих
  • D) Индекс нэмэх

Зөв хариулт: B

Тайлбар: @Transactional = enrollStudentInCourse() дотор 3 DB үйлдэл. Алдаа гарвал бүгд ROLLBACK. Spring AOP proxy-ээр удирдана.

Тест 66

NoSQL мэдээллийн сангийн давуу тал юу вэ?

  • A) JOIN хийж чадна
  • B) Уян хатан schema, хэвтээ scale, том хэмжээний өгөгдөлд тохиромжтой
  • C) ACID бүрэн дэмждэг
  • D) SQL хэл ашиглана

Зөв хариулт: B

Тайлбар: NoSQL = Schema-free (багана нэмэхэд ALTER TABLE шаардлагагүй). Horizontal scaling (сервер нэмэх). Том өгөгдөл, хурдан бичих. Гэхдээ JOIN хэцүү.

Тест 67

MongoDB юу вэ?

  • A) RDBMS
  • B) Document-based NoSQL — JSON (BSON) формат document хадгалах
  • C) Key-Value store
  • D) Graph DB

Зөв хариулт: B

Тайлбар: MongoDB = JSON-тэй адил document. Nested object, array хадгалж болно. Schema уян хатан. Вэб апп, CMS, бүртгэлд тохиромжтой.

Тест 68

Redis юу вэ?

  • A) RDBMS
  • B) In-memory Key-Value NoSQL — КЭШ, session хадгалалтад маш хурдан
  • C) Document DB
  • D) Graph DB

Зөв хариулт: B

Тайлбар: Redis = RAM-д хадгалдаг → Микросекундын хурдтай. Кэш (DB query үр дүнг хадгалах), session, rate limiting, queue-д ашиглана.

Тест 69

@Query annotation (Spring Data JPA) юу хийдэг вэ?

  • A) Хүснэгт үүсгэх
  • B) Гарын авлагын JPQL эсвэл native SQL query тодорхойлох
  • C) Индекс нэмэх
  • D) Transaction удирдах

Зөв хариулт: B

Тайлбар: @Query("SELECT s FROM Student s JOIN FETCH s.department") = JPQL query. nativeQuery = true бол шууд SQL. Spring Data-ийн автомат method naming-ээс нарийн query хэрэгтэй үед.

Тест 70

mappedBy parameter юу хийдэг вэ?

  • A) FK үүсгэх
  • B) Харилцааны НӨГӨӨ талыг зааж, FK ЭНЭ хүснэгтэд БАЙХГҮЙГ илэрхийлэх
  • C) PK тодорхойлох
  • D) Индекс нэмэх

Зөв хариулт: B

Тайлбар: @OneToMany(mappedBy = "department") = FK нь Student талд (department_id). Department хүснэгтэд FK БАЙХГҮЙ. mappedBy = "Би FK эзэмшигч биш".

Тест 71

Database Migration гэж юу вэ?

  • A) DB устгах
  • B) DB-ийн schema-ийн өөрчлөлтийг ХУВИЛБАРТАЙГААР удирдах — Flyway, Liquibase
  • C) Мөр нэмэх
  • D) Индекс нэмэх

Зөв хариулт: B

Тайлбар: Migration = Git for DB schema. V1__create_students.sql → V2__add_phone.sql. Бүх хөгжүүлэгч ижил schema-тай. Flyway, Liquibase = Java-д түгээмэл.

Тест 72

ddl-auto=validate юу хийдэг вэ?

  • A) Хүснэгт үүсгэх
  • B) Entity ба DB schema ТОХИРЧ БАЙГААГ шалгах — Тохирохгүй бол алдаа
  • C) Хүснэгт устгах
  • D) Мөр нэмэх

Зөв хариулт: B

Тайлбар: validate = Production-д тохиромжтой. Entity ба DB schema-г харьцуулж, зөрүүтэй бол програм эхлэхгүй. create-drop = Зөвхөн хөгжүүлэлтэд!

Тест 73

Дараах аль нь зөв DB нэрлэлт вэ?

  • A) StudentProfiles (PascalCase)
  • B) student_profiles (snake_case, олон тоо)
  • C) studentProfile (camelCase)
  • D) STUDENT_PROFILE (UPPER_CASE)

Зөв хариулт: B

Тайлбар: DB нэрлэлт стандарт: snake_case + олон тоо. students, student_profiles, created_at. Java-д camelCase, DB-д snake_case.

Тест 74

created_at ба updated_at баганууд яагаад хэрэгтэй вэ?

  • A) Шаардлагагүй
  • B) AUDIT TRAIL — Мөр хэзээ үүссэн, хэзээ шинэчлэгдсэнийг мэдэх
  • C) Зөвхөн formат
  • D) Зөвхөн тест

Зөв хариулт: B

Тайлбар: Audit trail = "Хэзээ хэн юу хийсэн?" created_at = Үүссэн. updated_at = Шинэчлэгдсэн. Debug, report, compliance-д чухал.

Тест 75

Дараах SQL-ийн үр дүн юу вэ?

SELECT COUNT(*) FROM students WHERE gpa IS NULL;
  • A) Бүх оюутны тоо
  • B) GPA утга NULL (оруулаагүй) оюутнуудын ТОО
  • C) Бүх GPA-ийн нийлбэр
  • D) Алдаа

Зөв хариулт: B

Тайлбар: WHERE gpa IS NULL = GPA утга оруулаагүй мөрүүд. COUNT(*) = Тэдний тоо. NULL = "Утга байхгүй" (0 биш!).

Тест 76

UNION ба UNION ALL-ийн ялгаа юу вэ?

  • A) Ялгаагүй
  • B) UNION = Давтагдсан мөрийг АРИЛГАХ, UNION ALL = Бүгдийг ХАДГАЛАХ
  • C) UNION ALL удаан
  • D) UNION хурдан

Зөв хариулт: B

Тайлбар: UNION = DISTINCT (давтагдал арилгах → нэмэлт зардал). UNION ALL = Бүгдийг (хурдан). Давтагдал байхгүй гэдэг итгэлтэй бол UNION ALL ашиглах.

Тест 77

EXISTS юу хийдэг вэ?

  • A) Мөр нэмэх
  • B) Дэд query-ийн ÜР ДҮН БАЙГАА ЭСЭХИЙГ шалгах (TRUE/FALSE)
  • C) Мөр устгах
  • D) Хүснэгт үүсгэх

Зөв хариулт: B

Тайлбар: WHERE EXISTS (SELECT 1 FROM enrollments WHERE student_id = s.id) = Хичээлд бүртгэлтэй оюутнууд. NOT EXISTS = Бүртгэлгүй оюутнууд.

Тест 78

CASE WHEN expression юу хийдэг вэ?

  • A) Transaction удирдах
  • B) SQL дотор НӨХЦӨЛТ логик (if-else) хэрэгжүүлэх
  • C) Индекс нэмэх
  • D) FK нэмэх

Зөв хариулт: B

Тайлбар: CASE WHEN gpa >= 3.5 THEN 'Excellent' WHEN gpa >= 3.0 THEN 'Good' ELSE 'Average' END = GPA-аас хамааран ангилал оноох.

Тест 79

Database Connection Pool гэж юу вэ?

  • A) Нэг холболт
  • B) Урьдчилан нээлттэй DB холболтуудын САНГАС — Шинэ холболт нээх зардлыг бууруулах
  • C) Индекс
  • D) Transaction

Зөв хариулт: B

Тайлбар: Connection Pool = HikariCP (Spring Boot default). Холболтуудыг нээж бэлэн байлгана → Хүсэлт бүрт шинэ холболт нээхгүй (3-5ms хэмнэнэ).

Тест 80

spring.datasource.url юу хийдэг вэ?

  • A) API URL тохируулах
  • B) DB-ийн ХОЛБОЛТЫН URL тодорхойлох — jdbc:postgresql://localhost:5432/mydb
  • C) Frontend URL
  • D) Тестийн URL

Зөв хариулт: B

Тайлбар: jdbc:postgresql://localhost:5432/mydb = PostgreSQL, localhost, port 5432, mydb database. H2: jdbc:h2:mem:testdb.

Тест 81

Referential Integrity гэж юу вэ?

  • A) Индексийн бүтэн байдал
  • B) FK-ийн заасан PK ЗААВАЛ байх — Байхгүй PK руу FK заах боломжгүй
  • C) Нэрлэлтийн дүрэм
  • D) Transaction дүрэм

Зөв хариулт: B

Тайлбар: student_id = 999 → students хүснэгтэд id=999 БАЙХ ЁСТОЙ. Байхгүй бол DB алдаа буцаана. FK = Мэдээллийн бүрэн бүтэн байдлын хамгаалагч.

Тест 82

SERIAL ба GENERATED ALWAYS AS IDENTITY-ийн ялгаа юу вэ?

  • A) Ялгаагүй
  • B) SERIAL = Хуучин PostgreSQL, IDENTITY = SQL стандарт, илүү АЮУЛГҮЙ (гараар утга оноохыг хориглоно)
  • C) SERIAL = Шинэ
  • D) IDENTITY = Хуучин

Зөв хариулт: B

Тайлбар: GENERATED ALWAYS AS IDENTITY = SQL:2003 стандарт. Гараар INSERT хийхэд ID оруулж БОЛОХГҮЙ → Бүрэн автомат. SERIAL = Legacy, гараар override хийж болно.

Тест 83

@EntityGraph annotation юу хийдэг вэ?

  • A) Entity үүсгэх
  • B) JPA query-д аль харилцааг НИЙЛҮҮЛЖ ачаалахыг тодорхойлох — N+1 шийдэл
  • C) Индекс нэмэх
  • D) Transaction удирдах

Зөв хариулт: B

Тайлбар: @EntityGraph(attributePaths = {"department"}) = findAll() дуудахад department-ийг нэг query-ээр (JOIN) ачаална. N+1 асуудлын нэг шийдэл.

Тест 84

Optimistic Locking гэж юу вэ?

  • A) Хүснэгт түгжих
  • B) @Version ашиглаж, update хийхдээ ХУВИЛБАР шалгах — Зэрэг update хийвэл алдаа
  • C) Мөр устгах
  • D) Индекс нэмэх

Зөв хариулт: B

Тайлбар: @Version private Long version; → A version=1-ийг update хийж version=2 болгоно. B ч version=1-ийг update хийхийг оролдвол → OptimisticLockException. Зөрчилдлийг шийднэ.

Тест 85

JSONB төрөл (PostgreSQL) юу вэ?

  • A) Текст
  • B) JSON өгөгдлийг BINARY хэлбэрээр хадгалах — Хайлт, индекс хурдан
  • C) Тоо
  • D) Огноо

Зөв хариулт: B

Тайлбар: JSONB = JSON Binary. PostgreSQL-д JSON өгөгдлийг хадгалж, дотор нь хайх, индекс нэмэх боломжтой. metadata->>'key' = JSON утга авах.

Тест 86

Data Integrity-ийн 3 түвшин юу вэ?

  • A) Зөвхөн нэг түвшин
  • B) Entity Integrity (PK), Referential Integrity (FK), Domain Integrity (CHECK, NOT NULL)
  • C) Зөвхөн PK
  • D) Зөвхөн FK

Зөв хариулт: B

Тайлбар: Entity = PK давтагдашгүй. Referential = FK заасан бичлэг байх. Domain = Утгын хязгаар (GPA 0-4, email формат). 3 түвшин = Өгөгдлийн чанар.

Тест 87

Window Function юу хийдэг вэ?

  • A) UI үүсгэх
  • B) Мөр бүрт бүлгийн тооцоолол хийх — GROUP BY шиг, гэхдээ мөр ХАДГАЛАГДАНА
  • C) Хүснэгт үүсгэх
  • D) Индекс нэмэх

Зөв хариулт: B

Тайлбар: SELECT name, gpa, RANK() OVER (ORDER BY gpa DESC) FROM students = Мөр бүрт дараалал (rank) нэмнэ. GROUP BY = Мөр нэгтгэнэ. Window = Мөр хадгална.

Тест 88

ROW_NUMBER() функц юу хийдэг вэ?

  • A) Мөр устгах
  • B) Мөр бүрт дарааллын ДУГААР оноох (1, 2, 3...)
  • C) Мөр нэмэх
  • D) Тоолох

Зөв хариулт: B

Тайлбар: ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY gpa DESC) = Тэнхим бүрт GPA-аар дараалсан дугаар. Тэнхим бүрийн шилдэг 3-ыг олоход ашиглана.

Тест 89

Database Sharding гэж юу вэ?

  • A) Backup хийх
  • B) Өгөгдлийг олон серверт ХУВААРИЛАХ — Хэвтээ scaling
  • C) Индекс нэмэх
  • D) Нормалчлах

Зөв хариулт: B

Тайлбар: Sharding = Оюутан A-M → Сервер 1, N-Z → Сервер 2. Маш том өгөгдлийг нэг серверт багтаахгүй үед. Нарийн төвөгтэй, гэхдээ Scale хийнэ.

Тест 90

Database Replication гэж юу вэ?

  • A) Мөр устгах
  • B) Нэг DB-ийн хуулбарыг олон серверт ХАДГАЛАХ — Read scaling, backup
  • C) Индекс нэмэх
  • D) Schema өөрчлөх

Зөв хариулт: B

Тайлбар: Primary → Replica(s). Write = Primary. Read = Replica(s). Read олон, Write бага бол маш тохиромжтой. Failover: Primary унтарвал Replica = Primary болно.

Тест 91

spring.jpa.show-sql=true юу хийдэг вэ?

  • A) SQL устгах
  • B) Hibernate-ийн үүсгэсэн SQL query-г КОНСОЛД харуулах — Debug-д тустай
  • C) SQL хурдасгах
  • D) SQL кэшлэх

Зөв хариулт: B

Тайлбар: show-sql=trueSELECT s FROM Student s WHERE s.id = ? консолд гарна. N+1 асуудлыг олж, query тоог шалгахад маш чухал.

Тест 92

VACUUM (PostgreSQL) юу хийдэг вэ?

  • A) Мөр нэмэх
  • B) Устгагдсан мөрүүдийн зайг ЧӨЛӨӨЛӨХ — DB-ийн хэмжээг бууруулах
  • C) Индекс нэмэх
  • D) Schema өөрчлөх

Зөв хариулт: B

Тайлбар: PostgreSQL-д DELETE хийхэд мөр "мэдэгдэнэ" (dead tuple), гэхдээ зай чөлөөлөгдөхгүй. VACUUM = Dead tuple-ийг цэвэрлэж зай чөлөөлнө. AUTOVACUUM = Автомат.

Тест 93

Materialized View юу вэ?

  • A) Энгийн View
  • B) Query-ийн ÜР ДҮНГ ХАДГАЛСАН View — Хурдан, гэхдээ REFRESH хийх шаардлагатай
  • C) Хүснэгт
  • D) Индекс

Зөв хариулт: B

Тайлбар: View = Дуудах бүрт query ажиллана (удаан). Materialized View = Үр дүнг хадгалж, хурдан авна. REFRESH MATERIALIZED VIEW = Шинэчлэх.

Тест 94

WITH (CTE - Common Table Expression) юу хийдэг вэ?

  • A) Хүснэгт үүсгэх
  • B) Нарийн query-г нэртэй, уншигдахуйц хэсгүүдэд ХУВААХ
  • C) Индекс нэмэх
  • D) Transaction удирдах

Зөв хариулт: B

Тайлбар: WITH top_students AS (SELECT ... WHERE gpa >= 3.5) SELECT * FROM top_students JOIN ... = Нарийн query-г хуваах. Уншихад хялбар, дахин ашиглах боломжтой.

Тест 95

pg_dump юу хийдэг вэ?

  • A) DB устгах
  • B) PostgreSQL мэдээллийн сангийн BACKUP (нөөц) файл үүсгэх
  • C) DB шинэчлэх
  • D) Индекс нэмэх

Зөв хариулт: B

Тайлбар: pg_dump mydb > backup.sql = mydb-ийн бүх schema + өгөгдлийг SQL файл руу хадгалах. pg_restore = Сэргээх. Production-д backup ЗААВАЛ.

Тест 96

Connection Pool-ийн maximumPoolSize юу хийдэг вэ?

  • A) Query хязгаарлах
  • B) Нэгэн зэрэг нээлттэй байх DB холболтын ДЭЭД ХЯЗГААР тодорхойлох
  • C) Мөр хязгаарлах
  • D) Хүснэгт хязгаарлах

Зөв хариулт: B

Тайлбар: HikariCP maximumPoolSize=10 = Хамгийн ихдээ 10 зэрэг холболт. Хэтэрвэл хүлээнэ. Хэт бага = Удаан, хэт их = DB ачаалал.

Тест 97

@Embeddable ба @Embedded юу хийдэг вэ?

  • A) Шинэ хүснэгт үүсгэх
  • B) Нэг Entity дотор нэмэлт объект ШИНГЭЭХ — Тусдаа хүснэгтгүй
  • C) FK нэмэх
  • D) Индекс нэмэх

Зөв хариулт: B

Тайлбар: @Embeddable class Address { city, street } + @Embedded private Address address; = Student хүснэгтэд city, street баганууд нэмэгдэнэ. Тусдаа address хүснэгт БАЙХГҮЙ.

Тест 98

SQL Injection гэж юу вэ?

  • A) SQL сурах
  • B) Хэрэглэгчийн оролтоор хортой SQL код ШИГТГЭЖ, DB-д зөвшөөрөгдөөгүй үйлдэл хийх
  • C) Индекс нэмэх
  • D) Backup хийх

Зөв хариулт: B

Тайлбар: WHERE name = ''; DROP TABLE students; --' = Хэрэглэгч оролт → SQL-д шууд оруулна → Хүснэгт устгана! Prepared Statement / Parameterized Query ашиглаж сэргийлнэ.

Тест 99

Prepared Statement яагаад SQL Injection-аас хамгаалдаг вэ?

  • A) Хурдан учраас
  • B) Параметрийг SQL КОД биш, УТГА гэж боловсруулна — ? placeholder
  • C) Индекс ашигладаг
  • D) Transaction ашигладаг

Зөв хариулт: B

Тайлбар: WHERE name = ? + setString(1, userInput) → userInput = "'; DROP TABLE--" байсан ч, SQL код биш ТЕКСТ утга гэж боловсруулна. JPA/Hibernate автоматаар Prepared Statement ашиглана.

Тест 100

Мэдээллийн сангийн дизайн яагаад програм хангамжийн бүтээлтэд хамгийн чухал вэ?

  • A) Зөвхөн хадгалалт
  • B) Сайн DB дизайн = Хурд, бүрэн бүтэн байдал, scale, аюулгүй байдал — Системийн СУУРЬ
  • C) Зөвхөн query
  • D) Зөвхөн backup

Зөв хариулт: B

Тайлбар: DB = Системийн суурь. Муу дизайн = Удаан query, давхардсан өгөгдөл, inconsistency, scale хийж чадахгүй. Сайн дизайн = Хурдан, найдвартай, өсгөж чадах систем. "Сайн суурь = Сайн байшин."


📚 Ашигласан эх сурвалжууд:

  • C.J. Date — An Introduction to Database Systems (8th Edition)
  • Abraham Silberschatz — Database System Concepts (7th Edition)
  • PostgreSQL Official Documentation — postgresql.org/docs
  • Spring Data JPA Reference — docs.spring.io/spring-data/jpa
  • Vlad Mihalcea — High-Performance Java Persistence
  • Use The Index, Luke — use-the-index-luke.com
  • Hibernate ORM Documentation — hibernate.org/orm/documentation
  • Martin Kleppmann — Designing Data-Intensive Applications (O'Reilly)