Впервые столкнулся с такой задачей, реализовать многопоточное приложение с использованием БД но без высокоуровневых библиотек типа Hibernate. Короче онли JDBC.
Суть игры: есть объект Clan который вступает в битву с другими кланами и в зависимости от результата меняется состояние его казны. Вместе с этим в БД сохраняется объект Event который хранит информацию о том кто с кем бился, чем закончилось и т.д.
Для начала я реализовал репозиторий клана:
public class ClanRepository implements TestTaskRepository<Clan> {
private static Connection connection = ConnectionManager.open();
private static ResultSetConverter resultSetConverter = new ResultSetConverter();
private static Lock lock = new ReentrantLock();
@Override
public Clan findById(Long id) {
lock.lock();
try {
Clan clan = null;
try (PreparedStatement preparedStatement =
connection.prepareStatement("SELECT * FROM clans WHERE clan_id=?")) {
preparedStatement.setLong(1, id);
ResultSet resultSet = preparedStatement.executeQuery();
resultSet.next();
clan = resultSetConverter.getClanFromResultSet(resultSet);
} catch (SQLException e) {
e.printStackTrace();
}
return clan;
} finally {
lock.unlock();
}
}
@Override
public void update(Clan clan) {
lock.lock();
try (PreparedStatement preparedStatement =
connection.prepareStatement("UPDATE clans SET clan_name =?, clan_gold=? WHERE clan_id=?")) {
preparedStatement.setString(1, clan.getName());
preparedStatement.setInt(2, clan.getGold());
preparedStatement.setLong(3, clan.getId());
preparedStatement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
Расставил lock в надежде что это что то изменит и написал класс поток в котором происходит битва:
@Override
public void run() {
Random random = new Random();
boolean result = random.nextBoolean();
Clan currentClan = clanRepository.findById(CurrentUser.getClan().getId());
Clan enemyClan = clanRepository.findById(choiceOfEnemy().getClan().getId());
if (result) {
currentClan.setGold(currentClan.getGold + 100);
enemyClan.setGold(enemyClan.getGold - 100);
} else {
currentClan.setGold(currentClan.getGold - 100);
enemyClan.setGold(enemyClan.getGold + 100);
}
clanRepository.update(currentClan);
clanRepository.update(enemyClan);
}
Пока поток один всё работает как швейцарские часы. При подключении второго или более в БД сохраняется чёрти что.
Я решил что дело в том что существует временной промежуток между получением кланов и сохранением их в изменённом состоянии. В связи с этим я написал класс ActionServise где собрал всё в один метод и код этого метода взял в lock(), unlock().
public void action() {
lock.lock();
try {
Random random = new Random();
boolean result = random.nextBoolean();
Clan currentClan = clanRepository.findById(CurrentUser.getClan().getId());
Clan enemyClan = clanRepository.findById(choiceOfEnemy().getClan().getId());
if (result) {
currentClan.setGold(currentClan.getGold + 100);
enemyClan.setGold(enemyClan.getGold - 100);
} else {
currentClan.setGold(currentClan.getGold - 100);
enemyClan.setGold(enemyClan.getGold + 100);
}
clanRepository.update(currentClan);
clanRepository.update(enemyClan);
} finally {
lock.unlock();
}
В методе run() я оставил только вызов данного метода action()
В результате этого не корректные данные стали сохраняться не на каждой первой строчке, а примерно на каждой пятой, что меня тоже не устраивает.
Помимо прочего, даже если бы это решение работало оно представляет собой такую точку в которой все потоки встанут в очередь и никакого параллельного хода битв не получится (прошу поправить если это не верно)
Потом я нагуглил в интернетах что проблема в соединении, в том что оно одно на все потоки. Я попробовал создавать connection для каждого потока и передавать по цепочке до самого репозитория. Не помогло.
Учитывая возраст технологий о которых идёт речь я уверен что моя проблема давно решена, Подскажите пожалуйста как это делается.