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