Mysql
 sql >> Teknologi Basis Data >  >> RDS >> Mysql

Cara mengelola sesi di Node.js menggunakan Passport, Redis, dan MySQL

HTTP dan HTTPS adalah protokol internet yang memungkinkan pengiriman data melalui internet dengan mengirimkan permintaan melalui browser web. Karena tidak memiliki kewarganegaraan, setiap permintaan yang dikirim ke browser diperlakukan secara independen. Ini berarti bahwa browser tidak dapat mengingat sumber permintaan, bahkan jika pengguna yang sama membuatnya. Sesi HTTP memecahkan masalah ini.

Artikel ini akan membahas manajemen sesi dan bagaimana alat seperti Passport, Redis, dan MySQL dapat membantu kami mengelola sesi Node.js. Mari selami.

Bagaimana cara kerja sesi HTTP?

Sesi HTTP memungkinkan server web untuk mempertahankan identitas pengguna dan menyimpan data khusus pengguna di beberapa interaksi permintaan/tanggapan antara aplikasi klien dan aplikasi web. Ketika klien masuk ke aplikasi, server menghasilkan SessionID. Sesi disimpan dalam memori menggunakan server tunggal, mekanisme penyimpanan persisten yang tidak direplikasi. Contoh mekanisme tersebut termasuk ketekunan JDBC, ketekunan sistem file, ketekunan sesi berbasis cookie, dan replikasi dalam memori. Saat pengguna mengirim permintaan berikutnya, sessionID diteruskan di header permintaan, dan browser memeriksa apakah ID cocok dengan yang ada di penyimpanan memori dan memberikan akses kepada pengguna hingga sesi berakhir.

Sesi HTTP menyimpan data berikut dalam memori:

  • Spesifikasi tentang sesi (pengidentifikasi sesi, waktu pembuatan, waktu terakhir diakses, dll.)
  • Informasi kontekstual tentang pengguna (misalnya, status login klien)

Apa itu Redis?

Redis (Remote Dictionary Server) adalah penyimpanan data nilai kunci dalam memori sumber terbuka yang cepat yang digunakan sebagai database, cache, perantara pesan, dan antrian.

Redis memiliki waktu respons sub-milidetik, memungkinkan jutaan permintaan per detik untuk aplikasi waktu nyata di industri seperti game, teknologi iklan, keuangan, perawatan kesehatan, dan IoT. Hasilnya, Redis kini menjadi salah satu mesin sumber terbuka paling populer, yang dinobatkan sebagai database "Paling Dicintai" oleh Stack Overflow lima tahun berturut-turut. Karena kinerjanya yang cepat, Redis adalah pilihan populer untuk caching, manajemen sesi, game, papan peringkat, analitik real-time, geospasial, ride-hailing, chat/messaging, streaming media, dan pub/sub-aplikasi.

Apa yang kita bangun?

Untuk mendemonstrasikan manajemen sesi di Node.js, kami akan membuat aplikasi pendaftaran dan masuk sederhana. Pengguna akan mendaftar dan masuk ke aplikasi ini dengan memberikan alamat email dan kata sandi mereka. Sesi dibuat dan disimpan di penyimpanan Redis untuk permintaan di masa mendatang saat pengguna masuk. Saat pengguna keluar, kami akan menghapus sesi mereka. Cukup berbicara; mari kita mulai!

Prasyarat

Tutorial ini adalah demonstrasi langsung. Pastikan Anda telah menginstal yang berikut ini sebelum memulai:

  • Node.js
  • CLI Redis
  • Database MySQL
  • Tipe Dasar

Kode untuk tutorial ini tersedia di repositori Github saya. Rasakan untuk mengkloning dan mengikuti.

Penyiapan proyek

Mari kita mulai dengan membuat folder proyek untuk aplikasi dengan perintah di bawah ini:

mkdir Session_management && cd Session_management

Kemudian, inisialisasi aplikasi Node.js untuk membuat file package.json dengan perintah di bawah ini:

npm init -y

-y flag pada perintah di atas memberitahu npm untuk menggunakan konfigurasi default. Sekarang buat struktur folder berikut di direktori root proyek Anda.

