‫استفاده از Database Connection Pool از طریق کتابخانه Apache DBCP

پیشگفتار

استفاده از دیتابیس امروزه یک امر بسیار ضروری در اکثر نرم افزارهای دنیا محسوب میشود چنانکه اکثر برنامه های کاربردی دیتاهای خود را متمرکز بر روی دیتابیس ها ذخیره میکنند تا ‫بتوانند اطلاعات یوزرهای خود را بررسی،تحلیل کنند و با استفاده از این داده بیزینس خود را بزرگتر و بهتر کنند.

‫برنامه هایی که با دیتابیس کار میکنند معمولا بیش از یک یوزر دارند و بعضی از این برنامه ها تعداد زیادی یوزر بصورت لایو در حال کار کردن با این برنامه ها هستند و ارتباط با دیتابیس باید به بهترین شکل انجام بپذیرد که این یوزرها سرعت و راحتی را در app شما تجربه کنند.

‫هر دیتابیسی در داخل جاوا یک کتابخانه ی بخصوصی را عرضه کرده است که به این کتابخانه ها کتابخانه های JDBC گفته می شود. JDBC مخفف Java Database Connectivity می باشد یعنی اتصال به دیتابیس از طریق زبان جاوا.

چرا باید برای اتصال به دیتابیس ها از Pool استفاده کنیم

‫حال سوال اینجاس که دلیل استفاده از Pool در دیتابیس ها چیست؟ خب من با یک مثال ساده برای شما قضیه را تشریح میکنم.

‫یک دیتابیس قابلیت این را دارد که بصورت همزمان روی چندین connection اتصالی به خودش به شما سرویس دهد. حال فرض کنید در داخل برنامه شما ۳۰ تا یوزره بصورت همزمان میخواهند از دیتابیس شما استفاده کنند. اگر فرض کنیم که این ۳۰ تا یوزر به وب سرور شما وصل شده اند و برای آن ها ۳۰ تا thread عه جداگانه درست شده است. هنگامی که اولین ‫thread یعنی یوزر اول connection عه دیتابیس رو به خود اختصاص دهد اگر که سرور شما تنها یک connection با دیتابیس برقرار کند، همه ۲۹ تا thread دیگر باید صبر کنند که کار transaction عه connection عه thread عه اول به پایان برسد تا بتوانند یکی یکی بصورت شانسی connectionعه بعدی را به خود اختصاص دهند.

‫این بدترین سناریویی است که میتوان برای اتصال به دیتابیس در نظر گرفت چون ‫سرعت سیستم به شدت پایین می آید.

‫در این مواقع ۲ راه برای حل مشکل وجود دارد :
‫۱- برای هر یوزری که رو thread عه جدا به شما درخواست میده، یک connection به دیتابیس درست کرد
‫۲-‫‫ یک pool از connection ها از قبل درست کرده باشیم و اون connection هایی که idle هستند رو به درخواست های جدید روی thread های جدید بدیم!

‫از ۲ مورد بالا گزینه ی اول بسیار گزینه ی بدی است چون :
‫۱- اتصال به یک دیتابیس کار بسیار پر هزینه ای است چون باید سوکتی را با دیتابیس باز کرد، connection برقرار کرد، توی همان لحظه ی اتصال ممکن است اتصال به مشکل بخورد
‫۲- اگر کنترلی روی connection های دیتابیسی نداشته باشیم ممکن است یهو ۲۰۰ کانکشن با دیتابیس برقرار شود که خوده سرور دیتابیس گنجایش پاسخگویی همزمان به اون همه کانکشن ‫رو نداشته باشد

