Socket, ResultSet e etc

Caríssimos,

Eu acredito que este seja um problema bem comum por aqui, muito embora, por mais que eu buscasse, não encontrei solução:

Preciso criar uma aplicação Cliente-Servidor usando Socket. Pra ir um passo a frente, configurei um JDBC em MySQL que está funcionando perfeitamente no servidor, faço consultas e as imprimo tranquilamente, repito, no servidor.

Mas eu não consigo achar em lugar nenhum alguma informação completa de como transportar os ResultSets. Depois de muito Googleing achei alguma coisa me dizendo que eu deveria serializá-lo, mas não achei nada que me ajudasse a faze-lo, então, de volta a estaca zero.

Nos comandos de UPDATE e INSERT INTO eu retor uma String dizendo se eu tive sucesso ou não na execução. Mas COMO EU RETORNO UM SELECT em uma conexão socket?

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import javax.sql.rowset.CachedRowSet;

public class ReceitasServidor {

	public static void main(String[] args) throws Exception{
		
		String comando;
		String retorno;

		ServerSocket welcomeSocket = new ServerSocket(7890);
		while(true){
			
			Socket connectionSocket = welcomeSocket.accept();
			BufferedReader inFromCliente = new BufferedReader(new InputStreamReader(connectionSocket.getInputStream()));
			DataOutputStream outToCliente = new DataOutputStream(connectionSocket.getOutputStream());
			comando = inFromCliente.readLine();
			retorno = executarQry(comando);
			outToCliente.writeBytes(retorno);
			
		}		
	}	

	public static String executarQry(String comando) throws SQLException{
		if(comando.substring(0, 11).equalsIgnoreCase("INSERT INTO")){
			Statement stm = ConexaoBD.getConnection().createStatement();  
			int count = stm.executeUpdate(comando);
			return count+" linha(s) inserida na Tabela.";
		}
		if(comando.substring(0, 6).equalsIgnoreCase("UPDATE")){
			Statement stm = ConexaoBD.getConnection().createStatement();  
			int count = stm.executeUpdate(comando);
			return count+" linha(s) atualizada(s) na Tabela.";
		}
		if(comando.equalsIgnoreCase("SELECT * FROM TAB_RECEITAS")){
			Statement stm = ConexaoBD.getConnection().createStatement();  
			ResultSet rs = stm.executeQuery(comando);
			//O que fazer??
		}
		if(comando.substring(0, 52).equalsIgnoreCase("SELECT * FROM TAB_RECEITAS WHERE TITULO_RECEITA LIKE")){
			Statement stm = ConexaoBD.getConnection().createStatement();  
			ResultSet rs = stm.executeQuery(comando);
			//O que fazer??
		}
		if(comando.substring(0, 48).equalsIgnoreCase("SELECT * FROM TAB_RECEITAS WHERE DSC_RECEITA LIKE")){
			Statement stm = ConexaoBD.getConnection().createStatement();  
			ResultSet rs = stm.executeQuery(comando);
			//O que fazer??
		}
		return "Query :'"+comando+"' Não foi reconhecida.";
	}
}

Eu sei que está rabugento, mas eu não tenho experiência nenhuma em aplicação Cliente-Servidor.

A tabela armazena receitas de cozinha. Tem um ID_RECEITA, NOME_RECEITA, DSC_RECEITA.

Vc ñ pode transportar ResultSets pois eles ñ são serializaveis. Ao invés, carregue os dados do ResultSet em estruturas de dados serializaveis como um Map e transporte ele pela rede.

Vamo nessa!!!

