INNER JOIN e OUTER JOIN
Se você é um DBA ou trabalha com programação e usa muito banco de dados SQL, este tópico não é você, a menos que não conheça as cláusulas INNER JOIN e OUTER JOIN, muito comuns em SGBDs SQL.
Não sabe o que é SGBD? Os Sistemas de Gerenciamento de Banco de Dados são programas que auxiliam na manipulação dos dados armazenados. O MySQL, PostgreSQL, MS SQL Server ou Oracle, que você tem em sua casa ou empresa, são SGBDs.
Para este tutorial você precisa ter, pelo menos, o conhecimento básico de SQL.
Chega de enrolação, vamos ao que interessa!
INNER JOIN
A cláusula INNER JOIN é usada quando se quer recuperar dados em mais de uma tabela através da igualdade de suas foreign keys.
Por exemplo, pense que você quer criar um programa que sirva como agenda telefônica. Você cadastra as pessoas e os contatos telefônicos, como uma pessoa poderá ter nenhum até N números de telefones, você separa em duas entidades como neste modelo:

Olhando rapidamente você percebe que a entidade TELEFONE tem o atributo NUM_PESSOA que é o número de identificação do registro na entidade PESSOA.
Vamos supor que as tabelas estejam preenchidas com os seguintes registros:
+----------------------------+ +---------------------------+ | PESSOA | | CONTATO | +------------+---------------+ +------------+--------------+ | NUM_PESSOA | NOME_PESSOA | | NUM_PESSOA | NUM_TELEFONE | | 1 | José | | 1 | 6589-3666 | | 2 | Arnaldo | | 3 | 9888-6699 | | 3 | Maria | | 3 | 8956-6666 | | 4 | Elverdelando | | 2 | 2888-9877 | +------------+---------------+ +------------+--------------+
Para recuperar os telefones de uma pessoa você deve usar INNER JOIN, implicitamente ou explicitamente. Vou explicar os dois casos.
O método explícito é usar a cláusula explicitamente (duh!), que é formada por INNER JOIN <tabela filha> ON <atributos de identificação>;, no nosso exemplo:
SELECT nome_completo, num_telefone FROM pessoa INNER JOIN telefone ON pessoa.num_pessoa = telefone.num_pessoa.
Estamos dizendo: "me traga o nome e os números dos telefones da pessoa onde o número da pessoa em TELEFONE seja igual ao número da pessoa em PESSOA".
Na forma implícita você não usa a cláusula INNER JOIN, o tratamento de igualdade é feito na cláusula WHERE. Usando o mesmo exemplo:
SELECT nome_completo, num_telefone FROM pessoa, telefone WHERE pessoa.num_pessoa = telefone.num_pessoa.
Em ambos os casos o resultado será:
+---------------------------+ | CONTATO | +------------+--------------+ | NOM_PESSOA | NUM_TELEFONE | | José | 6589-3666 | | Maria | 9888-6699 | | Maria | 8956-6666 | | Arnaldo | 2888-9877 | +------------+--------------+
"Que simples! Hummmm espera um pouco... Onde está o Elverdelando (que nominho, hein?!)."
Lembra que dissemos ao banco "me traga o nome e os números dos telefones da pessoa onde o número da pessoa em TELEFONE seja igual ao número da pessoa em PESSOA"? Então! O Elverdelando não esta na tabela TELEFONE, por isso não foi retornado no resultado.
Quando você precisar retornar os dados, mesmo não estando na tabela filha, usamos o OUTER JOIN.
OUTER JOIN
A clásula OUTER JOIN tem duas variantes, LEFT OUTER JOIN e RIGHT OUTER JOIN, sendo que estas cláusula devem ser declaradas sempre de maneira explícita. Alguns SGBDs facilitam a utilização do OUTER JOIN te polpando de declarar toda a sintaxe, como é o caso do Oracle que utiliza o símbolo (+) na identificação fraca, mas NÃO É ANSI, ou seja, não funcionará em qualquer SGBD.
Para que apareçam todas as pessoas, inclusive as que não têm número de telefone cadastrado usamos LEFT OUTER JOIN ou RIGHT OUTER JOIN.
A sintaxe é parecida com a INNER JOIN, mudando apenas o nome da cláusula: LEFT[RIGHT] OUTER JOIN <tabela filha> ON <atributos de identificação>;
Assim, faremos...
SELECT nome_completo, num_telefone FROM pessoa LEFT OUTER JOIN telefone ON pessoa.num_pessoa = telefone.num_pessoa
ou então
SELECT nome_completo, num_telefone FROM telefone RIGHT OUTER JOIN pessoa ON pessoa.num_pessoa = telefone.num_pessoa
Em ambos os casos o resultado será:
+-----------------------------+ | CONTATO | +--------------+--------------+ | NOM_PESSOA | NUM_TELEFONE | | José | 6589-3666 | | Maria | 9888-6699 | | Maria | 8956-6666 | | Arnaldo | 2888-9877 | | Elverdelando | | +--------------+--------------+
Como você pôde perceber, a única diferença entre o LEFT OUTER JOIN e o RIGHT OUTER JOIN é só a indicação de qual é o lado forte do relacionamento, ou seja, em qual lado da cláusula está a tabela que pode não ter valores na tabela filha. No nosso caso, ambos apontando para a tabela PESSOA.
INNER JOIN é muito usado para consulta em banco de dados, quase que totalmente em seu método implícito. Já as cláusulas OUTER JOIN também são usadas, mas não com tanta frequência, posso dizer que é raro encontra-las por aí.
Espero que este tutorial os ajude!
Inserindo arquivos em campos BLOB de uma base de dados
Para inserir um arquivo, seja ele de qualquer formato, é necessário utilizar o método setBinaryStream implementado pelo PreparedStatement.
PreparedStatemente.setBinaryStream(int índice, Inputstream is, int tamanho);
Para o exemplo definimos uma tabela chamada arquivo que contém um campo BLOB chamado ARQ.
//Conexão normal, como qualquer JDBC
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@<IP>:<PORTA>:<SID>","<USUARIO>","<SENHA>");
//Acessando o arquivo a ser enviado e recuperando um InputStream
File arquivo = new File("<CAMINHO_COMPLETO_DO_ARQUIVO>");
FileInputStream fis = new FileInputStream(arquivo);
//Preparando a chamada normalmente
PreparedStatement ps = conn.prepareStatement("INSERT INTO ARQUIVO (arq) VALUES(?)");
//Informando o inputstream e o tamanho do arquivo a ser enviado
ps.setBinaryStream(1, fis, (int)arquivo.length());
ps.execute();
ps.close();
conn.close();
No meu teste utilizei a base de dados Oracle 8i. Como não tenho à disposição uma base MySQL/PostgreSQL/MS SQL Server, deixo vocês encarregados de testar e me enviar os resultados, OK?
[]'s e até a próxima!
Recuperando cursor de uma procedure do Oracle no Java
Percebi que muitas pessoas acabam chegando aqui pesquisando uma forma de recuperar cursores através de procedures do Oracle, para estas pessoas eu coloco à disposição uma maneira de fazê-lo.
Para que o cursor possa ser retornado é preciso declara-lo como REF CURSOR no spec da Package.
--Criando o tipo REF CURSOR que será o cursor type g_cursor is ref cursor;
Em ambos, spec e body, você precisa declarar uma variável out do tipo do REF CURSOR mencionado acima.
procedure PRO_RETORNA_LISTA_CARROS(
i_id in tbl_car.car_id%type,
o_cursor in out g_cursor);
Para devolver o cursor com os resultados (caso haja), é necessário abri-lo no body da procedure, desta forma:
open o_cursor for
select car_id, company, model, color, hp, price
from tbl_car
where car_id = i_id;
A Package completa ficará assim:
create or replace package PAC_CURSOR is
--Criando o tipo REF CURSOR que será o cursor
type g_cursor is ref cursor;
--Procedure que retornará o cursor
procedure PRO_RETORNA_LISTA_CARROS(
i_id in tbl_car.car_id%type,
o_cursor in out g_cursor); -- Nosso cursor
end PAC_CURSOR;
/
create or replace package body PAC_CURSOR is
procedure PRO_RETORNA_LISTA_CARROS(
i_id in tbl_car.car_id%type,
o_cursor in out g_cursor) is
begin
--Abrindo o cursor para retornar os valores
open o_cursor for
select car_id, company, model, color, hp, price
from tbl_car
where car_id = i_id;
end PRO_RETORNA_LISTA_CARROS;
end PAC_CURSOR;
Temos o lado do Oracle pronto, agora precisamos tratar a chamada no Java.
Como o cursor está sendo retornado por uma procedure, usaremos um java.sql.CallableStatement.
CallableStatement cs = conn.prepareCall("{call PAC_CURSOR.PRO_RETORNA_LISTA_CARROS(?,?)}");
O registerOutParameter receberá o tipo oracle.jdbc.OracleTypes.CURSOR e retornará um java.sql.ResultSet. Iteraremos o ResultSet do mesmo modo que iteramos um Iterator.
Cada coluna retornada pelo SELECT será representado como um mapa, usando o getter correpondente. Por exemplo, chamaremos o método getString(<nome coluna>) quando retornar um varchar, getDate(<nome coluna>) quando retornar um date e etc.
O código completo fica assim:
//Chamando o procedure
CallableStatement cs = conn.prepareCall("{call PAC_CURSOR.PRO_RETORNA_LISTA_CARROS(?,?)}");
//Definindo o tipo do retorno, no caso o cursor
cs.registerOutParameter("o_cursor", OracleTypes.CURSOR);
cs.setLong("i_id", id);
cs.execute();//Executando a chamada
//Recuperando o cursor como um Resultset
ResultSet rs = (ResultSet)cs.getObject("o_cursor");
//Iterando as linhas retornadas
while(rs.next()){
//Obtendo o valor das colunas
System.out.println("ID: " + rs.getLong("car_id"));
System.out.println("Marca: " + rs.getString("company"));
System.out.println("Modelo: " + rs.getString("model"));
System.out.println("Cor: " + rs.getString("color"));
System.out.println("HP: " + rs.getString("hp"));
System.out.println("Preco: " + rs.getFloat("price"));
}
No final você conseguirá obter qualquer valor retornado em um SELECT.
Até a próxima!
Recuperando coleção de objetos de uma procedure Oracle
Como prometido há muito tempo atrás (e bota tempo nisso) no tópico "Passando Objetos Java para uma Procedure do Oracle", mostrarei como recuperar objetos Java contendo uma coleção de outro objetos através de uma procedure do Oracle. Para quem não leu, é altamente recomendado ler o post anterior.
Para este tutorial, precisamos incluir a tabela TBL_CLASS e adicionar sua primary key como foreign key na TBL_USER.
--num class será a PK e o desc_class será a descrição create table TBL_CLASS (num_class number, desc_class varchar(100)); alter table TBL_CLASS add primary key(num_class); alter table TBL_USER add num_class number; alter table TBL_USER add constraint FK_CLASS foreign key(num_class) references tbl_class(num_class);
Agora precisamos incluir os novos tipos:
create or replace type class_type as object (num_class number, desc_class varchar2(100), users arr_users); / create or replace type arr_class as table of class_type; /
O tipo class_type será o objeto. Repare que na sua assinatura incluímos o tipo arr_users, que será nossa coleção de user_type (vide post anterior), o tipo arr_class será uma coleção de class_type.
Agora incluiremos a procedure responsável por retornar nossa coleção de class_type.
procedure pro_select_class(clas in class_type, class_return in out arr_class)is
class_ref_cur ref_cur;
--Coleção de classes
classes arr_class := arr_class();
begin
open class_ref_cur for
select cast(
multiset(
select num_class,
desc_class,
(select cast(
multiset(
select user_name,
height,
b_date
from tbl_user
--Fazendo o JOIN com a TBL_USER
where tbl_user.num_class = tbl_class.num_class
) as arr_users)
from dual) users
from tbl_class
--Usando o atributo num_class do parâmetro de entrada
where num_class = clas.num_class) as arr_class
) classes
from dual;
--incluindo ao retorno no array
fetch class_ref_cur into classes;
--passando o array para a variável out
class_return := classes;
end pro_select_class;
Repare que a procedure recebe o tipo class_type como parâmetro in e retorna o tipo arr_class.
Separando trecho responsável por resgatar e montar nossos objetos teremos:
--Montará a coleção de retorno
select cast(
multiset(
--Retornará os objetos class_type e seus atributos
select num_class,
desc_class,
--Populará a coleção com user_type
(select cast(
multiset(
select user_name,
height,
b_date
from tbl_user
where tbl_user.num_class = tbl_class.num_class
) as arr_users)
from dual) users
from tbl_class
where num_class = clas.num_class) as arr_class
) classes
from dual;
Os objetos de banco estão prontos, agora para o Java!
Criaremos o objeto que será interpretado pelo Oracle. Vamos chamá-lo de TypeClass:
public class TypeClass implements SQLData{
public static final String ORACLE_OBJECT_NAME = "CLASS_TYPE"; //Nome do tipo no Oracle
public static final String ORACLE_CLASS_ARRAY_NAME = "ARR_CLASS"; //Nome do array no Oracle
//campos criados na tabela TBL_CLASS
private Long number;
private String desc;
private Array users; //Esta será a coleção de user_type (ou TypeUser no Java)
public String getSQLTypeName() throws SQLException {
return ORACLE_OBJECT_NAME;
}
public void readSQL(SQLInput stream, String typeName) throws SQLException {
setNumber(stream.readLong());
setDesc(stream.readString());
setUsers(stream.readArray());//Usado pelo JDBC driver para ler a coleção
}
public void writeSQL(SQLOutput stream) throws SQLException {
stream.writeLong(getNumber());
stream.writeString(getDesc());
stream.writeArray(getUsers());//Usado pelo JDBC driver para armazenar a coleção
}
//Getters e setters omitidos
}
Precisamos mapear os dois tipos já que ambos serão interpretados na requisição, desta forma:
Map> typeMaps = connection.getTypeMap(); typeMaps.put(TypeUser.ORACLE_OBJECT_NAME, TypeUser.class); typeMaps.put(TypeClass.ORACLE_OBJECT_NAME, TypeClass.class);
Também precisaremos mapear os dois arrays:
typeMaps.put(TypeClass.ORACLE_CLASS_ARRAY_NAME, TypeClass[].class);//retornado pela procedure typeMaps.put(TypeUser.ORACLE_USER_ARRAY_NAME, TypeUser[].class);//retornado pela coleção de class_type
Para a requisição faremos:
cs = conn.prepareCall("{call PAC_BEAN.PRO_SELECT_CLASS(?,?)}");
//registrando o tipo de saída, que será um array de TypeClass
cs.registerOutParameter("class_return", OracleTypes.ARRAY, TypeClass.ORACLE_CLASS_ARRAY_NAME);
//passando o objeto de parâmetros da query
cs.setObject("clas", classQry);
cs.execute();
//recuperando e iterando o array de TypeClass
Object[] array = (Object[])cs.getArray("class_return").getArray();
for(Object obj : array){
TypeClass objClass = ((TypeClass)obj);
System.out.println("Description: "+objClass.getDesc());
//Aqui pegamos a coleção (ou array) de user_type(TypeUser) retornada pela query.
Object[] userArray = (Object[])objClass.getUsers().getArray();
for(Object user : userArray){
System.out.println("\tName: " + ((TypeUser)user).getName());
System.out.println("\tHeight: " + ((TypeUser)user).getHeight());
System.out.println("\tBirth: " + sdf.format(((TypeUser)user).getBirth())+ "\r\n");
}
}
O final você terá um TypeClass que contém um java.sql.Array de TypeUser no atributo getUsers().
E aqui cumpro minha promessa. Baixe o código fonte com o exemplo contendo este e o post anterior aqui.
Até a próxima!
Tutorial iBatis, aprendendo o básico
Geralmente quando se fala sobre frameworks de persistência logo vem à cabeça Hibernate/JPA. Há pouco tempo fui apresentado ao iBatis, um framework que eu, particularmente, acho muito fácil de instalar, configurar e usar. Você pode baixa-lo através do site de sua mantenedora, a Apache, clicando aqui.
Neste tutorial usarei a versão 2.3.4 build 726.
Configurando o iBatis
Ao contrário dos demais frameworks, para configurar o iBatis você só precisa de um arquivo XML de configuração, chamado SqlMapConfig.
As principais seções do xml são:
<properties resource="tuto/ibatis/config/SqlMap.properties"/>
Este código é opcional e serve para indicar o arquivo properties com as variável de configuração que serão usada no XML.
<typeAlias alias="car" type="tuto.ibatis.beans.Car"/>
Indica o Bean utilizado e qual será seu aliás. Você pode configurar várias linhas, tudo depende da complexidade e necessidade da sua modelagem.
No nosso exemplo, usaremos o bean Car abaixo:
public class Car {
private Long carId;
private String company;
private String model;
private String color;
private Integer hp;
private Float price;
//Setters e getters omitidos
}
<transactionManager type="JDBC">
<dataSource type="SIMPLE">
<property name="JDBC.Driver" value="${driver}"/>
<property name="JDBC.ConnectionURL" value="${url}"/>
<property name="JDBC.Username" value="${username}"/>
<property name="JDBC.Password" value="${password}"/>
</dataSource>
</transactionManager>
Parâmetros usados na conexão com o banco. As variáveis ${driver}, ${url}, ${username} e ${password} estão definidas no arquivo .properties indicado na seção properties. Se você preferir, também pode colocar os valores diretamente nos campos, sem precisar defini-los em outro local.
Veja como será arquivo completo:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMapConfig PUBLIC "-//ibatis.apache.org//DTD SQL Map Config 2.0//EN"
"http://ibatis.apache.org/dtd/sql-map-config-2.dtd">
<sqlMapConfig>
<properties resource="tuto/ibatis/config/SqlMap.properties"/>
<settings
cacheModelsEnabled="true"
enhancementEnabled="true"
lazyLoadingEnabled="true"
maxRequests="32"
maxSessions="10"
maxTransactions="5"
useStatementNamespaces="false" />
<typeAlias alias="car" type="tuto.ibatis.beans.Car"/>
<transactionManager type="JDBC">
<dataSource type="SIMPLE">
<property name="JDBC.Driver" value="${driver}"/>
<property name="JDBC.ConnectionURL" value="${url}"/>
<property name="JDBC.Username" value="${username}"/>
<property name="JDBC.Password" value="${password}"/>
</dataSource>
</transactionManager>
<sqlMap resource="tuto/ibatis/sqlmaps/CarSqlMap.xml"/>
</sqlMapConfig>
O properties tem o seguinte conteúdo:
driver=oracle.jdbc.OracleDriver url=jdbc:oracle:thin:@<host>:<porta>:<sid> username=<login> password=<senha>
Em seguida devemos configurar nosso SqlMap. Este XML conterá as querys utilizadas na aplicação e deverá ter o nome do descrito na seçao sqlMap do SqlMapConfig, no nosso caso será CarSqlMap.xml.
No nosso exemplo apenas veremos a utilização das tags select, insert, update e delete.
<select id="getCars" resultClass="tuto.ibatis.beans.Car"
parameterClass="java.lang.Long">
SELECT COMPANY as company,
MODEL as model,
COLOR as color,
HP as hp,
PRICE as price
FROM TBL_CAR
WHERE CAR_ID = #var#
</select>
Executa o select podendo retornar uma única linha ou uma coleção, o tipo retornado é o mesmo especificado no atributo resultClass, o parameterClass é o tipo passado para executar a query e o id é a identificação para chamada da query.
Usaremos o SqlMap abaixo:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
"http://ibatis.apache.org/dtd/sql-map-2.dtd">
<sqlMap namespace="Car">
<select id="getCars" resultClass="tuto.ibatis.beans.Car"
parameterClass="java.lang.Long">
SELECT COMPANY as company,
MODEL as model,
COLOR as color,
HP as hp,
PRICE as price
FROM TBL_CAR
WHERE CAR_ID = #var#
</select>
<insert id="addCar" parameterClass="tuto.ibatis.beans.Car">
INSERT INTO TBL_CAR (CAR_ID, COMPANY, MODEL, COLOR, HP, PRICE)
VALUES (#carId#, #company#, #model#, #color#, #hp#, #price#)
</insert>
<delete id="delCar" parameterClass="java.lang.Long">
DELETE FROM TBL_CAR WHERE CAR_ID = #var#
</delete>
<update id="updCar" parameterClass="tuto.ibatis.beans.Car">
UPDATE TBL_CAR
SET COMPANY = #company#,
MODEL = #model#,
COLOR = #color#,
HP = #hp#,
PRICE = #price#
WHERE CAR_ID = #carId#
</update>
</sqlMap>
Agora que já configuramos o acesso ao banco de dados e o mapeamento dos objetos, vamos implementar a classe singleton que usaremos como SqlMapClient, aqui nós a chamaremos de OracleMapConfig.
package tuto.ibatis.connection;
import java.io.Reader;
import com.ibatis.common.resources.Resources;
import com.ibatis.sqlmap.client.SqlMapClient;
import com.ibatis.sqlmap.client.SqlMapClientBuilder;
public class OracleMapConfig {
private static final SqlMapClient sqlMapClient;
static{
try{
//Definindo o caminho para o SqlMapConfig e criando o reader
String res = "tuto/ibatis/config/SqlMapConfig.xml";
Reader reader = Resources.getResourceAsReader(res);
//Recuperando o client para o SqlMap
sqlMapClient = SqlMapClientBuilder.buildSqlMapClient(reader);
} catch(Exception e){
e.printStackTrace();
throw new RuntimeException(e);
}
}
//Método usado para recuperar o client
public static SqlMapClient getSqlMapClient(){
return sqlMapClient;
}
}
O client é responsável por executar as querys configuradas no SqlMap e nos fornecer os resultados.
Executando Querys e tratando retornos
Para chamar uma das querys é muito simples, apenas chame o cliente e o método correspondente de cada uma.
O select pode ser chamado da seguinte forma:
OracleMapConfig.getSqlMapClient().queryForObject("<id>", <parâmetro>);
O iddeve ser o id correspondente no SqlMap
O código acima tráz apenas uma linha retornada pela query, para trazer todas apenas troque o método para o queryForList, desta forma:
OracleMapConfig.getSqlMapClient().queryForList("<id>", <parâmetro>);
Assim, será retornado uma Collection contendo os objetos.
Vamos aos exemplos:
Select
try{
Car car = (Car)OracleMapConfig.getSqlMapClient().queryForObject("getCars",
new Long(readKeyboard()));
System.out.println("Marca: "+car.getCompany());
System.out.println("Modelo: "+car.getModel());
System.out.println("Cor: "+car.getColor());
System.out.println("HP: "+car.getHp());
System.out.println("Preço: "+car.getPrice());
}catch (Exception e) {
e.printStackTrace();
}
O id "getCars" é o que definimos nos atributos do select do SqlMap, passando um Long e recuperando o tipo Car, ambos também definidos na linha <select id="getCars" resultClass="tuto.ibatis.beans.Car" parameterClass="java.lang.Long">.
Insert
try{
OracleMapConfig.getSqlMapClient().insert("addCar", newCar);
}catch (Exception e) {
e.printStackTrace();
}
Agora passamos como parâmetros o próprio tipo Car, <insert id="addCar" parameterClass="tuto.ibatis.beans.Car"> e chamamos os métodos usando cerquilha (#, ou jogo-da-velha se preferirem), desta forma:
INSERT INTO TBL_CAR (CAR_ID, COMPANY, MODEL, COLOR, HP, PRICE) VALUES (#carId#, #company#, #model#, #color#, #hp#, #price#)
Delete
try{
int lines = OracleMapConfig.getSqlMapClient().delete("delCar",
new Long(readKeyboard()));
System.out.println(lines + " linhas excluídas");
}catch (Exception e) {
e.printStackTrace();
}
O método delete do client retorna um tipo int que representa a quantidade de linhas excluídas.
Update
try{
int lines = OracleMapConfig.getSqlMapClient().update("updCar", car);
System.out.println(lines + " carros incluídos");
}catch (Exception e) {
e.printStackTrace();
}
O Update retorna um tipo int, que informa a quantidade de linhas afetadas pelo update.
Como você pode ver, com apenas três arquivos XML e três classes conseguimos montar um sistema de manutenção de estoque de consulta de preço basico.
Você pode fazer o download com os fontes deste tutorial clicando aqui.
Até a próxima!