‫‫در نتیجه از یک connection pool استفاده میکنند و به هنگام راه اندازی سیستم تعدادی connection عه آماده را در این pool یا استخر نگه می دارند. هر وقت یوزر درخواستی داد یک‫ connection از این pool میگیرند و به یوزر درخواست کننده میدن. هر وقتم که کار یوزر‫ درخواست کننده تموم شد، همون connection ش رو بر میگردونند توی pool . یجورایی مثل‫  ‫یکسری کارگر آماده کار که هر وقتی بهشون نیاز باشه صداشون میزنیم. این هم از معنی و  مفهوم pool که در همه زبان های برنامه نویسی صدق میکند! حال به یک کتابخانه مشهور برای درست کردن pool می پردازیم. کتابخانه ی Apache DBCP

‫کتابخانه ی Apache DBCP

‫یک کتابخانه بسیار قدرتمند است که به شما امکان این رو میدهد که Connection Pool های با پرفورمنس بالا در داخل برنامه هاتون درست کنید.

‫این کتابخانه قابلیت تنظیم بر روی تامکت، کتابخانه ی هایبرنیت و هر سیستم جاوایی دیگری را  دارد. از داخل این وبسایت میتوانید کلیه ی پارامترهای موجود برای تنظیم این کتابخانه  را  مشاهده کنید. بطور کل پارامترهای اتصال اینها هستند‌:

توضیح پارامتر
یوزر اتصال به دیتابیس username
کلمه رمز اتصال به دیتابیس password
‫آدرس url برای اتصال به دیتابیس – دقیقا شبیه JDBC url
‫کلاسی که در jar عه درایور دیتابیس مورد نظر موجود هست و اجازه بوت شدن کلاس های درایور را میدهد! driverClassName

‫‫‫پارامترهای pool

توضیح پیش فرض پارامتر
تعداد اولیه کانکشن هایی که ‫در داخل pool درست میشن 0 initialSize
تعداد ماکزیمم کانکشن های ‫فعالی که در داخل pool درست میشن 8 maxTotal
تعداد ماکزیمم کانکشن هایی که میتوانند بصورت بلا استفاده در سیستم باقی بمونند 8 maxIdle
تعداد مینیمم کانکشن هایی که میتوانند بصورت بلا استفاده در سیستم باقی بمونند 0 minIdle
‫مقدار زمانی که pool منتظر میماند تا یه کانکشنه در حال استفاده برگرده به pool قبل از اینکه Exception یا خطا  ‫بگیرد- توجه داشته باشین این مسئله زمانی اتفاق می افتد که کلیه ی کانکشن های موجود در داخل pool در حال استفاده هستند بی نهایت maxWaitMillis

: از پارامترهای مهم دیگر میتوان به پارامترهای مرتبط با

توضیح پیش فرض پارامتر
NONE
READ_COMMITTED
READ_UNCOMMITTED
REPEATABLE_READ
‫SERIALIZABLE
پیش فرض درایور مربوطه TransactionIsolationLevel
‫برای commit شدن transaction بصورت اتوماتیک یا دستی پیش فرض درایور مربوطه defaultAutoCommit
‫تعداد ماکزیمم statement هایی که میشه توی pool بصورت همزمان باز باشه بی نهایت maxOpenPreparedStatements

‫نوشتن یه برنامه ی ساده با استفاده از کتابخانه ی Apache DBCP

‫ابتدا دیتابیس مورد نظر و ستونی با نام user را با ۳ رکورد در داخل دیتابیس mysql می سازیم:

CREATE SCHEMA `apache_dbpc` ;
CREATE TABLE `apache_dbpc`.`user` (
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `first_name` VARCHAR(45) NOT NULL,
  `last_name` VARCHAR(45) NOT NULL,
  `username` VARCHAR(60) NOT NULL,
  `email_address` VARCHAR(60) NOT NULL,
  PRIMARY KEY (`id`));

INSERT INTO user (first_name, last_name, username, email_address) 
VALUES 
('Mehdi', 'Afsari', 'mehdi', 'mahdi.afsari@gmail.com'), 
('Ahmad', 'Ahmadi', 'ahmad', 'ahmad@gmail.com'), 
('Javad', 'Javadi', 'javad', 'javad@gmail.com');