[code]public class ReceitasServidor {

public static void main(String[] args) throws Exception{
	
	String comando;
	Object retorno;

	ServerSocket welcomeSocket = new ServerSocket(7890);
	while(true){
		
		Socket connectionSocket = welcomeSocket.accept();
		BufferedReader inFromCliente = new BufferedReader(new InputStreamReader(connectionSocket.getInputStream()));
		ObjectOutputStream outToCliente = new ObjectOutputStream(connectionSocket.getOutputStream());
		comando = inFromCliente.readLine();
		retorno = executarQry(comando);
		outToCliente.writeObject(retorno);
		
	}		
}	

public static Object executarQry(String comando) throws Exception{
	
	MapConverter map = new MapConverter();
	
	if(comando.substring(0, 11).equalsIgnoreCase("INSERT INTO")){
		Statement stm = ConexaoBD.getConnection().createStatement();  
		int count = stm.executeUpdate(comando);
		return count+" linha(s) inserida na Tabela.";
	}
	else if(comando.substring(0, 6).equalsIgnoreCase("UPDATE")){
		Statement stm = ConexaoBD.getConnection().createStatement();  
		int count = stm.executeUpdate(comando);
		return count+" linha(s) atualizada(s) na Tabela.";
	}
	else if(comando.equalsIgnoreCase("SELECT * FROM TAB_RECEITAS")){
		Statement stm = ConexaoBD.getConnection().createStatement();  
		ResultSet rs = stm.executeQuery(comando);
		return map.toObject(rs);
	}
	else if(comando.substring(0, 34).equalsIgnoreCase("SELECT * FROM TAB_RECEITAS WHERE T")){
		Statement stm = ConexaoBD.getConnection().createStatement();  
		ResultSet rs = stm.executeQuery(comando);
		return map.toObject(rs);
	}
	else if(comando.substring(0, 34).equalsIgnoreCase("SELECT * FROM TAB_RECEITAS WHERE D")){
		Statement stm = ConexaoBD.getConnection().createStatement();  
		ResultSet rs = stm.executeQuery(comando);
		return map.toObject(rs);
	}
	else return "Query :'"+comando+"' Não foi reconhecida.";
}

}
[/code]
Troquei o DataOutputStream por ObjectOutputStream e para enviar para o cliente estou usando writeObject no lugar de writeBytes.

a função toObject, que converte resultSet pra map é essa:

	
	public Object toObject(ResultSet rs) throws Exception {
		Map map = new HashMap();
		java.sql.ResultSetMetaData rm = rs.getMetaData();
		// Load ResultSet into map by column name
		int numberOfColumns = rm.getColumnCount();
		for (int i = 1; i <= numberOfColumns; ++i) {
			String name = rm.getColumnName(i);
			Object value = rs.getObject(i);
			// place into map
			map.put(name, value);
		}
		return map;
	}

Confere?

No caso, executarQry vai me retornar um Object, que será uma String ou um Map, e eu posso passar esse Object atraves de writeObject

É isso?

Meu Cliente:

public class ReceitasCliente {
	
	String servidor = "Striker";
    String comando;
	Object retorno;
    Socket clientSocket;
    ObjectOutputStream outToServer;
    BufferedReader inFromServer;
    
    public void inserir(String titulo, String descricao, String foto) throws Exception{
    	clientSocket = new Socket(servidor, 7890);
		outToServer = new ObjectOutputStream(clientSocket.getOutputStream());
		inFromServer = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
		
		comando = "INSERT INTO TAB_RECEITAS (TITULO_RECEITA, DSC_RECEITA, FOTO_RECEITA) VALUES " +
		"( '" + titulo + "' ,'" + descricao + "' ,'" + foto + "' )";
        outToServer.writeObject(comando);
        retorno = inFromServer.getClass();
        System.out.println((String) retorno);
        clientSocket.close();
    }

Comecei com inserir, que tem um retorno mais tranqüilo. Se na main eu chamo inserir(“Nome”,“Receita”,“Link pra foto”) ele deveria atribuir a comando uma string com a query, certo? E passar ela pro Servidor, ok? Mais ai eu tô recebendo este erro:

Exception in thread "main" java.net.SocketException: Connection reset at java.net.SocketInputStream.read(Unknown Source) at sun.nio.cs.StreamDecoder.readBytes(Unknown Source) at sun.nio.cs.StreamDecoder.implRead(Unknown Source) at sun.nio.cs.StreamDecoder.read(Unknown Source) at java.io.InputStreamReader.read(Unknown Source) at java.io.BufferedReader.fill(Unknown Source) at java.io.BufferedReader.readLine(Unknown Source) at java.io.BufferedReader.readLine(Unknown Source) at projeto.ReceitasServidor.main(ReceitasServidor.java:24)
(ReceitasServidor.java:24) é onde tem “comando = inFromCliente.readLine();” eu teria que usar um comando diferente do readLine?

Kra, quando vc escreve um objeto através de um ObjectOutputStream vc só pode ser esse objeto através de um ObjectInputStream. Isso pq essas streams servem p/ serializar objetos e isso demanda mais informações do q apenas os seus atributos. Eis aqui um código q funcionou muito bem p/ mim.

public class Client {

