Manipulando campos BLOB e CLOB com JDBC

Java

09/04/2009

[b][u]Manipulando campos BLOB e CLOB com JDBC[/u][/b] Por: Giovane Roslindo Kuhn Este tutorial apresenta de forma sucinta como manipular campos dos tipos BLOB e CLOB utilizando o driver Oracle JDBC (ojdbc14.jar). Em linhas gerais, a prova de conceito possui os seguintes passos: 1) ler um arquivo texto (grande) e um arquivo binário (imagem); 2) inserir o conteúdo destes arquivos em um registro no banco de dados utilizando JDBC; 3) selecionar o registro do banco e ler o conteúdo dos campos BLOB e CLOB utilizando JDBC; 4) gerar um arquivo binário com o conteúdo do campo BLOB e um arquivo texto com o conteúdo do campo CLOB; 5) certificar que o conteúdo dos arquivos gerados é idêntico ao conteúdo dos arquivos originais; Para o entendimento deste tutorial é necessário algum conhecimento em JDBC e SQL, para isto veja em: [url=http://www.javafree.org/javabb/viewtopic.jbb?t=1356]Acessando banco de dados em Java (PARTE 1)[/url] [url=http://www.javafree.org/javabb/viewtopic.jbb?t=1357]Acessando banco de dados em Java (PARTE 2)[/url] [url=http://www.javafree.org/javabb/viewtopic.jbb?t=1358]Acessando banco de dados em Java (PARTE 3)[/url] Let's play !!! [b][u]Preparando o ambiente[/u][/b] A preparação do ambiente para a prova de conceito tem os seguintes passos: 1) criação da tabela de testes com os campos BLOB e CLOB, segue definição:
create table teste (cadastro number(19), imagem blob, texto clob)
2) criação do arquivo binário (teste.jpg) 3) criação do arquivo texto (teste.txt) PS: Não é necessário preparar manualmente o ambiente, pois o programa de teste executa os passos descritos acima. [b][u]Inserindo registro[/u][/b] Para criar um registro, primeiramente deve ser inserido um registro com os campos CLOB e BLOB vazios:
Statement stmt = conn.createStatement();
stmt.execute("insert into teste (cadastro, imagem, texto) values (1, empty_blob(), empty_clob())");
Depois deve-se selecionar o registro e efetivamente inserir o conteúdo dos campos CLOB e BLOB, para isto o registro é bloqueado com a cláusula "for update":
ResultSet rs = stmt.executeQuery("select cadastro, imagem, texto from teste where cadastro = 1 for update");
rs.next();
Segue o código para atribuir o conteúdo do arquivo binário para o campo BLOB, nota-se que uma linha é específica para o driver Oracle, para outros drivers utilizar a linha comentada:
// copia arquivo para campo blob
Blob blob = rs.getBlob("imagem");
byte[] bbuf = new byte[1024];
InputStream bin = new FileInputStream(byteFile);
OutputStream bout = ((BLOB) blob).getBinaryOutputStream(); // específico driver oracle
// cout = clob.setCharacterStream(0);
int bytesRead = 0;
while ((bytesRead = bin.read(bbuf)) != -1) {
    bout.write(bbuf, 0, bytesRead);
}
bin.close();
bout.close();
Segue o código para atribuir o conteúdo do arquivo texto para o campo CLOB, nota-se que uma linha é específica para o driver Oracle, para outros drivers utilizar a linha comentada:
// copia arquivo para campo clob
Clob clob = rs.getClob("texto");
char[] cbuf = new char[1024];
Reader cin = new FileReader(file);
Writer cout = ((CLOB) clob).getCharacterOutputStream(); // específico driver oracle
// cout = clob.setCharacterStream(0);
int charsRead = 0;
while ((charsRead = cin.read(cbuf)) != -1) {
    cout.write(cbuf, 0, charsRead);
}
cin.close();
cout.close();
Basta comitar a transação e fechar o cursor:
// comita transação
conn.commit();
rs.close();
[b][u]Selecionando registro[/u][/b] O próximo objetivo da prova de conceito é selecionar o registro inserido e criar um novo arquivo binário com o campo BLOB e outro para o campo CLOB. Primeiro é selecionado o registro, um "select" normal:
rs = stmt.executeQuery("select cadastro, imagem, texto from teste where cadastro = 1");
rs.next();
Depois é criado um novo arquivo binário com o conteúdo do campo BLOB:
// cria novo arquivo binário com blob
blob = rs.getBlob("imagem");
bin = blob.getBinaryStream();
bout = new FileOutputStream(byteFileDb);
while ((bytesRead = bin.read(bbuf)) != -1) {
    bout.write(bbuf, 0, bytesRead);
}
bin.close();
bout.close();
Depois é criado um novo arquivo texto com o conteúdo do campo CLOB:
// cria novo arquivo texto com clob
clob = rs.getClob("texto");
cin = clob.getCharacterStream();
cout = new FileWriter(fileDb);
while ((charsRead = cin.read(cbuf)) != -1) {
    cout.write(cbuf, 0, charsRead);
}
cin.close();
cout.close();
Para finalizar é fechado o cursor:
// fecha cursor
rs.close();
[b][u]Validando arquivos[/u][/b] Para validar a prova de conceito é feito uma comparação entre os arquivos originais e os arquivos novos, segue código que verifica se o conteúdo de dois arquivos são iguais:
/**
 * Compara dois arquivos caracter a caracter
 * @param f1 Arquivo um
 * @param f2 Arquivo dois
 * @return <code>true</code> se forem iguais, <code>false</code> se não forem
 */
