Если нельзя, но очень хочется, то нужно обязательно и ничего в мире не стоит того, чтобы делать из этого проблему!


Интересна Java? Кликай по ссылке и изучай!
Если тебе полезно что-то из того, чем я делюсь в своем блоге - можешь поделиться своими деньгами со мной.
с пожеланием
столько времени читатели провели на блоге - 
сейчас онлайн - 

понедельник, 9 февраля 2015 г.

Еще одна реализация пула коннекшенов (на покритиковать)

Есть у нас jdbc драйвер, через него коннектимся к базе и кверями достаем данные. Никаких там супер-умных ORM. Напрямую. Все запросы конечно же кучкуются в некотором DAO классе. Так вот при каждом запросе создавать Connection к базе и тут же его закрывать не оптимально. Не ну можно для начала, но дорого по времени. И вот у меня руки зачесались это все дело как-то причесать. Опыта подобного ранее у меня не было - все какие-то фремворки использовал монструозно-ентерпрайзные, инкапсулирующие это все и требующие только конфигурации. А тут надо почти велосипед. Потому и попрошу покритиковать, кто в теме.

Итак начну с клиентского кода - DAO.

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

public class AddressDAOImpl implements AddressDAO {
    private static Logger logger = Logger.getLogger(AddressDAOImpl.class.getName());

    private ConnectionPool connections;

    public AddressDAOImpl(Properties properties) {
        try {
            Class.forName(properties.getProperty("jdbc.driver"));
        } catch (ClassNotFoundException e) {
            logger.log(Level.WARNING, "Cant find jdbc driver", e);
        }
        // конфигурим пул пропертизами и количеством одновременно работающих тредов
        connections = ConnectionPool.with(properties).andThreads(18); 
    }

    @Override
    public void addPerson(final Person person) throws DAOException {
        // вот так запускаем кверю
        connections.query("add person",
                "insert into Persons values (?, ?, ?)",
                // это штука, которая вызовется когда коннекшен будет готов выполнить кверю
                new ConnectionRunner() {
                    @Override
                    public Object connect(PreparedStatement statement) throws SQLException {
                        statement.setLong(1, System.currentTimeMillis());
                        statement.setString(2, person.getName());
                        statement.setString(3, person.getPhoneNumber().getNumber());
                        statement.executeUpdate();
                        return null;
                    }
                });
    }

    @Override
    public Person findPerson(final String name) throws DAOException {
        return connections.query("find person",
                "select * from Persons where name = '" + name + "'",
                // эту штуку так дженерик, ее можно конфигурить типом возвращаемого результата
                // в данном случае Person
                new ConnectionRunner<Person>() {
                    @Override
                    public Person connect(PreparedStatement statement) throws SQLException {
                        try (ResultSet data = statement.executeQuery()) {
                            if (data.next()) {
                                String name = data.getString("name");
                                String phoneNumber = data.getString("phoneNumber");
                                long date = data.getLong("timestamp");

                                return new Person(name, phoneNumber, date);
                            }
                            return null;
                        }
                    }
                });
    }
... 
Как можно заметить я заюзал подход, который применяет Spring в своем jdbc templаte. Не так продуманно в мелочах как у них, но все же. Коннекшен пул конфигурится пропертями загружаемыми из файла properties и количеством тредов, которые будут инкапсулировать в себе коннекшен и выполнять запрос.
public class PropertiesReader {