Dengan package.json kami dibuat, mari instal paket yang diperlukan untuk proyek ini di bagian selanjutnya.

Menginstal dependensi

Kami akan menginstal dependensi berikut untuk aplikasi kami:

  • Bcryptjs - Modul ini akan digunakan untuk meng-hash kata sandi pengguna.
  • Hubungkan-redis - Modul ini akan menyediakan penyimpanan sesi Redis untuk Express.
  • Sesi ekspres - Modul ini akan digunakan untuk membuat sesi.
  • Ejs - Modul ini adalah mesin template kami
  • Paspor - Modul ini akan digunakan untuk otentikasi pengguna
  • Paspor-lokal - Modul ini akan digunakan untuk otentikasi nama pengguna dan sandi lokal
  • Sekuel - Modul ini adalah MySQL ORM kami untuk menghubungkan aplikasi kami ke database MySQL.
  • Dotenv - Modul ini akan digunakan untuk memuat variabel lingkungan kita.

Gunakan perintah di bawah ini untuk menginstal semua dependensi yang diperlukan.

npm install bcryptjs connect-redis redis express-session ejs passport passport-local sequelize dotenv

Tunggu hingga penginstalan selesai. Setelah instalasi selesai, lanjutkan dengan menyiapkan database MySQL di bagian berikutnya.

Menyiapkan Database MySQL

Kami akan membuat database MySQL untuk aplikasi kami. Namun sebelum itu, jalankan perintah di bawah ini untuk membuat akun pengguna MySQL.

CREATE USER 'newuser'@'localhost' IDENTIFIED BY '1234';

Sekarang buat database session_db, dan berikan akses pengguna baru ke database dengan perintah di bawah ini:

#Create database
CREATE DATABASE session_db; 

 #grant access
GRANT ALL PRIVILEGES ON session_db TO 'newuser'@'localhost';

ALTER USER 'newuser'@'localhost' IDENTIFIED WITH mysql_native_password BY '1234';

Sekarang muat ulang semua hak istimewa dengan perintah di bawah ini:

FLUSH PRIVILEGES;

Dengan setup database MySQL, mari kita buat users model database di bagian berikutnya.

Buat Server Ekspres

Dengan setup database MySQL, mari buat server ekspres untuk aplikasi kita. Buka file src/server.js dan tambahkan potongan kode di bawah ini:

const express = require("express");

const app = express();
const PORT = 4300;


//app middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

//Redis configurations

//Configure session middleware


//Router middleware


app.listen(PORT, () => {
 console.log(`Server started at port ${PORT}`);
});

Dalam cuplikan kode di atas, kita membuat server ekspres, yang akan mendengarkan permintaan pada Port 4300. Kemudian, kita mengurai permintaan yang masuk dengan muatan JSON menggunakan express.json() middleware dan mengurai permintaan masuk dengan urlencoded menggunakan Express.urlencoded() perangkat tengah.

Membuat model database

Pada titik ini, server Express kami telah diatur. Sekarang kita akan membuat Users model untuk mewakili data pengguna kita akan melihat database menggunakan Sequelize . Buka src/models/index.js file dan tambahkan cuplikan kode di bawah ini.

const { Sequelize, DataTypes } = require("sequelize");
const sequelize = new Sequelize({
 host: "localhost",
 database: "session_db",
 username: "newuser",
 password: "1234",
 dialect: "mysql",
});

exports.User = sequelize.define("users", {
 // Model attributes are defined here
 id: {
   type: DataTypes.INTEGER,
   autoIncrement: true,
   primaryKey: true,
 },
 email: {
   type: DataTypes.STRING,
 },
 password: {
   type: DataTypes.STRING,
 },
});

Dalam cuplikan kode di atas, kami mengimpor Sequelize dan DateTypes dari sequelize untuk terhubung ke database MySQL kami dan menetapkan tipe data ke properti model kami. Kemudian, kita terhubung ke MySQL dengan membuat sequelize contoh dari Sequelize kelas dan meneruskan kredensial basis data kami. Misalnya, dengan sequelize misalnya, kami mendefinisikan model kami dan propertinya. Kami hanya ingin bidang id, email, dan kata sandi tutorial ini. Tapi sekuel membuat dua bidang tambahan, createdAt , dan updatedAt bidang.

