Skip to content
This repository has been archived by the owner on Apr 16, 2023. It is now read-only.

Commit

Permalink
Merge pull request #126 from jeakfrw/fix/db-query-performance
Browse files Browse the repository at this point in the history
Fix/db query performance
  • Loading branch information
MarkL4YG committed Nov 14, 2019
2 parents 232f85a + 60ad8ed commit bfb1806
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 40 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
group 'de.fearnixx'
version '1.0.3'
version '1.0.4'
def projectVersion = project.version

apply plugin: 'java'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,23 @@
import javax.persistence.PersistenceException;
import javax.sql.DataSource;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

public class HHPersistenceUnit extends Configurable implements IPersistenceUnit, AutoCloseable {

private static final Logger logger = LoggerFactory.getLogger(HHPersistenceUnit.class);
private static final String DEFAULT_DATASOURCE_CONFIG = "/database/defaultDS.json";

private static final Map<String, Method> HIKARI_CONF_SETTER = new HashMap<>();

static {
Arrays.stream(HikariConfig.class.getDeclaredMethods())
.filter(m -> m.getName().startsWith("set"))
.forEach(m -> HIKARI_CONF_SETTER.put(m.getName(), m));
}

private final Map<String, String> dataSourceOpts = new HashMap<>();
private final List<EntityManager> entityManagers = new LinkedList<>();
private final IConfig config;
Expand Down Expand Up @@ -98,9 +105,14 @@ private void readConfiguration() {
getConfig().getNode("dataSourceOpts")
.optMap()
.ifPresent(map ->
map.forEach((key, value) ->
value.optString().ifPresent(strVal ->
dataSourceOpts.put(key, strVal))));
map.forEach((key, value) -> {
Optional<String> optVal = value.optString();
if (optVal.isEmpty()) {
logger.error("Cannot set DS property that is not a string! {} => {}", unitId, key);
} else {
dataSourceOpts.put(key, value.asString());
}
}));
}

