L2DatabaseFactory.java 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. /*
  2. * Copyright (C) 2004-2013 L2J Server
  3. *
  4. * This file is part of L2J Server.
  5. *
  6. * L2J Server is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * L2J Server is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. package com.l2jserver;
  20. import java.sql.Connection;
  21. import java.sql.PreparedStatement;
  22. import java.sql.SQLException;
  23. import java.util.concurrent.Executors;
  24. import java.util.concurrent.ScheduledExecutorService;
  25. import java.util.concurrent.TimeUnit;
  26. import java.util.logging.Level;
  27. import java.util.logging.Logger;
  28. import com.l2jserver.gameserver.ThreadPoolManager;
  29. import com.mchange.v2.c3p0.ComboPooledDataSource;
  30. /**
  31. * This class manages the database connections.
  32. */
  33. public class L2DatabaseFactory
  34. {
  35. private static final Logger _log = Logger.getLogger(L2DatabaseFactory.class.getName());
  36. /**
  37. * The Enum ProviderType.
  38. */
  39. private static enum ProviderType
  40. {
  41. MySql,
  42. MsSql
  43. }
  44. private static L2DatabaseFactory _instance;
  45. private static volatile ScheduledExecutorService _executor;
  46. private ProviderType _providerType;
  47. private ComboPooledDataSource _source;
  48. /**
  49. * Instantiates a new l2 database factory.
  50. * @throws SQLException the SQL exception
  51. */
  52. public L2DatabaseFactory() throws SQLException
  53. {
  54. try
  55. {
  56. if (Config.DATABASE_MAX_CONNECTIONS < 2)
  57. {
  58. Config.DATABASE_MAX_CONNECTIONS = 2;
  59. _log.warning("A minimum of " + Config.DATABASE_MAX_CONNECTIONS + " db connections are required.");
  60. }
  61. _source = new ComboPooledDataSource();
  62. _source.setAutoCommitOnClose(true);
  63. _source.setInitialPoolSize(10);
  64. _source.setMinPoolSize(10);
  65. _source.setMaxPoolSize(Math.max(10, Config.DATABASE_MAX_CONNECTIONS));
  66. _source.setAcquireRetryAttempts(0); // try to obtain connections indefinitely (0 = never quit)
  67. _source.setAcquireRetryDelay(500); // 500 milliseconds wait before try to acquire connection again
  68. _source.setCheckoutTimeout(0); // 0 = wait indefinitely for new connection
  69. // if pool is exhausted
  70. _source.setAcquireIncrement(5); // if pool is exhausted, get 5 more connections at a time
  71. // cause there is a "long" delay on acquire connection
  72. // so taking more than one connection at once will make connection pooling
  73. // more effective.
  74. // this "connection_test_table" is automatically created if not already there
  75. _source.setAutomaticTestTable("connection_test_table");
  76. _source.setTestConnectionOnCheckin(false);
  77. // testing OnCheckin used with IdleConnectionTestPeriod is faster than testing on checkout
  78. _source.setIdleConnectionTestPeriod(3600); // test idle connection every 60 sec
  79. _source.setMaxIdleTime(Config.DATABASE_MAX_IDLE_TIME); // 0 = idle connections never expire
  80. // *THANKS* to connection testing configured above
  81. // but I prefer to disconnect all connections not used
  82. // for more than 1 hour
  83. // enables statement caching, there is a "semi-bug" in c3p0 0.9.0 but in 0.9.0.2 and later it's fixed
  84. _source.setMaxStatementsPerConnection(100);
  85. _source.setBreakAfterAcquireFailure(false); // never fail if any way possible
  86. // setting this to true will make
  87. // c3p0 "crash" and refuse to work
  88. // till restart thus making acquire
  89. // errors "FATAL" ... we don't want that
  90. // it should be possible to recover
  91. _source.setDriverClass(Config.DATABASE_DRIVER);
  92. _source.setJdbcUrl(Config.DATABASE_URL);
  93. _source.setUser(Config.DATABASE_LOGIN);
  94. _source.setPassword(Config.DATABASE_PASSWORD);
  95. /* Test the connection */
  96. _source.getConnection().close();
  97. if (Config.DEBUG)
  98. {
  99. _log.fine("Database Connection Working");
  100. }
  101. if (Config.DATABASE_DRIVER.toLowerCase().contains("microsoft"))
  102. {
  103. _providerType = ProviderType.MsSql;
  104. }
  105. else
  106. {
  107. _providerType = ProviderType.MySql;
  108. }
  109. }
  110. catch (SQLException x)
  111. {
  112. if (Config.DEBUG)
  113. {
  114. _log.fine("Database Connection FAILED");
  115. }
  116. // re-throw the exception
  117. throw x;
  118. }
  119. catch (Exception e)
  120. {
  121. if (Config.DEBUG)
  122. {
  123. _log.fine("Database Connection FAILED");
  124. }
  125. throw new SQLException("Could not init DB connection:" + e.getMessage());
  126. }
  127. }
  128. /**
  129. * Prepared query select.
  130. * @param fields the fields
  131. * @param tableName the table name
  132. * @param whereClause the where clause
  133. * @param returnOnlyTopRecord the return only top record
  134. * @return the string
  135. */
  136. public final String prepQuerySelect(String[] fields, String tableName, String whereClause, boolean returnOnlyTopRecord)
  137. {
  138. String msSqlTop1 = "";
  139. String mySqlTop1 = "";
  140. if (returnOnlyTopRecord)
  141. {
  142. if (getProviderType() == ProviderType.MsSql)
  143. {
  144. msSqlTop1 = " Top 1 ";
  145. }
  146. if (getProviderType() == ProviderType.MySql)
  147. {
  148. mySqlTop1 = " Limit 1 ";
  149. }
  150. }
  151. String query = "SELECT " + msSqlTop1 + safetyString(fields) + " FROM " + tableName + " WHERE " + whereClause + mySqlTop1;
  152. return query;
  153. }
  154. /**
  155. * Shutdown.
  156. */
  157. public void shutdown()
  158. {
  159. try
  160. {
  161. _source.close();
  162. }
  163. catch (Exception e)
  164. {
  165. _log.log(Level.INFO, "", e);
  166. }
  167. try
  168. {
  169. _source = null;
  170. }
  171. catch (Exception e)
  172. {
  173. _log.log(Level.INFO, "", e);
  174. }
  175. }
  176. /**
  177. * Safety string.
  178. * @param whatToCheck the what to check
  179. * @return the string
  180. */
  181. public final String safetyString(String... whatToCheck)
  182. {
  183. // NOTE: Use brace as a safety precaution just in case name is a reserved word
  184. final char braceLeft;
  185. final char braceRight;
  186. if (getProviderType() == ProviderType.MsSql)
  187. {
  188. braceLeft = '[';
  189. braceRight = ']';
  190. }
  191. else
  192. {
  193. braceLeft = '`';
  194. braceRight = '`';
  195. }
  196. int length = 0;
  197. for (String word : whatToCheck)
  198. {
  199. length += word.length() + 4;
  200. }
  201. final StringBuilder sbResult = new StringBuilder(length);
  202. for (String word : whatToCheck)
  203. {
  204. if (sbResult.length() > 0)
  205. {
  206. sbResult.append(", ");
  207. }
  208. sbResult.append(braceLeft);
  209. sbResult.append(word);
  210. sbResult.append(braceRight);
  211. }
  212. return sbResult.toString();
  213. }
  214. /**
  215. * Gets the single instance of L2DatabaseFactory.
  216. * @return single instance of L2DatabaseFactory
  217. * @throws SQLException the SQL exception
  218. */
  219. public static L2DatabaseFactory getInstance() throws SQLException
  220. {
  221. synchronized (L2DatabaseFactory.class)
  222. {
  223. if (_instance == null)
  224. {
  225. _instance = new L2DatabaseFactory();
  226. }
  227. }
  228. return _instance;
  229. }
  230. /**
  231. * Gets the connection.
  232. * @return the connection
  233. */
  234. public Connection getConnection()
  235. {
  236. Connection con = null;
  237. while (con == null)
  238. {
  239. try
  240. {
  241. con = _source.getConnection();
  242. if (Server.serverMode == Server.MODE_GAMESERVER)
  243. {
  244. ThreadPoolManager.getInstance().scheduleGeneral(new ConnectionCloser(con, new RuntimeException()), Config.CONNECTION_CLOSE_TIME);
  245. }
  246. else
  247. {
  248. getExecutor().schedule(new ConnectionCloser(con, new RuntimeException()), Config.CONNECTION_CLOSE_TIME, TimeUnit.SECONDS);
  249. }
  250. }
  251. catch (SQLException e)
  252. {
  253. _log.log(Level.WARNING, "L2DatabaseFactory: getConnection() failed, trying again " + e.getMessage(), e);
  254. }
  255. }
  256. return con;
  257. }
  258. /**
  259. * The Class ConnectionCloser.
  260. */
  261. private static class ConnectionCloser implements Runnable
  262. {
  263. private static final Logger _log = Logger.getLogger(ConnectionCloser.class.getName());
  264. /** The connection. */
  265. private final Connection c;
  266. /** The exception. */
  267. private final RuntimeException exp;
  268. /**
  269. * Instantiates a new connection closer.
  270. * @param con the con
  271. * @param e the e
  272. */
  273. public ConnectionCloser(Connection con, RuntimeException e)
  274. {
  275. c = con;
  276. exp = e;
  277. }
  278. @Override
  279. public void run()
  280. {
  281. try
  282. {
  283. if (!c.isClosed())
  284. {
  285. _log.log(Level.WARNING, "Unclosed connection! Trace: " + exp.getStackTrace()[1], exp);
  286. }
  287. }
  288. catch (SQLException e)
  289. {
  290. _log.log(Level.WARNING, "", e);
  291. }
  292. }
  293. }
  294. /**
  295. * Gets the executor.
  296. * @return the executor
  297. */
  298. private static ScheduledExecutorService getExecutor()
  299. {
  300. if (_executor == null)
  301. {
  302. synchronized (L2DatabaseFactory.class)
  303. {
  304. if (_executor == null)
  305. {
  306. _executor = Executors.newSingleThreadScheduledExecutor();
  307. }
  308. }
  309. }
  310. return _executor;
  311. }
  312. /**
  313. * Gets the busy connection count.
  314. * @return the busy connection count
  315. * @throws SQLException the SQL exception
  316. */
  317. public int getBusyConnectionCount() throws SQLException
  318. {
  319. return _source.getNumBusyConnectionsDefaultUser();
  320. }
  321. /**
  322. * Gets the idle connection count.
  323. * @return the idle connection count
  324. * @throws SQLException the SQL exception
  325. */
  326. public int getIdleConnectionCount() throws SQLException
  327. {
  328. return _source.getNumIdleConnectionsDefaultUser();
  329. }
  330. /**
  331. * Gets the provider type.
  332. * @return the provider type
  333. */
  334. public final ProviderType getProviderType()
  335. {
  336. return _providerType;
  337. }
  338. /**
  339. * Designed to execute simple db operations like INSERT, UPDATE, DELETE.
  340. * @param query
  341. * @param params
  342. * @throws SQLException
  343. */
  344. public void executeQuery(String query, Object... params) throws SQLException
  345. {
  346. try (Connection con = getConnection();
  347. PreparedStatement st = con.prepareStatement(query))
  348. {
  349. for (int i = 0; i < params.length; i++)
  350. {
  351. st.setObject(i + 1, params[i]);
  352. }
  353. st.execute();
  354. }
  355. }
  356. }