‫یک پروژه ی maven ی می سازیم و کتابخانه Apache DBPC و کتابخانه درایور mysql را به این شکل بهش اضافه میکنیم:

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-dbcp2</artifactId>
    <version>2.7.0</version>
</dependency>	


<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.48</version>
</dependency>

‫سپس میتوانید با استفاده از این کد ارتباط با دیتابیس را با استفاده از یک pool عه ۱۰ تایی بطور کامل شبیه سازی کنیم :

import org.apache.commons.dbcp2.BasicDataSource;

import javax.sql.DataSource;

public class DataSourceSingleton {

    // این بخش رو پر کنین اطلاعات اتصال به دیتابیس می باشد
    private static final String USERNAME = "";
    private static final String PASSWORD = "";
    private static final String DB_CONNECTION_URL = "jdbc:mysql://localhost:3306/apache_dbpc";
    private static final String DB_DRIVER_CLASS_NAME = "com.mysql.jdbc.Driver";
    private static final int MIN_POOL_IDLE = 3;
    private static final int MAX_POOL_IDLE = 10;
    private static final int INITIAL_POOL_SIZE = 0;
    private static final int MAX_TOTAL_POOL_SIZE = 30;
    private static final int MAX_WAIT_MILIS = 15000;
    private static volatile Object lock = new Object();
    private static volatile DataSource dataSource;


    private DataSourceSingleton(){}



    public static DataSource getInstance() {

        if(dataSource == null){
            synchronized (lock){
                if(dataSource == null){
                    BasicDataSource ds = new BasicDataSource();
                    //یوزر دیتابیس
                    ds.setUsername(USERNAME);
                    // پسورد یوزر دیتابیس
                    ds.setPassword(PASSWORD);
                    // کلاس درایوره دیتابیس
                    ds.setDriverClassName(DB_DRIVER_CLASS_NAME);
                    // کانکشن مورد نیاز برای اتصال
                    ds.setUrl(DB_CONNECTION_URL);
                    // سایز اولیه ی پول
                    ds.setInitialSize(INITIAL_POOL_SIZE);
                    //بیشترین سایزی که پول میتواند داشته باشد
                    ds.setMaxTotal(MAX_TOTAL_POOL_SIZE);
                    // کمترین سایز پول های بیکار
                    ds.setMinIdle(MIN_POOL_IDLE);
                    // بیشترین سایز برای پول های بیکار
                    ds.setMaxIdle(MAX_POOL_IDLE);
                    // زمانی که فریمورک منتظر کانکشن درگیر می ماند تا کانکشن رو مجدد برگردونه توی پول.
                    // زمانی اتفاق می افتد که همه کانکشن ها درگیرن
                    ds.setMaxWaitMillis(MAX_WAIT_MILIS);

                    //برای تنظیم مدل transaction در داخل دیتابیس
                    //ds.setDefaultTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
                    dataSource = ds;
                }
            }
        }

        return dataSource;
    }
}


public class User {

    private String firstName, lastName, username, emailAddress;

    public User(String firstName, String lastName, String username, String emailAddress) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.username = username;
        this.emailAddress = emailAddress;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public String getUsername() {
        return username;
    }

    public String getEmailAddress() {
        return emailAddress;
    }

    @Override
    public String toString() {
        return String.format("First Name: %s | Last Name: %s | Username: %s | Email Address: %s", firstName, lastName, username, emailAddress);
    }

}


import com.iropensource.model.User;
import org.apache.commons.dbcp2.BasicDataSource;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

public class Launcher {
    public static final int CONCURRENT_INT = 10;

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // دیتاسورس ارتباط با دیتابیس را برام بساز
        DataSource dataSource = DataSourceSingleton.getInstance();

        // حال اینکه این کتابخانه چطور کار میکند را با ۱۰ تا ترد شبیه سازی میکنیم
        ExecutorService executor = Executors.newFixedThreadPool(CONCURRENT_INT);