private boolean initializeHikariSource() {
Expand All @@ -109,8 +121,8 @@ private boolean initializeHikariSource() {
hikariConfig.setUsername(getUsername());
hikariConfig.setPassword(getPassword());

dataSourceOpts.forEach(hikariConfig::addDataSourceProperty);
try {
dataSourceOpts.forEach((key, value) -> hardSetDSProperty(key, value, hikariConfig));
hikariDS = new HikariDataSource(hikariConfig);
return true;
} catch (HikariPool.PoolInitializationException e) {
Expand All @@ -119,6 +131,37 @@ private boolean initializeHikariSource() {
}
}

private void hardSetDSProperty(String key, String value, HikariConfig hikariConfig) {
String methodName = "set" + key.substring(0, 1).toUpperCase() + key.substring(1);
Method setter = HIKARI_CONF_SETTER.getOrDefault(methodName, null);
if (setter == null) {
NoSuchMethodException iA = new NoSuchMethodException("Hikari does not support setting the dataSource property: " + key);
throw new HikariPool.PoolInitializationException(iA);
}

try {
Class<?> paramType = setter.getParameterTypes()[0];
Object param = null;

if (paramType.isAssignableFrom(Integer.class) || paramType.equals(int.class)) {
param = Integer.parseInt(value);
} else if (paramType.isAssignableFrom(Long.class) || paramType.equals(long.class)) {
param = Long.parseLong(value);
} else if (paramType.isAssignableFrom(String.class)) {
param = value;
} else if (paramType.isAssignableFrom(Boolean.class) || paramType.equals(boolean.class)) {
param = "true".equalsIgnoreCase(value) || "1".equals(value);
}

logger.info("Setting bean property \"{}\" -> \"{}\" on HikariConfig of \"{}\"", key, value, unitId);
setter.invoke(hikariConfig, param);
hikariConfig.addDataSourceProperty(key, value);
} catch (IllegalAccessException | InvocationTargetException | NumberFormatException e) {
Throwable cause = e instanceof InvocationTargetException ? e.getCause() : e;
throw new HikariPool.PoolInitializationException(cause);
}
}

private boolean initializeHibernateServices() {
try {
StandardServiceRegistryBuilder registryBuilder = new StandardServiceRegistryBuilder(baseRegistry);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@

public abstract class AbstractTS3PermissionProvider implements ITS3PermissionProvider {

private static final Logger logger = LoggerFactory.getLogger(AbstractTS3PermissionProvider.class);

private final PermIdCache permIdCache = new PermIdCache();

@Inject
Expand All @@ -41,9 +39,6 @@ protected IServer getServer() {
return server;
}

@Override
public abstract void clearCache(ITS3Permission.PriorityType type, Integer optClientOrGroupID, Integer optChannelID);

@Override
public Optional<ITS3Permission> getActivePermission(String permSID, String clientTS3UniqueID) {
Optional<IClient> optClient = userService.findClientByUniqueID(clientTS3UniqueID)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.slf4j.LoggerFactory;

import javax.persistence.PersistenceUnit;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
Expand Down Expand Up @@ -57,30 +58,31 @@ private Optional<ITS3Permission> selectFromTable(Integer idOne, String permSID,
sql += " AND id2 = ?";
}

try (PreparedStatement statement = persistenceUnit.getDataSource()
.getConnection()
.prepareStatement(sql)) {
statement.setInt(1, getServerId());
statement.setString(2, permSID);
statement.setInt(3, idOne);
if (idTwo != null) {
statement.setInt(4, idTwo);
try (Connection dbConnection = persistenceUnit.getDataSource().getConnection()) {
try (PreparedStatement statement = dbConnection
.prepareStatement(sql)) {
statement.setInt(1, getServerId());
statement.setString(2, permSID);
statement.setInt(3, idOne);
if (idTwo != null) {
statement.setInt(4, idTwo);
}

TS3Permission perm;
try (ResultSet res = statement.executeQuery()) {
perm = new TS3Permission(ITS3Permission.PriorityType.CLIENT, permSID);
if (res.next()) {
perm.setValue(res.getInt(1));
perm.setNegated(res.getInt(2) == 1);
perm.setSkipped(res.getInt(3) == 1);
} else {
perm.setValue(0);
perm.setNegated(false);
perm.setSkipped(false);
}
}
return Optional.of(perm);
}

ResultSet res = statement.executeQuery();
TS3Permission perm = new TS3Permission(ITS3Permission.PriorityType.CLIENT, permSID);
if (res.isBeforeFirst()) {
res.next();
perm.setValue(res.getInt(1));
perm.setNegated(res.getInt(2) == 1);
perm.setSkipped(res.getInt(3) == 1);
} else {
perm.setValue(0);
perm.setNegated(false);
perm.setSkipped(false);
}
return Optional.of(perm);

} catch (SQLException e) {
logger.error("Failed to get permission value from database! {} for {}", permSID, idOne, e);
return Optional.empty();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package de.fearnixx.jeak.service.teamspeak;

import de.fearnixx.jeak.teamspeak.data.IUser;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

public class CachedUserResult {

private final List<IUser> users;
private final LocalDateTime expiry;

public CachedUserResult(List<IUser> users, LocalDateTime expiry) {
this.users = users;
this.expiry = expiry;
}

public List<IUser> getUsers() {
return new ArrayList<>(users);
}

public LocalDateTime getExpiry() {
return expiry;
}
}
41 changes: 38 additions & 3 deletions src/main/java/de/fearnixx/jeak/service/teamspeak/UserService.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package de.fearnixx.jeak.service.teamspeak;

import de.fearnixx.jeak.Main;
import de.fearnixx.jeak.event.bot.IBotStateEvent;
import de.fearnixx.jeak.reflect.FrameworkService;
import de.fearnixx.jeak.reflect.IInjectionService;
Expand All @@ -13,8 +14,12 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Predicate;
import java.util.function.Supplier;

/**
* Small loader around the user service that decides whether or not to use direct DB access or queries.
Expand All @@ -23,6 +28,7 @@
public class UserService implements IUserService {

public static final String PERSISTENCE_UNIT_NAME = "teamspeak";
private static final int USR_CACHE_TTL = Main.getProperty("jeak.cache.keepUsersSecs", 30);
private static final Logger logger = LoggerFactory.getLogger(UserService.class);

@Inject
Expand All @@ -34,6 +40,8 @@ public class UserService implements IUserService {
@Inject
private IInjectionService injectionService;

private final List<CachedUserResult> userCache = new CopyOnWriteArrayList<>();

private IUserService serviceImplementation;

@Listener
Expand All @@ -54,17 +62,44 @@ public void onPreInitialize(IBotStateEvent.IPreInitializeEvent event) {

@Override
public List<IUser> findUserByUniqueID(String ts3uniqueID) {
return serviceImplementation.findUserByUniqueID(ts3uniqueID);
return computeIfNotCached(
u -> u.getClientUniqueID().equals(ts3uniqueID),
() -> serviceImplementation.findUserByUniqueID(ts3uniqueID)
);
}

private List<IUser> computeIfNotCached(Predicate<IUser> matchBy, Supplier<List<IUser>> getBy) {
synchronized (userCache) {
final LocalDateTime now = LocalDateTime.now();
userCache.removeIf(entry -> entry.getExpiry().isBefore(now));
Optional<CachedUserResult> optResult = userCache.stream()
.filter(entry -> entry.getUsers().stream().allMatch(matchBy))
.findFirst();
return optResult.map(CachedUserResult::getUsers)
.orElseGet(() -> {
List<IUser> users = getBy.get();
CachedUserResult result =
new CachedUserResult(users, LocalDateTime.now().plusSeconds(USR_CACHE_TTL));
userCache.add(result);
return result.getUsers();
});
}
}

@Override
public List<IUser> findUserByDBID(int ts3dbID) {
return serviceImplementation.findUserByDBID(ts3dbID);
return computeIfNotCached(
u -> u.getClientDBID().equals(ts3dbID),
() -> serviceImplementation.findUserByDBID(ts3dbID)
);
}

@Override
public List<IUser> findUserByNickname(String ts3nickname) {
return serviceImplementation.findUserByNickname(ts3nickname);
return computeIfNotCached(
u -> u.getNickName().contains(ts3nickname),
() -> serviceImplementation.findUserByNickname(ts3nickname)
);
}

@Override
Expand Down

0 comments on commit bfb1806

Please sign in to comment.