Siapkan Paspor dan Redis

Untuk menangani dan menyimpan kredensial pengguna kami, kami akan menggunakan dan mengonfigurasi Redis . Untuk melakukannya, buka src/index.js file dan impor dependensi berikut di bawah ini:

const session = require("express-session");
const connectRedis = require("connect-redis");
const dotenv = require("dotenv").config()
const { createClient } = require("redis");
const passport = require("passport");

Kemudian, cari area yang dikomentari //Redis configurations dan tambahkan cuplikan kode di bawah ini:

const redisClient = createClient({ legacyMode: true });
redisClient.connect().catch(console.error);
const RedisStore = connectRedis(session);

Dalam cuplikan kode di atas, kami membuat koneksi ke database kami, yang akan mengelola data nama pengguna pengguna kami.

Selanjutnya, cari area yang dikomentari //Commented session middleware dan tambahkan cuplikan kode di bawah ini:

//Configure session middleware
const SESSION_SECRET = process.env.SESSION_SECRET;

app.use(
 session({
   store: new RedisStore({ client: redisClient }),
   secret: SESSION_SECRET,
   resave: false,
   saveUninitialized: false,
   cookie: {
     secure: false,  // if true only transmit cookie over https
     httpOnly: false, // if true prevent client side JS from reading the cookie
     maxAge: 1000 * 60 * 10, // session max age in milliseconds
   },
 })
);
app.use(passport.initialize());
app.use(passport.session());

Dalam cuplikan kode di atas, kami membuat SESSION_SECRET variabel dalam .env file untuk menyimpan rahasia sesi kami, kemudian membuat middleware sesi dan menggunakan Redis sebagai toko kami. Agar sesi berfungsi, kami menambahkan dua middleware lagi passport.initialize() , dan passport.session() .

Buat pengontrol aplikasi

Dengan Redis dan penyiapan sesi ekspres, kami akan membuat rute untuk menangani informasi pengguna. Untuk melakukannya, buka src/controllers/index.js file dan tambahkan cuplikan kode di bawah ini:

const { User } = require("../models");
const bcrypt = require("bcrypt");

exports.Signup = async (req, res) => {
 try {
   const { email, password } = req.body;

   //generate hash salt for password
   const salt = await bcrypt.genSalt(12);

   //generate the hashed version of users password
   const hashed_password = await bcrypt.hash(password, salt);

   const user = await User.create({ email, password: hashed_password });
   if (user) {
     res.status(201).json({ message: "new user created!" });
   }
 } catch (e) {
   console.log(e);
 }
};

Dalam cuplikan kode di atas, kami mengimpor bcrypt dan User . kami model, kami merusak email pengguna dan password dari req.body obyek. Kemudian kita hash password menggunakan bcrypt dan membuat user baru menggunakan sequelize create metode.

Selanjutnya, buat home page , registration page , login page dengan cuplikan kode di bawah ini:

exports.HomePage = async (req, res) => {
 if (!req.user) {
   return res.redirect("/");
 }
 res.render("home", {
   sessionID: req.sessionID,
   sessionExpireTime: new Date(req.session.cookie.expires) - new Date(),
   isAuthenticated: req.isAuthenticated(),
   user: req.user,
 });
};

exports.LoginPage = async (req, res) => {
 res.render("auth/login");
};

exports.registerPage = async (req, res) => {
 res.render("auth/register");
};

Di HomePage , kami akan menampilkan beberapa detail pengguna yang diautentikasi di samping home lihat.

Terakhir, buat logout route, untuk menghapus data username pengguna dengan cuplikan kode di bawah ini:

exports.Logout = (req, res) => {
 req.session.destroy((err) => {
   if (err) {
     return console.log(err);
   }
   res.redirect("/");
 });
};

Buat strategi Paspor

