Есть у нас 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;
}
Вот как бы и все

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