在 Java Swing 中关闭数据库连接

Posted

技术标签:

【中文标题】在 Java Swing 中关闭数据库连接【英文标题】:Closing Database Connections in Java Swing 【发布时间】:2014-12-02 20:03:38 【问题描述】:

我对通过 jdbc 关闭连接有点困惑。

package Login;
public class LoginFrame 
    private JFrame loginFrame;
    Connection conn = null;
    /**
     * Launch the application.
     */
    public static void main(String[] args) 
        EventQueue.invokeLater(new Runnable() 
            public void run() 
                try 
                    LoginFrame window = new LoginFrame();
                    window.loginFrame.setVisible(true);
                 catch (Exception e) 
                    e.printStackTrace();
                
            
        );
    

    /**
     * Create the application.
     */
    public LoginFrame() 
        initialize();
        conn = DBConnect.connectDB();
    

    /**
     * Initialize the contents of the frame.
     */
    private void initialize() 
        loginFrame = new JFrame();
        loginFrame.setResizable(false);
        loginFrame.setTitle("XXX");
        loginFrame.setBounds(100, 100, 350, 300);
        loginFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        loginFrame.getContentPane().setLayout(null);

        panel = new JPanel();
        panel.setBorder(new TitledBorder(UIManager.getBorder("TitledBorder.border"), "Login", TitledBorder.LEADING, TitledBorder.TOP, null, SystemColor.inactiveCaptionText));
        panel.setBounds(96, 140, 139, 99);
        loginFrame.getContentPane().add(panel);
        panel.setLayout(null);

        loginField = new JTextField();
        loginField.setBounds(47, 16, 86, 20);
        panel.add(loginField);
        loginField.setColumns(10);

        passwordField = new JPasswordField();
        passwordField.setBounds(47, 37, 86, 20);
        panel.add(passwordField);

        JButton loginButton = new JButton("Login");
        loginButton.addActionListener(new ActionListener() 
            public void actionPerformed(ActionEvent arg0) 

                String sql = "select * from employees where login=? and password=?";
                try
                    PreparedStatement pst = conn.prepareStatement(sql);
                    pst.setString(1, loginField.getText());
                    pst.setString(2, passwordField.getText());
                    ResultSet rs = pst.executeQuery();
                    int countUsr = 0;
                    while(rs.next())
                        countUsr++;
                    
                    if(countUsr == 1)
                        loginFrame.dispose();
                        AdminFrame adminFrame = new AdminFrame();
                        adminFrame.setVisible(true);
                    else  if(countUsr > 1)
                        JOptionPane.showMessageDialog(null, "ERR");
                    else
                        JOptionPane.showMessageDialog(null, "ERR");
                        passwordField.setText("");
                    
                rs.close();
                pst.close();
                catch(Exception e)
                    JOptionPane.showMessageDialog(null, "ERR: "+e.getMessage());
                
            
        );
        loginButton.setBounds(25, 65, 89, 23);
        panel.add(loginButton);
    
   

我不确定使用哪种方法更好地关闭连接:

@Override
protected void finalize() throws Throwable 
    conn.close();
    super.finalize();

finally 
    conn.close();

在按钮 ActionListener 中的 try catch 块之后。

在某些示例中,人们说 finally 块更好,但是当我有很多方法(4 个示例动作侦听器)并且在每个方法中我都在 DB 上执行一些操作时,该怎么办。我应该在所有方法中打开和关闭连接还是只使用 finalize 方法?

【问题讨论】:

您应该在尽可能短的范围内打开和关闭连接。 如果您正在重用连接,那么您可以像第一个示例一样在完全完成后将其保持打开状态并关闭它。为每个函数创建新连接没有意义。 @brso05 如果连接超时,则无法重新使用连接。即使您使用来自数据库连接池的连接,您也应该调用close 方法。 我在另外 10 个 ActionListener 中重用了这个连接。 @Luiggi Mendoza 更好的方法是在每个 ActionListener 方法中使用 finally 块?这不是降低性能吗? @LuiggiMendoza 您应该检查连接是否超时以及是否创建新连接。你实际上甚至不需要显式调用close 方法,它会自动关闭。 【参考方案1】:

使用数据库时,需要考虑一些概念:

不要让连接保持打开的时间过长。为此,您应该在尽可能短的范围内打开和关闭它。这也将帮助您使用同一连接在同一事务中执行多个操作,特别是在多线程环境中。 仅重用物理连接。这意味着,您打开连接一次,将其发送到 SLEEP 状态并在将来重用它。要做到这一点,不要重新发明***,而是使用数据库连接池来自动为您处理。

