2009-09-19 6 views
2

J'ai récemment écrit et déployé une application Web Java sur un serveur et je trouve un problème inhabituel qui n'apparaissait pas lors du développement ou du test.Mise en commun de connexions avec Java et MySQL dans l'application Web Tomcat

Lorsqu'un utilisateur se connecte après si longtemps et affiche des données de la base de données, la page indique qu'il n'y a aucun enregistrement à afficher. Mais lors de l'actualisation de la page, les premiers enregistrements x sont affichés conformément aux règles de pagination.

Vérification des journaux, je trouve:

ERROR|19 09 2009|09 28 54|http-8080-4|myDataSharer.database_access.Database_Metadata_DBA| - Error getting types of columns of tabular Dataset 12 

com.mysql.jdbc.CommunicationsException: Communications link failure due to underlying exception: 

** BEGIN NESTED EXCEPTION ** 

java.io.EOFException 

STACKTRACE: 

java.io.EOFException 
    at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:1956) 
    at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:2368) 
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:2867) 
    at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1616) 

Et ainsi de suite pour plusieurs centaines de lignes.

L'application est actuellement définie pour environ 100 utilisateurs mais n'est pas encore pleinement utilisée. Il utilise la mise en commun de connexion entre les servlets Apache Tomcat/JSP et une base de données MySQL avec l'exemple de code suivant la formation de la disposition générale d'une opération de base de données, dont il existe généralement plusieurs par page:

// Gets a Dataset. 
public static Dataset getDataset(int DatasetNo) { 
    ConnectionPool_DBA pool = ConnectionPool_DBA.getInstance(); 
    Connection connection = pool.getConnection(); 
    PreparedStatement ps = null; 
    ResultSet rs = null; 

    String query = ("SELECT * " + 
        "FROM Dataset " + 
        "WHERE DatasetNo = ?;"); 

    try { 
     ps = connection.prepareStatement(query); 
     ps.setInt(1, DatasetNo); 
     rs = ps.executeQuery(); 
     if (rs.next()) { 
      Dataset d = new Dataset(); 
      d.setDatasetNo(rs.getInt("DatasetNo")); 
      d.setDatasetName(rs.getString("DatasetName")); 
      ... 

      } 

      return d; 
     } 
     else { 
      return null; 
     } 
    } 
    catch(Exception ex) { 
     logger.error("Error getting Dataset " + DatasetNo + "\n", ex);    
     return null; 
    } 
    finally { 
     DatabaseUtils.closeResultSet(rs); 
     DatabaseUtils.closePreparedStatement(ps); 
     pool.freeConnection(connection); 
    } 
} 

t-il quelqu'un capable de conseiller un moyen de corriger ce problème? Je crois que cela est dû au fait que MySQL laisse les connexions d'interrogation de connexion ouvertes jusqu'à huit heures mais je ne suis pas certain.

Merci

Martin O'Shea.


Juste pour clarifier un point fait à propos de ma méthode de mise en commun de connexion, il est Oracle que j'utilise dans ma demande, mais une classe de mon propre comme suit:

package myDataSharer.database_access; 

import java.sql.*; 
import javax.sql.DataSource; 
import javax.naming.InitialContext; 
import org.apache.log4j.Logger; 

public class ConnectionPool_DBA { 

    static Logger logger = Logger.getLogger(ConnectionPool_DBA.class.getName()); 

    private static ConnectionPool_DBA pool = null; 
    private static DataSource dataSource = null; 


    public synchronized static ConnectionPool_DBA getInstance() { 
     if (pool == null) { 
      pool = new ConnectionPool_DBA(); 
     } 
     return pool; 
    } 

    private ConnectionPool_DBA() { 
     try { 
      InitialContext ic = new InitialContext(); 
      dataSource = (DataSource) ic.lookup("java:/comp/env/jdbc/myDataSharer"); 
     } 
     catch(Exception ex) { 
      logger.error("Error getting a connection pool's datasource\n", ex); 
     } 
    } 

    public void freeConnection(Connection c) { 
     try { 
      c.close(); 
     } 
     catch (Exception ex) { 
      logger.error("Error terminating a connection pool connection\n", ex);   
     } 
    } 

    public Connection getConnection() { 
     try { 
      return dataSource.getConnection(); 
     } 
     catch (Exception ex) { 
      logger.error("Error getting a connection pool connection\n", ex);    
      return null; 
     } 
    }  
} 

I pense que la mention d'Oracle est due à moi en utilisant un nom similaire.

+0

Pouvez-vous expliquer comment vous êtes arrivé à la conclusion que cela est dû à une erreur dans le regroupement de connexions? Je ne vois aucune indication de cela n'importe où ... –

Répondre

3

Je me demande pourquoi vous utilisez ConnectionPool_DBA dans votre code au lieu de laisser Tomcat gérer le regroupement et simplement rechercher la connexion en utilisant JNDI. Pourquoi utilisez-vous un pool de connexion Oracle avec MySQL? Lorsque je fais des recherches JNDI et la mise en commun de connexions, je préfère la bibliothèque Apache DBCP. Je trouve que cela fonctionne très bien.