    public static void main(String[] args) throws UnknownHostException,
            IOException {
        Socket socket = new Socket("localhost", 9000);
        ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
        String sql = "insert into DISCOUNT_CODE(DISCOUNT_CODE, RATE, VERSION) values ('A', 0.5, 0)";
        out.writeObject(sql);
        socket.close();
    }

}
public class Server {

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ServerSocket service = new ServerSocket(9000);
        Socket client = service.accept();
        ObjectInputStream in = new ObjectInputStream(client.getInputStream());
        System.out.println(in.readObject());
        client.close();
        service.close();
    }

}

Agora só umas dicas. Métodos q retornam coisas diferentes como o seu executarQry (pode retornar ResultSet ou String) são difíceis de ler e entender. Ao invés disse crie um método q retorne apenas ResultSet ou null ou q lance uma exeção e escreva uma String no socket p/ avisar ao cliente do problema. Tb seria bom q vc sempre escrevesse uma String no socket mesmo quando vc for retornar um Map. Pense nisso como um cabeçalho, quando der certo vc envia uma String (tipo “Ok”) e logo depois envia o Map. Isso vai tornar o código do cliente mais simples:

if (header.equals("Ok")) {
  // código p/ receber o resultado da query...
}
else {
  // código p/ mostra mensagem de erro.
}

Outro problema é esse monte de if/else aninhado. Isso reduz a legibilidade do seu código e ainda é mais suscetível ao aparecimento de bugs. Ao invés use o método genérico Statement.execute(). Esse método executa tanto inserts/updates/deletes quando selects. P/ saber se vc precisa retornar alguma informação use o método Statement.getResultSet(). Esse método vai retornar o ResultSet do ultimo select executado ou null, se a ultima query ñ foi do tipo select.

Evite declara Exception em métodos q ñ lançam Exceptions como o MapConverter.toObject() e evite declara o supertipo Exception, prefira tipos mais específicos como IOException ou SQLException pq assim fica mais fácil determinar a origem do problema quando uma exceção ocorrer.

Essa ultima é muito importante, eu ñ vi em ponto algum vc fecar o ResultSet, o Statement e o Connection do JDBC. Lembre-se de fechar todos eles p/ evitar problemas de estabilidade no seu aplicativo.

Seguindo a maioria das recomendações e dicas, o codigo evoluiu para:

public class ReceitasServidor {

	public static void main(String[] args) throws Exception{
		
		String comando;
		Object retorno;

		ServerSocket welcomeSocket = new ServerSocket(7890);
		while(true){
			
			Socket connectionSocket = welcomeSocket.accept();
			ObjectInputStream inFromCliente = new ObjectInputStream(connectionSocket.getInputStream());
			ObjectOutputStream outToCliente = new ObjectOutputStream(connectionSocket.getOutputStream());
			comando = (String) inFromCliente.readObject();
			retorno = executarQry(comando);
			outToCliente.writeObject(retorno);
			connectionSocket.close();
		}		
	}	