Pada titik ini, pengguna dapat mendaftar, masuk, dan keluar dari aplikasi kami. Sekarang, mari buat strategi paspor untuk mengautentikasi pengguna dan membuat sesi. Untuk melakukannya, buka src/utils/passport.js file, dan tambahkan cuplikan kode di bawah ini:

const LocalStrategy = require("passport-local/lib").Strategy;
const passport = require("passport");
const { User } = require("../models");
const bcrypt = require("bcrypt");

module.exports.passportConfig = () => {
 passport.use(
   new LocalStrategy(
     { usernameField: "email", passwordField: "password" },
     async (email, password, done) => {
       const user = await User.findOne({ where: { email: email } });
       if (!user) {
         return done(null, false, { message: "Invalid credentials.\n" });
       }
       if (!bcrypt.compareSync(password, user.password)) {
         return done(null, false, { message: "Invalid credentials.\n" });
       }
       return done(null, user);

     }
   )
 );

 passport.serializeUser((user, done) => {
   done(null, user.id);
 });

 passport.deserializeUser(async (id, done) => {
   const user = await User.findByPk(id);
   if (!user) {
     done(error, false);
   }
   done(null, user);
 });
};

Dalam cuplikan kode di atas kami mengimpor passport , bcrypt , dan model Pengguna kami, dan kami membuat middleware paspor untuk menggunakan local-strategy . Kemudian kita rename nama file default menjadi nama field ( email , password ) yang kami gunakan untuk mengautentikasi pengguna. Sekarang, kami memeriksa apakah detail pengguna ada di database sebelum sesi dapat dibuat untuk mereka.

Passport.serialize dan passport.deserialize perintah digunakan untuk mempertahankan id pengguna sebagai cookie di browser pengguna dan untuk mengambil id dari cookie bila perlu, yang kemudian digunakan untuk mengambil informasi pengguna dalam panggilan balik.

done() fungsinya adalah passport.js internal fungsi yang menggunakan id pengguna sebagai parameter kedua.

Buat rute aplikasi

Dengan strategi paspor kami dibuat, mari kita lanjutkan dengan membuat rute untuk pengontrol kami. Untuk melakukannya, buka src/routes/index.js file dan tambahkan cuplikan kode berikut di bawah ini:

const express = require("express");
const {
 Signup,
 HomePage,
 LoginPage,
 registerPage,
 Logout,
} = require("../controllers");
const passport = require("passport");

const router = express.Router();

router.route("/").get(LoginPage);
router.route("/register").get(registerPage);
router.route("/home").get(HomePage);
router.route("/api/v1/signin").post(
 passport.authenticate("local", {
   failureRedirect: "/",
   successRedirect: "/home",
 }),
 function (req, res) {}
);
router.route("/api/v1/signup").post(Signup);
router.route("/logout").get(Logout);

module.exports = router;

Dalam cuplikan kode di atas, kami mengimpor fungsi pengontrol kami dan membuat rute untuknya. Untuk signin route , kami menggunakan passport.authenticate metode untuk mengautentikasi pengguna menggunakan local strategi dalam penyiapan di bagian sebelumnya.

Sekarang kembali ke server.js kami file, kami akan membuat middleware untuk rute kami. Sebelum itu, kita perlu mengimpor router dan passportConfig fungsi.

const router = require("./routes");
const { passportConfig } = require("./utils/passport");

Kemudian, kita akan memanggil passportConfig berfungsi tepat di bawah kode di area yang dikomentari //Configure session middleware .

passportConfig();

Kemudian, kami akan membuat middleware rute kami tepat setelah area dikomentari//Router middleware .

app.use(router);

Buat tampilan aplikasi kami

Dengan rute kami dibuat, kami akan membuat tampilan yang dirender di HomePage kami , LoginPage , dan RegisterPage pengontrol. Sebelum itu, kami akan menyiapkan mesin tampilan ejs kami di file server.js dengan cuplikan kode di bawah tepat di bawah area yang dikomentari //app middleware .

app.set("view engine", "ejs");

Kemudian, kita akan mulai dengan halaman beranda, buka views/home.ejs file dan tambahkan markup berikut.