    // просто прочитали
    public static Properties read(String fileName) {
        Properties result = new Properties();
        try (InputStream input = new FileInputStream(getUrl(fileName).getFile())) {
            result.load(input);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }

    // просто записали, ничего необычного
    private static URL getUrl(String fileName) {
        return PropertiesReader.class.getClassLoader().getResource(fileName);
    }

    public static void write(String fileName, Properties properties) {
        try (OutputStream output = new FileOutputStream(getUrl(fileName).getFile())) {
            properties.store(output, null);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
А вот сам пропертиз файл
jdbc.driver=org.sqlite.JDBC
url=jdbc:sqlite:resources/db.db
user=user
password=pass
Вот так конфигурим дао (в тестах)
        Properties properties = PropertiesReader.read("database.properties");
        AddressDAOImpl dao = new AddressDAOImpl(properties);
        dao.addPerson(new Person("alex", "0993527", 0));
Запрос хоть и выполняется с претензией на ассинхронность, но в данной реализации я все же фьючер дергаю сразу и жду ответа. Конечно же пул коннекшенов можно было реализовать и через синхронизированную какую-то Queue. Но я хотел поиграться с потоками. Заявкой на это был Executors.newFixedThreadPool, встречавшийся в исходном коде. Разобравшись что оно такое значит пришел к такой реализации. В общем вод реализация...
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Properties;
import java.util.concurrent.*;
import java.util.logging.Level;
import java.util.logging.Logger;

// вот тут все самое вкусное. Три часа у меня этот "красавец" забрал 
public class ConnectionPool {
    private static Logger logger = Logger.getLogger(ConnectionPool.class.getName());

    private ExecutorService executor;
    private Properties properties;

// тут методы конфигурирования. 

    public ConnectionPool(Properties properties) {
        this.properties = properties;
    }

    public static ConnectionPool with(Properties properties) {
        return new ConnectionPool(properties);
    }

    public ConnectionPool andThreads(int count) {
        // создаем екзекьютор и конфигурируем его факторей
        this.executor = Executors.newFixedThreadPool(count, new ThreadFactory() {
            @Override
            public Thread newThread(Runnable runnable) {
                // которая будет нам создавать наши треды с готовым коннекшеном (только с пылу-жару)
                // runnable тут то, что мы будем просить выполнить у екзекьютора - 
                // это будет передаваться свободному треду на выполнение
                return new WorkerThread(getConnection(), runnable);
            }
        });
        return this;
    }

// конец конфигурирования

    public <T> T query(final String message, final String query, 
                             final ConnectionRunner<T> runner) throws DAOException {
        if (executor == null) { // если вдруг забыли проконфигурить на клиенте, то сделаем это
            andThreads(1); // одного треда-коннекшена нам достаточно
        }

        // а вот тут сделаем хитрость попросим екзевьютора выполнить наш запрос 
        // завернутый в Callable, потому как Runnable нам не ок
        // по одной причине - из него не так просто вернуть результат, 
        // а Callable Как раз для этого придуман.
        Future<T> result = executor.submit(new Callable<T>() {
            @Override
            public T call() throws Exception {
                try {
                    // получаем ссылку на наш тред, из него вытаскиваем коннекшен
                    WorkerThread thread = (WorkerThread) Thread.currentThread();
                    Connection connection = thread.getConnection();
                    logger.log(Level.INFO, "Query on connection " + connection.hashCode());

                    // фигачим кверю, получаем стейтмент и передаем клиенту 
                    // в его реализацию интерфейса ConnectionRunner 
                    try (PreparedStatement statement = connection.prepareStatement(query)) {
                        return (T) runner.connect(statement);
                    }
                } catch (SQLException e) {
                    throw error(e, message);
                }
            }
        });

        // но мы получили так называемый фьючер сейчас, но результата сейчас еще может не быть в нем
        // потому если мы попросим его гет, то зависнем тут пока не получим результат. 
        // если бы мы вернули фиючер клиенту, то можно было бы сказать, 
        // что у нас запросы выполняются ассинхронно
        // но если подождем тут - то все будет синхронным
        try {
            return result.get();
        } catch (InterruptedException e) {
            throw error(e, message);
        } catch (ExecutionException e) {
            throw error(e, message);
        }
    }

    // генерим наш бизнес эксцепшен если что
    private DAOException error(Exception exception, String message) throws DAOException {
        String log = "Can not " + message;
        logger.log(Level.WARNING, log, exception);
        return new DAOException(log, exception);
    }

    // метод получения коннекшена, все просто 
    public Connection getConnection() throws DAOException {
        logger.log(Level.INFO, "Get connection!");

        try {
            return DriverManager.getConnection(
                    properties.getProperty("url"),
                    properties.getProperty("user"),
                    properties.getProperty("password"));
        } catch (SQLException e) {
            throw error(e, "get connection");
        }
    }

    // на случай, если мы захотим потушить свет и освободить все треды и их коннекшены 
    public void shutdown() {
        logger.log(Level.INFO, "Shutdown pool!");

        executor.shutdown();

        try {
            executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
        } catch (InterruptedException e) {
            throw error(e, "shutdown connection pool");
        }
    }
}
Дальше идут не сложные тред
import java.sql.Connection;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class WorkerThread extends Thread {
    private static Logger logger = Logger.getLogger(WorkerThread.class.getName());

    private Connection connection; // инкапсулируем коннекшен
    private Runnable task; // и ту часть задачи, которую хотим решить. 
    // помнишь Callable с выполнением запроса, так вот он будет внутри этого Runnable, каждый раз новый 

    public WorkerThread(Connection connection, Runnable task) {
        this.task = task;
        this.connection = connection;
    }

    @Override
    public void run() {
        logger.log(Level.INFO, "WorkerThread start task!");
        try {
            task.run(); // выполняем наш runnable и вместе с ним объявленный выше Callable
        } finally { 
            // в любом случае, что бы не случилось (отработал ли тред или 
            // поломалось че по ошибке) - нам надо закрыть коннекшен
            logger.log(Level.INFO, "WorkerThread finished task. Connection " + connection.hashCode() + " closed!");
            if (connection != null) {
                try {
                    connection.close();
                    connection = null;
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public Connection getConnection() {
        return connection;
    }
}
И интерфейс
import java.sql.PreparedStatement;
import java.sql.SQLException;

public interface ConnectionRunner<T> {
    T connect(PreparedStatement statement) throws SQLException;
}
Вот как бы и все

Комментариев нет:

Отправить комментарий