        List<Future<List<User>>> list = new ArrayList<Future<List<User>>>();
        for (int i = 0; i < CONCURRENT_INT; i++) {
            Future<List<User>> future = executor.submit(new UserCallable(dataSource));
            list.add(future);
        }

        //یدونه رو برای نمایش چاپ کن
        printFirst(list.get(0).get());

        for (Future<List<User>> fut : list) {
            List<User> users = fut.get();
        }

        executor.shutdown();
    }

    private static void printFirst(List<User>users){
        System.out.format("\n\n فقط درخواست یدونه از یوزرهارو چاپ کن\n");
        for(User user: users){
            System.out.println(user);
        }
    }

    static class UserCallable implements Callable<List<User>> {

        private DataSource dataSource;

        public UserCallable(DataSource dataSource) {
            this.dataSource = dataSource;
        }

        public List<User> call() throws Exception {
            Connection conn = null;
            Statement stmt = null;
            ResultSet rset = null;
            List<User> users = new ArrayList<User>();

            try {
                // یه کانکشن بگیر
                conn = dataSource.getConnection();
                int num = ((BasicDataSource)dataSource).getNumActive();

                System.out.format("Number of Active Connections are: %d -AND- Connection info: %s & \n", num,conn.toString());

                // برای بهتر نمایش داده شدن شبیه سازی
                Thread.currentThread().sleep(2000);

                //یه استیتمنت باز کن
                stmt = conn.createStatement();

                //این کوئری رو اجرا کن
                rset = stmt.executeQuery("SELECT * FROM user");

                while (rset.next()) {
                    String firstName = rset.getString("first_name");
                    String lastName = rset.getString("last_name");
                    String username = rset.getString("username");
                    String emailAddress = rset.getString("email_address");
                    User user = new User(firstName, lastName, username, emailAddress);
                    users.add(user);
                }
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (rset != null) rset.close();
                    if (stmt != null) stmt.close();
                    if (conn != null) conn.close();
                } catch (Exception e) {
                }
            }
            return users;
        }
    }
}

 

دانلود کل پروژه

 

نکات مهم: ‫توجه داشته باشین که درست کردن connection pool بر روی یک برنامه ی desktop ی یا مثلا اندرویدی که مستقیم به دیتابیس connection میزنه اصلا معنا و مفهوم درستی نداره چون:

‫۱- اون یوزر داره روی process عه کامپیوتر یا دیوایس خودش درخواست میزنه، یعنی اینکه ۱ کانکشن براش کافیه. یعنی اینکه سرور نیست که مثلا چندین درخواست بخواد با هم بیاد داخل و پردازش بشه و بعدش کانکشن ها مدیریت بشن و به thread های درخواست دهنده اختصاص داده شوند!!!
‫۲- با توجه به مسئله گفته شده در شماره ی ۱ و اینکه مسائلی که عدم کنترل کانکشن ها بوجود می آورند (که در بالا اشاره شد) اجازه ی درست کردن connection توسط خوده یوزر خیلی کار اشتباهی است. حتما باید سروری باشد که اینهارو با pool مدیریت کند

‫‫پس یادتون باشه هیچوقت به یوزر اجازه مستقیم درست کردن connection رو مثلا توی برنامه  ی دسکتاپی یا اندرویدی ندین! یک راه بسیار کارا اینست که connection pool رو توی ‫سرورتون تنظیم کنید و با استفاده از JNDI یک DataSource رو از سرور دریافت کنید و از connection pool ی که سرور دارد یک connection بگیرید. در این روش مدیریت بازم در دست سرور است که روش خوبی است.

امیدوارم مورد قبول واقع شده باشه، کانال ها و گروه های ما را از طریق بخش ارتباط سایت ‫دنبال کنید.

Published by

mehdi

I'm a Passionate Software Developer and Team Leader