Кирилл Данилов (donz_ru) wrote,
Кирилл Данилов
donz_ru

Category:

Одно из решений проблемы с wait_timeout у MySQL

Используем Tomcat, датасорсы настраиваются как бины в контексте томката. Пул соединений — apache commons-db.
В сети нашел, как справится с wait_timeout для встроенного Hibernate'овского пула C3P0. Но его не советуют использовать даже сами авторы хибернейта.
Для апачевского пула есть возможность указать validationQuery в качестве параметра к датасорсу. Но этот запрос, пусть даже select 1, будет выполняться при каждом запросе коннекта из пула соединений. Короче, накладно. Решил слабать свой класс, который после некоторого времени простоя пробегается по всем JNDI бинам, и для каждого найденного мускульного датасорса делает этот самый select 1. Вызов статического метода делается один раз за пользовательскую сессию, например, при логине пользователя в систему.
Есть советы/другие варианты?








MySQLConnectionReviver.java



 
3    import org.apache.commons.dbcp.BasicDataSource; 
4     
5    import javax.naming.*; 
6    import java.sql.Connection; 
7    import java.sql.SQLException; 
8    import java.sql.Statement; 
9    import java.sql.ResultSet; 
10    
11   /** 
12    * Класс для оживления коннектов к базам MySQL. Получает все JNDI бины, смотрит, кто из них MySQL'ный датасорс и делает в него select, 
13    * если прошло более заданного времени 
14    * 
15    * @author danilov 
16    */ 
17   public class MySQLConnectionReviver 
18   { 
19     /** 
20      * Максимальное время, в течении которого не надо оживлять коннекты к мускульным базам. Указано в секундах 
21      */ 
22     public static final long MAX_IDLE_TIME = 7L * 60 * 60; 
23    
24     /** 
25      * Имя JNDI контекста, в котором содержатся бины датасорсов 
26      */ 
27     private static final String JNDI_CONTEXT = "java:comp/env/jdbc"; 
28     /** 
29      * Идентификатор класса jdbc драйвера Mysql 
30      */ 
31     private static final String MYSQL_DRIVER_ID = "com.mysql.jdbc.Driver"; 
32    
33     /** 
34      * Значение, которое в тестовом запросе будет запрошено из БД 
35      */ 
36     private static final int RECONNECT_VALUE_TO_READ = 1; 
37     /** 
38      * Запрос, который будет выполняться для реконнекта 
39      */ 
40     private static final String RECONNECT_SQL_QUERY = "select " + RECONNECT_VALUE_TO_READ; 
41     /** 
42      * Время последнего вызова оживителя мускульных коннектов 
43      */ 
44     private static long lastReconnection = 0;
45 46
/** 47 * Ищет все доступные JNDI-бины для контекста JNDI_CONTEXT, и для бинов-датасорсов MySQL делает запрос RECONNECT_SQL_QUERY, если прошло 48 * более MAX_IDLE_TIME секунд с последнего вызова этого метода. Предполагается, что после вызова этого метода в течении пары десятков 49 * минут будет сделан какой-либо запрос к мускульным базам. Поэтому если по времени запрос делать в самом методе рано, решаем, что этот 50 * запрос, который продлит таймаут соединения еще на восемь часов (или сколько установлено в БД), неявно сделает пользователь через 51 * запросы страниц 52 * 53 * @throws SQLException при ошибке соединения с базой 54 * @throws NamingException при ошибке доступа к JNDI-бинам 55 */ 56 public static void reconnectAll() throws SQLException, NamingException 57 { 58 if( lastReconnection == 0 || System.nanoTime() - lastReconnection > MAX_IDLE_TIME * 1000 * 1000 * 1000 ) 59 { 60 Context ic = new InitialContext(); 61 62 NamingEnumeration<Binding> ne = ic.listBindings( JNDI_CONTEXT ); 63 while( ne.hasMore() ) 64 { 65 Object obj = ne.next().getObject(); 66 //Затачиваемся на то, что используется Apache commons-dbcp. Томкат по умолчанию использует какой-то свой пул, возможно 67 // скопированный из стандартного commons-dbcp, но с 68 if( obj instanceof BasicDataSource ) 69 { 70 BasicDataSource bds = ( BasicDataSource ) obj; 71 if( MYSQL_DRIVER_ID.equals( bds.getDriverClassName() ) ) 72 { 73 makeReviveConnection( bds ); 74 } 75 } 76 } 77 } 78 lastReconnection = System.nanoTime(); 79 }
80 81
/** 82 * Выполняет сам запрос для продления времени жизни коннекта 83 * 84 * @param bds датасорс, для которого надо сделать оживительный запрос 85 * @throws SQLException при ошибке работы с БД 86 */ 87 private static void makeReviveConnection( BasicDataSource bds ) throws SQLException 88 { 89 Connection conn = null; 90 try 91 { 92 conn = bds.getConnection(); 93 Statement st = null; 94 ResultSet rs = null; 95 try 96 { 97 st = conn.createStatement(); 98 st.execute( RECONNECT_SQL_QUERY ); 99 rs = st.getResultSet(); 100 rs.first(); 101 Number value = ( Number ) rs.getObject( 1 ); 102 if( value == null || value.intValue() != RECONNECT_VALUE_TO_READ ) 103 { 104 //Если дошли до получения резалтсета, то исключений уже быть не должно, так что и это будет проигнорировано, чтобы 105 //не морочить пользователю голову с исключениями еще до начала работы. Возможно, после выполнения этих запросов, ошибок 106 // больше не будет, так как датасорсы и его коннекты "раскачаются" 107 throw new SQLException( 108 "Wrong ResultSet. Query was: " + RECONNECT_SQL_QUERY + ". Returned value is " + ( value == null ? "null" : value ) ); 109 } 110 System.out.println( "Connection to " + bds.getUrl() + " with query \"" + RECONNECT_SQL_QUERY + "\" is SUCCESSFUL" ); 111 } 112 catch( SQLException ignored ) 113 { 114 //По идее исключение может быть, только если коннект был разорван 115 ignored.printStackTrace(); 116 } 117 finally 118 { 119 if( st != null ) 120 { 121 st.close(); 122 if( rs != null ) 123 { 124 rs.close(); 125 } 126 } 127 } 128 } 129 finally 130 { 131 if( conn != null && !conn.isClosed() ) 132 { 133 conn.close(); 134 } 135 } 136 } 137 } 138




Tags: java, mysql
Subscribe

  • Post a new comment

    Error

    default userpic

    Your IP address will be recorded 

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 0 comments