	public static Object executarQry(String comando) throws Exception{
		
		MapConverter map = new MapConverter();
		Object retorno;
		
		Connection conn = ConexaoBD.getConnection();
		Statement stm = conn.createStatement();  
		stm.execute(comando);
		ResultSet rs = stm.getResultSet();
		if (rs == null)
			retorno = null;
		else{
			retorno =  map.toObject(rs);
			rs.close();
		}
		stm.close();
		conn.close();
		return retorno;

	}
}

Percebi que metódo map.toObject(ResultSet rs) não tava correndo o result set, então o aumentei para esse abaixo, fiz bem, ou melhor, percebi bem?

	public Object toObject(ResultSet rs){
		Map map = new HashMap();
		java.sql.ResultSetMetaData rm = rs.getMetaData();
		int numberOfColumns = rm.getColumnCount();

		for (int i = 1; i <= numberOfColumns; ++i) {
			String name = rm.getColumnName(i);
			Object value = rs.getObject(i);
			map.put(name, value);
		}

		while (rs.next()){
			for (int i = 1; i <= numberOfColumns; ++i) {
				String name = rm.getColumnName(i);
				Object value = rs.getObject(i);
				map.put(name, value);
			}
		}
		return map;
	}

Os comandos estão chegando legal no socket, sendo executados no banco mas:

-quando eu executo operações do tipo INSERT/UPDATE, eu recebo a exceção java.net.SocketException: Connection reset by peer: socket write error na linha outToCliente.writeObject(retorno); (no Servidor)

-quando eu executo um comando do tipo SELECT, eu recebo a exceção java.sql.SQLException: Before start of result set na linha Object value = rs.getObject(i);(do Object toObject(ResultSet rs)), mesmo eu me garantindo de que a tabela NÃO está vazia.

O seu método executarQry(String) está retornando null quando a operação é INSERT/UPDATE/DELETE e vc, então, envia esse “null” direto p/ o cliente. Ñ faça isso. No meu método main faça algo como:

Map retorno = executarQry(comando);
if (retorno == null) {
  outToCliente.write(0);
}
else {
  outToCliente.write(0);
  outToCliente.writeObject(retorno);
}

E no cliente:

enum Operation { INSERT, UPDATE, DELETE, SELECT };
...

Operation myLastOperation = // A operação q vc vai executar.
int h = inFromServer.read();
if (h == 0) {
  switch (myLastOperation) {
    case INSERT:
    case UPDATE:
    case DELETE:
      System.out.println("Operação realizada com sucesso.");
      break;

    case SELECT:
      Map m = inFromServer.readObject();
      break;
  }
}

Mais uma coisa, evite o uso de Exception em favor de exceções mais específicas. E se vc sabe q um método vai retornar um Map então coloque Map como tipo de retorno e ñ Object, isso vai tornar o seu código muito mais fácil de ser lido.

To com uma virose braba, então só tive forças pra voltar ao PC agora… mal aê.

[code]public class ReceitasServidor {

public static void main(String[] args) throws IOException, ClassNotFoundException, SQLException{
	
	String comando;
	Map retorno;

	ServerSocket welcomeSocket = new ServerSocket(7890);
	while(true){
		
		Socket connectionSocket = welcomeSocket.accept();
		ObjectInputStream inFromCliente = new ObjectInputStream(connectionSocket.getInputStream());
		ObjectOutputStream outToCliente = new ObjectOutputStream(connectionSocket.getOutputStream());
		comando = (String) inFromCliente.readObject();
		retorno = executarQry(comando);
        if (retorno == null){
			outToCliente.write(0);
		}
		else{
			outToCliente.write(0);
			outToCliente.writeObject(retorno);
		}
		connectionSocket.close();
	}		
}	

public static Map executarQry(String comando) throws SQLException {
	
	MapConverter map = new MapConverter();
	Map retorno;
	
	Connection conn = ConexaoBD.getConnection();
	Statement stm = conn.createStatement();  
	stm.execute(comando);
	ResultSet rs = stm.getResultSet();
	if (rs == null)
		retorno = null;
	else{
		retorno =  (Map) map.toObject(rs);
		rs.close();
	}
	stm.close();
	conn.close();
	return retorno;

}

}[/code]

