Bem-vindos ao blog Preciso Estudar Sempre. Meu nome é João Paulo Maida e minha paixão é estudar.
Hoje iremos ousar, hoje iremos ostentar no camarote. Se você lembrou da imagem abaixo, sim, você está certo.
Figura 1 - O rei do camarote |
Se você é novo por aqui, bote seu email na caixa de texto para se inscrever no blog (clique aqui para ver como) ou curta a página do facebook, o link está no fim do post.
Voltando.....
Primeiro, precisamos entender como funciona um método que está na camada DAO. Se você não sabe o que é DAO, clique aqui ou dê uma olhada nas referências.
Figura 2 - O funcionamento de um método da camada DAO |
- Abertura de conexão.
- Configuração do commit automático.
- Criação do statement.
- Passagem de parâmetros para statement.
- Execução do statement.
- Commit da transação.
- Rollback da transação (em caso de erro).
- Encerramento da conexão.
E o que não se repete ? O que é diferente de um método para o outro ?
- Query SQL
- Parâmetros
A resposta é simples. Temos que ter um bom projeto orientado a objetos onde, nossa meta é a reusabilidade de código.
----------------------------------------------------------------------------------------------------------
AVISO: É necessário o conhecimento dos seguintes assuntos para que, tenhamos um total aproveitamento do assunto:
- Java
- Orientação a objetos
- Java + JDBC
- SQL
- UML
----------------------------------------------------------------------------------------------------------
Vamos modelar nossa solução.
Vamos modelar nossa solução.
Figura 3 - Diagrama de classes |
Essa superclasse possui os métodos que realizam as operações de banco (insert, update, delete, select) de forma genérica, configura os parâmetros da query, abrem conexão e encerram conexão. Como existem muitos métodos nessa classe, farei comentários pontuais sobre alguns.
- insertUpdateDelete - Método responsável pelas operações de escrita (insert, update e delete). Consegue desempenhar a função genérica porque recebe como parâmetros, a query e os parâmetros da query e porque utiliza o método receiveParameters o qual, realiza a configuração dos parâmetros no objeto Statement. O índice dos parâmetros é conservado pela sua posição na lista.
- receiveParameters - Método responsável pela configuração dos parâmetros da query recebidos no objeto Statement. Essa configuração é feita através de uma estrutura condicional que verifica o tipo da instância que está na lista de parâmetros. Uma vez que é determinado o tipo, o método correto do objeto Statement pode ser invocado. O índice dos parâmetros é conservado pela sua posição na lista.
- findAll - Método responsável por listar registros de acordo com a query passada. O mapeamento dos dados do objeto ResultSet em objetos do tipo Funcionario acontece dentro da iteração do primeiro objeto e é feito através da implementação do método mapping.
- findById - Método responsável por listar somente 1 registro de acordo com a query passada. Assim como no método acima, o mapeamento também é feito através do método mapping. Contudo, é necessário retornar sempre o primeiro registro portanto, uma verificação de cursor é feita em cima do retorno da query. Caso a query não tenha retornado nada, o objeto ResultSet se torna nulo e o método retorna um objeto zerado. Caso contrário, o cursor é movido em 1 posição e os dados são obtidos.
public void insertUpdateDelete(String sql, List<Object> parametros){
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
connection = this.openConnection();
connection.setAutoCommit(false);
preparedStatement = connection.prepareStatement(sql);
this.receiveParameters(preparedStatement, parametros);
preparedStatement.execute();
connection.commit();
} catch (SQLException ex) {
ex.printStackTrace();
if(connection != null){
try {
connection.rollback();
} catch (SQLException sqle) {
sqle.printStackTrace();
}
}
} finally {
this.closeConnection(connection, preparedStatement);
}
}
private void receiveParameters(PreparedStatement preparedStatement, List<Object> parametros) throws SQLException{
int paramPos = 1;
for(Object parametro : parametros){
if(parametro == null){
preparedStatement.setNull(paramPos, 1);
} else if(parametro instanceof Integer){
preparedStatement.setInt(paramPos, (Integer) parametro);
} else if(parametro instanceof Long){
preparedStatement.setLong(paramPos, (Long) parametro);
} else if(parametro instanceof Float){
preparedStatement.setFloat(paramPos, (Float) parametro);
} else if(parametro instanceof Double){
preparedStatement.setDouble(paramPos, (Double) parametro);
} else if(parametro instanceof Date){
preparedStatement.setDate(paramPos, new java.sql.Date(((Date) parametro).getTime()));
} else if(parametro instanceof String){
preparedStatement.setString(paramPos, parametro.toString());
}
paramPos++;
}
}
public List findAll(String sql, List<Object> parametros, RowMapping mapping){
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
List rows = new ArrayList();
try {
connection = this.openConnection();
connection.setAutoCommit(false);
preparedStatement = connection.prepareStatement(sql);
this.receiveParameters(preparedStatement, parametros);
resultSet = preparedStatement.executeQuery();
while(resultSet.next()){
rows.add(mapping.mapping(resultSet));
}
connection.commit();
} catch (SQLException ex) {
ex.printStackTrace();
if(connection != null){
try {
connection.rollback();
} catch (SQLException sqle) {
sqle.printStackTrace();
}
}
} finally {
this.closeConnection(connection, preparedStatement, resultSet);
}
return rows;
}
public Object findById(String sql, List<Object> parametros, RowMapping mapping){
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
Object row = null;
try {
connection = this.openConnection();
connection.setAutoCommit(false);
preparedStatement = connection.prepareStatement(sql);
this.receiveParameters(preparedStatement, parametros);
resultSet = preparedStatement.executeQuery();
resultSet = resultSet.isBeforeFirst() ? resultSet : null;
if(resultSet != null){
resultSet.next();
}
row = mapping.mapping(resultSet);
connection.commit();
} catch (SQLException ex) {
ex.printStackTrace();
if(connection != null){
try {
connection.rollback();
} catch (SQLException sqle) {
sqle.printStackTrace();
}
}
} finally {
this.closeConnection(connection, preparedStatement, resultSet);
}
return row;
}
}
FuncionarioDAO
Esta é uma subclasse de GenericDAO. Utiliza os métodos da classe pai para persistir ou recuperar dados do banco de dados relacional. Os métodos de escrita são bem simples, englobam somente a criação da query e a adição dos parâmetros na lista. Os métodos que recuperam dados utilizam um recurso muito prático do Java, a implementação de interfaces em métodos. Dessa forma, eu não preciso criar uma classe nova a qual, implementa RowMapping. Eu posso passar diretamente a implementação da interface como um parâmetro do método. O método definido na interface RowMapping é o responsável pelo mapeamento ResultSet - Funcionario.
public void insert(Funcionario funcionario){
String sql = "INSERT INTO funcionario ("
+ "NM_FUNCIONARIO, "
+ "EM_FUNCIONARIO,"
+ "DT_NASCIMENTO_FUNCIONARIO,"
+ "MAT_FUNCIONARIO,"
+ "NM_LOGRADOURO,"
+ "NUM_LOGRADOURO,"
+ "NM_BAIRRO"
+ ") VALUES (?,?,?,?,?,?,?)";
List<Object> parametros = new ArrayList<>();
parametros.add(funcionario.getNome());
parametros.add(funcionario.getEmail());
parametros.add(funcionario.getDataNascimento());
parametros.add(funcionario.getMatricula());
parametros.add(funcionario.getLogradouro());
parametros.add(funcionario.getNumero());
parametros.add(funcionario.getBairro());
super.insertUpdateDelete(sql, parametros);
}
public List<Funcionario> findAll(){
String sql = "SELECT * FROM FUNCIONARIO";
List<Object> parametros = new ArrayList<>();
return super.findAll(sql, parametros, new RowMapping<Funcionario>() {
@Override
public Funcionario mapping(ResultSet resultSet) throws SQLException{
Funcionario funcionario = new Funcionario();
if(resultSet != null){
funcionario.setId(resultSet.getLong("ID"));
funcionario.setNome(resultSet.getString("NM_FUNCIONARIO"));
funcionario.setEmail(resultSet.getString("EM_FUNCIONARIO"));
funcionario.setDataNascimento(resultSet.getDate("DT_NASCIMENTO_FUNCIONARIO"));
funcionario.setMatricula(resultSet.getString("MAT_FUNCIONARIO"));
funcionario.setLogradouro(resultSet.getString("NM_LOGRADOURO"));
funcionario.setNumero(resultSet.getInt("NUM_LOGRADOURO"));
funcionario.setBairro(resultSet.getString("NM_BAIRRO"));
}
return funcionario;
}
});
}
public Funcionario findById(Long id){
String sql = "SELECT * FROM FUNCIONARIO WHERE ID = ?";
List<Object> parametros = new ArrayList<>();
parametros.add(id);
return (Funcionario) super.findById(sql, parametros, new RowMapping<Funcionario>() {
@Override
public Funcionario mapping(ResultSet resultSet) throws SQLException{
Funcionario funcionario = new Funcionario();
if(resultSet != null){
funcionario.setId(resultSet.getLong("ID"));
funcionario.setNome(resultSet.getString("NM_FUNCIONARIO"));
funcionario.setEmail(resultSet.getString("EM_FUNCIONARIO"));
funcionario.setDataNascimento(resultSet.getDate("DT_NASCIMENTO_FUNCIONARIO"));
funcionario.setMatricula(resultSet.getString("MAT_FUNCIONARIO"));
funcionario.setLogradouro(resultSet.getString("NM_LOGRADOURO"));
funcionario.setNumero(resultSet.getInt("NUM_LOGRADOURO"));
funcionario.setBairro(resultSet.getString("NM_BAIRRO"));
}
return funcionario;
}
});
}
}
RowMapping
Interface que possui único método, mapping. Este método é responsável pelo mapeamento do objeto ResultSet em uma entidade do sistema. Pelo fato de sempre retornar uma nova instância sendo essa, qualquer objeto do sistema, a interface é marcada como genérica
Como citado anteriormente, a implementação dessa interface acontece na passagem de parâmetros dos métodos os quais, esperam por uma implementação da mesma.
public interface RowMapping<T> {
/**
* Método que realiza o mapeamento do result set em um objeto.
* @param resultSet Objeto do tipo ResultSet. Contém o retorno da query.
* @return T Retorna um objeto especificado pela generic.
* @throws SQLException Erro de acesso ao banco ou SQL.
*/
T mapping(ResultSet resultSet) throws SQLException;
}
Funcionario
Um POJO comum.
Para criar a tabela do banco de dados, utilize o script abaixo:
CREATE TABLE `funcionario` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`NM_FUNCIONARIO` varchar(45) DEFAULT NULL,
`EM_FUNCIONARIO` varchar(45) DEFAULT NULL,
`DT_NASCIMENTO_FUNCIONARIO` date DEFAULT NULL,
`MAT_FUNCIONARIO` varchar(45) DEFAULT NULL,
`NM_LOGRADOURO` varchar(45) DEFAULT NULL,
`NUM_LOGRADOURO` int(11) DEFAULT NULL,
`NM_BAIRRO` varchar(45) DEFAULT NULL,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=44 DEFAULT CHARSET=utf8$$
Terminamos nosso DAO reutilizável e genérico. Sinta-se a vontade para criticar, comentar ou propor melhorias. Até porque é assim que conseguimos evoluir.
Baixe o projeto
Dropbox: https://www.dropbox.com/s/epg48k36xfp1brv/GenericDAO.rar?dl=0
GitHub: https://github.com/PrecisoEstudarSempre/GenericDAO.git
Agradecimentos a Bruno Souza.
Dúvidas !? Sugestões ?! Críticas ou elogios ?!
Deixe aí nos comentários, na nossa página do facebook ou mande um email.
E-mail: precisoestudarsempre@gmail.com
Referências:
Objeto de acesso a dados - https://pt.wikipedia.org/wiki/DAO
3 comentários:
Fala João, tudo bem?
Mais um ótimo post, parabéns!
Como conversamos, seria bacana adicionar o recurso de Generics nos métodos da classe GenericDao para evitar a possibilidade de erro de execução, tipo ClassCastException, onde a verificação do tipo ficaria em tempo de compilação.
Outro ponto que me incomodou um pouco é o fato da interface RowMapping não estar atrelada a classe GenericDao. Se deixar por conta do desenvolvedor implementar ou não a interface, perderia facilmente o controle do uso desse poderoso recurso.
Mas claro que tudo isso é no campo das ideias. Na prática a teoria é outra!
Um abraço meu amigo!
Tudo bem meu amigo. Melhor agora porque estou respondendo ao seu comentário.
Obrigado pela parabenização e contribuição. Tento sempre entregar o melhor conteúdo para vocês.
Anotei e gostei todas as suas contribuições. Breve, todas estarão em prática em um novo post.
Ótimo para o pessoal que está aprendendo JDBC
Postar um comentário