<html lang="en">
 <head>
   <meta charset="UTF-8" />
   <meta http-equiv="X-UA-Compatible" content="IE=edge" />
   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
   <title>Document</title>
   <link
     href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
     rel="stylesheet"
     integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC"
     crossorigin="anonymous"
   />
 </head>

 <body>
   <section>
     <!-- As a heading -->
     <nav class="navbar navbar-light bg-light">
       <div class="container-fluid">
         <a class="navbar-brand">Navbar</a>
         <% if(isAuthenticated){ %>
         <a href="/logout" class="btn btn-danger btn-md">Logout</a>
         <% } %>
       </div>
     </nav>
     <div class="">
       <p class="center">
         Welcome: <b><%= user.email %></b> your sessionID is <b><%= sessionID %></b>
       </p>
       <p>Your session expires in <b><%= sessionExpireTime %></b> seconds</p>
     </div>
   </section>
 </body>
</html>

Di sini, di beranda kami, kami menggunakan bootstrap untuk menambahkan beberapa gaya ke markup kami. Kemudian kami memeriksa apakah pengguna diautentikasi untuk menampilkan tombol logout. Kami juga menampilkan Email pengguna , sessionID , dan ExpirationTime dari bagian belakang.

Selanjutnya, buka src/views/auth/resgister dan tambahkan markup berikut di bawah ini untuk halaman register.

<html lang="en">
 <head>
   <meta charset="UTF-8" />
   <meta http-equiv="X-UA-Compatible" content="IE=edge" />
   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
   <title>Document</title>
   <link
     href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
     rel="stylesheet"
     integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC"
     crossorigin="anonymous"
   />
 </head>
 <body>
   <section class="vh-100" style="background-color: #9a616d">
     <div class="container py-5 h-100">
       <div class="row d-flex justify-content-center align-items-center h-100">
         <div class="col col-xl-10">
           <div class="card" style="border-radius: 1rem">
             <div class="row g-0">
               <div class="col-md-6 col-lg-5 d-none d-md-block">
                 <img
                   src="https://mdbcdn.b-cdn.net/img/Photos/new-templates/bootstrap-login-form/img1.webp"
                   alt="login form"
                   class="img-fluid"
                   style="border-radius: 1rem 0 0 1rem"
                 />
               </div>
               <div class="col-md-6 col-lg-7 d-flex align-items-center">
                 <div class="card-body p-4 p-lg-5 text-black">
                   <form action="api/v1/signup" method="post">
                     <h5
                       class="fw-normal mb-3 pb-3"
                       style="letter-spacing: 1px"
                     >
                       Signup into your account
                     </h5>

                     <div class="form-outline mb-4">
                       <input
                         name="email"
                         type="email"
                         id="form2Example17"
                         class="form-control form-control-lg"
                       />
                       <label class="form-label" for="form2Example17"
                         >Email address</label
                       >
                     </div>

                     <div class="form-outline mb-4">
                       <input
                         name="password"
                         type="password"
                         id="form2Example27"
                         class="form-control form-control-lg"
                       />
                       <label class="form-label" for="form2Example27"
                         >Password</label
                       >
                     </div>

                     <div class="pt-1 mb-4">
                       <button
                         class="btn btn-dark btn-lg btn-block"
                         type="submit"
                       >
                         Register
                       </button>
                     </div>

                     <a class="small text-muted" href="#!">Forgot password?</a>
                     <p class="mb-5 pb-lg-2" style="color: #393f81">
                       Don't have an account?
                       <a href="/" style="color: #393f81">Login here</a>
                     </p>
                     <a href="#!" class="small text-muted">Terms of use.</a>
                     <a href="#!" class="small text-muted">Privacy policy</a>
                   </form>
                 </div>
               </div>
             </div>
           </div>
         </div>
       </div>
     </div>
   </section>
 </body>
</html>

Di halaman daftar, kami membuat formulir html untuk menerima detail pengguna. Dalam formulir, kami juga menambahkan atribut aktif dan menentukan titik akhir pendaftaran. Artinya, ketika pengguna mengklik tombol kirim, permintaan akan dikirim ke /api/v1/signup titik akhir.