[code]public class ReceitasCliente {

String servidor = "Striker";
String comando;
Socket clientSocket;
ObjectOutputStream outToServer;
ObjectInputStream inFromServer;

public String inserir(String titulo, String descricao) throws IOException{
	int head;
	String retorno = null;
	try{
    	clientSocket = new Socket(servidor, 7890);
		outToServer = new ObjectOutputStream(clientSocket.getOutputStream());
		inFromServer = new ObjectInputStream(clientSocket.getInputStream());
		
		comando = "INSERT INTO TAB_RECEITAS (TITULO_RECEITA, DSC_RECEITA) VALUES " +
		"( '" + titulo + "' ,'" + descricao +  "' )";
		
        outToServer.writeObject(comando);
        head = inFromServer.read();
        System.out.println(head);
        clientSocket.close();
        if (head == 0){
        	retorno = "Operação efetuada com sucesso!";
        }
    }catch(IOException e){
    	retorno = "Erro: " + e.getMessage();
    }
    return retorno;
}

}[/code]

Se eu dou um System.out.println(cli.inserir(“Receita 1”,“Descrição”)); ele imprime null. Nem dá erro, nem recebe 0. Eu olhei no banco, e a operação foi executada sim.

Ai eu coloquei o System.out.println(head); pra saber o que estava acontecendo…e ele imprime -1!! Por que essa incoerência?

Eu me perdi no uso do enum Operation, como eu aviso a ele que operação eu usei?

Kra, to meio sem tempo agora. Ai vai um código q funcionou p/ mim, vê se serve de inspiração.

// Server
public class Server {

    private static Socket client;
    private static ObjectInputStream in;
    private static ObjectOutputStream out;
    private static ServerSocket server;

    public static void main(String[] args) throws IOException,
            ClassNotFoundException {
        try {
            server = new ServerSocket(4321);
            client = server.accept();

            in = new ObjectInputStream(client.getInputStream());
            out = new ObjectOutputStream(client.getOutputStream());

            String msg = (String) in.readObject();
            System.out.println("Received: " + msg);
            out.write(0);
            out.writeObject("Hello you too!");
        }
        finally {
            in.close();
            out.close();
            client.close();
            server.close();
        }
    }

}

// Cliente
public class Client {

    private static ObjectInputStream in;
    private static ObjectOutputStream out;
    private static Socket socket;

    public static void main(String[] args) throws UnknownHostException,
            IOException, ClassNotFoundException {
        try {
            socket = new Socket("localhost", 4321);

            out = new ObjectOutputStream(socket.getOutputStream());
            in = new ObjectInputStream(socket.getInputStream());

            out.writeObject("Hello!");
            int number = in.read();
            String answer = (String) in.readObject();
            System.out.println("number = " + number);
            System.out.println("Received" + answer);
        }
        finally {
            in.close();
            out.close();
            socket.close();
        }
    }

}

Se ñ ajudar, posta ai q atarde eu dou uma olhada no seu código.

Aê!!! Postando os meus códigos funcionando: (agora não me perguntem como…abri os códigos de dev.rafael numa tela, os meus noutra, e refiz os meus com base no dele. Deixei o mais simples e sem laços possível…vai demorar uns dias pra eu olhar esse código de novo…)