执行此操作的步骤:

在您的应用程序开始时,创建数据库连接池。 在每种方法中,当您需要对数据库执行某些操作时,请从数据库连接池中获取连接,对数据库执行操作,然后关闭连接。 在应用程序结束时,关闭数据库连接池。

这是使用HikariCP 执行此操作的示例:

定义将使用数据库连接池的数据源:

public final class DataSourceFactory 

    private static final Logger LOG = LoggerFactory.getLogger(DataSourceFactory.class);

    private static DataSource mysqlDataSource;

    private DataSourceFactory()  

    private static DataSource getDataSource(String configurationProperties) 
        Properties conf = new Properties();
        try 
            conf.load(DataSourceFactory.class.getClassLoader().getResourceAsStream(configurationProperties));
         catch (IOException e) 
            LOG.error("Can't locate database configuration", e);
        
        HikariConfig config = new HikariConfig(conf);
        HikariDataSource dataSource = new HikariDataSource(config);
        return dataSource;
    

    public static DataSource getMySQLDataSource() 
        LOG.debug("Retrieving data source for MySQL");
        if (mySQLDataSource == null) 
            synchronized(DataSourceFactory.class) 
                if (mySQLDataSource == null) 
                    LOG.debug("Creating data source for MySQL");
                    mySQLDataSource = getDataSource("mysql-connection.properties");
                
            
        
        return mySQLDataSource;
    

在尽可能短的范围内使用连接。

public class LoginFrame 
    private JFrame loginFrame;
    //remove the Connection from here, this is not the shortest possible scope
    //Connection conn = null;
    /**
     * Launch the application.
     */
    public static void main(String[] args) 
        EventQueue.invokeLater(new Runnable() 
            public void run() 
                try 
                    LoginFrame window = new LoginFrame();
                    window.loginFrame.setVisible(true);
                 catch (Exception e) 
                    e.printStackTrace();
                
            
        );
    

    /**
     * Create the application.
     */
    public LoginFrame() 
        initialize();
        conn = DBConnect.connectDB();
    

    /**
     * Initialize the contents of the frame.
     */
    private void initialize() 
        //...
        loginButton.addActionListener(new ActionListener() 
            public void actionPerformed(ActionEvent arg0) 
                //This is the shortest possible scope for the connection
                //Declare it here and use it
                Connection conn = DataSourceFactory.getMySQLDataSource().getConnection();
                String sql = "select * from employees where login=? and password=?";
                try
                    PreparedStatement pst = conn.prepareStatement(sql);
                    pst.setString(1, loginField.getText());
                    pst.setString(2, passwordField.getText());
                    ResultSet rs = pst.executeQuery();
                    int countUsr = 0;
                    while(rs.next())
                        countUsr++;
                    
                    if(countUsr == 1)
                        loginFrame.dispose();
                        AdminFrame adminFrame = new AdminFrame();
                        adminFrame.setVisible(true);
                    else  if(countUsr > 1)
                        JOptionPane.showMessageDialog(null, "ERR");
                    else
                        JOptionPane.showMessageDialog(null, "ERR");
                        passwordField.setText("");
                    
                 catch(Exception e) 
                    //ALWAYS log the exception, don't just show a message
                    e.printStackTrace();
                    JOptionPane.showMessageDialog(null, "ERR: "+e.getMessage());
                 finally 
                    try 
                        rs.close();
                        pst.close();
                        con.close();
                     catch (SQLException silent) 
                        //do nothing
                    
                
            
        );
        loginButton.setBounds(25, 65, 89, 23);
        panel.add(loginButton);
    

如果您使用的是 Java 7 或更高版本,请使用 try-with-resources(毕竟这是语法糖):