private static boolean equals(File f1, File f2) throws IOException {

    FileReader r1 = new FileReader(f1);
    FileReader r2 = new FileReader(f2);

    while (true) {
        int c1 = r1.read();
        int c2 = r2.read();
        if (c1 != c2) {
            return false;
        }
        if (c1 == -1 && c2 == -1) {
            break;
        }
    }
    return true;
}
Primeiro verifica os arquivos binários:
// arquivos binarios devem ser iguais
if (equals(byteFile, byteFileDb)) {
    System.out.println(byteFile.toString() + " == " + byteFileDb.toString());
} else {
    System.err.println(byteFile.toString() + " <> " + byteFileDb.toString());
}
E por fim, verifica os arquivos textos:
// arquivos texto devem ser iguais
if (equals(file, fileDb)) {
    System.out.println(file.toString() + " == " + fileDb.toString());
} else {
    System.out.println(file.toString() + " <> " + fileDb.toString());
}
Well, done !!! [url=mailto:brain@netuno.com.br][b]Giovane Roslindo Kuhn[/b][/url] é bacharel em Ciências da Computação pela [url=http://www.furb.br]Universidade Regional de Blumenau[/url]. Atuando profissionalmente no Projeto Jakare da empresa [url=http://www.senior.com.br]Senior Sistemas[/url], que consiste em um framework para desenvolvimento de aplicações J2EE e é reponsável pelos projetos [url=http://www.babaxp.org]Baba XP[/url], [url=http://snaildb.dev.java.net]SnailDB[/url] e [url=http://apollo.dev.java.net]Apollo[/url]. Incentivador do uso de metodologias ágeis de desenvolvimento, especialmente XP e desing patterns.
Giovane Kuhn

Giovane Kuhn

Curtidas 0

Respostas

Giovane Kuhn

Giovane Kuhn

09/04/2009

Segue fonte completo:
GOSTEI 0
Vitor Pamplona

Vitor Pamplona

09/04/2009

Agiliza um pouco ae :D
    private static boolean equals(File f1, File f2) throws IOException {

        if (f1.length() != f2.length()) return false;

        FileReader r1 = new FileReader(f1);
        FileReader r2 = new FileReader(f2);
        while (true) {
            int c1 = r1.read();
            int c2 = r2.read();
            if (c1 != c2) {
                return false;
            }
            if (c1 == -1 && c2 == -1) {
                break;
            }
        }
        return true;
    }
GOSTEI 0
Bruno Navarro

Bruno Navarro

09/04/2009

ei kuhn, esse tutorial q vc fez... me diz uma coisa... vc vai colocar isso no seu curriculum como 'artigos publicados' ???? pretende fazer mestrado ou algo do tipo? abracao
GOSTEI 0
Giovane Kuhn

Giovane Kuhn

09/04/2009

[quote="bb"]ei kuhn, esse tutorial q vc fez... me diz uma coisa... vc vai colocar isso no seu curriculum como 'artigos publicados' ???? pretende fazer mestrado ou algo do tipo? abracao
Opa... na real estes tipos de artigos nem contam para a carreira acadêmica... escrevo estes tipos de artigos para auxiliar a comunidade Java e nada mais =DDD !!! Quanto ao mestrado, tem fortes pretensões !!!! Vamos ver se final do ano rola !!! Mas pq a curiosidade cara ??? []'s
GOSTEI 0
Bruno Navarro

Bruno Navarro

09/04/2009

eh pq meu chefe tah afim de fazer mestrado tb., e tah afim de fazer tipo umas pesquisas pra contar pontos saca., publicar artigos e etc... entao eu tava wondering se esse tutorial nao poderia ser na pratica um artigo publicado., soh isso :D
GOSTEI 0
Giovane Kuhn

Giovane Kuhn

09/04/2009

Eh... acho difícil artigos deste gênero auxiliarem o seu chefe para ingressar num mestrado !!! Mas sem sobra de dúvida auxiliariam e muito a galera Java :o :o :o !!! Flow
GOSTEI 0
Achilles Bisneto

Achilles Bisneto

09/04/2009

A pergunta vai parecer meio boba, mas tô me batento aqui. Se o campo CLOB não for um arquivo ou uma imagem, se for simplemente uma string gigantesca, um texto, eu até consigo pegar o campo clob e transformar de volta a string, mas quando servidor é linux essa string vem com caracteres estranhos no lugar da acentução ou cedilha, engraçado que o servidor no windows não acontece isso. Vocês podem me dar uma luz?
GOSTEI 0
Andre Valdestilhas

Andre Valdestilhas

09/04/2009

Tenho um campo Blob que gravo um objeto tipo HashMap, mas quando vou tentar recupera-lo da minha base de dados ele volta como um array de bytes, como faço pra converter devolta em um HashMap. Agradecido, André Valdestilhas
GOSTEI 0
Gustavo Gatto

Gustavo Gatto

09/04/2009

Cara deixa eu explicar o que eu quero fazer: eu tenho um site onde tem notícias, como o corpo desta notícia tem o tamanho maior que 4.000 caracteres tive que optar pelo campo CLOB, porém tenho um número grande de jornalistas na qual podem inserir notícias ao mesmo tempo... Eu sou iniciante em java e to completamente perdido em relação a isto. Eu preciso que os dados do formulário, inclusive a notícia tamanho família, vá para o banco de dados... Eu consegui inserir dados neste campo, apenas utilizando um arquivo texto com o conteúdo, mas não posso depender de um arquivo, pois podem haver mais de um jornalista naquele mesmo instante cadastrando uma notícia... E estas não podem se conflitar e nem gerar sobrecarregamento de E/S... O que preciso não é nada de outro mundo, mas tô emperrado nisto e não consigo solucionar... please, help-me!
GOSTEI 0
Gustavo Gatto

Gustavo Gatto

09/04/2009

GOSTEI 0
Giovane Kuhn

Giovane Kuhn

09/04/2009

[quote="firmao"]Tenho um campo Blob que gravo um objeto tipo HashMap, mas quando vou tentar recupera-lo da minha base de dados ele volta como um array de bytes, como faço pra converter devolta em um HashMap. Agradecido, André Valdestilhas
André, recomendo você utilizar a serialização do Java para colocar o [i]HashMap[/i] no campo BLOB do banco, para isto utilize a classe [i]ObjectOutputStream[/i], por exemplo:
// hashmap a ser gravado no banco
HashMap map = new HashMap();
ObjectOutputStream oout = new ObjectOutputStream(((BLOB) blob).getBinaryOutputStream());
oout.writeObject(map);
oout.close();
Para ler utilize classe [i]ObjectInputStream[/i], por exemplo:
// le hashmap do banco
ObjectInputStream oin = new ObjectInputStream(blob.getBinaryStream());
HashMap map = (HashMap) oin.readObject();
oin.close();
Eu não testei estes dois códigos, mas qq coisa só berra novamente, flow !
GOSTEI 0
Giovane Kuhn

Giovane Kuhn

09/04/2009

[quote="gustavogatto"]Cara deixa eu explicar o que eu quero fazer: eu tenho um site onde tem notícias, como o corpo desta notícia tem o tamanho maior que 4.000 caracteres tive que optar pelo campo CLOB, porém tenho um número grande de jornalistas na qual podem inserir notícias ao mesmo tempo... Eu sou iniciante em java e to completamente perdido em relação a isto. Eu preciso que os dados do formulário, inclusive a notícia tamanho família, vá para o banco de dados... Eu consegui inserir dados neste campo, apenas utilizando um arquivo texto com o conteúdo, mas não posso depender de um arquivo, pois podem haver mais de um jornalista naquele mesmo instante cadastrando uma notícia... E estas não podem se conflitar e nem gerar sobrecarregamento de E/S... O que preciso não é nada de outro mundo, mas tô emperrado nisto e não consigo solucionar... please, help-me!
Gustavo, Não precisas criar um arquivo e depois ler deste arquivo, podes trabalhar diretamente com [i]String[/i] que vem da sua tela de cadastro. Para inserir o valor no campo CLOB, no lugar da classe [i]FileReader[/i], utilize a classe [i]StringReader[/i]. Para ler o valor do campo CLOB para uma [i]String[/i], no lugar da classe [i]FileWriter[/i], utilize a classe [i]StringWriter[/i] e depois o método [i]getBuffer()[/i]. Qq coisa berra novamente, flow !!
GOSTEI 0
Giovane Kuhn

Giovane Kuhn

09/04/2009

[quote="Achilles"]A pergunta vai parecer meio boba, mas tô me batento aqui. Se o campo CLOB não for um arquivo ou uma imagem, se for simplemente uma string gigantesca, um texto, eu até consigo pegar o campo clob e transformar de volta a string, mas quando servidor é linux essa string vem com caracteres estranhos no lugar da acentução ou cedilha, engraçado que o servidor no windows não acontece isso. Vocês podem me dar uma luz?
Achilles, Este problema normalmente ocorre devido problemas de configuração do "charset" do seu banco de dados. Leia a respeito de "charset" ou "character encoding". QQ novidade posta pra gente aí !!!
GOSTEI 0
Gustavo Gatto

Gustavo Gatto

09/04/2009

[quote="brain"]Gustavo, Não precisas criar um arquivo e depois ler deste arquivo, podes trabalhar diretamente com [i]String[/i] que vem da sua tela de cadastro. Para inserir o valor no campo CLOB, no lugar da classe [i]FileReader[/i], utilize a classe [i]StringReader[/i]. Para ler o valor do campo CLOB para uma [i]String[/i], no lugar da classe [i]FileWriter[/i], utilize a classe [i]StringWriter[/i] e depois o método [i]getBuffer()[/i]. Qq coisa berra novamente, flow !!
Opa.. deu certo, mas estou com outro problema...
ERRO: java.lang.RuntimeException: java.io.IOException: Não serão lidos mais dados do soquete
Meu post segue no link: [url]http://www.javafree.org/javabb/viewtopic.jbb?t=851545[/url]
GOSTEI 0
Marcelo Luciano

Marcelo Luciano

09/04/2009

Pessoal, consegui fazer o Upload gravar no campo Blob, mas to com 2 probleminhas: 1 - nao está fazendo upload de arquivos maiores que 4kb (mesmo eu tendo alterado no file.setSizeMax(50*1024*1024); caso o arquivo tenha 5kb por exemplo, já não faz upload informando a seguinte exceção "tamanho dos dados maior que o tamanho máximo para este tipo: "tamanho do arquivo""; 2 - como fazer a recuperação desse arquivo que foi gravado como array de bytes e mostralo no formao original??; Alguém tem alguma ideia de como fazer ele aceitar arquivos de qualquer tamanho e recuperar o arquivo no BD????? obs:já coloquei (-1) no lugar do 50*1024*1024 e continua a mesma coisa!! Valew)
GOSTEI 0
Giovane Kuhn

Giovane Kuhn

09/04/2009

[quote="marceloplis"]Pessoal, consegui fazer o Upload gravar no campo Blob, mas to com 2 probleminhas: 1 - nao está fazendo upload de arquivos maiores que 4kb (mesmo eu tendo alterado no file.setSizeMax(50*1024*1024); caso o arquivo tenha 5kb por exemplo, já não faz upload informando a seguinte exceção "tamanho dos dados maior que o tamanho máximo para este tipo: "tamanho do arquivo""; 2 - como fazer a recuperação desse arquivo que foi gravado como array de bytes e mostralo no formao original??; Alguém tem alguma ideia de como fazer ele aceitar arquivos de qualquer tamanho e recuperar o arquivo no BD????? obs:já coloquei (-1) no lugar do 50*1024*1024 e continua a mesma coisa!! Valew)
Sua dúvida já esta no tópico [url]http://www.javafree.org/javabb/viewtopic.jbb?t=851658[/url] Flow.
GOSTEI 0
Racelike

Racelike

09/04/2009

Brain, por favor me ajuda ai eu estou com o mesmo problema do marceloplis acima, mas já desisti de fazer do jeito simples que estava fazendo. (usando setBytes direto sem inserir primeiro um campo vazio). o meu problema de fazer igual ao seu tutorial acima é já na consulta de seleção do registro, onde eu quero selcionar os campos blob. a consulta é equivalente a esta sua:
ResultSet rs = stmt.executeQuery("select cadastro, imagem, texto from teste where cadastro = 1 for update");
essa consulta retorna null. Já coloquei diretamente a consulta sql usando o sql tools ou o squirrel, e a mensagem que eles dão é ORA-01002: extração fora de seqüência voce sabe o que é isso? quando eu tiro o "for update" da consulta, ai nao dá problema. outra pergunta, no meu campo blob vai ter só arquivo .doc, .pdf, .rtf. vc recomenda alterar para Clob? se eu conseguir passar por esse erro, vou ter outras perguntas, mas por enquanto fica só nessas agradeço qualquer comentario
GOSTEI 0
Racelike

Racelike

09/04/2009

eu consegui resolver. o problema era so o autocommit que nao tava setado pra falso. e é isso que bloqueia a consulta, ate vc dar o commit manualmente, depois de mandar o arquivo
GOSTEI 0
Luís Matos

Luís Matos

09/04/2009

EhOlá Experimentei este método para inserir uma imagem em um blob e apareceu-me o seguinte erro: java.lang.NullPointerException Este é o código que estou a usar:

try {
    Statement stmt = conn.createStatement();

    ResultSet rs = stmt.executeQuery("select imagem from tabela where id = 1 for update");
    rs.next();

    // copia arquivo para campo blob
    Blob blob = rs.getBlob("logo");
    byte[] bbuf = new byte[1024];
    InputStream bin = new FileInputStream("c:\\imagem.jpg");
    OutputStream bout = ((BLOB) blob).getBinaryOutputStream(); // específico driver oracle
    // cout = clob.setCharacterStream(0);
    int bytesRead = 0;
    while ((bytesRead = bin.read(bbuf)) != -1) {
        bout.write(bbuf, 0, bytesRead);
    }
    bin.close();
    bout.close();
    
} catch (Exception e)
    {
      out.print(e);
    }
Alguem sabe qual e o problema? Obrigado Luís Matos
GOSTEI 0
Giovane Kuhn

Giovane Kuhn

09/04/2009

Eh[quote="luismatos"]Olá Experimentei este método para inserir uma imagem em um blob e apareceu-me o seguinte erro: java.lang.NullPointerException Este é o código que estou a usar:

try {
    Statement stmt = conn.createStatement();

    ResultSet rs = stmt.executeQuery("select imagem from tabela where id = 1 for update");
    rs.next();

    // copia arquivo para campo blob
    Blob blob = rs.getBlob("logo");
    byte[] bbuf = new byte[1024];
    InputStream bin = new FileInputStream("c:\\imagem.jpg");
    OutputStream bout = ((BLOB) blob).getBinaryOutputStream(); // específico driver oracle
    // cout = clob.setCharacterStream(0);
    int bytesRead = 0;
    while ((bytesRead = bin.read(bbuf)) != -1) {
        bout.write(bbuf, 0, bytesRead);
    }
    bin.close();
    bout.close();
    
} catch (Exception e)
    {
      out.print(e);
    }
Alguem sabe qual e o problema? Obrigado Luís Matos
Em qual linha que ocorreu o NPE ??? Tem certeza que o existe um registro com imagem no banco ??? Caso não tenha, pode estar retornando nulo o código abaixo:
rs.getBlob("logo");
Aguardando retorno, flow !
GOSTEI 0
Rafael

Rafael

09/04/2009

Não estou conseguindo dar um getClob no meu RS, ele da um erro... pq será??
GOSTEI 0
Daniel Martins

Daniel Martins

09/04/2009

Cola ae o StackTrace ae pra gente ver o erro :P
GOSTEI 0
Gerfferson Santos

Gerfferson Santos

09/04/2009

Olá... Estou precisando ler um arquivo blob do oracle e gravar no disco... O arquivo é do tipo "txt". Atenciosamente, Gerfferson Santos.
GOSTEI 0
Giovane Kuhn

Giovane Kuhn

09/04/2009

[quote="gerfferson"]Olá... Estou precisando ler um arquivo blob do oracle e gravar no disco... O arquivo é do tipo "txt". Atenciosamente, Gerfferson Santos.
Ué, o tutorial não te ajudou ??? Leia com atenção pq ele faz exatamente isso, ler um clob do banco e criar um arquivo txt e ler um blob e criar um jpg. Flow
GOSTEI 0
Leonardo Coelho

Leonardo Coelho

09/04/2009

vc esta referenciando um objeto que esta nulo ou não foi inicializado
GOSTEI 0
Leonardo Coelho

Leonardo Coelho

09/04/2009

Como utilizo o MySQL, claro que a linha de comando: OutputStream bout = ((BLOB) blob).getBinaryOutputStream(); não será possivel, assim qual a solução para utilizar no MySQL? Eu só quero inserir uma imagem no banco e depois acessa-la, ela pode vim em ImageIcon. O que posso fazer ?
GOSTEI 0
Leonardo Coelho

Leonardo Coelho

09/04/2009

Gostaria de saber o tamanho em bytes do tipo blob.....alguem poderi me ajudar? tipo...um char é 1 byte.....um int são 4 bytes.... o tipo blob é quanto????
GOSTEI 0
Leonardo Coelho

Leonardo Coelho

09/04/2009

[quote="Leticia_Nayara"]Gostaria de saber o tamanho em bytes do tipo blob.....alguem poderi me ajudar? tipo...um char é 1 byte.....um int são 4 bytes.... o tipo blob é quanto????
Oi, o BLOB tem o tamanho que voce quiser :-) falando sério, o BLOB tem tamanho variável, dependendo do que estiver armazenado nele e da implementação do banco de dados. É semelhante a um String. [quote="documentação do BLOB no MySQL"]...A BLOB is a binary large object that can hold a [b]variable[/b] amount of data...
[]]
GOSTEI 0
Leonardo Coelho

