Ho un po' di confusione riguardo alla gestione dei cicli try catch per la chiusura di una connessione a DB.

Ho due tipologie di funzioni che frullano per la testa:

codice:
public void esempioQuery1(){
		
		Connection conn = getConnection("blablabla");
		Statement st = null;
		ResultSet rs = null;
		
		String query = "";
		
		try{
			st=conn.createStatement();
			rs=st.executeQuery(query);
			
			while(rs.next()){
				//...
			}
			
		}catch(Exception ex){
			log.error("Errore nell'esecuzione della query: "+query+" "+ex);
		}finally{
			try{
				rs.close();
				st.close();
				conn.close();
			}catch (Exception ex) {
				log.error("Errore nel rilascio delle risorse "+ex);
			}
		}
		
	}

e

codice:
public void esempioQuery2() throws SQLException{
		
		String query = "";
		
		try{
			
			Connection conn = getConnection("blablabla");
			
			Statement st=conn.createStatement();
			ResultSet rs=st.executeQuery(query);
				
			while(rs.next()){
				//...
			}
				
			rs.close();
			st.close();
			conn.close();
		
		}catch (SQLException ex) {
			throw new SQLException("Errore nell'esecuzione della query: "+query+" "+ex);
		}
	}
Potreste darmi dei consigli a riguardo, nessuna delle due funzioni che vi ho postato mi convice molto.
Un dubbio che mi affligge sempre è:
Se creo una funzione che si connette al db, nel caso in cui la funzione va in crash e non riesce a fare la chiusura della connessione, il garbage collector rilascia la connessione o sul db mi resta una connessione appesa?
In teoria mi verrebbe da pensare che siccome la connessione è nello scope della funzione, quando muore questa, muore anche la connessione... ma mi chiedo, per Oracle dall'altra parte è tutto ok?

Grazie degli eventuali suggerimenti.