loginButton.addActionListener(new ActionListener() 
    public void actionPerformed(ActionEvent arg0) 
        //This is the shortest possible scope for the connection
        //Declare it here and use it
        Connection conn = ;
        String sql = "select * from employees where login=? and password=?";
        try(Connection conn = DataSourceFactory.getMySQLDataSource().getConnection();
            PreparedStatement pst = conn.prepareStatement(sql);) 
            pst.setString(1, loginField.getText());
            pst.setString(2, passwordField.getText());
            try (ResultSet rs = pst.executeQuery();) 
                int countUsr = 0;
                while(rs.next())
                    countUsr++;
                
                if(countUsr == 1)
                    loginFrame.dispose();
                    AdminFrame adminFrame = new AdminFrame();
                    adminFrame.setVisible(true);
                 else if(countUsr > 1)
                    JOptionPane.showMessageDialog(null, "ERR");
                 else 
                    JOptionPane.showMessageDialog(null, "ERR");
                    passwordField.setText("");
                
            
         catch(Exception e) 
            //ALWAYS log the exception, don't just show a message
            e.printStackTrace();
            JOptionPane.showMessageDialog(null, "ERR: "+e.getMessage());
        
    
);

【讨论】:

【参考方案2】:

如果您使用的是 Java 7 及更高版本,我建议您使用try with resources。

The try-with-resources statement ensures that each resource is closed at the end of the statement. Any object that implements java.lang.AutoCloseable, which includes all objects which implement java.io.Closeable, can be used as a resource.

在你的情况下:

        try (PreparedStatement pst = conn.prepareStatement(sql))//use try with resources
            
            pst.setString(1, loginField.getText());
            pst.setString(2, passwordField.getText());
            ResultSet rs = pst.executeQuery();
            int countUsr = 0;
            while(rs.next())
                countUsr++;
            
            if(countUsr == 1)
                loginFrame.dispose();
                AdminFrame adminFrame = new AdminFrame();
                adminFrame.setVisible(true);
            else  if(countUsr > 1)
                JOptionPane.showMessageDialog(null, "ERR");
            else
                JOptionPane.showMessageDialog(null, "ERR");
                passwordField.setText("");
            
        //removed rst closing, no need to close if your PreparedStatement is being closed.
        //No need to explicitly close our PreparedStatement since we are using try with resources
        catch(Exception e)
            JOptionPane.showMessageDialog(null, "ERR: "+e.getMessage());
        
    

您还应该注意,如果您要关闭 PreparedStatement,则无需关闭 ResultSet。 (见this答案)

【讨论】:

所以在 try 块中的每个 ActionListener 方法中,我应该打开连接,然后在 finaly 块中关闭连接?如果我有很多 ActionListeners 方法,这不会降低性能吗? 我认为您不会注意到性能上有太大变化。最好根据需要打开和关闭,而不是担心“全局连接”。您应该努力使您的范围尽可能小。【参考方案3】:

Java 的优点在于 PreparedStatementConnectionResultSet 都使用 AutoCloseable。如果你想创建一个方法来一次性关闭所有这些实例:

public static void close(AutoCloseable... closeables) 
    for (AutoCloseable c : closeables) 
        try 
            if (c != null) 
                c.close();
            
         catch (Exception ex) 
            //do something, Logger or or your own message
        
    


然后您可以调用此方法并在您创建的任何使用AutoCloseable 且没有固定长度参数的实例中折腾。

最好在finally 块中使用close() 调用,因为如果抛出异常,finally 块无论如何都会执行。否则,您可能会遇到多个连接处于打开状态的问题。

【讨论】:

【参考方案4】:

finally 总是被调用更合适。记住任何检查 conn 不为空

【讨论】:

【参考方案5】:

标准的做法是在 finally 块中关闭它,以节省 DB 资源并避免泄漏。使用空闲超时的连接池可以获得最佳结果:http://www.mchange.com/projects/c3p0/index.html

【讨论】:

【参考方案6】:

我会为所有方法/事务使用 try/catch/finally。并且您应该在关闭它们之前检查连接、结果集和准备好的语句是否不为空。方法的 finally 块是这样的:

finally 
    try 
    if (rset != null) 
        rset.close();
    
    if (st != null) 
        st.close();
    
     catch (Exception e) 
    // do something
    

当你使用完数据库后,我会用一个方法关闭连接:

public void close() 
if (!isOpen) return;
try 
    if (conn != null) 
    conn.close();
    
 catch (Exception e) 
    // do something

isOpen = false;
conn = null;

希望对你有帮助:-)

【讨论】:

以上是关于在 Java Swing 中关闭数据库连接的主要内容,如果未能解决你的问题,请参考以下文章

在 Java 中关闭数据库连接

如何在 jmeter 中关闭 JDBC 连接

jTDS 提交语句在 MSSQL 2008 中关闭未结束事务

java中关闭窗口的方法

如何在android中关闭数据连接?

为什么我们应该在JDBC中关闭连接?如果我们不这样做,会发生什么