Terakhir, buka src/views/auth/signin.js file, dan tambahkan cuplikan markup berikut di bawah ini:

<html lang="en">
 <head>
   <meta charset="UTF-8" />
   <meta http-equiv="X-UA-Compatible" content="IE=edge" />
   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
   <title>Document</title>
   <link
     href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
     rel="stylesheet"
     integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC"
     crossorigin="anonymous"
   />
 </head>
 <body>
   <section class="vh-100" style="background-color: #9a616d">
     <div class="container py-5 h-100">
       <div class="row d-flex justify-content-center align-items-center h-100">
         <div class="col col-xl-10">
           <div class="card" style="border-radius: 1rem">
             <div class="row g-0">
               <div class="col-md-6 col-lg-5 d-none d-md-block">
                 <img
                   src="https://mdbcdn.b-cdn.net/img/Photos/new-templates/bootstrap-login-form/img1.webp"
                   alt="login form"
                   class="img-fluid"
                   style="border-radius: 1rem 0 0 1rem"
                 />
               </div>
               <div class="col-md-6 col-lg-7 d-flex align-items-center">
                 <div class="card-body p-4 p-lg-5 text-black">
                   <form action="api/v1/signin" method="post">
                     <h5
                       class="fw-normal mb-3 pb-3"
                       style="letter-spacing: 1px"
                     >
                       Sign into your account
                     </h5>

                     <div class="form-outline mb-4">
                       <input
                         name="email"
                         type="email"
                         id="form2Example17"
                         class="form-control form-control-lg"
                       />
                       <label class="form-label" for="form2Example17"
                         >Email address</label
                       >
                     </div>

                     <div class="form-outline mb-4">
                       <input
                         name="password"
                         type="password"
                         id="form2Example27"
                         class="form-control form-control-lg"
                       />
                       <label class="form-label" for="form2Example27"
                         >Password</label
                       >
                     </div>

                     <div class="pt-1 mb-4">
                       <button
                         class="btn btn-dark btn-lg btn-block"
                         type="submit"
                       >
                         Login
                       </button>
                     </div>

                     <a class="small text-muted" href="#!">Forgot password?</a>
                     <p class="mb-5 pb-lg-2" style="color: #393f81">
                       Don't have an account?
                       <a href="/register" style="color: #393f81"
                         >Register here</a
                       >
                     </p>
                     <a href="#!" class="small text-muted">Terms of use.</a>
                     <a href="#!" class="small text-muted">Privacy policy</a>
                   </form>
                 </div>
               </div>
             </div>
           </div>
         </div>
       </div>
     </div>
   </section>
 </body>
</html>

Pada markup diatas, kita menambahkan form html yang akan digunakan untuk login user dengan mengirimkan request ke /api/v1/signin titik akhir.

Lihat data pengguna dengan Arctype

Kami sekarang telah berhasil membuat aplikasi manajemen sesi Node.js. Mari kita lihat data pengguna dengan Arctype. Untuk memulai, luncurkan Arctype, klik tab MySQL, dan masukkan kredensial MySQL berikut, seperti yang ditunjukkan pada gambar di bawah:

Kemudian, klik users tabel untuk menunjukkan pengguna terdaftar seperti yang ditunjukkan pada tangkapan layar di bawah ini:

Kesimpulan

Dengan membangun aplikasi login demo, kita telah mempelajari cara mengimplementasikan manajemen sesi di Node.js menggunakan Passport dan Redis. Kami mulai dengan pengenalan sesi HTTP dan cara kerjanya, kemudian kami melihat apa itu Redis, dan membuat proyek untuk mempraktikkan semua ini. Sekarang setelah Anda memiliki pengetahuan yang Anda cari, bagaimana Anda akan mengautentikasi proyek pengguna?


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Bagaimana saya bisa tahu kapan tabel MySQL terakhir diperbarui?

  2. meneruskan LIMIT sebagai parameter ke MySQL sproc

  3. Beberapa kueri dieksekusi di java dalam satu pernyataan

  4. Bagaimana cara melakukan Pemodelan Warisan dalam Basis Data Relasional?

  5. Maksimum berdasarkan grup