[code]public class ReceitasServidor {

private static Socket client;  
private static ObjectInputStream in;  
private static ObjectOutputStream out;
private static ServerSocket server;  

public static void main(String[] args) throws IOException,  
        ClassNotFoundException, SQLException {  
	while(true){
        try {  
            server = new ServerSocket(4321);  
            client = server.accept();  
  
            in = new ObjectInputStream(client.getInputStream());  
            out = new ObjectOutputStream(client.getOutputStream());   
  
            String cmd = (String) in.readObject();  
            //System.out.println("Comando: " + cmd);
            Map ret = executarQry(cmd);
            //System.out.println("Retorno: " + ret);
            out.write(0); 
            out.writeObject(ret);

        }  
        finally {  
            in.close();  
            out.close();  
            client.close();  
            server.close();  
        }  
	}
}	

public static Map executarQry(String comando) throws SQLException {
	
	MapConverter map = new MapConverter();
	Map retorno;
	
	Connection conn = ConexaoBD.getConnection();
	Statement stm = conn.createStatement();  
	stm.execute(comando);
	ResultSet rs = stm.getResultSet();
	if (rs == null)
		retorno = null;
	else{
		retorno =  (Map) map.toObject(rs);
		rs.close();
	}
	stm.close();
	conn.close();
	return retorno;

}

}

public class MapConverter {

public Object toObject(ResultSet rs) throws SQLException {
	Map map = new HashMap();
	java.sql.ResultSetMetaData rm = rs.getMetaData();
	int numberOfColumns = rm.getColumnCount();
	
	while (rs.next()){
		for (int i = 1; i <= numberOfColumns; ++i) {
			String name = rm.getColumnName(i);
			Object value = rs.getObject(i);
			map.put(name, value);
		}
	}
	
	return map;
}

}[/code]

[code]public class ReceitasCliente {

private static ObjectInputStream in;  
private static ObjectOutputStream out;  
private static Socket socket;  

public static Map Cliente(String cmd) throws UnknownHostException,  
        IOException, ClassNotFoundException {
	
	Map ret = null;
	
    try {  
        socket = new Socket("localhost", 4321);  

        out = new ObjectOutputStream(socket.getOutputStream());  
        in = new ObjectInputStream(socket.getInputStream());  

        out.writeObject(cmd);  
        int h = in.read();              
        //System.out.println("number = " + h);  
        if (h==0){
        	//System.out.println("Comando executado!");
        	ret = (Map) in.readObject();
        	//System.out.println(ret);
        }
    }  
    finally {  
        in.close();  
        out.close();  
        socket.close();  
    }
    
	return ret;  
}

public void inserir(String titulo, String descricao, String foto) throws IOException, ClassNotFoundException{
	String cmd = "INSERT INTO TAB_RECEITAS (TITULO_RECEITA, DSC_RECEITA, FOTO_RECEITA) VALUES " +
	"( '" + titulo + "' ,'" + descricao + "' ,'" + foto + "' )";

	Cliente(cmd);
}

public void atualizar(int idReceita, String titulo,	String descricao, String foto) throws IOException, ClassNotFoundException{
	String cmd = "UPDATE TAB_RECEITAS SET TITULO_RECEITA = '" + titulo + "', DSC_RECEITA = '" + descricao +
	"', FOTO_RECEITA = '" + foto + "' WHERE ID_RECEITA = " + idReceita;

	Cliente(cmd);
}

public Map consultar(String titulo, String palavra) throws IOException, ClassNotFoundException{
	String cmd = "SELECT * FROM TAB_RECEITAS WHERE DSC_RECEITA LIKE '%" + palavra + "%' OR" +
	" TITULO_RECEITA LIKE '%" + palavra + "%'";

	return Cliente(cmd);
}

public Map consultar(String titulo) throws IOException, ClassNotFoundException{
	String cmd = "SELECT * FROM TAB_RECEITAS WHERE TITULO_RECEITA LIKE '%" + titulo + "%'";

	return Cliente(cmd);
}

public Map consultar() throws IOException, ClassNotFoundException{
	String cmd = "SELECT * FROM TAB_RECEITAS";

	return Cliente(cmd);
}

} [/code]

Brigadão dev.rafael, de verdade!!!

CachedRowSet e WebRowSet são serializaveis,…

inclusive é possivel gerar XML a partir do ResultSEt e converter novamente para ResultSEt