Leonardo Coelho

09/04/2009

tá.....mas no caso de eu estar armazenando uma imagem jpg nele...qual seria esse tamanho...... preciso estipular qnt espaço o banco ira ocupar....e estou utiliando um campo do tipo blob.....
GOSTEI 0
Leonardo Coelho

Leonardo Coelho

09/04/2009

se colocar uma imagem de 1MB ele vai ocupar 1MB... se a imagem for de 30kb, vai ocupar 30kb... na prática ele deve ocupar uns bytes a mais pra controle interno, mas comparado com o tamanho do arquivo armazenado, dá pra desprezar esse valor...
GOSTEI 0
Leonardo Coelho

Leonardo Coelho

09/04/2009

[quote="brain"]Segue fonte completo:
Olá brain, Não estou conseguindo baixar o arquivo exemplo que você anexou. Tem como verificar? Grato Siberiun
GOSTEI 0
Leonardo Coelho

Leonardo Coelho

09/04/2009

Galera, se em vez de imagens eu preciso armazenar videos, pois sera uma pagina com varios videos pequeno(quadros) e qdo eu clicar vai rodar em tamanho maior... Posso usar a mesma logica do exemplo citado? valeu
GOSTEI 0
Leonardo Coelho

Leonardo Coelho

09/04/2009

Pessoal.. Vocês poderiam me ajudar com meu problema também? http://javafree.uol.com.br/viewtopic.jbb?t=887107
GOSTEI 0
Leonardo Coelho

Leonardo Coelho

09/04/2009

Pessoal, usando um map com os campos vindos de um formulário preencho um relatório, salvo em pdf e gravo esse pdf num campo blob na tabela. A pergunta é: Tem como recuperar o conteúdo dos campos que eu preenchi o pdf gravado ?
GOSTEI 0
Leonardo Coelho

Leonardo Coelho

09/04/2009

bom hein. tava precisando :D
GOSTEI 0
POSTAR