Je demanderais également si vos méthodes DatabaseUtils lèvent des exceptions, car si l'un des appels avant votre appel à pool.freeConnection() en lance un, vous ne libérerez jamais cette connexion. Je n'aime pas beaucoup votre code parce qu'une classe qui exécute des opérations SQL devrait avoir son instance de connexion passée, et ne devrait pas avoir la double responsabilité d'acquérir et d'utiliser la connexion. Une classe de persistance ne peut pas savoir si elle est utilisée dans une transaction plus importante. Mieux vaut avoir une couche de service distincte qui acquiert la connexion, gère la transaction, rassemble les classes de persistance et nettoie quand elle est terminée.

MISE À JOUR:

Google relevai la classe Oracle avec le même nom que le vôtre. Maintenant, je n'aime vraiment pas votre code, parce que vous avez écrit quelque chose de votre propre quand une meilleure alternative était facilement disponible. Je vais abandonner le vôtre tout de suite et refaire cela en utilisant DBCP et JNDI.

+0

C'est probablement improbable en raison du temps et d'autres contraintes. Merci quand même. –

+0

Pourrait devenir plus attrayant si vous ne pouvez pas résoudre ce problème et que cela se reproduise. – duffymo

4

Il existe quelques pointeurs permettant d'éviter cette situation, obtenus à partir d'autres sources, notamment à partir des implémentations du pool de connexions d'autres pilotes et d'autres serveurs d'applications. Certaines informations sont déjà disponibles dans la documentation Tomcat sur les sources de données JNDI. Établir un calendrier de nettoyage/de récolte qui fermera les connexions dans le bassin, si elles sont inactives au-delà d'une certaine période. Ce n'est pas une bonne pratique de laisser une connexion à la base de données ouverte pendant 8 heures (la valeur par défaut de MySQL). Sur la plupart des serveurs d'applications, la valeur de délai de connexion inactive est configurable et est généralement inférieure à 15 minutes (les connexions ne peuvent pas rester plus de 15 minutes dans le pool sauf si elles sont réutilisées à maintes reprises). Dans Tomcat, lorsque vous utilisez une source de données JNDI, use the removeAbandoned and removeAbandonedTimeout settings pour faire la même chose.

  • Lorsqu'une nouvelle connexion est renvoyée du pool vers l'application, vérifiez qu'elle a été testée en premier. Par exemple, la plupart des serveurs d'applications que je connais peuvent être configurés pour que la connexion à une base de données Oracle soit testée avec une exécution de "SELECT 1 FROM dual". Dans Tomcat, utilisez la propriété validationQuery pour définir la requête appropriée pour MySQL. Je crois que c'est "SELECT 1" (sans les guillemets). La raison pour laquelle la définition de la valeur de la propriété validationQuery est utile est que si la requête échoue, la connexion est supprimée du pool et une nouvelle est créée à sa place. En ce qui concerne le comportement de votre application, l'utilisateur voit probablement le résultat du pool renvoyer une connexion périmée à l'application pour la première fois. La deuxième fois, le pool renvoie probablement une connexion différente qui peut gérer les requêtes de l'application.

    Les sources de données JNDI de Tomcat sont basées sur Commons DBCP, donc le configuration properties applicable à DBCP s'appliquera également à Tomcat.

  • 0

    Existe-t-il un routeur entre le serveur Web et la base de données qui ferme de manière transparente les connexions TCP/IP inactives?

    Si tel est le cas, votre pool de connexions doit soit ignorer les connexions inutilisées pendant plus de XX minutes du pool, soit effectuer une sorte de ping toutes les YY minutes sur la connexion pour le maintenir actif.

    +0

    Aucun pour autant que je sache. Je pense que c'est quelque chose dans les paramètres comme le suggère Vineet. –

    2

    Cette erreur indique que le serveur ferme la connexion de façon inattendue. Cela peut se produire dans 2 cas suivants,

    1. MySQL ferme la connexion au ralenti après un certain temps (par défaut est de 8 heures). Lorsque cela se produit, aucun thread n'est responsable de la fermeture de la connexion afin qu'il soit obsolète. C'est probablement la cause si cette erreur se produit seulement après longtemps inactif.

    2. Si vous ne lisez pas complètement toutes les réponses, la connexion peut être renvoyée au pool dans l'état occupé. La prochaine fois, une commande est envoyée à MySQL et elle ferme la connexion pour un mauvais état. Si l'erreur se produit assez fréquemment, c'est probablement la cause.

    Pendant ce temps, la mise en place d'un thread d'éviction aidera à résoudre le problème. Ajouter quelque chose comme ceci à la source de données,

      ... 
          removeAbandoned="true" 
          removeAbandonedTimeout="120" 
          logAbandoned="true" 
          testOnBorrow="false" 
          testOnReturn="false" 
          timeBetweenEvictionRunsMillis="60000" 
          numTestsPerEvictionRun="5" 
          minEvictableIdleTimeMillis="30000" 
          testWhileIdle="true" 
          validationQuery="select now()" 
    
    0

    Au hasard vous ne trouvez pas votre réponse que j'ai eu affaire à cela pour le dernier jour. Je fais essentiellement la même chose que vous, sauf que je base ma mise en pool de apache.commons.pool. Même erreur exacte que vous voyez EOF. Vérifiez votre fichier journal d'erreurs mysqld qui est le plus susceptible dans votre répertoire de données.Cherchez mysqld s'écraser. mysqld_safe redémarrera votre mysqld rapidement s'il se bloque, donc il ne sera pas évident que c'est le cas sauf si vous regardez dans son fichier journal./var/log n'est pas utile pour ce scénario.

    Les connexions créées avant le crash seront EOF après le crash.

    Questions connexes